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