├── .babelrc.js ├── .devcontainer ├── Dockerfile ├── devcontainer.json └── docker-compose.yml ├── .github ├── dependabot.yml └── workflows │ └── cicd.yaml ├── .gitignore ├── .hadolint.yaml ├── .markdownlint.json ├── .ncurc.json ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── connection.schema.json ├── icon.png ├── icons ├── active.png ├── default.png └── inactive.png ├── package-lock.json ├── package.json ├── src ├── constants.ts ├── extension.ts └── ls │ ├── driver.ts │ ├── keywords.ts │ ├── plugin.ts │ └── queries.ts ├── tsconfig.json └── ui.schema.json /.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | require.resolve('@babel/preset-env'), 5 | { 6 | targets: { 7 | node: '8', 8 | }, 9 | }, 10 | ], 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/devcontainers/javascript-node:1-22-bookworm 2 | 3 | # [Optional] Uncomment this section to install additional OS packages. 4 | SHELL ["/bin/bash", "-o", "pipefail", "-c"] 5 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 6 | && grep -q sse4_2 /proc/cpuinfo && echo "SSE 4.2 supported" || { echo "SSE 4.2 not supported" && exit 1; } \ 7 | && GNUPGHOME=$(mktemp -d) \ 8 | && GNUPGHOME="$GNUPGHOME" gpg --no-default-keyring --keyring /usr/share/keyrings/clickhouse-keyring.gpg --keyserver keyserver.ubuntu.com --recv-keys 8919F6BD2B48D754 \ 9 | && rm -rf "$GNUPGHOME" \ 10 | && chmod +r /usr/share/keyrings/clickhouse-keyring.gpg \ 11 | && echo "deb [signed-by=/usr/share/keyrings/clickhouse-keyring.gpg] https://packages.clickhouse.com/deb stable main" | tee /etc/apt/sources.list.d/clickhouse.list \ 12 | && apt-get update \ 13 | && apt-get install --yes --no-install-recommends clickhouse-client clickhouse-common-static \ 14 | && apt-get clean \ 15 | && rm -rf /var/lib/apt/lists/* 16 | 17 | # [Optional] Uncomment if you want to install an additional version of node using nvm 18 | # ARG EXTRA_NODE_VERSION=10 19 | # RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" 20 | 21 | # [Optional] Uncomment if you want to install more global node modules 22 | RUN su node -c "npm install -g typescript npm-check-updates" 23 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node-postgres 3 | { 4 | "name": "Node.js & ClickHouse", 5 | "dockerComposeFile": "docker-compose.yml", 6 | "service": "app", 7 | "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", 8 | "features": { 9 | "ghcr.io/devcontainers/features/git:1": { 10 | "ppa": true, 11 | "version": "os-provided" 12 | }, 13 | "ghcr.io/devcontainers/features/github-cli:1": { 14 | "installDirectlyFromGitHubRelease": true, 15 | "version": "latest" 16 | } 17 | }, 18 | 19 | // Features to add to the dev container. More info: https://containers.dev/features. 20 | // "features": {}, 21 | 22 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 23 | // This can be used to network with other containers or with the host. 24 | "forwardPorts": [8123, 9000], 25 | 26 | // Use 'postCreateCommand' to run commands after the container is created. 27 | "postCreateCommand": "npm install", 28 | 29 | // Configure tool-specific properties. 30 | "customizations": { 31 | // Configure properties specific to VS Code. 32 | "vscode": { 33 | // Add the IDs of extensions you want installed when the container is created. 34 | "extensions": [ 35 | "dbaeumer.vscode-eslint", 36 | "mhutchie.git-graph", 37 | "github.vscode-github-actions", 38 | "github.vscode-pull-request-github", 39 | "oderwat.indent-rainbow", 40 | "davidanson.vscode-markdownlint", 41 | "esbenp.prettier-vscode", 42 | "redhat.vscode-yaml", 43 | "visualstudioexptteam.vs", 44 | "christian-kohler.npm-intellisense", 45 | "onlyutkarsh.vsix-viewer", 46 | "mtxr.sqltools" 47 | ] 48 | } 49 | }, 50 | 51 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 52 | "remoteUser": "node" 53 | } 54 | -------------------------------------------------------------------------------- /.devcontainer/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | app: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | 9 | volumes: 10 | - ../..:/workspaces:cached 11 | 12 | # Overrides default command so things don't shut down after the process ends. 13 | command: sleep infinity 14 | 15 | # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. 16 | network_mode: service:db 17 | 18 | # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. 19 | # (Adding the "ports" property to this file will not forward from a Codespace.) 20 | 21 | db: 22 | image: clickhouse/clickhouse-server:23.3-alpine 23 | restart: unless-stopped 24 | volumes: 25 | - clickhouse-data:/var/lib/clickhouse/ 26 | - clickhouse-logs:/var/log/clickhouse-server/ 27 | 28 | volumes: 29 | clickhouse-data: 30 | clickhouse-logs: 31 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | 8 | - package-ecosystem: "npm" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | ignore: 13 | - dependency-name: "@types/vscode" 14 | groups: 15 | app-deps: 16 | applies-to: version-updates 17 | patterns: 18 | - "*" 19 | exclude-patterns: 20 | - "@clickhouse/client" 21 | -------------------------------------------------------------------------------- /.github/workflows/cicd.yaml: -------------------------------------------------------------------------------- 1 | name: CI/CD 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - ".devcontainer/**" 7 | - ".github/dependabot.yml" 8 | - ".hadolint.yaml" 9 | - ".markdownlint.json" 10 | - ".ncurc.json" 11 | - "*.md" 12 | - "LICENSE" 13 | pull_request: 14 | paths-ignore: 15 | - ".devcontainer/**" 16 | - ".github/dependabot.yml" 17 | - ".hadolint.yaml" 18 | - ".markdownlint.json" 19 | - ".ncurc.json" 20 | - "*.md" 21 | - "LICENSE" 22 | 23 | jobs: 24 | build: 25 | name: Build 26 | runs-on: ubuntu-24.04 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v4 30 | 31 | - name: Setup Node.js 32 | uses: actions/setup-node@v4 33 | with: 34 | node-version: 22 35 | 36 | - name: Install dependencies 37 | run: npm install 38 | 39 | - name: Bundle 40 | run: npm run package 41 | 42 | - name: Upload artifact 43 | uses: actions/upload-artifact@v4 44 | with: 45 | name: vsix 46 | path: sqltools-clickhouse-driver-*.vsix 47 | 48 | release: 49 | name: Release 50 | runs-on: ubuntu-24.04 51 | if: startsWith(github.ref, 'refs/tags/') 52 | needs: build 53 | steps: 54 | - name: Checkout 55 | uses: actions/checkout@v4 56 | 57 | - name: Download artifact 58 | uses: actions/download-artifact@v4 59 | with: 60 | name: vsix 61 | 62 | - name: Get the version 63 | id: get_version 64 | run: echo VERSION=$(jq -r .version package.json) >> $GITHUB_OUTPUT 65 | 66 | - name: Publish to Visual Studio Marketplace 67 | uses: HaaLeo/publish-vscode-extension@v2 68 | with: 69 | pat: ${{ secrets.VSCE_PAT }} 70 | extensionFile: sqltools-clickhouse-driver-${{ steps.get_version.outputs.VERSION }}.vsix 71 | registryUrl: https://marketplace.visualstudio.com 72 | 73 | - name: Publish to Open VSX Registry 74 | uses: HaaLeo/publish-vscode-extension@v2 75 | with: 76 | pat: ${{ secrets.OVSX_PAT }} 77 | extensionFile: sqltools-clickhouse-driver-${{ steps.get_version.outputs.VERSION }}.vsix 78 | registryUrl: https://open-vsx.org 79 | 80 | - name: Create release body 81 | id: extract_changes 82 | uses: ultram4rine/extract-changes-action@v2 83 | with: 84 | changelog: CHANGELOG.md 85 | version: ${{ steps.get_version.outputs.VERSION }} 86 | 87 | - name: Create release 88 | uses: softprops/action-gh-release@v2 89 | with: 90 | body: ${{ steps.extract_changes.outputs.changes }} 91 | files: | 92 | sqltools-clickhouse-driver-*.vsix 93 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | out/ 4 | *.vsix -------------------------------------------------------------------------------- /.hadolint.yaml: -------------------------------------------------------------------------------- 1 | ignored: 2 | - DL3008 3 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "MD024": { "siblings_only": true } 3 | } 4 | -------------------------------------------------------------------------------- /.ncurc.json: -------------------------------------------------------------------------------- 1 | { 2 | "reject": ["@types/vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Run ClickHouse Driver Extension", 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "runtimeExecutable": "${execPath}", 9 | "args": [ 10 | "${workspaceFolder}/../../test/test-vscode-sqltools.code-workspace", 11 | "--extensionDevelopmentPath=${workspaceFolder}" 12 | ], 13 | "outFiles": [ 14 | "${workspaceFolder}/../../dist/*.js", 15 | "${workspaceFolder}/out/**/*.js" 16 | ], 17 | "sourceMapPathOverrides": { 18 | "src/*": "${workspaceRoot}/src/*" 19 | }, 20 | "sourceMaps": true, 21 | "preLaunchTask": "npm: dev", 22 | "env": { 23 | "SQLTOOLS_DEBUG_PORT_LS": "6099" 24 | } 25 | }, 26 | { 27 | "type": "node", 28 | "request": "attach", 29 | "name": "Attach SQLTools LS", 30 | "port": 6099, 31 | "restart": true, 32 | "sourceMapPathOverrides": { 33 | "src/*": "${workspaceRoot}/src/*" 34 | }, 35 | "sourceMaps": true, 36 | "protocol": "inspector", 37 | "timeout": 100000, 38 | "outFiles": ["${workspaceFolder}/out/**/*.js"], 39 | "skipFiles": ["/**"] 40 | }, 41 | { 42 | "name": "Run Driver Extension", 43 | "type": "extensionHost", 44 | "request": "launch", 45 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 46 | "outFiles": ["${workspaceFolder}/out/**/*.js"], 47 | "preLaunchTask": "${defaultBuildTask}", 48 | "env": { 49 | "SQLTOOLS_DEBUG_PORT_LS": "6099" 50 | } 51 | }, 52 | { 53 | "type": "node", 54 | "request": "attach", 55 | "name": "Attach SQLTools LS", 56 | "port": 6099, 57 | "restart": true, 58 | "sourceMaps": true, 59 | "protocol": "inspector", 60 | "timeout": 100000, 61 | "outFiles": ["${workspaceFolder}/out/**/*.js"], 62 | "skipFiles": ["/**"] 63 | } 64 | ], 65 | "compounds": [ 66 | { 67 | "name": "Run Driver Ext and Attach LS", 68 | "configurations": ["Run Driver Extension", "Attach SQLTools LS"] 69 | } 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off", 11 | "cSpell.words": ["clickhouse", "sqltools"] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "dev", 9 | "problemMatcher": [ 10 | { 11 | "severity": "error", 12 | "applyTo": "closedDocuments", 13 | "source": "esbuild", 14 | "owner": "typescript", 15 | "fileLocation": ["relative", "${workspaceFolder}"], 16 | "background": { 17 | "activeOnStart": true, 18 | "beginsPattern": { 19 | "regexp": "\\[watch\\] build started" 20 | }, 21 | "endsPattern": { 22 | "regexp": "\\[watch\\] build finished" 23 | } 24 | }, 25 | "pattern": { 26 | "regexp": ".*\\s*(.+):(\\d+):(\\d+):$", 27 | "file": 1, 28 | "line": 2, 29 | "column": 3 30 | } 31 | } 32 | ], 33 | "isBackground": true, 34 | "presentation": { 35 | "reveal": "always" 36 | }, 37 | "group": { 38 | "kind": "build", 39 | "isDefault": true 40 | } 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .devcontainer/ 2 | .github/ 3 | .vscode/ 4 | .vscode-test/ 5 | node_modules/ 6 | src/ 7 | 8 | **/*.map 9 | **/*.ts 10 | **/*.vsix 11 | 12 | .babelrs.js 13 | .gitignore 14 | .hadolint.yaml 15 | .markdownlint.json 16 | .ncurc.json 17 | tsconfig.json 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.9.0] - 2024-12-31 11 | 12 | ### Added 13 | 14 | - Add `JWT` auth option (#656). This is a ClickHouse Cloud feature. 15 | 16 | ### Changed 17 | 18 | - Improved connection test (#655). 19 | - Updated dependencies. 20 | 21 | ## [0.8.0] - 2024-11-11 22 | 23 | ### Added 24 | 25 | - Add `role` parameter (#644). Read more [here](https://clickhouse.com/docs/en/interfaces/http#setting-role-with-query-parameters). 26 | This feature added in ClickHouse 24.4, older versions will throw an error. 27 | 28 | ### Changed 29 | 30 | - Updated dependencies. 31 | 32 | ## [0.7.0] - 2024-09-29 33 | 34 | ### Added 35 | 36 | - Add request timeout option (#632). 37 | 38 | ### Changed 39 | 40 | - Updated connection schema, `Server` now in `uri` format. That's mean you need 41 | to specify protocol (#634). 42 | - Removed `useHTTPS` option. If you need a `HTTPS` connection, 43 | specify it in server, e.g. `https://node01.clickhouse.cloud`. 44 | - Updated dependencies. 45 | 46 | ## [0.6.0] - 2024-08-03 47 | 48 | ### Changed 49 | 50 | - Updated dependencies. As `@clickhouse/client` is `v1.0.0` now, incompatibility 51 | with ClickHouse < 23.3 is possible, according to the [doc](https://clickhouse.com/docs/en/integrations/language-clients/javascript#compatibility-with-clickhouse). 52 | 53 | ### Fixed 54 | 55 | - Fix work with tables with special characters in name (#544). 56 | 57 | ## [0.5.0] - 2023-12-24 58 | 59 | ### Added 60 | 61 | - Added `passwordMode` property to connection schema. It can be set to use 62 | `sqltools-driver-credentials` authentication provider. 63 | 64 | ### Changed 65 | 66 | - Refactor TLS options in connection schema. 67 | - Updated dependencies. 68 | 69 | ## [0.4.2] - 2023-08-31 70 | 71 | ### Fixed 72 | 73 | - Fix `Show Table Records` and `Describe Table` explorer commands. 74 | 75 | ## [0.4.1] - 2023-06-20 76 | 77 | ### Fixed 78 | 79 | - Check TLS config for `undefined`. 80 | 81 | ## [0.4.0] - 2023-06-17 82 | 83 | ### Added 84 | 85 | - Added TLS configuration option in connection schema. 86 | 87 | ### Changed 88 | 89 | - Change icons and colors to match new ClickHouse design. 90 | 91 | ## [0.3.1] - 2023-04-14 92 | 93 | ### Fixed 94 | 95 | - Fix empty output from queries that should return output. 96 | 97 | ## [0.3.0] - 2023-04-09 98 | 99 | ### Added 100 | 101 | - Show query metrics. 102 | 103 | ### Changed 104 | 105 | - Moved to [official client](https://github.com/Clickhouse/clickhouse-js). 106 | Incompatibility with ClickHouse < 22.8 possible, according to the 107 | [doc](https://clickhouse.com/docs/en/integrations/language-clients/javascript#compatibility-with-clickhouse). 108 | - Use new official ClickHouse icon. 109 | - Updated dependencies. 110 | 111 | ### Fixed 112 | 113 | - Correctly display page size. 114 | 115 | ## [0.2.5] - 2023-01-17 116 | 117 | ### Changed 118 | 119 | - Updated dependencies. 120 | 121 | ### Security 122 | 123 | - Bump `json5` to fix prototype pollution (#322). 124 | 125 | ## [0.2.4] - 2022-12-26 126 | 127 | ### Changed 128 | 129 | - Updated dependencies. 130 | 131 | ### Fixed 132 | 133 | - Fixed CI/CD badge. 134 | 135 | ## [0.2.3] - 2022-07-24 136 | 137 | ### Changed 138 | 139 | - Updated dependencies. 140 | 141 | ## [0.2.2] - 2022-04-23 142 | 143 | ### Changed 144 | 145 | - Extension now bundled with `esbuild`. 146 | 147 | ## [0.2.1] - 2021-02-12 148 | 149 | ### Fixed 150 | 151 | - Fixed driver loading. 152 | 153 | ## [0.2.0] - 2021-02-12 154 | 155 | ### Added 156 | 157 | - More keywords. 158 | 159 | ### Fixed 160 | 161 | - Fixed problem with export/save result; 162 | 163 | ## [0.1.0] - 2020-11-12 164 | 165 | ### Changed 166 | 167 | - Normal readme. 168 | 169 | ### Fixed 170 | 171 | - Fixed driver host and username settings; 172 | 173 | ## [0.0.1] - 2020-11-10 174 | 175 | ### Added 176 | 177 | - Minimum functionality. 178 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 ultram4rine 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SQLTools ClickHouse Driver 2 | 3 | [![Visual Studio Marketplace Version](https://img.shields.io/visual-studio-marketplace/v/ultram4rine.sqltools-clickhouse-driver?style=flat-square)](https://marketplace.visualstudio.com/items/ultram4rine.sqltools-clickhouse-driver) 4 | [![Open VSX Version](https://img.shields.io/open-vsx/v/ultram4rine/sqltools-clickhouse-driver?style=flat-square)](https://open-vsx.org/extension/ultram4rine/sqltools-clickhouse-driver) 5 | [![License](https://img.shields.io/github/license/ultram4rine/sqltools-clickhouse-driver?style=flat-square)](https://github.com/ultram4rine/sqltools-clickhouse-driver/blob/master/LICENSE) 6 | [![CI/CD](https://img.shields.io/github/actions/workflow/status/ultram4rine/sqltools-clickhouse-driver/cicd.yaml?label=CI%2FCD&logo=github&style=flat-square)](https://github.com/ultram4rine/sqltools-clickhouse-driver/actions/workflows/cicd.yaml) 7 | 8 | [ClickHouse](https://clickhouse.com/) driver for 9 | [SQLTools](https://vscode-sqltools.mteixeira.dev/) VS Code extension. 10 | 11 | ## Installation 12 | 13 | - Directly from VS Code by searching `@tag:sqltools clickhouse` or just `clickhouse`; 14 | - From [marketplace](https://marketplace.visualstudio.com/items/ultram4rine.sqltools-clickhouse-driver). 15 | - Download `vsix` from [releases](https://github.com/ultram4rine/sqltools-clickhouse-driver/releases/latest). 16 | 17 | ## Usage 18 | 19 | After installation you will be able to explore tables and views, run queries, etc. 20 | For more details see SQLTools [documentation](https://vscode-sqltools.mteixeira.dev/features/bookmarks). 21 | 22 | ## Limits 23 | 24 | - According to the [docs](https://clickhouse.com/docs/en/integrations/language-clients/javascript#compatibility-with-clickhouse) 25 | of official nodejs client, this extension should be compatible with ClickHouse 26 | version `23.3` and above. 27 | - Don't use `FORMAT` clause. Driver already uses `JSON` format to show records 28 | and statistics. 29 | - Don't send multiple queries, this is not supported 30 | by SQLTools (yet). 31 | -------------------------------------------------------------------------------- /connection.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "type": "object", 4 | "required": ["server", "port", "passwordMode"], 5 | "definitions": { 6 | "password": { 7 | "title": "Password", 8 | "type": "string", 9 | "default": "" 10 | }, 11 | "tls": { 12 | "title": "TLS configuration", 13 | "type": "object", 14 | "properties": { 15 | "ca_cert": { 16 | "title": "CA Cert", 17 | "type": "string" 18 | }, 19 | "cert": { 20 | "title": "Cert", 21 | "type": "string" 22 | }, 23 | "key": { 24 | "title": "Key", 25 | "type": "string" 26 | } 27 | }, 28 | "dependencies": { 29 | "cert": ["ca_cert", "key"], 30 | "key": ["ca_cert", "cert"] 31 | } 32 | } 33 | }, 34 | "properties": { 35 | "server": { 36 | "title": "Server", 37 | "type": "string", 38 | "format": "uri", 39 | "default": "http://localhost", 40 | "minLength": 1 41 | }, 42 | "port": { 43 | "title": "Port", 44 | "type": "integer", 45 | "default": 8123 46 | }, 47 | "database": { 48 | "title": "Database", 49 | "type": "string", 50 | "default": "default" 51 | }, 52 | "useJWT": { 53 | "type": "boolean", 54 | "title": "Use JWT token instead of password", 55 | "default": false 56 | }, 57 | "passwordMode": { 58 | "title": "Password mode", 59 | "type": "string", 60 | "enum": [ 61 | "SQLTools Driver Credentials", 62 | "Ask on connect", 63 | "Use empty password", 64 | "Save as plaintext in settings" 65 | ], 66 | "default": "Use empty password" 67 | }, 68 | "role": { 69 | "title": "Role", 70 | "type": "string" 71 | }, 72 | "requestTimeout": { 73 | "title": "Request timeout in milliseconds", 74 | "type": "integer", 75 | "default": 30000 76 | }, 77 | "enableTls": { 78 | "type": "boolean", 79 | "title": "Enable TLS", 80 | "default": false 81 | } 82 | }, 83 | "dependencies": { 84 | "passwordMode": { 85 | "oneOf": [ 86 | { 87 | "properties": { 88 | "passwordMode": { 89 | "enum": ["SQLTools Driver Credentials"] 90 | } 91 | } 92 | }, 93 | { 94 | "properties": { 95 | "passwordMode": { 96 | "enum": ["Ask on connect"] 97 | } 98 | } 99 | }, 100 | { 101 | "properties": { 102 | "passwordMode": { 103 | "enum": ["Use empty password"] 104 | } 105 | } 106 | }, 107 | { 108 | "properties": { 109 | "passwordMode": { 110 | "enum": ["Save as plaintext in settings"] 111 | }, 112 | "password": { 113 | "$ref": "#/definitions/password" 114 | } 115 | }, 116 | "required": ["password"] 117 | } 118 | ] 119 | }, 120 | "useJWT": { 121 | "oneOf": [ 122 | { 123 | "properties": { 124 | "useJWT": { 125 | "enum": [false] 126 | }, 127 | "username": { 128 | "title": "Username", 129 | "type": "string", 130 | "default": "default" 131 | } 132 | } 133 | }, 134 | { 135 | "properties": { 136 | "useJWT": { 137 | "enum": [true] 138 | } 139 | } 140 | } 141 | ] 142 | }, 143 | "enableTls": { 144 | "oneOf": [ 145 | { 146 | "properties": { 147 | "enableTls": { 148 | "enum": [false] 149 | } 150 | } 151 | }, 152 | { 153 | "properties": { 154 | "enableTls": { 155 | "enum": [true] 156 | }, 157 | "tls": { 158 | "$ref": "#/definitions/tls" 159 | } 160 | } 161 | } 162 | ] 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultram4rine/sqltools-clickhouse-driver/69813d4245992b4d2dc2ae0b14379123266633e6/icon.png -------------------------------------------------------------------------------- /icons/active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultram4rine/sqltools-clickhouse-driver/69813d4245992b4d2dc2ae0b14379123266633e6/icons/active.png -------------------------------------------------------------------------------- /icons/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultram4rine/sqltools-clickhouse-driver/69813d4245992b4d2dc2ae0b14379123266633e6/icons/default.png -------------------------------------------------------------------------------- /icons/inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultram4rine/sqltools-clickhouse-driver/69813d4245992b4d2dc2ae0b14379123266633e6/icons/inactive.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sqltools-clickhouse-driver", 3 | "displayName": "SQLTools ClickHouse Driver", 4 | "description": "SQLTools driver for ClickHouse", 5 | "version": "0.9.0", 6 | "publisher": "ultram4rine", 7 | "license": "MIT", 8 | "icon": "icon.png", 9 | "author": { 10 | "name": "ultram4rine", 11 | "email": "ultramarine730@gmail.com" 12 | }, 13 | "homepage": "https://github.com/ultram4rine/sqltools-clickhouse-driver#readme", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/ultram4rine/sqltools-clickhouse-driver" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/ultram4rine/sqltools-clickhouse-driver/issues" 20 | }, 21 | "badges": [ 22 | { 23 | "url": "https://img.shields.io/visual-studio-marketplace/v/ultram4rine.sqltools-clickhouse-driver?style=flat-square", 24 | "href": "https://marketplace.visualstudio.com/items/ultram4rine.sqltools-clickhouse-driver", 25 | "description": "Visual Studio Marketplace Version" 26 | }, 27 | { 28 | "url": "https://img.shields.io/open-vsx/v/ultram4rine/sqltools-clickhouse-driver?style=flat-square", 29 | "href": "https://open-vsx.org/extension/ultram4rine/sqltools-clickhouse-driver", 30 | "description": "Open VSX Version" 31 | }, 32 | { 33 | "url": "https://img.shields.io/github/license/ultram4rine/sqltools-clickhouse-driver?style=flat-square", 34 | "href": "https://github.com/ultram4rine/sqltools-clickhouse-driver/blob/master/LICENSE", 35 | "description": "License" 36 | }, 37 | { 38 | "url": "https://img.shields.io/github/actions/workflow/status/ultram4rine/sqltools-clickhouse-driver/cicd.yaml?label=CI%2FCD&logo=github&style=flat-square", 39 | "href": "https://github.com/ultram4rine/sqltools-clickhouse-driver/actions/workflows/cicd.yaml", 40 | "description": "CI/CD status" 41 | } 42 | ], 43 | "engines": { 44 | "vscode": "^1.42.0" 45 | }, 46 | "galleryBanner": { 47 | "color": "#151515", 48 | "theme": "dark" 49 | }, 50 | "scripts": { 51 | "clean": "rimraf out *.vsix", 52 | "predev": "npm run clean", 53 | "dev": "npm run watch", 54 | "esbuild": "esbuild --bundle --external:vscode --platform=node --tsconfig=./tsconfig.json --log-level=info --color=true --format=cjs --target=es2017", 55 | "compile:ext": "npm run esbuild -- ./src/extension.ts --outfile=./out/extension.js --define:process.env.PRODUCT=\"'ext'\"", 56 | "compile:ls": "npm run esbuild -- ./src/ls/plugin.ts --outfile=./out/ls/plugin.js --define:process.env.PRODUCT=\"'ls'\"", 57 | "watch": "concurrently \"npm:watch:*\"", 58 | "watch:ext": "npm run compile:ext -- --define:process.env.NODE_ENV=\"'development'\" --sourcemap --watch", 59 | "watch:ls": "npm run compile:ls -- --define:process.env.NODE_ENV=\"'development'\" --sourcemap --watch", 60 | "prebuild": "npm run clean", 61 | "build": "cross-env NODE_ENV=production concurrently \"npm:build:*\"", 62 | "build:ext": "npm run compile:ext -- --define:process.env.NODE_ENV=\"'production'\" --minify", 63 | "build:ls": "npm run compile:ls -- --define:process.env.NODE_ENV=\"'production'\" --minify", 64 | "prepackage": "npm run build", 65 | "package": "vsce package --no-yarn --allow-star-activation -o .", 66 | "publish:vsce": "vsce publish --no-yarn --allow-star-activation -p $VSCE_PAT -i", 67 | "publish:ovsx": "ovsx publish -p $OVSX_PAT" 68 | }, 69 | "keywords": [ 70 | "clickhouse", 71 | "sqltools-driver" 72 | ], 73 | "categories": [ 74 | "Programming Languages", 75 | "Snippets", 76 | "Formatters", 77 | "Other" 78 | ], 79 | "extensionDependencies": [ 80 | "mtxr.sqltools" 81 | ], 82 | "activationEvents": [ 83 | "*", 84 | "onLanguage:sql", 85 | "onCommand:sqltools.*" 86 | ], 87 | "main": "./out/extension.js", 88 | "dependencies": { 89 | "@clickhouse/client": "^1.10.0", 90 | "@sqltools/base-driver": "latest", 91 | "@sqltools/types": "latest", 92 | "uuid": "^11.0.3" 93 | }, 94 | "devDependencies": { 95 | "@babel/preset-env": "^7.26.0", 96 | "@types/node": ">=22", 97 | "@types/uuid": "^10.0.0", 98 | "@types/vscode": "^1.42.0", 99 | "@vscode/vsce": "3.2.1", 100 | "concurrently": "^9.1.1", 101 | "cross-env": "^7.0.3", 102 | "esbuild": "^0.24.2", 103 | "ovsx": "^0.10.1", 104 | "rimraf": "^6.0.1", 105 | "typescript": "^5.7.2" 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import { IDriverAlias } from "@sqltools/types"; 2 | 3 | /** 4 | * Aliases for yout driver. EG: PostgreSQL, PG, postgres can all resolve to your driver 5 | */ 6 | export const DRIVER_ALIASES: IDriverAlias[] = [ 7 | { displayName: "ClickHouse", value: "ClickHouse" }, 8 | ]; 9 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { 3 | IExtension, 4 | IExtensionPlugin, 5 | IDriverExtensionApi, 6 | } from "@sqltools/types"; 7 | import { ExtensionContext } from "vscode"; 8 | import { DRIVER_ALIASES } from "./constants"; 9 | import { publisher, name } from "../package.json"; 10 | 11 | const AUTHENTICATION_PROVIDER = "sqltools-driver-credentials"; 12 | const driverName = "ClickHouse Driver"; 13 | 14 | export async function activate( 15 | extContext: ExtensionContext 16 | ): Promise { 17 | const sqltools = vscode.extensions.getExtension("mtxr.sqltools"); 18 | if (!sqltools) { 19 | throw new Error("SQLTools not installed"); 20 | } 21 | await sqltools.activate(); 22 | 23 | const api = sqltools.exports; 24 | 25 | const extensionId = `${publisher}.${name}`; 26 | const plugin: IExtensionPlugin = { 27 | extensionId, 28 | name: `${driverName} Plugin`, 29 | type: "driver", 30 | async register(extension) { 31 | // register ext part here 32 | extension.resourcesMap().set(`driver/${DRIVER_ALIASES[0].value}/icons`, { 33 | active: extContext.asAbsolutePath("icons/active.png"), 34 | default: extContext.asAbsolutePath("icons/default.png"), 35 | inactive: extContext.asAbsolutePath("icons/inactive.png"), 36 | }); 37 | DRIVER_ALIASES.forEach(({ value }) => { 38 | extension 39 | .resourcesMap() 40 | .set(`driver/${value}/extension-id`, extensionId); 41 | extension 42 | .resourcesMap() 43 | .set( 44 | `driver/${value}/connection-schema`, 45 | extContext.asAbsolutePath("connection.schema.json") 46 | ); 47 | extension 48 | .resourcesMap() 49 | .set( 50 | `driver/${value}/ui-schema`, 51 | extContext.asAbsolutePath("ui.schema.json") 52 | ); 53 | }); 54 | await extension.client.sendRequest("ls/RegisterPlugin", { 55 | path: extContext.asAbsolutePath("out/ls/plugin.js"), 56 | }); 57 | }, 58 | }; 59 | api.registerPlugin(plugin); 60 | return { 61 | driverName, 62 | parseBeforeSaveConnection: ({ connInfo }) => { 63 | const propsToRemove = ["passwordMode"]; 64 | 65 | if ( 66 | !connInfo.server.startsWith("http://") && 67 | !connInfo.server.startsWith("https://") 68 | ) { 69 | connInfo.server = "http://" + connInfo.server; 70 | } 71 | 72 | if (connInfo.useJWT) { 73 | propsToRemove.push("username"); 74 | } 75 | 76 | if (connInfo.passwordMode) { 77 | if (connInfo.passwordMode.toString().toLowerCase().includes("ask")) { 78 | connInfo.askForPassword = true; 79 | propsToRemove.push("password"); 80 | } else if ( 81 | connInfo.passwordMode.toString().toLowerCase().includes("empty") 82 | ) { 83 | connInfo.password = ""; 84 | propsToRemove.push("askForPassword"); 85 | } else if ( 86 | connInfo.passwordMode.toString().toLowerCase().includes("save") 87 | ) { 88 | propsToRemove.push("askForPassword"); 89 | } else if ( 90 | connInfo.passwordMode.toString().toLowerCase().includes("secure") 91 | ) { 92 | propsToRemove.push("password"); 93 | propsToRemove.push("askForPassword"); 94 | } 95 | } 96 | 97 | propsToRemove.forEach((p) => delete connInfo[p]); 98 | 99 | return connInfo; 100 | }, 101 | parseBeforeEditConnection: ({ connInfo }) => { 102 | const formData: typeof connInfo = { 103 | ...connInfo, 104 | }; 105 | 106 | if ( 107 | !connInfo.server.startsWith("http://") && 108 | !connInfo.server.startsWith("https://") 109 | ) { 110 | formData.server = "http://" + connInfo.server; 111 | } 112 | 113 | if (connInfo.askForPassword) { 114 | formData.passwordMode = "Ask on connect"; 115 | delete formData.password; 116 | } else if (typeof connInfo.password === "string") { 117 | delete formData.askForPassword; 118 | formData.passwordMode = connInfo.password 119 | ? "Save as plaintext in settings" 120 | : "Use empty password"; 121 | } else { 122 | formData.passwordMode = "SQLTools Driver Credentials"; 123 | } 124 | 125 | if (connInfo.enableTls) { 126 | if (!connInfo.tls) { 127 | formData.enableTls = false; 128 | } else { 129 | if (!connInfo.tls.ca_cert) { 130 | formData.enableTls = false; 131 | } 132 | } 133 | } 134 | 135 | return formData; 136 | }, 137 | resolveConnection: async ({ connInfo }) => { 138 | /** 139 | * This hook is called after a connection definition has been fetched 140 | * from settings and is about to be used to connect. 141 | */ 142 | if (connInfo.password === undefined && !connInfo.askForPassword) { 143 | const scopes = [connInfo.name, connInfo.username || ""]; 144 | let session = await vscode.authentication.getSession( 145 | AUTHENTICATION_PROVIDER, 146 | scopes, 147 | { silent: true } 148 | ); 149 | if (!session) { 150 | session = await vscode.authentication.getSession( 151 | AUTHENTICATION_PROVIDER, 152 | scopes, 153 | { createIfNone: true } 154 | ); 155 | } 156 | if (session) { 157 | connInfo.password = session.accessToken; 158 | } 159 | } 160 | return connInfo; 161 | }, 162 | driverAliases: DRIVER_ALIASES, 163 | }; 164 | } 165 | 166 | export function deactivate() {} 167 | -------------------------------------------------------------------------------- /src/ls/driver.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from "fs"; 2 | import { 3 | createClient, 4 | ClickHouseClient, 5 | ResponseJSON, 6 | ClickHouseClientConfigOptions, 7 | } from "@clickhouse/client"; 8 | import AbstractDriver from "@sqltools/base-driver"; 9 | import { 10 | IConnectionDriver, 11 | MConnectionExplorer, 12 | NSDatabase, 13 | ContextValue, 14 | Arg0, 15 | } from "@sqltools/types"; 16 | import { v4 as generateId } from "uuid"; 17 | 18 | import queries from "./queries"; 19 | import keywordsCompletion from "./keywords"; 20 | 21 | export default class ClickHouseDriver 22 | extends AbstractDriver 23 | implements IConnectionDriver 24 | { 25 | queries = queries; 26 | 27 | public async open() { 28 | if (this.connection) { 29 | return this.connection; 30 | } 31 | 32 | // If all three files provided, create mutual TLS configuration, 33 | // else if only CA file provided, create basic TLS configuration. 34 | const tlsConfig = 35 | this.credentials.enableTls && 36 | this.credentials.tls && 37 | this.credentials.tls.ca_cert && 38 | this.credentials.tls.cert && 39 | this.credentials.tls.key 40 | ? { 41 | ca_cert: readFileSync(this.credentials.tls.ca_cert), 42 | cert: readFileSync(this.credentials.tls.cert), 43 | key: readFileSync(this.credentials.tls.key), 44 | } 45 | : this.credentials.enableTls && 46 | this.credentials.tls && 47 | this.credentials.tls.ca_cert 48 | ? { 49 | ca_cert: readFileSync(this.credentials.tls.ca_cert), 50 | } 51 | : undefined; 52 | 53 | let server = this.credentials.server; 54 | if (!server.startsWith("http://") && !server.startsWith("https://")) { 55 | server = "http://" + server; 56 | } 57 | 58 | const url = new URL(server); 59 | url.port = this.credentials.port.toString(); 60 | 61 | const opts = { 62 | url: url, 63 | role: this.credentials.role, 64 | request_timeout: this.credentials.requestTimeout, 65 | application: "sqltools-clickhouse-driver", 66 | database: this.credentials.database, 67 | tls: tlsConfig, 68 | } as ClickHouseClientConfigOptions; 69 | 70 | if (this.credentials.useJWT) { 71 | opts.access_token = this.credentials.password; 72 | } else { 73 | opts.username = this.credentials.username; 74 | opts.password = this.credentials.password; 75 | } 76 | 77 | this.connection = Promise.resolve(createClient(opts)); 78 | return this.connection; 79 | } 80 | 81 | public async close() { 82 | if (!this.connection) { 83 | return Promise.resolve(); 84 | } 85 | 86 | return (await this.connection).close(); 87 | } 88 | 89 | public query: (typeof AbstractDriver)["prototype"]["query"] = async ( 90 | query, 91 | opt = {} 92 | ) => { 93 | const ch = await this.open(); 94 | 95 | return new Promise(async (resolve) => { 96 | const { requestId } = opt; 97 | const queryStart = query.toString().trimStart().toUpperCase(); 98 | const method = 99 | queryStart.startsWith("SELECT") || 100 | queryStart.startsWith("SHOW") || 101 | queryStart.startsWith("WITH") || 102 | queryStart.startsWith("DESC") 103 | ? "query" 104 | : "command"; 105 | 106 | try { 107 | if (method === "query") { 108 | const result = await ( 109 | await ch.query({ 110 | query: query.toString(), 111 | format: "JSON", 112 | }) 113 | ).json(); 114 | 115 | return resolve([ 116 | { 117 | requestId, 118 | connId: this.getId(), 119 | resultId: generateId(), 120 | cols: result.meta?.map((v) => v.name) ?? [], 121 | results: result.data, 122 | pageSize: result.data.length, 123 | query, 124 | messages: [ 125 | this.prepareMessage([ 126 | `Elapsed: ${result.statistics.elapsed} sec, read ${result.statistics.rows_read} rows, ${result.statistics.bytes_read} B.`, 127 | ]), 128 | ], 129 | }, 130 | ]); 131 | } else { 132 | await ch.command({ 133 | query: query.toString(), 134 | }); 135 | 136 | return resolve([ 137 | { 138 | requestId, 139 | connId: this.getId(), 140 | resultId: generateId(), 141 | cols: [], 142 | results: [], 143 | pageSize: 0, 144 | query, 145 | messages: [this.prepareMessage([`Done.`])], 146 | }, 147 | ]); 148 | } 149 | } catch (err) { 150 | return resolve([ 151 | { 152 | requestId, 153 | connId: this.getId(), 154 | resultId: generateId(), 155 | error: true, 156 | rawError: err, 157 | cols: [], 158 | results: [], 159 | query, 160 | messages: [ 161 | this.prepareMessage( 162 | [err.message.replace(/\n/g, " ")].filter(Boolean).join(" ") 163 | ), 164 | ], 165 | }, 166 | ]); 167 | } 168 | }); 169 | }; 170 | 171 | public async testConnection() { 172 | try { 173 | const ch = await this.open(); 174 | 175 | // Try ping 176 | const pingResult = await ch.ping(); 177 | if (!pingResult.success) { 178 | throw "Failed to ping ClickHouse server"; 179 | } 180 | 181 | // Try "select 1" 182 | await ch.query({ 183 | query: "SELECT 1", 184 | format: "JSON", 185 | }); 186 | } catch (err) { 187 | return Promise.reject(err); 188 | } finally { 189 | await this.close(); 190 | } 191 | } 192 | 193 | private async getColumns( 194 | parent: NSDatabase.ITable 195 | ): Promise { 196 | const results = await this.queryResults(this.queries.fetchColumns(parent)); 197 | return results.map((col) => ({ 198 | ...col, 199 | iconName: col.isPk ? "pk" : null, 200 | childType: ContextValue.NO_CHILD, 201 | table: parent, 202 | })); 203 | } 204 | 205 | /** 206 | * This method is a helper to generate the connection explorer tree. 207 | * it gets the child items based on current item 208 | */ 209 | public async getChildrenForItem({ 210 | item, 211 | parent, 212 | }: Arg0) { 213 | switch (item.type) { 214 | case ContextValue.CONNECTION: 215 | case ContextValue.CONNECTED_CONNECTION: 216 | return (await this.queryResults(this.queries.fetchDatabases())) 217 | .map((d) => { 218 | return { 219 | ...d, 220 | iconName: 221 | this.credentials.database === d.label 222 | ? "database-active" 223 | : "database", 224 | }; 225 | }) 226 | .sort((x, y) => { 227 | return x.label === this.credentials.database 228 | ? -1 229 | : y.label === this.credentials.database 230 | ? 1 231 | : 0; 232 | }); 233 | case ContextValue.DATABASE: 234 | return [ 235 | { 236 | label: "Tables", 237 | type: ContextValue.RESOURCE_GROUP, 238 | iconId: "folder", 239 | childType: ContextValue.TABLE, 240 | }, 241 | { 242 | label: "Views", 243 | type: ContextValue.RESOURCE_GROUP, 244 | iconId: "folder", 245 | childType: ContextValue.VIEW, 246 | }, 247 | ]; 248 | case ContextValue.TABLE: 249 | case ContextValue.VIEW: 250 | return this.getColumns(item as NSDatabase.ITable); 251 | case ContextValue.RESOURCE_GROUP: 252 | return this.getChildrenForGroup({ item, parent }); 253 | } 254 | return []; 255 | } 256 | 257 | /** 258 | * This method is a helper to generate the connection explorer tree. 259 | * It gets the child based on child types 260 | */ 261 | private async getChildrenForGroup({ 262 | parent, 263 | item, 264 | }: Arg0) { 265 | switch (item.childType) { 266 | case ContextValue.TABLE: 267 | return this.queryResults( 268 | this.queries.fetchTables(parent as NSDatabase.ISchema) 269 | ); 270 | case ContextValue.VIEW: 271 | return this.queryResults( 272 | this.queries.fetchViews(parent as NSDatabase.ISchema) 273 | ); 274 | } 275 | return []; 276 | } 277 | 278 | public async getInsertQuery(params: { 279 | item: NSDatabase.ITable; 280 | columns: Array; 281 | }) { 282 | const { item, columns } = params; 283 | let insertQuery = `INSERT INTO ${item.database}.\`${item.label}\` (${columns 284 | .map((col) => col.label) 285 | .join(", ")}) VALUES (`; 286 | columns.forEach((col, index) => { 287 | insertQuery = insertQuery.concat( 288 | `'\${${index + 1}:${col.label}:${col.dataType}}', ` 289 | ); 290 | }); 291 | return insertQuery; 292 | } 293 | 294 | /** 295 | * This method is a helper for intellisense and quick picks. 296 | */ 297 | public async searchItems( 298 | itemType: ContextValue, 299 | search: string, 300 | extraParams: any = {} 301 | ): Promise { 302 | switch (itemType) { 303 | case ContextValue.TABLE: 304 | return this.queryResults(this.queries.searchTables({ search })); 305 | case ContextValue.COLUMN: 306 | return this.queryResults( 307 | this.queries.searchColumns({ search, ...extraParams }) 308 | ); 309 | } 310 | return []; 311 | } 312 | 313 | private completionsCache: { 314 | [w: string]: NSDatabase.IStaticCompletion; 315 | } = null; 316 | public getStaticCompletions = async () => { 317 | if (this.completionsCache) { 318 | return this.completionsCache; 319 | } 320 | 321 | try { 322 | this.completionsCache = keywordsCompletion; 323 | 324 | const functions = await this.queryResults( 325 | "SELECT name AS label FROM system.functions ORDER BY name ASC" 326 | ); 327 | const dataTypes = await this.queryResults( 328 | "SELECT name AS label, alias_to AS desc FROM system.data_type_families ORDER BY name ASC" 329 | ); 330 | 331 | functions.concat(dataTypes).forEach((item: any) => { 332 | this.completionsCache[item.label] = { 333 | label: item.label, 334 | detail: item.label, 335 | filterText: item.label, 336 | sortText: "" + item.label, 337 | documentation: { 338 | value: `\`\`\`yaml\nWORD: ${item.label}\nTYPE: ${item.desc}\n\`\`\``, 339 | kind: "markdown", 340 | }, 341 | }; 342 | }); 343 | } catch (error) { 344 | // use default reserved words 345 | this.completionsCache = keywordsCompletion; 346 | } 347 | 348 | return this.completionsCache; 349 | }; 350 | } 351 | -------------------------------------------------------------------------------- /src/ls/keywords.ts: -------------------------------------------------------------------------------- 1 | import { NSDatabase } from "@sqltools/types"; 2 | 3 | // TODO: more keywods! 4 | const keywordsArr = [ 5 | "ALL", 6 | "ALTER", 7 | "ANTI", 8 | "ANY", 9 | "ARRAY JOIN", 10 | "ASOF", 11 | "CREATE", 12 | "CROSS", 13 | "DISTINCT", 14 | "DROP", 15 | "FINAL", 16 | "FROM", 17 | "FULL", 18 | "GLOBAL", 19 | "GROUP BY", 20 | "HAVING", 21 | "INNER", 22 | "INSERT INTO", 23 | "LEFT", 24 | "LIMIT", 25 | "LIMIT BY", 26 | "ON", 27 | "ORDER BY", 28 | "OUTER", 29 | "PREWHERE", 30 | "RIGHT", 31 | "SAMPLE", 32 | "SELECT", 33 | "SEMI", 34 | "STEP", 35 | "TABLE", 36 | "TO", 37 | "UNION", 38 | "USING", 39 | "VALUES", 40 | "WITH", 41 | "WITH CUBE", 42 | "WITH FILL", 43 | "WITH ROLLUP", 44 | "WITH TIES", 45 | "WITH TOTALS", 46 | ]; 47 | 48 | const keywordsCompletion: { 49 | [w: string]: NSDatabase.IStaticCompletion; 50 | } = keywordsArr.reduce((agg, word) => { 51 | agg[word] = { 52 | label: word, 53 | detail: word, 54 | filterText: word, 55 | sortText: 56 | (["SELECT", "CREATE", "UPDATE"].includes(word) ? "2:" : "") + word, 57 | documentation: { 58 | value: `\`\`\`yaml\nWORD: ${word}\n\`\`\``, 59 | kind: "markdown", 60 | }, 61 | }; 62 | return agg; 63 | }, {}); 64 | 65 | export default keywordsCompletion; 66 | -------------------------------------------------------------------------------- /src/ls/plugin.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IConnectionDriverConstructor, 3 | ILanguageServerPlugin, 4 | } from "@sqltools/types"; 5 | import ClickHouseDriver from "./driver"; 6 | import { DRIVER_ALIASES } from "./../constants"; 7 | 8 | const ClickHouseDriverPlugin: ILanguageServerPlugin = { 9 | register(server) { 10 | DRIVER_ALIASES.forEach(({ value }) => { 11 | server 12 | .getContext() 13 | .drivers.set(value, ClickHouseDriver as IConnectionDriverConstructor); 14 | }); 15 | }, 16 | }; 17 | 18 | export default ClickHouseDriverPlugin; 19 | -------------------------------------------------------------------------------- /src/ls/queries.ts: -------------------------------------------------------------------------------- 1 | import { IBaseQueries, ContextValue } from "@sqltools/types"; 2 | import queryFactory from "@sqltools/base-driver/dist/lib/factory"; 3 | 4 | const describeTable: IBaseQueries["describeTable"] = queryFactory` 5 | DESCRIBE TABLE ${(p) => p.database}.\`${(p) => p.label}\` 6 | `; 7 | 8 | const fetchColumns: IBaseQueries["fetchColumns"] = queryFactory` 9 | SELECT name AS label, 10 | c.type AS dataType, 11 | c.default_expression AS defaultValue, 12 | '${ContextValue.COLUMN}' AS type, 13 | c.is_in_primary_key AS isPk 14 | FROM system.columns AS c 15 | WHERE c.table == '${(p) => p.label}' 16 | ORDER BY c.position ASC 17 | `; 18 | 19 | const fetchRecords: IBaseQueries["fetchRecords"] = queryFactory` 20 | SELECT * 21 | FROM ${(p) => p.table.database}.\`${(p) => p.table.label || p.table}\` 22 | LIMIT ${(p) => p.limit || 50} 23 | OFFSET ${(p) => p.offset || 0} 24 | `; 25 | 26 | const countRecords: IBaseQueries["countRecords"] = queryFactory` 27 | SELECT count(1) AS total 28 | FROM ${(p) => p.table.database}.\`${(p) => p.table.label || p.table}\` 29 | `; 30 | 31 | const fetchDatabases: IBaseQueries["fetchDatabases"] = queryFactory` 32 | SELECT d.name AS label, 33 | d.name AS database, 34 | '${ContextValue.DATABASE}' AS type, 35 | 'database' AS detail 36 | FROM system.databases AS d 37 | ORDER BY d.name 38 | `; 39 | 40 | const fetchTables: IBaseQueries["fetchTables"] = queryFactory` 41 | SELECT t.name AS label, 42 | t.database AS database, 43 | '${ContextValue.TABLE}' AS type 44 | FROM system.tables AS t 45 | WHERE t.database == '${(p) => p.database}' 46 | AND t.engine != 'View' 47 | ORDER BY t.name 48 | `; 49 | const fetchViews: IBaseQueries["fetchTables"] = queryFactory` 50 | SELECT t.name AS label, 51 | t.database AS database, 52 | '${ContextValue.VIEW}' AS type 53 | FROM system.tables AS t 54 | WHERE t.database == '${(p) => p.database}' 55 | AND t.engine == 'View' 56 | ORDER BY t.name 57 | `; 58 | 59 | const searchTables: IBaseQueries["searchTables"] = queryFactory` 60 | SELECT t.name AS label, 61 | t.database AS database, 62 | '${ContextValue.TABLE}' AS type 63 | FROM system.tables AS t 64 | WHERE t.database NOT IN ('_temporary_and_external_tables', 'default', 'system') 65 | ${(p) => (p.search ? `AND t.name ILIKE '%${p.search}%'` : "")} 66 | ORDER BY t.name 67 | `; 68 | const searchColumns: IBaseQueries["searchColumns"] = queryFactory` 69 | SELECT c.name AS label, 70 | c.table AS table, 71 | c.database AS database, 72 | c.type AS dataType, 73 | '${ContextValue.COLUMN}' as type 74 | FROM system.columns AS c 75 | WHERE c.database NOT IN ('_temporary_and_external_tables', 'default', 'system') 76 | ${(p) => 77 | p.tables.filter((t) => !!t.label).length 78 | ? `AND LOWER(c.table) IN (${p.tables 79 | .filter((t) => !!t.label) 80 | .map((t) => `'${t.label}'`.toLowerCase()) 81 | .join(", ")})` 82 | : ""} 83 | ${(p) => 84 | p.search 85 | ? `AND ( 86 | (C.TABLE_NAME || '.' || C.COLUMN_NAME) ILIKE '%${p.search}%' 87 | OR C.COLUMN_NAME ILIKE '%${p.search}%' 88 | )` 89 | : ""} 90 | ORDER BY c.name ASC 91 | LIMIT ${(p) => p.limit || 100} 92 | `; 93 | 94 | export default { 95 | describeTable, 96 | countRecords, 97 | fetchColumns, 98 | fetchRecords, 99 | fetchDatabases, 100 | fetchTables, 101 | fetchViews, 102 | searchTables, 103 | searchColumns, 104 | }; 105 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "esModuleInterop": true, 5 | "noUnusedLocals": true, 6 | "noUnusedParameters": true, 7 | "experimentalDecorators": true, 8 | "lib": ["ES6"], 9 | "module": "CommonJS", 10 | "moduleResolution": "Node", 11 | "noEmit": false, 12 | "removeComments": true, 13 | "resolveJsonModule": true, 14 | "sourceMap": true, 15 | "skipLibCheck": true, 16 | "declaration": false, 17 | "target": "ESNext", 18 | "outDir": "out" 19 | }, 20 | "include": ["src/**/*"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /ui.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui:order": [ 3 | "server", 4 | "port", 5 | "database", 6 | "useJWT", 7 | "username", 8 | "passwordMode", 9 | "password", 10 | "role", 11 | "requestTimeout", 12 | "enableTls", 13 | "tls" 14 | ], 15 | "password": { "ui:widget": "password" }, 16 | "useJWT": { 17 | "ui:help": "This is a ClickHouse Cloud feature. Put token in password field" 18 | }, 19 | "role": { 20 | "ui:help": "Only for ClickHouse >=24.4! See https://clickhouse.com/docs/en/interfaces/http#setting-role-with-query-parameters" 21 | }, 22 | "tls": { 23 | "ca_cert": { "ui:widget": "file" }, 24 | "cert": { "ui:widget": "file" }, 25 | "key": { "ui:widget": "file" } 26 | } 27 | } 28 | --------------------------------------------------------------------------------