From eed48d67bdf5bbe74d8cbe88f544ec4c3e721a1a Mon Sep 17 00:00:00 2001 From: joe-re Date: Fri, 26 Jun 2020 01:02:34 +0800 Subject: [PATCH 1/5] fix type error --- packages/sql-parser/index.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/sql-parser/index.d.ts b/packages/sql-parser/index.d.ts index 139ef109..50b7152e 100644 --- a/packages/sql-parser/index.d.ts +++ b/packages/sql-parser/index.d.ts @@ -106,6 +106,7 @@ export type InsertStatement = { table: string columns: string[] values: ValuesClause + location: NodeRange } export type ValuesClause = { From db95014be48f959c187339f7552d51b8b0b16f3e Mon Sep 17 00:00:00 2001 From: joe-re Date: Fri, 26 Jun 2020 04:09:03 +0800 Subject: [PATCH 2/5] implement sqlite3 client --- .gitignore | 1 + .vscodeignore | 3 +- package.json | 5 + packages/client/extension.ts | 20 ++++ packages/client/package.json | 1 + packages/client/rebuild.ts | 6 ++ packages/server/SettingStore.ts | 16 ++-- packages/server/createServer.ts | 32 ++++--- .../server/database_libs/AbstractClient.ts | 7 +- packages/server/database_libs/MysqlClient.ts | 1 + .../server/database_libs/PostgresClient.ts | 1 + .../server/database_libs/Sqlite3Client.ts | 95 +++++++++++++++++++ .../server/database_libs/getDatabaseClient.ts | 2 + packages/server/package.json | 2 + 14 files changed, 171 insertions(+), 21 deletions(-) create mode 100644 packages/client/rebuild.ts create mode 100644 packages/server/database_libs/Sqlite3Client.ts diff --git a/.gitignore b/.gitignore index c83d4a46..d3317fb7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ packages/server/dist/* **/*/package-lock.json **/*/yarn.lock yarn.lock +*.sqlite3 diff --git a/.vscodeignore b/.vscodeignore index a1e99fab..ab616bd2 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -11,4 +11,5 @@ **/*/jest.config.js **/*/yarn-error.log README.md -**/*/.npmignore \ No newline at end of file +**/*/.npmignore +!./node_modules/sqlite3 \ No newline at end of file diff --git a/package.json b/package.json index 74f42015..4ad721d0 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,11 @@ "command": "extension.fixAllFixableProblems", "title": "Fix all auto-fixable problems", "category": "SQLLanguageServer" + }, + { + "command": "extension.rebuildSqlite3", + "title": "Rebuild SQLite3 Client", + "category": "SQLLanguageServer" } ] }, diff --git a/packages/client/extension.ts b/packages/client/extension.ts index a7947631..e11b77a0 100644 --- a/packages/client/extension.ts +++ b/packages/client/extension.ts @@ -7,6 +7,7 @@ import { TransportKind, } from 'vscode-languageclient' import { ExecuteCommandParams } from 'vscode-languageserver-protocol' +import { rebuild } from './rebuild' export function activate(context: ExtensionContext) { let serverModule = context.asAbsolutePath(path.join('packages', 'server', 'dist', 'cli.js')) @@ -67,8 +68,27 @@ export function activate(context: ExtensionContext) { client.sendRequest('workspace/executeCommand', params) }) + let isRebuilding = false + const rebuildSqlite3 = commands.registerCommand('extension.rebuildSqlite3', async () => { + if (isRebuilding) { + Window.showInformationMessage('Already started rebuild Sqlite3 process') + return + } + isRebuilding = true + try { + Window.showInformationMessage('Start to rebuild Sqlite3.') + await rebuild() + Window.showInformationMessage('Done to rebuild Sqlite3.') + } catch (e) { + Window.showErrorMessage(e) + } finally { + isRebuilding = false + } + }) + context.subscriptions.push(switchConnection) context.subscriptions.push(fixAllFixableProblem) + context.subscriptions.push(rebuildSqlite3) context.subscriptions.push(disposable) client.onReady().then(() => { client.onNotification('sqlLanguageServer.finishSetup', (params) => { diff --git a/packages/client/package.json b/packages/client/package.json index 82acebdd..a5b15e25 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -17,6 +17,7 @@ ], "dependencies": { "@types/node": "12.12.6", + "electron-rebuild": "^1.11.0", "typescript": "^3.9.2", "vscode-languageclient": "^6.1.3", "vscode-test": "^1.3.0" diff --git a/packages/client/rebuild.ts b/packages/client/rebuild.ts new file mode 100644 index 00000000..3f2d6c41 --- /dev/null +++ b/packages/client/rebuild.ts @@ -0,0 +1,6 @@ +import electronRebuild from 'electron-rebuild' + +const electronVersion = (process.versions as any).electron +export function rebuild(): Promise { + return electronRebuild({ buildPath: `${__dirname}/../../../node_modules/sqlite3`, electronVersion }) +} diff --git a/packages/server/SettingStore.ts b/packages/server/SettingStore.ts index 6cbbbc38..c3cefbd0 100644 --- a/packages/server/SettingStore.ts +++ b/packages/server/SettingStore.ts @@ -15,13 +15,14 @@ export type SSHConfig = { } export type Settings = { name: string | null, - adapter: 'mysql' | 'postgresql' | null, - host: string | null, - port: number | null, - user: string | null, - database: string | null, - password: string | null, - projectPaths: string[], + adapter: 'mysql' | 'postgresql' | 'sqlite3' | null, + host: string | null + port: number | null + user: string | null + database: string | null + password: string | null + filename: string | null // for sqlite3 + projectPaths: string[] ssh: SSHConfig | null } @@ -55,6 +56,7 @@ export default class SettingStore extends EventEmitter { database: null, password: null, ssh: null, + filename: null, projectPaths: [] } private static instance: SettingStore; diff --git a/packages/server/createServer.ts b/packages/server/createServer.ts index fe08bc07..8475d4c2 100644 --- a/packages/server/createServer.ts +++ b/packages/server/createServer.ts @@ -41,18 +41,28 @@ export default function createServer() { connection.onInitialize((params): InitializeResult => { logger.debug(`onInitialize: ${params.rootPath}`) SettingStore.getInstance().on('change', async () => { + logger.debug('onInitialize: receive change event from SettingStore') try { - const client = getDatabaseClient(SettingStore.getInstance().getSetting()) - schema = await client.getSchema() - logger.debug('get schema') - logger.debug(JSON.stringify(schema)) - connection.sendNotification('sqlLanguageServer.finishSetup', { - personalConfig: SettingStore.getInstance().getPersonalConfig(), - config: SettingStore.getInstance().getSetting() - }) - } catch (e) { - logger.error(e) - } + setTimeout(() => { + connection.sendNotification('sqlLanguageServer.finishSetup', { + personalConfig: SettingStore.getInstance().getPersonalConfig(), + config: SettingStore.getInstance().getSetting() + }) + }, 1000) // TODO: Need to think about better way to sendNotification + try { + const client = getDatabaseClient( + SettingStore.getInstance().getSetting() + ) + schema = await client.getSchema() + logger.debug("get schema") + logger.debug(JSON.stringify(schema)) + } catch (e) { + logger.error("failed to get schema info") + throw e + } + } catch (e) { + logger.error(e) + } }) if (params.rootPath) { SettingStore.getInstance().setSettingFromFile( diff --git a/packages/server/database_libs/AbstractClient.ts b/packages/server/database_libs/AbstractClient.ts index 74321606..ebfb0d40 100644 --- a/packages/server/database_libs/AbstractClient.ts +++ b/packages/server/database_libs/AbstractClient.ts @@ -27,7 +27,7 @@ export default abstract class AbstractClient { constructor(protected settings: Settings) {} - abstract connect(): void + abstract connect(): boolean abstract disconnect(): void abstract getTables(): Promise abstract getColumns(tableName: string): Promise @@ -57,7 +57,10 @@ export default abstract class AbstractClient { return [] }) } - this.connect() + if (!this.connect()) { + logger.error('AbstractClinet.getSchema: failed to connect database') + return [] + } try { const tables = await this.getTables() schema = await Promise.all( diff --git a/packages/server/database_libs/MysqlClient.ts b/packages/server/database_libs/MysqlClient.ts index 76948534..c4eb25e2 100644 --- a/packages/server/database_libs/MysqlClient.ts +++ b/packages/server/database_libs/MysqlClient.ts @@ -21,6 +21,7 @@ export default class MysqlClient extends AbstractClient { port: this.settings.port || this.DefaultPort, database: this.settings.database || '' }) + return true } disconnect() { diff --git a/packages/server/database_libs/PostgresClient.ts b/packages/server/database_libs/PostgresClient.ts index 93f0e3fb..c97f0f35 100644 --- a/packages/server/database_libs/PostgresClient.ts +++ b/packages/server/database_libs/PostgresClient.ts @@ -23,6 +23,7 @@ export default class PosgresClient extends AbstractClient { }) client.connect() this.connection = client + return true } disconnect() { diff --git a/packages/server/database_libs/Sqlite3Client.ts b/packages/server/database_libs/Sqlite3Client.ts new file mode 100644 index 00000000..cdbe94a6 --- /dev/null +++ b/packages/server/database_libs/Sqlite3Client.ts @@ -0,0 +1,95 @@ +import { Settings } from '../SettingStore' +import AbstractClient, { RawField } from './AbstractClient' +import { sqlite3 as SQLite3, Database } from 'sqlite3' +import log4js from 'log4js' + +const logger = log4js.getLogger() + +export default class Sqlite3Client extends AbstractClient { + connection: Database | null = null + + get DefaultPort() { return 0 } + get DefaultHost() { return '' } + get DefaultUser() { return '' } + + constructor(settings: Settings) { + super(settings) + } + + connect(): boolean { + if (!this.settings.filename) { + throw new Error('Need to specify filename to use sqlite3 connection.') + } + try { + // use commonjs to avoid dynamic import + const sqlite3: SQLite3 = require('sqlite3') + + this.connection = new sqlite3.Database( + this.settings.filename, + sqlite3.OPEN_READONLY + ) + } catch (e) { + logger.error('Sqlite3Client: failed to connect to database', e) + return false + } + return true + } + + disconnect() { + if (this.connection) { + this.connection.close() + } + this.connection = null + } + + getTables(): Promise { + const sql = `SELECT name FROM sqlite_master WHERE type='table'` + return new Promise((resolve, reject) => { + if (!this.connection) { + reject(new Error("Don't have database connection.")) + return + } + this.connection.all(sql, (err, rows: { name: string}[]) => { + if (err) { + reject(new Error(err.message)) + return + } + logger.debug('Sqlite3Clinet: done to get table names', rows) + const tables = rows.map(v => v.name) + resolve(tables) + }) + }) + } + + getColumns(tableName: string): Promise { + const sql = `SELECT * FROM pragma_table_info('${tableName}')` + return new Promise((resolve, reject) => { + if (!this.connection) { + reject(new Error("Don't have database connection.")) + return + } + this.connection.all(sql, (err, rows: { + cld: number + name: string + type: string + notnull: number + dflt_value: any + pk: number + }[]) => { + if (err) { + reject(new Error(err.message)) + return + } + logger.debug('Sqlite3Clinet: done to get column names', rows) + const columns: RawField[] = rows.map(v => ({ + field: v.name, + type: v.type, + null: v.notnull ? 'Yes' : 'No', + default: v.dflt_value, + comment: v.pk ? 'PRIMARY KEY' : '' + })) + resolve(columns) + }) + }) + } +} \ No newline at end of file diff --git a/packages/server/database_libs/getDatabaseClient.ts b/packages/server/database_libs/getDatabaseClient.ts index 4300604f..40b38013 100644 --- a/packages/server/database_libs/getDatabaseClient.ts +++ b/packages/server/database_libs/getDatabaseClient.ts @@ -2,11 +2,13 @@ import AbstractClient from './AbstractClient' import MysqlClient from './MysqlClient' import PostgresClient from './PostgresClient' import { Settings } from '../SettingStore' +import Sqlite3Client from './Sqlite3Client' export default function getDatabaseClient(settings: Settings): AbstractClient { switch (settings.adapter) { case 'mysql': return new MysqlClient(settings) case 'postgresql': return new PostgresClient(settings) + case 'sqlite3': return new Sqlite3Client(settings) default: throw new Error(`not support ${settings.adapter}`) } } \ No newline at end of file diff --git a/packages/server/package.json b/packages/server/package.json index 22f35123..3eada674 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -24,6 +24,7 @@ "mysql": "^2.15.0", "node-ssh-forward": "^0.6.3", "pg": "^7.4.3", + "sqlite3": "^4.2.0", "vscode-languageclient": "^6.1.3", "vscode-languageserver": "^6.1.1", "vscode-languageserver-textdocument": "^1.0.1", @@ -37,6 +38,7 @@ "@types/jest": "^25.2.2", "@types/mysql": "^2.15.5", "@types/node": "^8.10.0", + "@types/sqlite3": "^3.1.6", "jest": "^26.0.1", "rollup": "^2.10.2", "ts-jest": "^26.0.0", From 576c134cc7416d29b75bf2ec5cee4ace66ac861c Mon Sep 17 00:00:00 2001 From: joe-re Date: Sun, 28 Jun 2020 01:22:56 +0800 Subject: [PATCH 3/5] notify to rebuild sqlite3 if it's needed --- packages/client/rebuild.ts | 2 +- packages/server/createServer.ts | 6 ++++++ packages/server/database_libs/Sqlite3Client.ts | 11 +++++++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/client/rebuild.ts b/packages/client/rebuild.ts index 3f2d6c41..d1f09e93 100644 --- a/packages/client/rebuild.ts +++ b/packages/client/rebuild.ts @@ -2,5 +2,5 @@ import electronRebuild from 'electron-rebuild' const electronVersion = (process.versions as any).electron export function rebuild(): Promise { - return electronRebuild({ buildPath: `${__dirname}/../../../node_modules/sqlite3`, electronVersion }) + return electronRebuild({ buildPath: `${__dirname}/../../../node_modules/sqlite3`, electronVersion, force: true, useCache: false }) } diff --git a/packages/server/createServer.ts b/packages/server/createServer.ts index 8475d4c2..98cc8404 100644 --- a/packages/server/createServer.ts +++ b/packages/server/createServer.ts @@ -18,6 +18,7 @@ import getDatabaseClient from './database_libs/getDatabaseClient' import initializeLogging from './initializeLogging' import { lint, LintResult } from 'sqlint' import log4js from 'log4js' +import { RequireSqlite3Error } from './database_libs/Sqlite3Client' export type ConnectionMethod = 'node-ipc' | 'stdio' type Args = { @@ -58,6 +59,11 @@ export default function createServer() { logger.debug(JSON.stringify(schema)) } catch (e) { logger.error("failed to get schema info") + if (e instanceof RequireSqlite3Error) { + connection.sendNotification('sqlLanguageServer.error', { + message: "Need to rebuild sqlite3 module." + }) + } throw e } } catch (e) { diff --git a/packages/server/database_libs/Sqlite3Client.ts b/packages/server/database_libs/Sqlite3Client.ts index cdbe94a6..36f1a748 100644 --- a/packages/server/database_libs/Sqlite3Client.ts +++ b/packages/server/database_libs/Sqlite3Client.ts @@ -5,6 +5,13 @@ import log4js from 'log4js' const logger = log4js.getLogger() +export class RequireSqlite3Error extends Error { + constructor(message: any) { + super(message) + this.name = "RequireSQLite3Error" + } +} + export default class Sqlite3Client extends AbstractClient { connection: Database | null = null @@ -21,7 +28,7 @@ export default class Sqlite3Client extends AbstractClient { throw new Error('Need to specify filename to use sqlite3 connection.') } try { - // use commonjs to avoid dynamic import + // use commonjs to avoid dynamic import build error const sqlite3: SQLite3 = require('sqlite3') this.connection = new sqlite3.Database( @@ -30,7 +37,7 @@ export default class Sqlite3Client extends AbstractClient { ) } catch (e) { logger.error('Sqlite3Client: failed to connect to database', e) - return false + throw new RequireSqlite3Error(e) } return true } From c51adab596d2629465495379eb4fe2fac6ae14a6 Mon Sep 17 00:00:00 2001 From: joe-re Date: Sun, 28 Jun 2020 01:23:27 +0800 Subject: [PATCH 4/5] delete binding files before publish --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4ad721d0..b726520a 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "watch": "run-p watch:client watch:server", "watch:client": "cd ./packages/client && yarn run watch", "watch:server": "cd ./packages/server && yarn run watch", - "vscode:prepublish": "yarn run compile" + "vscode:prepublish": "yarn run compile && rm -rf node_modules/lib/binding" }, "engines": { "vscode": "^1.45.1" From 4b76fe1a0026935cb1340af7a85e3d68a6b56f0d Mon Sep 17 00:00:00 2001 From: joe-re Date: Sun, 28 Jun 2020 01:35:15 +0800 Subject: [PATCH 5/5] write README about sqlite3 connection --- README.md | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 73e42b45..c1488f6c 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,13 @@ There are two ways to use configuration files. "user": "postgres", "password": "pg_pass", "database": "pg_test", - "projectPaths": ["/Users/joe-re/src/postgres_ptoject"] + "projectPaths": ["/Users/joe-re/src/postgres_project"] + }, + { + "name": "sqlite3-project", + "adapter": "sqlite3", + "filename": "/Users/noguchimasato/src/sql-language-server/packages/server/test.sqlite3", + "projectPaths": ["/Users/joe-re/src/sqlite2_project"] } ] } @@ -105,12 +111,13 @@ Please restart sql-language-server process after create .sqlrc.json. | Key | Description | value | required | default | | ------------ | ------------------------------------------------------------------------------------------------------------------------- | ----------------------- | -------- | --------------------------------- | | name | Connection name(free-form text) | | true | | -| adapter | Database type | "mysql" #124; "postgres" | true | | -| host | Database host | string | true | | +| adapter | Database type | "mysql" #124; "postgres" #124; "sqlite3" | true | | +| host | Database host | string | false | | | port | Database port | string | false | mysql:3306, postgres:5432 | -| user | Database user | string | true | mysql:"root", postgres:"postgres" | +| user | Database user | string | false | mysql:"root", postgres:"postgres" | | password | Database password | string | false | | | database | Database name | string | false | | +| filename | Database filename(only for sqlite3) | string | false | | | projectPaths | Project path that you want to apply(if you don't set it configuration will not apply automatically when lsp's started up) | string[] | false | [] | | ssh | Settings for port fowarding | \*see below SSH section | false | | @@ -237,6 +244,17 @@ command: switchDataBaseConnection arguments: string(project name) ``` + +#### SQLite3 Notes + +If you get error when you use sqlite3 connection, you may need to rebuild sqlite3 on your environment. + +VSC extension provides the command to rebuild it.(Name: `Rebuild SQLite3 Client`) +![image](https://user-images.githubusercontent.com/4954534/85928359-ef952180-b8de-11ea-8cb3-7a9a509cd6d7.png) + +If you're using sql-language-server directly, after go to the directry of it and call `npm rebuild sqlite` to rebuild it. + + #### Lint You can use lint rules that are provided [sqlint](https://github.com/joe-re/sql-language-server/blob/master/packages/sqlint/README.md).