@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.
@@ -1,20 +1,27 @@
1
1
  import type {
2
2
  AdapterCapabilityOverrides,
3
- AdapterExecuteRequest,
4
- AdapterResult,
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 { compileSqliteStatement } from './sql-compiler.ts'
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: SqliteDatabaseConnection
47
+ #database: BetterSqliteDatabase
34
48
  #transactions = new Set<string>()
35
49
  #transactionCounter = 0
36
50
 
37
- constructor(database: SqliteDatabaseConnection, options?: SqliteDatabaseAdapterOptions) {
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
- async execute(request: AdapterExecuteRequest): Promise<AdapterResult> {
47
- if (request.statement.kind === 'insertMany' && request.statement.values.length === 0) {
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.statement.returning ? [] : undefined,
86
+ rows: request.operation.returning ? [] : undefined,
52
87
  }
53
88
  }
54
89
 
55
- let statement = compileSqliteStatement(request.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.statement.kind === 'count' || request.statement.kind === 'exists') {
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.statement.kind, rows),
68
- insertId: normalizeInsertIdForReader(request.statement.kind, request.statement, rows),
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.statement.kind, result),
76
- insertId: normalizeInsertIdForRun(request.statement.kind, request.statement, result),
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: SqliteDatabaseConnection,
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: AdapterExecuteRequest['statement']['kind'],
320
+ kind: DataManipulationRequest['operation']['kind'],
179
321
  rows: Record<string, unknown>[],
180
322
  ): number | undefined {
181
- if (isWriteStatementKind(kind)) {
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: AdapterExecuteRequest['statement']['kind'],
190
- statement: AdapterExecuteRequest['statement'],
331
+ kind: DataManipulationRequest['operation']['kind'],
332
+ operation: DataManipulationRequest['operation'],
191
333
  rows: Record<string, unknown>[],
192
334
  ): unknown {
193
- if (!isInsertStatementKind(kind) || !isInsertStatement(statement)) {
335
+ if (!isInsertOperationKind(kind) || !isInsertOperation(operation)) {
194
336
  return undefined
195
337
  }
196
338
 
197
- let primaryKey = getTablePrimaryKey(statement.table)
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: AdapterExecuteRequest['statement']['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: AdapterExecuteRequest['statement']['kind'],
222
- statement: AdapterExecuteRequest['statement'],
363
+ kind: DataManipulationRequest['operation']['kind'],
364
+ operation: DataManipulationRequest['operation'],
223
365
  result: RunResult,
224
366
  ): unknown {
225
- if (!isInsertStatementKind(kind) || !isInsertStatement(statement)) {
367
+ if (!isInsertOperationKind(kind) || !isInsertOperation(operation)) {
226
368
  return undefined
227
369
  }
228
370
 
229
- if (getTablePrimaryKey(statement.table).length !== 1) {
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 isWriteStatementKind(kind: AdapterExecuteRequest['statement']['kind']): boolean {
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 isInsertStatementKind(kind: AdapterExecuteRequest['statement']['kind']): boolean {
400
+ function isInsertOperationKind(kind: DataManipulationRequest['operation']['kind']): boolean {
251
401
  return kind === 'insert' || kind === 'insertMany' || kind === 'upsert'
252
402
  }
253
403
 
254
- function isInsertStatement(
255
- statement: AdapterExecuteRequest['statement'],
256
- ): statement is Extract<
257
- AdapterExecuteRequest['statement'],
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
- statement.kind === 'insert' || statement.kind === 'insertMany' || statement.kind === 'upsert'
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
+ }