@remix-run/data-table-sqlite 0.1.0 → 0.2.0
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/README.md +8 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/adapter.d.ts +87 -8
- package/dist/lib/adapter.d.ts.map +1 -1
- package/dist/lib/adapter.js +501 -20
- package/dist/lib/sql-compiler.d.ts +2 -7
- package/dist/lib/sql-compiler.d.ts.map +1 -1
- package/dist/lib/sql-compiler.js +50 -80
- package/package.json +6 -7
- package/src/index.ts +1 -1
- package/src/lib/adapter.ts +606 -38
- package/src/lib/sql-compiler.ts +66 -105
package/src/lib/adapter.ts
CHANGED
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AdapterCapabilityOverrides,
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
DataManipulationRequest,
|
|
4
|
+
DataMigrationRequest,
|
|
5
|
+
DataMigrationResult,
|
|
6
|
+
DataMigrationOperation,
|
|
7
|
+
DataManipulationResult,
|
|
8
|
+
DataManipulationOperation,
|
|
5
9
|
DatabaseAdapter,
|
|
10
|
+
ColumnDefinition,
|
|
11
|
+
SqlStatement,
|
|
12
|
+
TableRef,
|
|
6
13
|
TransactionOptions,
|
|
7
14
|
TransactionToken,
|
|
8
15
|
} from '@remix-run/data-table'
|
|
9
16
|
import { getTablePrimaryKey } from '@remix-run/data-table'
|
|
17
|
+
import {
|
|
18
|
+
isDataManipulationOperation as isDataManipulationOperationHelper,
|
|
19
|
+
quoteLiteral as quoteLiteralHelper,
|
|
20
|
+
quoteTableRef as quoteTableRefHelper,
|
|
21
|
+
} from '@remix-run/data-table/sql-helpers'
|
|
10
22
|
import type { Database as BetterSqliteDatabase, RunResult } from 'better-sqlite3'
|
|
11
23
|
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Better SQLite3 database handle accepted by the sqlite adapter.
|
|
16
|
-
*/
|
|
17
|
-
export type SqliteDatabaseConnection = BetterSqliteDatabase
|
|
24
|
+
import { compileSqliteOperation } from './sql-compiler.ts'
|
|
18
25
|
|
|
19
26
|
/**
|
|
20
27
|
* Sqlite adapter configuration.
|
|
@@ -27,56 +34,153 @@ export type SqliteDatabaseAdapterOptions = {
|
|
|
27
34
|
* `DatabaseAdapter` implementation for Better SQLite3.
|
|
28
35
|
*/
|
|
29
36
|
export class SqliteDatabaseAdapter implements DatabaseAdapter {
|
|
37
|
+
/**
|
|
38
|
+
* The SQL dialect identifier reported by this adapter.
|
|
39
|
+
*/
|
|
30
40
|
dialect = 'sqlite'
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Feature flags describing the sqlite behaviors supported by this adapter.
|
|
44
|
+
*/
|
|
31
45
|
capabilities
|
|
32
46
|
|
|
33
|
-
#database:
|
|
47
|
+
#database: BetterSqliteDatabase
|
|
34
48
|
#transactions = new Set<string>()
|
|
35
49
|
#transactionCounter = 0
|
|
36
50
|
|
|
37
|
-
constructor(database:
|
|
51
|
+
constructor(database: BetterSqliteDatabase, options?: SqliteDatabaseAdapterOptions) {
|
|
38
52
|
this.#database = database
|
|
39
53
|
this.capabilities = {
|
|
40
54
|
returning: options?.capabilities?.returning ?? true,
|
|
41
55
|
savepoints: options?.capabilities?.savepoints ?? true,
|
|
42
56
|
upsert: options?.capabilities?.upsert ?? true,
|
|
57
|
+
transactionalDdl: options?.capabilities?.transactionalDdl ?? true,
|
|
58
|
+
migrationLock: options?.capabilities?.migrationLock ?? false,
|
|
43
59
|
}
|
|
44
60
|
}
|
|
45
61
|
|
|
46
|
-
|
|
47
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Compiles a data or migration operation to sqlite SQL statements.
|
|
64
|
+
* @param operation Operation to compile.
|
|
65
|
+
* @returns Compiled SQL statements.
|
|
66
|
+
*/
|
|
67
|
+
compileSql(operation: DataManipulationOperation | DataMigrationOperation): SqlStatement[] {
|
|
68
|
+
if (isDataManipulationOperation(operation)) {
|
|
69
|
+
let compiled = compileSqliteOperation(operation)
|
|
70
|
+
return [{ text: compiled.text, values: compiled.values }]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return compileSqliteMigrationOperations(operation)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Executes a sqlite data-manipulation request.
|
|
78
|
+
* @param request Request to execute.
|
|
79
|
+
* @returns Execution result.
|
|
80
|
+
*/
|
|
81
|
+
async execute(request: DataManipulationRequest): Promise<DataManipulationResult> {
|
|
82
|
+
if (request.operation.kind === 'insertMany' && request.operation.values.length === 0) {
|
|
48
83
|
return {
|
|
49
84
|
affectedRows: 0,
|
|
50
85
|
insertId: undefined,
|
|
51
|
-
rows: request.
|
|
86
|
+
rows: request.operation.returning ? [] : undefined,
|
|
52
87
|
}
|
|
53
88
|
}
|
|
54
89
|
|
|
55
|
-
let statement =
|
|
90
|
+
let statement = this.compileSql(request.operation)[0]
|
|
56
91
|
let prepared = this.#database.prepare(statement.text)
|
|
57
92
|
|
|
58
93
|
if (prepared.reader) {
|
|
59
94
|
let rows = normalizeRows(prepared.all(...statement.values))
|
|
60
95
|
|
|
61
|
-
if (request.
|
|
96
|
+
if (request.operation.kind === 'count' || request.operation.kind === 'exists') {
|
|
62
97
|
rows = normalizeCountRows(rows)
|
|
63
98
|
}
|
|
64
99
|
|
|
65
100
|
return {
|
|
66
101
|
rows,
|
|
67
|
-
affectedRows: normalizeAffectedRowsForReader(request.
|
|
68
|
-
insertId: normalizeInsertIdForReader(request.
|
|
102
|
+
affectedRows: normalizeAffectedRowsForReader(request.operation.kind, rows),
|
|
103
|
+
insertId: normalizeInsertIdForReader(request.operation.kind, request.operation, rows),
|
|
69
104
|
}
|
|
70
105
|
}
|
|
71
106
|
|
|
72
107
|
let result = prepared.run(...statement.values)
|
|
73
108
|
|
|
74
109
|
return {
|
|
75
|
-
affectedRows: normalizeAffectedRowsForRun(request.
|
|
76
|
-
insertId: normalizeInsertIdForRun(request.
|
|
110
|
+
affectedRows: normalizeAffectedRowsForRun(request.operation.kind, result),
|
|
111
|
+
insertId: normalizeInsertIdForRun(request.operation.kind, request.operation, result),
|
|
77
112
|
}
|
|
78
113
|
}
|
|
79
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Executes sqlite migration operations.
|
|
117
|
+
* @param request Migration request to execute.
|
|
118
|
+
* @returns Migration result.
|
|
119
|
+
*/
|
|
120
|
+
async migrate(request: DataMigrationRequest): Promise<DataMigrationResult> {
|
|
121
|
+
let statements = this.compileSql(request.operation)
|
|
122
|
+
|
|
123
|
+
for (let statement of statements) {
|
|
124
|
+
let prepared = this.#database.prepare(statement.text)
|
|
125
|
+
prepared.run(...statement.values)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
affectedOperations: statements.length,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Checks whether a table exists in sqlite.
|
|
135
|
+
* @param table Table reference to inspect.
|
|
136
|
+
* @param transaction Optional transaction token.
|
|
137
|
+
* @returns `true` when the table exists.
|
|
138
|
+
*/
|
|
139
|
+
async hasTable(table: TableRef, transaction?: TransactionToken): Promise<boolean> {
|
|
140
|
+
if (transaction) {
|
|
141
|
+
this.#assertTransaction(transaction)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let masterTable = table.schema
|
|
145
|
+
? quoteIdentifier(table.schema) + '.sqlite_master'
|
|
146
|
+
: 'sqlite_master'
|
|
147
|
+
let statement = this.#database.prepare(
|
|
148
|
+
'select 1 from ' + masterTable + ' where type = ? and name = ? limit 1',
|
|
149
|
+
)
|
|
150
|
+
let row = statement.get('table', table.name)
|
|
151
|
+
return row !== undefined
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Checks whether a column exists in sqlite.
|
|
156
|
+
* @param table Table reference to inspect.
|
|
157
|
+
* @param column Column name to look up.
|
|
158
|
+
* @param transaction Optional transaction token.
|
|
159
|
+
* @returns `true` when the column exists.
|
|
160
|
+
*/
|
|
161
|
+
async hasColumn(
|
|
162
|
+
table: TableRef,
|
|
163
|
+
column: string,
|
|
164
|
+
transaction?: TransactionToken,
|
|
165
|
+
): Promise<boolean> {
|
|
166
|
+
if (transaction) {
|
|
167
|
+
this.#assertTransaction(transaction)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
let schemaPrefix = table.schema ? quoteIdentifier(table.schema) + '.' : ''
|
|
171
|
+
let statement = this.#database.prepare(
|
|
172
|
+
'pragma ' + schemaPrefix + 'table_info(' + quoteIdentifier(table.name) + ')',
|
|
173
|
+
)
|
|
174
|
+
let rows = statement.all() as Array<Record<string, unknown>>
|
|
175
|
+
|
|
176
|
+
return rows.some((row) => row.name === column)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Starts a sqlite transaction.
|
|
181
|
+
* @param options Transaction options.
|
|
182
|
+
* @returns Transaction token.
|
|
183
|
+
*/
|
|
80
184
|
async beginTransaction(options?: TransactionOptions): Promise<TransactionToken> {
|
|
81
185
|
if (options?.isolationLevel === 'read uncommitted') {
|
|
82
186
|
this.#database.pragma('read_uncommitted = true')
|
|
@@ -91,28 +195,56 @@ export class SqliteDatabaseAdapter implements DatabaseAdapter {
|
|
|
91
195
|
return token
|
|
92
196
|
}
|
|
93
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Commits an open sqlite transaction.
|
|
200
|
+
* @param token Transaction token to commit.
|
|
201
|
+
* @returns A promise that resolves when the transaction is committed.
|
|
202
|
+
*/
|
|
94
203
|
async commitTransaction(token: TransactionToken): Promise<void> {
|
|
95
204
|
this.#assertTransaction(token)
|
|
96
205
|
this.#database.exec('commit')
|
|
97
206
|
this.#transactions.delete(token.id)
|
|
98
207
|
}
|
|
99
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Rolls back an open sqlite transaction.
|
|
211
|
+
* @param token Transaction token to roll back.
|
|
212
|
+
* @returns A promise that resolves when the transaction is rolled back.
|
|
213
|
+
*/
|
|
100
214
|
async rollbackTransaction(token: TransactionToken): Promise<void> {
|
|
101
215
|
this.#assertTransaction(token)
|
|
102
216
|
this.#database.exec('rollback')
|
|
103
217
|
this.#transactions.delete(token.id)
|
|
104
218
|
}
|
|
105
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Creates a savepoint in an open sqlite transaction.
|
|
222
|
+
* @param token Transaction token to use.
|
|
223
|
+
* @param name Savepoint name.
|
|
224
|
+
* @returns A promise that resolves when the savepoint is created.
|
|
225
|
+
*/
|
|
106
226
|
async createSavepoint(token: TransactionToken, name: string): Promise<void> {
|
|
107
227
|
this.#assertTransaction(token)
|
|
108
228
|
this.#database.exec('savepoint ' + quoteIdentifier(name))
|
|
109
229
|
}
|
|
110
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Rolls back to a savepoint in an open sqlite transaction.
|
|
233
|
+
* @param token Transaction token to use.
|
|
234
|
+
* @param name Savepoint name.
|
|
235
|
+
* @returns A promise that resolves when the rollback completes.
|
|
236
|
+
*/
|
|
111
237
|
async rollbackToSavepoint(token: TransactionToken, name: string): Promise<void> {
|
|
112
238
|
this.#assertTransaction(token)
|
|
113
239
|
this.#database.exec('rollback to savepoint ' + quoteIdentifier(name))
|
|
114
240
|
}
|
|
115
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Releases a savepoint in an open sqlite transaction.
|
|
244
|
+
* @param token Transaction token to use.
|
|
245
|
+
* @param name Savepoint name.
|
|
246
|
+
* @returns A promise that resolves when the savepoint is released.
|
|
247
|
+
*/
|
|
116
248
|
async releaseSavepoint(token: TransactionToken, name: string): Promise<void> {
|
|
117
249
|
this.#assertTransaction(token)
|
|
118
250
|
this.#database.exec('release savepoint ' + quoteIdentifier(name))
|
|
@@ -130,9 +262,19 @@ export class SqliteDatabaseAdapter implements DatabaseAdapter {
|
|
|
130
262
|
* @param database Better SQLite3 database instance.
|
|
131
263
|
* @param options Optional adapter capability overrides.
|
|
132
264
|
* @returns A configured sqlite adapter.
|
|
265
|
+
* @example
|
|
266
|
+
* ```ts
|
|
267
|
+
* import BetterSqlite3 from 'better-sqlite3'
|
|
268
|
+
* import { createDatabase } from 'remix/data-table'
|
|
269
|
+
* import { createSqliteDatabaseAdapter } from 'remix/data-table-sqlite'
|
|
270
|
+
*
|
|
271
|
+
* let sqlite = new BetterSqlite3('./data/app.db')
|
|
272
|
+
* let adapter = createSqliteDatabaseAdapter(sqlite)
|
|
273
|
+
* let db = createDatabase(adapter)
|
|
274
|
+
* ```
|
|
133
275
|
*/
|
|
134
276
|
export function createSqliteDatabaseAdapter(
|
|
135
|
-
database:
|
|
277
|
+
database: BetterSqliteDatabase,
|
|
136
278
|
options?: SqliteDatabaseAdapterOptions,
|
|
137
279
|
): SqliteDatabaseAdapter {
|
|
138
280
|
return new SqliteDatabaseAdapter(database, options)
|
|
@@ -175,10 +317,10 @@ function normalizeCountRows(rows: Record<string, unknown>[]): Record<string, unk
|
|
|
175
317
|
}
|
|
176
318
|
|
|
177
319
|
function normalizeAffectedRowsForReader(
|
|
178
|
-
kind:
|
|
320
|
+
kind: DataManipulationRequest['operation']['kind'],
|
|
179
321
|
rows: Record<string, unknown>[],
|
|
180
322
|
): number | undefined {
|
|
181
|
-
if (
|
|
323
|
+
if (isWriteOperationKind(kind)) {
|
|
182
324
|
return rows.length
|
|
183
325
|
}
|
|
184
326
|
|
|
@@ -186,15 +328,15 @@ function normalizeAffectedRowsForReader(
|
|
|
186
328
|
}
|
|
187
329
|
|
|
188
330
|
function normalizeInsertIdForReader(
|
|
189
|
-
kind:
|
|
190
|
-
|
|
331
|
+
kind: DataManipulationRequest['operation']['kind'],
|
|
332
|
+
operation: DataManipulationRequest['operation'],
|
|
191
333
|
rows: Record<string, unknown>[],
|
|
192
334
|
): unknown {
|
|
193
|
-
if (!
|
|
335
|
+
if (!isInsertOperationKind(kind) || !isInsertOperation(operation)) {
|
|
194
336
|
return undefined
|
|
195
337
|
}
|
|
196
338
|
|
|
197
|
-
let primaryKey = getTablePrimaryKey(
|
|
339
|
+
let primaryKey = getTablePrimaryKey(operation.table)
|
|
198
340
|
|
|
199
341
|
if (primaryKey.length !== 1) {
|
|
200
342
|
return undefined
|
|
@@ -207,7 +349,7 @@ function normalizeInsertIdForReader(
|
|
|
207
349
|
}
|
|
208
350
|
|
|
209
351
|
function normalizeAffectedRowsForRun(
|
|
210
|
-
kind:
|
|
352
|
+
kind: DataManipulationRequest['operation']['kind'],
|
|
211
353
|
result: RunResult,
|
|
212
354
|
): number | undefined {
|
|
213
355
|
if (kind === 'select' || kind === 'count' || kind === 'exists') {
|
|
@@ -218,15 +360,15 @@ function normalizeAffectedRowsForRun(
|
|
|
218
360
|
}
|
|
219
361
|
|
|
220
362
|
function normalizeInsertIdForRun(
|
|
221
|
-
kind:
|
|
222
|
-
|
|
363
|
+
kind: DataManipulationRequest['operation']['kind'],
|
|
364
|
+
operation: DataManipulationRequest['operation'],
|
|
223
365
|
result: RunResult,
|
|
224
366
|
): unknown {
|
|
225
|
-
if (!
|
|
367
|
+
if (!isInsertOperationKind(kind) || !isInsertOperation(operation)) {
|
|
226
368
|
return undefined
|
|
227
369
|
}
|
|
228
370
|
|
|
229
|
-
if (getTablePrimaryKey(
|
|
371
|
+
if (getTablePrimaryKey(operation.table).length !== 1) {
|
|
230
372
|
return undefined
|
|
231
373
|
}
|
|
232
374
|
|
|
@@ -237,7 +379,15 @@ function quoteIdentifier(value: string): string {
|
|
|
237
379
|
return '"' + value.replace(/"/g, '""') + '"'
|
|
238
380
|
}
|
|
239
381
|
|
|
240
|
-
function
|
|
382
|
+
function quoteTableRef(table: TableRef): string {
|
|
383
|
+
return quoteTableRefHelper(table, quoteIdentifier)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function quoteLiteral(value: unknown): string {
|
|
387
|
+
return quoteLiteralHelper(value, { booleansAsIntegers: true })
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function isWriteOperationKind(kind: DataManipulationRequest['operation']['kind']): boolean {
|
|
241
391
|
return (
|
|
242
392
|
kind === 'insert' ||
|
|
243
393
|
kind === 'insertMany' ||
|
|
@@ -247,17 +397,435 @@ function isWriteStatementKind(kind: AdapterExecuteRequest['statement']['kind']):
|
|
|
247
397
|
)
|
|
248
398
|
}
|
|
249
399
|
|
|
250
|
-
function
|
|
400
|
+
function isInsertOperationKind(kind: DataManipulationRequest['operation']['kind']): boolean {
|
|
251
401
|
return kind === 'insert' || kind === 'insertMany' || kind === 'upsert'
|
|
252
402
|
}
|
|
253
403
|
|
|
254
|
-
function
|
|
255
|
-
|
|
256
|
-
):
|
|
257
|
-
|
|
404
|
+
function isInsertOperation(
|
|
405
|
+
operation: DataManipulationRequest['operation'],
|
|
406
|
+
): operation is Extract<
|
|
407
|
+
DataManipulationRequest['operation'],
|
|
258
408
|
{ kind: 'insert' | 'insertMany' | 'upsert' }
|
|
259
409
|
> {
|
|
260
410
|
return (
|
|
261
|
-
|
|
411
|
+
operation.kind === 'insert' || operation.kind === 'insertMany' || operation.kind === 'upsert'
|
|
262
412
|
)
|
|
263
413
|
}
|
|
414
|
+
|
|
415
|
+
function isDataManipulationOperation(
|
|
416
|
+
operation: DataManipulationOperation | DataMigrationOperation,
|
|
417
|
+
): operation is DataManipulationOperation {
|
|
418
|
+
return isDataManipulationOperationHelper(operation)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function compileSqliteMigrationOperations(operation: DataMigrationOperation): SqlStatement[] {
|
|
422
|
+
if (operation.kind === 'raw') {
|
|
423
|
+
return [{ text: operation.sql.text, values: [...operation.sql.values] }]
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (operation.kind === 'createTable') {
|
|
427
|
+
let columns = Object.keys(operation.columns).map(
|
|
428
|
+
(columnName) =>
|
|
429
|
+
quoteIdentifier(columnName) + ' ' + compileSqliteColumn(operation.columns[columnName]),
|
|
430
|
+
)
|
|
431
|
+
let constraints: string[] = []
|
|
432
|
+
|
|
433
|
+
if (operation.primaryKey) {
|
|
434
|
+
constraints.push(
|
|
435
|
+
'constraint ' +
|
|
436
|
+
quoteIdentifier(operation.primaryKey.name) +
|
|
437
|
+
' primary key (' +
|
|
438
|
+
operation.primaryKey.columns.map((column) => quoteIdentifier(column)).join(', ') +
|
|
439
|
+
')',
|
|
440
|
+
)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
for (let unique of operation.uniques ?? []) {
|
|
444
|
+
constraints.push(
|
|
445
|
+
'constraint ' +
|
|
446
|
+
quoteIdentifier(unique.name) +
|
|
447
|
+
' ' +
|
|
448
|
+
'unique (' +
|
|
449
|
+
unique.columns.map((column) => quoteIdentifier(column)).join(', ') +
|
|
450
|
+
')',
|
|
451
|
+
)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
for (let check of operation.checks ?? []) {
|
|
455
|
+
constraints.push(
|
|
456
|
+
'constraint ' + quoteIdentifier(check.name) + ' ' + 'check (' + check.expression + ')',
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
for (let foreignKey of operation.foreignKeys ?? []) {
|
|
461
|
+
let clause =
|
|
462
|
+
'constraint ' +
|
|
463
|
+
quoteIdentifier(foreignKey.name) +
|
|
464
|
+
' ' +
|
|
465
|
+
'foreign key (' +
|
|
466
|
+
foreignKey.columns.map((column) => quoteIdentifier(column)).join(', ') +
|
|
467
|
+
') references ' +
|
|
468
|
+
quoteTableRef(foreignKey.references.table) +
|
|
469
|
+
' (' +
|
|
470
|
+
foreignKey.references.columns.map((column) => quoteIdentifier(column)).join(', ') +
|
|
471
|
+
')'
|
|
472
|
+
|
|
473
|
+
if (foreignKey.onDelete) {
|
|
474
|
+
clause += ' on delete ' + foreignKey.onDelete
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (foreignKey.onUpdate) {
|
|
478
|
+
clause += ' on update ' + foreignKey.onUpdate
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
constraints.push(clause)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return [
|
|
485
|
+
{
|
|
486
|
+
text:
|
|
487
|
+
'create table ' +
|
|
488
|
+
(operation.ifNotExists ? 'if not exists ' : '') +
|
|
489
|
+
quoteTableRef(operation.table) +
|
|
490
|
+
' (' +
|
|
491
|
+
[...columns, ...constraints].join(', ') +
|
|
492
|
+
')',
|
|
493
|
+
values: [],
|
|
494
|
+
},
|
|
495
|
+
]
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (operation.kind === 'alterTable') {
|
|
499
|
+
let statements: SqlStatement[] = []
|
|
500
|
+
|
|
501
|
+
for (let change of operation.changes) {
|
|
502
|
+
let sql = 'alter table ' + quoteTableRef(operation.table) + ' '
|
|
503
|
+
|
|
504
|
+
if (change.kind === 'addColumn') {
|
|
505
|
+
sql +=
|
|
506
|
+
'add column ' +
|
|
507
|
+
quoteIdentifier(change.column) +
|
|
508
|
+
' ' +
|
|
509
|
+
compileSqliteColumn(change.definition)
|
|
510
|
+
} else if (change.kind === 'changeColumn') {
|
|
511
|
+
sql +=
|
|
512
|
+
'alter column ' +
|
|
513
|
+
quoteIdentifier(change.column) +
|
|
514
|
+
' type ' +
|
|
515
|
+
compileSqliteColumnType(change.definition)
|
|
516
|
+
} else if (change.kind === 'renameColumn') {
|
|
517
|
+
sql += 'rename column ' + quoteIdentifier(change.from) + ' to ' + quoteIdentifier(change.to)
|
|
518
|
+
} else if (change.kind === 'dropColumn') {
|
|
519
|
+
sql += 'drop column ' + quoteIdentifier(change.column)
|
|
520
|
+
} else if (change.kind === 'addPrimaryKey') {
|
|
521
|
+
sql +=
|
|
522
|
+
'add primary key (' +
|
|
523
|
+
change.constraint.columns.map((column) => quoteIdentifier(column)).join(', ') +
|
|
524
|
+
')'
|
|
525
|
+
} else if (change.kind === 'dropPrimaryKey') {
|
|
526
|
+
sql += 'drop primary key'
|
|
527
|
+
} else if (change.kind === 'addUnique') {
|
|
528
|
+
sql +=
|
|
529
|
+
'add ' +
|
|
530
|
+
'constraint ' +
|
|
531
|
+
quoteIdentifier(change.constraint.name) +
|
|
532
|
+
' ' +
|
|
533
|
+
'unique (' +
|
|
534
|
+
change.constraint.columns.map((column) => quoteIdentifier(column)).join(', ') +
|
|
535
|
+
')'
|
|
536
|
+
} else if (change.kind === 'dropUnique') {
|
|
537
|
+
sql += 'drop constraint ' + quoteIdentifier(change.name)
|
|
538
|
+
} else if (change.kind === 'addForeignKey') {
|
|
539
|
+
sql +=
|
|
540
|
+
'add ' +
|
|
541
|
+
'constraint ' +
|
|
542
|
+
quoteIdentifier(change.constraint.name) +
|
|
543
|
+
' ' +
|
|
544
|
+
'foreign key (' +
|
|
545
|
+
change.constraint.columns.map((column) => quoteIdentifier(column)).join(', ') +
|
|
546
|
+
') references ' +
|
|
547
|
+
quoteTableRef(change.constraint.references.table) +
|
|
548
|
+
' (' +
|
|
549
|
+
change.constraint.references.columns.map((column) => quoteIdentifier(column)).join(', ') +
|
|
550
|
+
')'
|
|
551
|
+
} else if (change.kind === 'dropForeignKey') {
|
|
552
|
+
sql += 'drop constraint ' + quoteIdentifier(change.name)
|
|
553
|
+
} else if (change.kind === 'addCheck') {
|
|
554
|
+
sql +=
|
|
555
|
+
'add ' +
|
|
556
|
+
'constraint ' +
|
|
557
|
+
quoteIdentifier(change.constraint.name) +
|
|
558
|
+
' ' +
|
|
559
|
+
'check (' +
|
|
560
|
+
change.constraint.expression +
|
|
561
|
+
')'
|
|
562
|
+
} else if (change.kind === 'dropCheck') {
|
|
563
|
+
sql += 'drop constraint ' + quoteIdentifier(change.name)
|
|
564
|
+
} else if (change.kind === 'setTableComment') {
|
|
565
|
+
continue
|
|
566
|
+
} else {
|
|
567
|
+
continue
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
statements.push({ text: sql, values: [] })
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return statements
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (operation.kind === 'renameTable') {
|
|
577
|
+
return [
|
|
578
|
+
{
|
|
579
|
+
text:
|
|
580
|
+
'alter table ' +
|
|
581
|
+
quoteTableRef(operation.from) +
|
|
582
|
+
' rename to ' +
|
|
583
|
+
quoteIdentifier(operation.to.name),
|
|
584
|
+
values: [],
|
|
585
|
+
},
|
|
586
|
+
]
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (operation.kind === 'dropTable') {
|
|
590
|
+
return [
|
|
591
|
+
{
|
|
592
|
+
text:
|
|
593
|
+
'drop table ' + (operation.ifExists ? 'if exists ' : '') + quoteTableRef(operation.table),
|
|
594
|
+
values: [],
|
|
595
|
+
},
|
|
596
|
+
]
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (operation.kind === 'createIndex') {
|
|
600
|
+
return [
|
|
601
|
+
{
|
|
602
|
+
text:
|
|
603
|
+
'create ' +
|
|
604
|
+
(operation.index.unique ? 'unique ' : '') +
|
|
605
|
+
'index ' +
|
|
606
|
+
(operation.ifNotExists ? 'if not exists ' : '') +
|
|
607
|
+
quoteIdentifier(operation.index.name) +
|
|
608
|
+
' on ' +
|
|
609
|
+
quoteTableRef(operation.index.table) +
|
|
610
|
+
' (' +
|
|
611
|
+
operation.index.columns.map((column) => quoteIdentifier(column)).join(', ') +
|
|
612
|
+
')' +
|
|
613
|
+
(operation.index.where ? ' where ' + operation.index.where : ''),
|
|
614
|
+
values: [],
|
|
615
|
+
},
|
|
616
|
+
]
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (operation.kind === 'dropIndex') {
|
|
620
|
+
return [
|
|
621
|
+
{
|
|
622
|
+
text:
|
|
623
|
+
'drop index ' +
|
|
624
|
+
(operation.ifExists ? 'if exists ' : '') +
|
|
625
|
+
quoteIdentifier(operation.name),
|
|
626
|
+
values: [],
|
|
627
|
+
},
|
|
628
|
+
]
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if (operation.kind === 'renameIndex') {
|
|
632
|
+
return [
|
|
633
|
+
{
|
|
634
|
+
text:
|
|
635
|
+
'alter table ' +
|
|
636
|
+
quoteTableRef(operation.table) +
|
|
637
|
+
' rename index ' +
|
|
638
|
+
quoteIdentifier(operation.from) +
|
|
639
|
+
' to ' +
|
|
640
|
+
quoteIdentifier(operation.to),
|
|
641
|
+
values: [],
|
|
642
|
+
},
|
|
643
|
+
]
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (operation.kind === 'addForeignKey') {
|
|
647
|
+
return [
|
|
648
|
+
{
|
|
649
|
+
text:
|
|
650
|
+
'alter table ' +
|
|
651
|
+
quoteTableRef(operation.table) +
|
|
652
|
+
' add ' +
|
|
653
|
+
'constraint ' +
|
|
654
|
+
quoteIdentifier(operation.constraint.name) +
|
|
655
|
+
' ' +
|
|
656
|
+
'foreign key (' +
|
|
657
|
+
operation.constraint.columns.map((column) => quoteIdentifier(column)).join(', ') +
|
|
658
|
+
') references ' +
|
|
659
|
+
quoteTableRef(operation.constraint.references.table) +
|
|
660
|
+
' (' +
|
|
661
|
+
operation.constraint.references.columns
|
|
662
|
+
.map((column) => quoteIdentifier(column))
|
|
663
|
+
.join(', ') +
|
|
664
|
+
')' +
|
|
665
|
+
(operation.constraint.onDelete ? ' on delete ' + operation.constraint.onDelete : '') +
|
|
666
|
+
(operation.constraint.onUpdate ? ' on update ' + operation.constraint.onUpdate : ''),
|
|
667
|
+
values: [],
|
|
668
|
+
},
|
|
669
|
+
]
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (operation.kind === 'dropForeignKey') {
|
|
673
|
+
return [
|
|
674
|
+
{
|
|
675
|
+
text:
|
|
676
|
+
'alter table ' +
|
|
677
|
+
quoteTableRef(operation.table) +
|
|
678
|
+
' drop constraint ' +
|
|
679
|
+
quoteIdentifier(operation.name),
|
|
680
|
+
values: [],
|
|
681
|
+
},
|
|
682
|
+
]
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (operation.kind === 'addCheck') {
|
|
686
|
+
return [
|
|
687
|
+
{
|
|
688
|
+
text:
|
|
689
|
+
'alter table ' +
|
|
690
|
+
quoteTableRef(operation.table) +
|
|
691
|
+
' add ' +
|
|
692
|
+
'constraint ' +
|
|
693
|
+
quoteIdentifier(operation.constraint.name) +
|
|
694
|
+
' ' +
|
|
695
|
+
'check (' +
|
|
696
|
+
operation.constraint.expression +
|
|
697
|
+
')',
|
|
698
|
+
values: [],
|
|
699
|
+
},
|
|
700
|
+
]
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (operation.kind === 'dropCheck') {
|
|
704
|
+
return [
|
|
705
|
+
{
|
|
706
|
+
text:
|
|
707
|
+
'alter table ' +
|
|
708
|
+
quoteTableRef(operation.table) +
|
|
709
|
+
' drop constraint ' +
|
|
710
|
+
quoteIdentifier(operation.name),
|
|
711
|
+
values: [],
|
|
712
|
+
},
|
|
713
|
+
]
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
throw new Error('Unsupported data migration operation kind')
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function compileSqliteColumn(definition: ColumnDefinition): string {
|
|
720
|
+
let parts = [compileSqliteColumnType(definition)]
|
|
721
|
+
|
|
722
|
+
if (definition.nullable === false) {
|
|
723
|
+
parts.push('not null')
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (definition.default) {
|
|
727
|
+
if (definition.default.kind === 'now') {
|
|
728
|
+
parts.push('default current_timestamp')
|
|
729
|
+
} else if (definition.default.kind === 'sql') {
|
|
730
|
+
parts.push('default ' + definition.default.expression)
|
|
731
|
+
} else {
|
|
732
|
+
parts.push('default ' + quoteLiteral(definition.default.value))
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if (definition.primaryKey) {
|
|
737
|
+
parts.push('primary key')
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (definition.unique) {
|
|
741
|
+
parts.push('unique')
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (definition.computed) {
|
|
745
|
+
parts.push('generated always as (' + definition.computed.expression + ')')
|
|
746
|
+
parts.push(definition.computed.stored ? 'stored' : 'virtual')
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
if (definition.references) {
|
|
750
|
+
let clause =
|
|
751
|
+
'references ' +
|
|
752
|
+
quoteTableRef(definition.references.table) +
|
|
753
|
+
' (' +
|
|
754
|
+
definition.references.columns.map((column) => quoteIdentifier(column)).join(', ') +
|
|
755
|
+
')'
|
|
756
|
+
|
|
757
|
+
if (definition.references.onDelete) {
|
|
758
|
+
clause += ' on delete ' + definition.references.onDelete
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (definition.references.onUpdate) {
|
|
762
|
+
clause += ' on update ' + definition.references.onUpdate
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
parts.push(clause)
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (definition.checks && definition.checks.length > 0) {
|
|
769
|
+
for (let check of definition.checks) {
|
|
770
|
+
parts.push('check (' + check.expression + ')')
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
return parts.join(' ')
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
function compileSqliteColumnType(definition: ColumnDefinition): string {
|
|
778
|
+
if (definition.type === 'varchar') {
|
|
779
|
+
return 'text'
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
if (definition.type === 'text') {
|
|
783
|
+
return 'text'
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if (definition.type === 'integer') {
|
|
787
|
+
return 'integer'
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (definition.type === 'bigint') {
|
|
791
|
+
return 'integer'
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
if (definition.type === 'decimal') {
|
|
795
|
+
return 'numeric'
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
if (definition.type === 'boolean') {
|
|
799
|
+
return 'integer'
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
if (definition.type === 'uuid') {
|
|
803
|
+
return 'text'
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
if (definition.type === 'date') {
|
|
807
|
+
return 'text'
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
if (definition.type === 'time') {
|
|
811
|
+
return 'text'
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
if (definition.type === 'timestamp') {
|
|
815
|
+
return 'text'
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
if (definition.type === 'json') {
|
|
819
|
+
return 'text'
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (definition.type === 'binary') {
|
|
823
|
+
return 'blob'
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
if (definition.type === 'enum') {
|
|
827
|
+
return 'text'
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
return 'text'
|
|
831
|
+
}
|