From b9556909f592f53bc3ecadec02fc6c13b59479e8 Mon Sep 17 00:00:00 2001 From: Tim Clark Date: Wed, 17 Jan 2024 10:55:25 -0500 Subject: [PATCH 1/2] Add MSSQL support --- README.md | 4 ++ package.json | 19 ++++++-- src/cli/constants.ts | 8 +++- src/core/connection-string-parser.ts | 4 ++ src/core/dialect-manager.ts | 6 ++- src/dialects/index.ts | 2 +- src/dialects/mssql/index.ts | 2 + src/dialects/mssql/mssql-adapter.ts | 38 +++++++++++++++ src/dialects/mssql/mssql-dialect.ts | 61 ++++++++++++++++++++++++ src/dialects/mssql/mssql-introspector.ts | 10 ++++ 10 files changed, 148 insertions(+), 6 deletions(-) create mode 100644 src/dialects/mssql/index.ts create mode 100644 src/dialects/mssql/mssql-adapter.ts create mode 100644 src/dialects/mssql/mssql-dialect.ts create mode 100644 src/dialects/mssql/mssql-introspector.ts diff --git a/README.md b/README.md index a49fee9..ee0c29c 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ npm install kysely pg npm install kysely mysql2 npm install kysely better-sqlite3 npm install @libsql/kysely-libsql +npm install kysely tedious tarn @tediousjs/connection-string ``` ## Generating type definitions @@ -36,6 +37,9 @@ DATABASE_URL=C:/Program Files/sqlite3/db # LibSQL DATABASE_URL=libsql://token@host:port/database + +# MSSQL +DATABASE_URL=Server=mssql;Database=database;User Id=user;Password=password ``` > If your URL contains a password with special characters, those characters may need to be [percent-encoded](https://en.wikipedia.org/wiki/Percent-encoding#Reserved_characters). diff --git a/package.json b/package.json index c7702c8..a632cbb 100644 --- a/package.json +++ b/package.json @@ -49,10 +49,11 @@ "@types/minimist": "^1.2.5", "@types/node": "^20.10.3", "@types/pg": "^8.10.9", + "@types/tedious": "4.0.14", "@typescript-eslint/parser": "^6.13.1", "better-sqlite3": "^9.2.0", "eslint": "^8.55.0", - "kysely": "^0.26.3", + "kysely": "^0.27.0", "mysql2": "^3.6.5", "npm-run-all": "^4.1.5", "pg": "^8.11.3", @@ -62,16 +63,22 @@ }, "peerDependencies": { "@libsql/kysely-libsql": "^0.3.0", + "@tediousjs/connection-string": "^0.5.0", "better-sqlite3": ">=7.6.2", "kysely-bun-worker": "^0.5.3", - "kysely": ">=0.19.12", + "kysely": "^0.27.0", "mysql2": "^2.3.3 || ^3.0.0", - "pg": "^8.8.0" + "pg": "^8.8.0", + "tarn": "^3.0.0", + "tedious": "^16.6.0" }, "peerDependenciesMeta": { "@libsql/kysely-libsql": { "optional": true }, + "@tediousjs/connection-string": { + "optional": true + }, "better-sqlite3": { "optional": true }, @@ -83,6 +90,12 @@ }, "pg": { "optional": true + }, + "tarn": { + "optional": true + }, + "tedious": { + "optional": true } }, "eslintConfig": { diff --git a/src/cli/constants.ts b/src/cli/constants.ts index 2431aca..0af0dc2 100644 --- a/src/cli/constants.ts +++ b/src/cli/constants.ts @@ -21,4 +21,10 @@ export const LOG_LEVEL_NAMES = [ 'debug', ] as const; -export const VALID_DIALECTS = ['mysql', 'postgres', 'sqlite', 'libsql']; +export const VALID_DIALECTS = [ + 'mysql', + 'postgres', + 'sqlite', + 'libsql', + 'mssql', +]; diff --git a/src/core/connection-string-parser.ts b/src/core/connection-string-parser.ts index 330dca7..23ee9f3 100644 --- a/src/core/connection-string-parser.ts +++ b/src/core/connection-string-parser.ts @@ -41,6 +41,10 @@ export class ConnectionStringParser { return 'postgres'; } + if (connectionString.toLowerCase().includes('user id=')) { + return 'mssql'; + } + return 'sqlite'; } diff --git a/src/core/dialect-manager.ts b/src/core/dialect-manager.ts index 5fc5d95..c7fb97f 100644 --- a/src/core/dialect-manager.ts +++ b/src/core/dialect-manager.ts @@ -1,6 +1,7 @@ import { BunSqliteDialect, LibsqlDialect, + MssqlDialect, MysqlDialect, PostgresDialect, SqliteDialect, @@ -12,7 +13,8 @@ export type DialectName = | 'libsql' | 'mysql' | 'postgres' - | 'sqlite'; + | 'sqlite' + | 'mssql'; /** * Returns a dialect instance for a pre-defined dialect name. @@ -28,6 +30,8 @@ export class DialectManager { return new MysqlDialect(); case 'postgres': return new PostgresDialect(); + case 'mssql': + return new MssqlDialect(); default: return new SqliteDialect(); } diff --git a/src/dialects/index.ts b/src/dialects/index.ts index b4ddf33..0c0ed8a 100644 --- a/src/dialects/index.ts +++ b/src/dialects/index.ts @@ -1,6 +1,6 @@ export * from './bun-sqlite'; export * from './libsql'; +export * from './mssql'; export * from './mysql'; export * from './postgres'; export * from './sqlite'; - diff --git a/src/dialects/mssql/index.ts b/src/dialects/mssql/index.ts new file mode 100644 index 0000000..8f966e2 --- /dev/null +++ b/src/dialects/mssql/index.ts @@ -0,0 +1,2 @@ +export * from './mssql-adapter'; +export * from './mssql-dialect'; diff --git a/src/dialects/mssql/mssql-adapter.ts b/src/dialects/mssql/mssql-adapter.ts new file mode 100644 index 0000000..f8cfb4e --- /dev/null +++ b/src/dialects/mssql/mssql-adapter.ts @@ -0,0 +1,38 @@ +import { IdentifierNode } from '../../ast'; +import { Adapter } from '../../core'; + +export class MssqlAdapter extends Adapter { + // https://github.com/tediousjs/tedious/tree/master/src/data-types + override readonly scalars = { + bigint: new IdentifierNode('number'), + binary: new IdentifierNode('Buffer'), + bit: new IdentifierNode('Buffer'), + char: new IdentifierNode('string'), + date: new IdentifierNode('Date'), + datetime: new IdentifierNode('Date'), + datetime2: new IdentifierNode('Date'), + datetimeoffset: new IdentifierNode('Date'), + decimal: new IdentifierNode('number'), + number: new IdentifierNode('number'), + double: new IdentifierNode('number'), + float: new IdentifierNode('number'), + int: new IdentifierNode('number'), + image: new IdentifierNode('Buffer'), + money: new IdentifierNode('number'), + nchar: new IdentifierNode('string'), + ntext: new IdentifierNode('string'), + numeric: new IdentifierNode('number'), + nvarchar: new IdentifierNode('string'), + real: new IdentifierNode('number'), + smalldatetime: new IdentifierNode('Date'), + smallint: new IdentifierNode('number'), + smallmoney: new IdentifierNode('number'), + text: new IdentifierNode('string'), + time: new IdentifierNode('Date'), + tinyint: new IdentifierNode('number'), + tvp: new IdentifierNode('unknown'), + uniqueidentifier: new IdentifierNode('string'), + varbinary: new IdentifierNode('Buffer'), + varchar: new IdentifierNode('string'), + }; +} diff --git a/src/dialects/mssql/mssql-dialect.ts b/src/dialects/mssql/mssql-dialect.ts new file mode 100644 index 0000000..a87fa31 --- /dev/null +++ b/src/dialects/mssql/mssql-dialect.ts @@ -0,0 +1,61 @@ +import { MssqlDialect as KyselyMssqlDialect } from 'kysely'; +import { CreateKyselyDialectOptions, Dialect } from '../../core'; +import { MssqlAdapter } from './mssql-adapter'; +import { MssqlIntrospector } from './mssql-introspector'; + +export class MssqlDialect extends Dialect { + readonly adapter = new MssqlAdapter(); + readonly introspector = new MssqlIntrospector(); + + async createKyselyDialect(options: CreateKyselyDialectOptions) { + const tedious = await import('tedious'); + const tarn = await import('tarn'); + const { parseConnectionString } = await import( + '@tediousjs/connection-string' + ); + + let server = ''; + let port = 1433; + + const connectionParams: any = parseConnectionString( + options.connectionString, + ); + + server = connectionParams.server; + // https://www.connectionstrings.com/microsoft-data-sqlclient/using-a-non-standard-port/ + if (server.includes(',')) { + const [serverName, portString] = server.split(',') as [string, string]; + server = serverName; + port = parseInt(portString, 10); + } + + return new KyselyMssqlDialect({ + tarn: { + ...tarn, + options: { + min: 0, + max: 1, + }, + }, + tedious: { + ...tedious, + connectionFactory: () => + new tedious.Connection({ + authentication: { + type: 'default', + options: { + password: connectionParams.password, + userName: connectionParams['user id'], + }, + }, + options: { + database: connectionParams.database, + port, + trustServerCertificate: true, + }, + server, + }), + }, + }); + } +} diff --git a/src/dialects/mssql/mssql-introspector.ts b/src/dialects/mssql/mssql-introspector.ts new file mode 100644 index 0000000..0762d53 --- /dev/null +++ b/src/dialects/mssql/mssql-introspector.ts @@ -0,0 +1,10 @@ +import { DatabaseMetadata, EnumCollection } from '../../core'; +import { IntrospectOptions, Introspector } from '../../introspector'; + +export class MssqlIntrospector extends Introspector { + async introspect(options: IntrospectOptions) { + const tables = await this.getTables(options); + const enums = new EnumCollection(); + return new DatabaseMetadata(tables, enums); + } +} From ad0d665695545f43b1a311fa3e1f71c8b25dee85 Mon Sep 17 00:00:00 2001 From: Tim Date: Sun, 4 Feb 2024 14:29:11 -0500 Subject: [PATCH 2/2] resolve conflicts --- package.json | 15 ++++++++------- src/cli/constants.ts | 1 + 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index a632cbb..adefc25 100644 --- a/package.json +++ b/package.json @@ -47,14 +47,15 @@ "@types/git-diff": "^2.0.7", "@types/micromatch": "^4.0.6", "@types/minimist": "^1.2.5", - "@types/node": "^20.10.3", - "@types/pg": "^8.10.9", + "@types/node": "^20.11.16", + "@types/pg": "^8.11.0", "@types/tedious": "4.0.14", - "@typescript-eslint/parser": "^6.13.1", - "better-sqlite3": "^9.2.0", - "eslint": "^8.55.0", - "kysely": "^0.27.0", - "mysql2": "^3.6.5", + "@typescript-eslint/parser": "^6.20.0", + "better-sqlite3": "^9.3.0", + "eslint": "^8.56.0", + "kysely-bun-worker": "^0.5.6", + "kysely": "^0.27.2", + "mysql2": "^3.9.1", "npm-run-all": "^4.1.5", "pg": "^8.11.3", "pnpm": "^8.11.0", diff --git a/src/cli/constants.ts b/src/cli/constants.ts index 0af0dc2..76277c9 100644 --- a/src/cli/constants.ts +++ b/src/cli/constants.ts @@ -26,5 +26,6 @@ export const VALID_DIALECTS = [ 'postgres', 'sqlite', 'libsql', + 'bun-sqlite', 'mssql', ];