@kysera/dialects 0.8.2 → 0.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +787 -71
- package/dist/index.js +62 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/adapters/mssql.ts +203 -57
- package/src/adapters/mysql.ts +105 -36
- package/src/adapters/postgres.ts +619 -28
- package/src/adapters/sqlite.ts +126 -36
- package/src/factory.ts +71 -17
- package/src/helpers.ts +405 -18
- package/src/index.ts +85 -9
- package/src/types.ts +103 -9
package/src/adapters/mssql.ts
CHANGED
|
@@ -2,15 +2,33 @@
|
|
|
2
2
|
* Microsoft SQL Server Dialect Adapter
|
|
3
3
|
*
|
|
4
4
|
* Supports SQL Server 2017+, Azure SQL Database, and Azure SQL Edge
|
|
5
|
+
* with full schema support (default: 'dbo')
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import type { Kysely } from 'kysely'
|
|
8
9
|
import { sql } from 'kysely'
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
10
|
+
import { silentLogger, type KyseraLogger } from '@kysera/core'
|
|
11
|
+
import type { DialectAdapter, DialectAdapterOptions, SchemaOptions } from '../types.js'
|
|
12
|
+
import { assertValidIdentifier, resolveSchema as resolveSchemaUtil, errorMatchers } from '../helpers.js'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* MSSQL-specific adapter options
|
|
16
|
+
*/
|
|
17
|
+
export interface MSSQLAdapterOptions extends DialectAdapterOptions {
|
|
18
|
+
/** Logger instance for error reporting */
|
|
19
|
+
logger?: KyseraLogger
|
|
20
|
+
}
|
|
11
21
|
|
|
12
22
|
export class MSSQLAdapter implements DialectAdapter {
|
|
13
23
|
readonly dialect = 'mssql' as const
|
|
24
|
+
readonly defaultSchema: string
|
|
25
|
+
private logger: KyseraLogger
|
|
26
|
+
|
|
27
|
+
constructor(options: MSSQLAdapterOptions = {}) {
|
|
28
|
+
// MSSQL uses 'dbo' as the default schema
|
|
29
|
+
this.defaultSchema = options.defaultSchema ?? 'dbo'
|
|
30
|
+
this.logger = options.logger ?? silentLogger
|
|
31
|
+
}
|
|
14
32
|
|
|
15
33
|
getDefaultPort(): number {
|
|
16
34
|
return 1433
|
|
@@ -31,51 +49,39 @@ export class MSSQLAdapter implements DialectAdapter {
|
|
|
31
49
|
}
|
|
32
50
|
|
|
33
51
|
isUniqueConstraintError(error: unknown): boolean {
|
|
34
|
-
|
|
35
|
-
const message = e.message?.toLowerCase() || ''
|
|
36
|
-
const code = e.code || ''
|
|
37
|
-
// MSSQL error 2627: Violation of PRIMARY KEY/UNIQUE constraint
|
|
38
|
-
// MSSQL error 2601: Cannot insert duplicate key row
|
|
39
|
-
return (
|
|
40
|
-
code === '2627' ||
|
|
41
|
-
code === '2601' ||
|
|
42
|
-
message.includes('violation of unique key constraint') ||
|
|
43
|
-
message.includes('cannot insert duplicate key') ||
|
|
44
|
-
message.includes('unique constraint')
|
|
45
|
-
)
|
|
52
|
+
return errorMatchers.mssql.uniqueConstraint(error)
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
isForeignKeyError(error: unknown): boolean {
|
|
49
|
-
|
|
50
|
-
const message = e.message?.toLowerCase() || ''
|
|
51
|
-
const code = e.code || ''
|
|
52
|
-
// MSSQL error 547: FOREIGN KEY constraint violation
|
|
53
|
-
return (
|
|
54
|
-
code === '547' ||
|
|
55
|
-
message.includes('foreign key constraint') ||
|
|
56
|
-
message.includes('conflicted with the foreign key')
|
|
57
|
-
)
|
|
56
|
+
return errorMatchers.mssql.foreignKey(error)
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
isNotNullError(error: unknown): boolean {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async tableExists(
|
|
60
|
+
return errorMatchers.mssql.notNull(error)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Resolve the schema to use for an operation.
|
|
65
|
+
* Uses the shared resolveSchema utility from helpers.ts.
|
|
66
|
+
*/
|
|
67
|
+
private resolveSchema(options?: SchemaOptions): string {
|
|
68
|
+
return resolveSchemaUtil(this.defaultSchema, options)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async tableExists(
|
|
72
|
+
db: Kysely<any>,
|
|
73
|
+
tableName: string,
|
|
74
|
+
options?: SchemaOptions
|
|
75
|
+
): Promise<boolean> {
|
|
73
76
|
assertValidIdentifier(tableName, 'table name')
|
|
77
|
+
const schema = this.resolveSchema(options)
|
|
78
|
+
|
|
74
79
|
try {
|
|
75
80
|
const result = await db
|
|
76
81
|
.selectFrom('INFORMATION_SCHEMA.TABLES')
|
|
77
82
|
.select('TABLE_NAME')
|
|
78
83
|
.where('TABLE_NAME', '=', tableName)
|
|
84
|
+
.where('TABLE_SCHEMA', '=', schema)
|
|
79
85
|
.where('TABLE_TYPE', '=', 'BASE TABLE')
|
|
80
86
|
.executeTakeFirst()
|
|
81
87
|
return !!result
|
|
@@ -84,13 +90,20 @@ export class MSSQLAdapter implements DialectAdapter {
|
|
|
84
90
|
}
|
|
85
91
|
}
|
|
86
92
|
|
|
87
|
-
async getTableColumns(
|
|
93
|
+
async getTableColumns(
|
|
94
|
+
db: Kysely<any>,
|
|
95
|
+
tableName: string,
|
|
96
|
+
options?: SchemaOptions
|
|
97
|
+
): Promise<string[]> {
|
|
88
98
|
assertValidIdentifier(tableName, 'table name')
|
|
99
|
+
const schema = this.resolveSchema(options)
|
|
100
|
+
|
|
89
101
|
try {
|
|
90
102
|
const results = await db
|
|
91
103
|
.selectFrom('INFORMATION_SCHEMA.COLUMNS')
|
|
92
104
|
.select('COLUMN_NAME')
|
|
93
105
|
.where('TABLE_NAME', '=', tableName)
|
|
106
|
+
.where('TABLE_SCHEMA', '=', schema)
|
|
94
107
|
.execute()
|
|
95
108
|
return results.map(r => (r as { COLUMN_NAME: string }).COLUMN_NAME)
|
|
96
109
|
} catch {
|
|
@@ -98,13 +111,15 @@ export class MSSQLAdapter implements DialectAdapter {
|
|
|
98
111
|
}
|
|
99
112
|
}
|
|
100
113
|
|
|
101
|
-
async getTables(db: Kysely<any
|
|
114
|
+
async getTables(db: Kysely<any>, options?: SchemaOptions): Promise<string[]> {
|
|
115
|
+
const schema = this.resolveSchema(options)
|
|
116
|
+
|
|
102
117
|
try {
|
|
103
118
|
const results = await db
|
|
104
119
|
.selectFrom('INFORMATION_SCHEMA.TABLES')
|
|
105
120
|
.select('TABLE_NAME')
|
|
106
121
|
.where('TABLE_TYPE', '=', 'BASE TABLE')
|
|
107
|
-
.where('TABLE_SCHEMA', '=',
|
|
122
|
+
.where('TABLE_SCHEMA', '=', schema)
|
|
108
123
|
.execute()
|
|
109
124
|
return results.map(r => (r as { TABLE_NAME: string }).TABLE_NAME)
|
|
110
125
|
} catch {
|
|
@@ -127,24 +142,28 @@ export class MSSQLAdapter implements DialectAdapter {
|
|
|
127
142
|
}
|
|
128
143
|
}
|
|
129
144
|
|
|
130
|
-
async truncateTable(
|
|
145
|
+
async truncateTable(
|
|
146
|
+
db: Kysely<any>,
|
|
147
|
+
tableName: string,
|
|
148
|
+
options?: SchemaOptions
|
|
149
|
+
): Promise<boolean> {
|
|
131
150
|
assertValidIdentifier(tableName, 'table name')
|
|
151
|
+
const schema = this.resolveSchema(options)
|
|
152
|
+
|
|
132
153
|
try {
|
|
154
|
+
const qualifiedTable = `${this.escapeIdentifier(schema)}.${this.escapeIdentifier(tableName)}`
|
|
155
|
+
|
|
133
156
|
// MSSQL: First try TRUNCATE, fall back to DELETE if FK constraints exist
|
|
134
157
|
try {
|
|
135
|
-
await sql.raw(`TRUNCATE TABLE ${
|
|
158
|
+
await sql.raw(`TRUNCATE TABLE ${qualifiedTable}`).execute(db)
|
|
136
159
|
} catch (truncateError) {
|
|
137
160
|
// If truncate fails due to FK, use DELETE
|
|
138
161
|
const errorMsg = String(truncateError)
|
|
139
162
|
if (errorMsg.includes('FOREIGN KEY') || errorMsg.includes('Cannot truncate')) {
|
|
140
|
-
await sql.raw(`DELETE FROM ${
|
|
163
|
+
await sql.raw(`DELETE FROM ${qualifiedTable}`).execute(db)
|
|
141
164
|
// Reset identity if table has one
|
|
142
165
|
try {
|
|
143
|
-
|
|
144
|
-
const escapedTableName = this.escapeIdentifier(tableName)
|
|
145
|
-
await sql
|
|
146
|
-
.raw(`DBCC CHECKIDENT (${escapedTableName}, RESEED, 0)`)
|
|
147
|
-
.execute(db)
|
|
166
|
+
await sql.raw(`DBCC CHECKIDENT (${qualifiedTable}, RESEED, 0)`).execute(db)
|
|
148
167
|
} catch {
|
|
149
168
|
// Ignore if table doesn't have identity column
|
|
150
169
|
}
|
|
@@ -161,21 +180,26 @@ export class MSSQLAdapter implements DialectAdapter {
|
|
|
161
180
|
) {
|
|
162
181
|
return false
|
|
163
182
|
}
|
|
164
|
-
//
|
|
165
|
-
|
|
183
|
+
// Log and rethrow with context
|
|
184
|
+
this.logger.error(`Failed to truncate MSSQL table "${schema}.${tableName}":`, error)
|
|
185
|
+
throw new Error(`Failed to truncate MSSQL table "${schema}.${tableName}": ${String(error)}`)
|
|
166
186
|
}
|
|
167
187
|
}
|
|
168
188
|
|
|
169
|
-
async truncateAllTables(
|
|
170
|
-
|
|
189
|
+
async truncateAllTables(
|
|
190
|
+
db: Kysely<any>,
|
|
191
|
+
exclude: string[] = [],
|
|
192
|
+
options?: SchemaOptions
|
|
193
|
+
): Promise<void> {
|
|
194
|
+
const tables = await this.getTables(db, options)
|
|
195
|
+
const schema = this.resolveSchema(options)
|
|
171
196
|
|
|
172
197
|
// MSSQL: Disable all FK constraints first
|
|
173
198
|
for (const table of tables) {
|
|
174
199
|
if (!exclude.includes(table)) {
|
|
175
200
|
try {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
.execute(db)
|
|
201
|
+
const qualifiedTable = `${this.escapeIdentifier(schema)}.${this.escapeIdentifier(table)}`
|
|
202
|
+
await sql.raw(`ALTER TABLE ${qualifiedTable} NOCHECK CONSTRAINT ALL`).execute(db)
|
|
179
203
|
} catch {
|
|
180
204
|
// Ignore errors for tables without constraints
|
|
181
205
|
}
|
|
@@ -185,7 +209,7 @@ export class MSSQLAdapter implements DialectAdapter {
|
|
|
185
209
|
// Truncate all tables
|
|
186
210
|
for (const table of tables) {
|
|
187
211
|
if (!exclude.includes(table)) {
|
|
188
|
-
await this.truncateTable(db, table)
|
|
212
|
+
await this.truncateTable(db, table, options)
|
|
189
213
|
}
|
|
190
214
|
}
|
|
191
215
|
|
|
@@ -193,15 +217,137 @@ export class MSSQLAdapter implements DialectAdapter {
|
|
|
193
217
|
for (const table of tables) {
|
|
194
218
|
if (!exclude.includes(table)) {
|
|
195
219
|
try {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
.execute(db)
|
|
220
|
+
const qualifiedTable = `${this.escapeIdentifier(schema)}.${this.escapeIdentifier(table)}`
|
|
221
|
+
await sql.raw(`ALTER TABLE ${qualifiedTable} CHECK CONSTRAINT ALL`).execute(db)
|
|
199
222
|
} catch {
|
|
200
223
|
// Ignore errors
|
|
201
224
|
}
|
|
202
225
|
}
|
|
203
226
|
}
|
|
204
227
|
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Check if a schema exists in the database
|
|
231
|
+
*
|
|
232
|
+
* @param db - Kysely database instance
|
|
233
|
+
* @param schemaName - Name of the schema to check
|
|
234
|
+
* @returns true if schema exists, false otherwise
|
|
235
|
+
*/
|
|
236
|
+
async schemaExists(db: Kysely<any>, schemaName: string): Promise<boolean> {
|
|
237
|
+
assertValidIdentifier(schemaName, 'schema name')
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
const result = await db
|
|
241
|
+
.selectFrom('INFORMATION_SCHEMA.SCHEMATA')
|
|
242
|
+
.select('SCHEMA_NAME')
|
|
243
|
+
.where('SCHEMA_NAME', '=', schemaName)
|
|
244
|
+
.executeTakeFirst()
|
|
245
|
+
return !!result
|
|
246
|
+
} catch {
|
|
247
|
+
return false
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Get all schemas in the database (excluding system schemas)
|
|
253
|
+
*
|
|
254
|
+
* @param db - Kysely database instance
|
|
255
|
+
* @returns Array of schema names
|
|
256
|
+
*/
|
|
257
|
+
async getSchemas(db: Kysely<any>): Promise<string[]> {
|
|
258
|
+
try {
|
|
259
|
+
const results = await db
|
|
260
|
+
.selectFrom('INFORMATION_SCHEMA.SCHEMATA')
|
|
261
|
+
.select('SCHEMA_NAME')
|
|
262
|
+
.where('SCHEMA_NAME', 'not in', [
|
|
263
|
+
'INFORMATION_SCHEMA',
|
|
264
|
+
'sys',
|
|
265
|
+
'guest',
|
|
266
|
+
'db_owner',
|
|
267
|
+
'db_accessadmin',
|
|
268
|
+
'db_securityadmin',
|
|
269
|
+
'db_ddladmin',
|
|
270
|
+
'db_backupoperator',
|
|
271
|
+
'db_datareader',
|
|
272
|
+
'db_datawriter',
|
|
273
|
+
'db_denydatareader',
|
|
274
|
+
'db_denydatawriter'
|
|
275
|
+
])
|
|
276
|
+
.execute()
|
|
277
|
+
return results.map(r => (r as { SCHEMA_NAME: string }).SCHEMA_NAME)
|
|
278
|
+
} catch {
|
|
279
|
+
return []
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Create a new schema in the database
|
|
285
|
+
*
|
|
286
|
+
* @param db - Kysely database instance
|
|
287
|
+
* @param schemaName - Name of the schema to create
|
|
288
|
+
* @returns true if schema was created, false if it already exists
|
|
289
|
+
*/
|
|
290
|
+
async createSchema(db: Kysely<any>, schemaName: string): Promise<boolean> {
|
|
291
|
+
assertValidIdentifier(schemaName, 'schema name')
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
await sql.raw(`CREATE SCHEMA ${this.escapeIdentifier(schemaName)}`).execute(db)
|
|
295
|
+
return true
|
|
296
|
+
} catch (error) {
|
|
297
|
+
const errorMessage = String(error)
|
|
298
|
+
if (errorMessage.includes('already exists')) {
|
|
299
|
+
return false
|
|
300
|
+
}
|
|
301
|
+
this.logger.error(`Failed to create schema "${schemaName}":`, error)
|
|
302
|
+
throw error
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Drop a schema from the database
|
|
308
|
+
*
|
|
309
|
+
* @param db - Kysely database instance
|
|
310
|
+
* @param schemaName - Name of the schema to drop
|
|
311
|
+
* @returns true if schema was dropped, false if it doesn't exist
|
|
312
|
+
*/
|
|
313
|
+
async dropSchema(db: Kysely<any>, schemaName: string): Promise<boolean> {
|
|
314
|
+
assertValidIdentifier(schemaName, 'schema name')
|
|
315
|
+
|
|
316
|
+
// Prevent dropping protected schemas
|
|
317
|
+
const protectedSchemas = ['dbo', 'sys', 'INFORMATION_SCHEMA', 'guest']
|
|
318
|
+
if (protectedSchemas.includes(schemaName)) {
|
|
319
|
+
throw new Error(`Cannot drop protected schema: ${schemaName}`)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
await sql.raw(`DROP SCHEMA ${this.escapeIdentifier(schemaName)}`).execute(db)
|
|
324
|
+
return true
|
|
325
|
+
} catch (error) {
|
|
326
|
+
const errorMessage = String(error)
|
|
327
|
+
if (errorMessage.includes('does not exist') || errorMessage.includes('Cannot find')) {
|
|
328
|
+
return false
|
|
329
|
+
}
|
|
330
|
+
this.logger.error(`Failed to drop schema "${schemaName}":`, error)
|
|
331
|
+
throw error
|
|
332
|
+
}
|
|
333
|
+
}
|
|
205
334
|
}
|
|
206
335
|
|
|
336
|
+
/**
|
|
337
|
+
* Default MSSQL adapter instance with 'dbo' schema
|
|
338
|
+
*/
|
|
207
339
|
export const mssqlAdapter = new MSSQLAdapter()
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Create a new MSSQL adapter with custom configuration
|
|
343
|
+
*
|
|
344
|
+
* @param options - Adapter configuration options
|
|
345
|
+
* @returns Configured MSSQLAdapter instance
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* // Create adapter with custom default schema
|
|
349
|
+
* const adapter = createMSSQLAdapter({ defaultSchema: 'app' })
|
|
350
|
+
*/
|
|
351
|
+
export function createMSSQLAdapter(options?: MSSQLAdapterOptions): MSSQLAdapter {
|
|
352
|
+
return new MSSQLAdapter(options)
|
|
353
|
+
}
|
package/src/adapters/mysql.ts
CHANGED
|
@@ -1,19 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MySQL Dialect Adapter
|
|
3
|
+
*
|
|
4
|
+
* Note: In MySQL, "schema" and "database" are synonymous.
|
|
5
|
+
* The schema option maps to the current database context.
|
|
3
6
|
*/
|
|
4
7
|
|
|
5
8
|
import type { Kysely } from 'kysely'
|
|
6
9
|
import { sql } from 'kysely'
|
|
7
10
|
import { silentLogger, type KyseraLogger } from '@kysera/core'
|
|
8
|
-
import type { DialectAdapter,
|
|
9
|
-
import { assertValidIdentifier } from '../helpers.js'
|
|
11
|
+
import type { DialectAdapter, DialectAdapterOptions, SchemaOptions } from '../types.js'
|
|
12
|
+
import { assertValidIdentifier, errorMatchers } from '../helpers.js'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* MySQL-specific adapter options
|
|
16
|
+
*/
|
|
17
|
+
export interface MySQLAdapterOptions extends DialectAdapterOptions {
|
|
18
|
+
/** Logger instance for error reporting */
|
|
19
|
+
logger?: KyseraLogger
|
|
20
|
+
}
|
|
10
21
|
|
|
11
22
|
export class MySQLAdapter implements DialectAdapter {
|
|
12
23
|
readonly dialect = 'mysql' as const
|
|
24
|
+
readonly defaultSchema: string
|
|
13
25
|
private logger: KyseraLogger
|
|
14
26
|
|
|
15
|
-
constructor(
|
|
16
|
-
|
|
27
|
+
constructor(options: MySQLAdapterOptions = {}) {
|
|
28
|
+
// In MySQL, defaultSchema is the database name
|
|
29
|
+
// Empty string means use current database (DATABASE())
|
|
30
|
+
this.defaultSchema = options.defaultSchema ?? ''
|
|
31
|
+
this.logger = options.logger ?? silentLogger
|
|
17
32
|
}
|
|
18
33
|
|
|
19
34
|
getDefaultPort(): number {
|
|
@@ -34,67 +49,91 @@ export class MySQLAdapter implements DialectAdapter {
|
|
|
34
49
|
}
|
|
35
50
|
|
|
36
51
|
isUniqueConstraintError(error: unknown): boolean {
|
|
37
|
-
|
|
38
|
-
const message = e.message?.toLowerCase() || ''
|
|
39
|
-
const code = e.code || ''
|
|
40
|
-
return code === 'ER_DUP_ENTRY' || code === '1062' || message.includes('duplicate entry')
|
|
52
|
+
return errorMatchers.mysql.uniqueConstraint(error)
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
isForeignKeyError(error: unknown): boolean {
|
|
44
|
-
|
|
45
|
-
const code = e.code || ''
|
|
46
|
-
return (
|
|
47
|
-
code === 'ER_ROW_IS_REFERENCED' ||
|
|
48
|
-
code === '1451' ||
|
|
49
|
-
code === 'ER_NO_REFERENCED_ROW' ||
|
|
50
|
-
code === '1452'
|
|
51
|
-
)
|
|
56
|
+
return errorMatchers.mysql.foreignKey(error)
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
isNotNullError(error: unknown): boolean {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
return errorMatchers.mysql.notNull(error)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get the schema (database) filter for queries.
|
|
65
|
+
* In MySQL, schema = database, so we use DATABASE() if not specified.
|
|
66
|
+
*/
|
|
67
|
+
private getSchemaFilter(options?: SchemaOptions): ReturnType<typeof sql> | string {
|
|
68
|
+
const schema = options?.schema ?? this.defaultSchema
|
|
69
|
+
if (schema) {
|
|
70
|
+
assertValidIdentifier(schema, 'schema/database name')
|
|
71
|
+
return schema
|
|
72
|
+
}
|
|
73
|
+
return sql`DATABASE()`
|
|
58
74
|
}
|
|
59
75
|
|
|
60
|
-
async tableExists(
|
|
76
|
+
async tableExists(
|
|
77
|
+
db: Kysely<any>,
|
|
78
|
+
tableName: string,
|
|
79
|
+
options?: SchemaOptions
|
|
80
|
+
): Promise<boolean> {
|
|
61
81
|
assertValidIdentifier(tableName, 'table name')
|
|
82
|
+
const schemaFilter = this.getSchemaFilter(options)
|
|
83
|
+
|
|
62
84
|
try {
|
|
63
|
-
const
|
|
85
|
+
const query = db
|
|
64
86
|
.selectFrom('information_schema.tables')
|
|
65
87
|
.select('table_name')
|
|
66
88
|
.where('table_name', '=', tableName)
|
|
67
|
-
|
|
68
|
-
|
|
89
|
+
|
|
90
|
+
const result = typeof schemaFilter === 'string'
|
|
91
|
+
? await query.where('table_schema', '=', schemaFilter).executeTakeFirst()
|
|
92
|
+
: await query.where('table_schema', '=', schemaFilter).executeTakeFirst()
|
|
93
|
+
|
|
69
94
|
return !!result
|
|
70
95
|
} catch {
|
|
71
96
|
return false
|
|
72
97
|
}
|
|
73
98
|
}
|
|
74
99
|
|
|
75
|
-
async getTableColumns(
|
|
100
|
+
async getTableColumns(
|
|
101
|
+
db: Kysely<any>,
|
|
102
|
+
tableName: string,
|
|
103
|
+
options?: SchemaOptions
|
|
104
|
+
): Promise<string[]> {
|
|
76
105
|
assertValidIdentifier(tableName, 'table name')
|
|
106
|
+
const schemaFilter = this.getSchemaFilter(options)
|
|
107
|
+
|
|
77
108
|
try {
|
|
78
|
-
const
|
|
109
|
+
const query = db
|
|
79
110
|
.selectFrom('information_schema.columns')
|
|
80
111
|
.select('column_name')
|
|
81
112
|
.where('table_name', '=', tableName)
|
|
82
|
-
|
|
83
|
-
|
|
113
|
+
|
|
114
|
+
const results = typeof schemaFilter === 'string'
|
|
115
|
+
? await query.where('table_schema', '=', schemaFilter).execute()
|
|
116
|
+
: await query.where('table_schema', '=', schemaFilter).execute()
|
|
117
|
+
|
|
84
118
|
return results.map(r => r.column_name as string)
|
|
85
119
|
} catch {
|
|
86
120
|
return []
|
|
87
121
|
}
|
|
88
122
|
}
|
|
89
123
|
|
|
90
|
-
async getTables(db: Kysely<any
|
|
124
|
+
async getTables(db: Kysely<any>, options?: SchemaOptions): Promise<string[]> {
|
|
125
|
+
const schemaFilter = this.getSchemaFilter(options)
|
|
126
|
+
|
|
91
127
|
try {
|
|
92
|
-
const
|
|
128
|
+
const query = db
|
|
93
129
|
.selectFrom('information_schema.tables')
|
|
94
130
|
.select('table_name')
|
|
95
|
-
.where('table_schema', '=', sql`DATABASE()`)
|
|
96
131
|
.where('table_type', '=', 'BASE TABLE')
|
|
97
|
-
|
|
132
|
+
|
|
133
|
+
const results = typeof schemaFilter === 'string'
|
|
134
|
+
? await query.where('table_schema', '=', schemaFilter).execute()
|
|
135
|
+
: await query.where('table_schema', '=', schemaFilter).execute()
|
|
136
|
+
|
|
98
137
|
return results.map(r => r.table_name as string)
|
|
99
138
|
} catch {
|
|
100
139
|
return []
|
|
@@ -126,12 +165,21 @@ export class MySQLAdapter implements DialectAdapter {
|
|
|
126
165
|
}
|
|
127
166
|
}
|
|
128
167
|
|
|
129
|
-
async truncateTable(
|
|
168
|
+
async truncateTable(
|
|
169
|
+
db: Kysely<any>,
|
|
170
|
+
tableName: string,
|
|
171
|
+
options?: SchemaOptions
|
|
172
|
+
): Promise<boolean> {
|
|
130
173
|
assertValidIdentifier(tableName, 'table name')
|
|
174
|
+
const schema = options?.schema ?? this.defaultSchema
|
|
175
|
+
|
|
131
176
|
try {
|
|
132
177
|
await sql.raw('SET FOREIGN_KEY_CHECKS = 0').execute(db)
|
|
133
178
|
try {
|
|
134
|
-
|
|
179
|
+
const qualifiedTable = schema
|
|
180
|
+
? `${this.escapeIdentifier(schema)}.${this.escapeIdentifier(tableName)}`
|
|
181
|
+
: this.escapeIdentifier(tableName)
|
|
182
|
+
await sql.raw(`TRUNCATE TABLE ${qualifiedTable}`).execute(db)
|
|
135
183
|
return true
|
|
136
184
|
} finally {
|
|
137
185
|
// Always try to re-enable FK checks
|
|
@@ -152,14 +200,35 @@ export class MySQLAdapter implements DialectAdapter {
|
|
|
152
200
|
}
|
|
153
201
|
}
|
|
154
202
|
|
|
155
|
-
async truncateAllTables(
|
|
156
|
-
|
|
203
|
+
async truncateAllTables(
|
|
204
|
+
db: Kysely<any>,
|
|
205
|
+
exclude: string[] = [],
|
|
206
|
+
options?: SchemaOptions
|
|
207
|
+
): Promise<void> {
|
|
208
|
+
const tables = await this.getTables(db, options)
|
|
157
209
|
for (const table of tables) {
|
|
158
210
|
if (!exclude.includes(table)) {
|
|
159
|
-
await this.truncateTable(db, table)
|
|
211
|
+
await this.truncateTable(db, table, options)
|
|
160
212
|
}
|
|
161
213
|
}
|
|
162
214
|
}
|
|
163
215
|
}
|
|
164
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Default MySQL adapter instance
|
|
219
|
+
*/
|
|
165
220
|
export const mysqlAdapter = new MySQLAdapter()
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Create a new MySQL adapter with custom configuration
|
|
224
|
+
*
|
|
225
|
+
* @param options - Adapter configuration options
|
|
226
|
+
* @returns Configured MySQLAdapter instance
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* // Create adapter with specific database as default
|
|
230
|
+
* const adapter = createMySQLAdapter({ defaultSchema: 'my_database' })
|
|
231
|
+
*/
|
|
232
|
+
export function createMySQLAdapter(options?: MySQLAdapterOptions): MySQLAdapter {
|
|
233
|
+
return new MySQLAdapter(options)
|
|
234
|
+
}
|