@mikro-orm/sql 7.0.2-dev.8 → 7.0.2

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.
Files changed (87) hide show
  1. package/AbstractSqlConnection.d.ts +95 -47
  2. package/AbstractSqlConnection.js +240 -232
  3. package/AbstractSqlDriver.d.ts +412 -155
  4. package/AbstractSqlDriver.js +2062 -1937
  5. package/AbstractSqlPlatform.d.ts +84 -73
  6. package/AbstractSqlPlatform.js +163 -158
  7. package/PivotCollectionPersister.d.ts +33 -15
  8. package/PivotCollectionPersister.js +158 -160
  9. package/README.md +128 -294
  10. package/SqlEntityManager.d.ts +68 -20
  11. package/SqlEntityManager.js +54 -37
  12. package/SqlEntityRepository.d.ts +15 -14
  13. package/SqlEntityRepository.js +24 -23
  14. package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +12 -12
  15. package/dialects/mssql/MsSqlNativeQueryBuilder.js +192 -194
  16. package/dialects/mysql/BaseMySqlPlatform.d.ts +64 -45
  17. package/dialects/mysql/BaseMySqlPlatform.js +134 -131
  18. package/dialects/mysql/MySqlExceptionConverter.d.ts +6 -6
  19. package/dialects/mysql/MySqlExceptionConverter.js +91 -77
  20. package/dialects/mysql/MySqlNativeQueryBuilder.d.ts +3 -3
  21. package/dialects/mysql/MySqlNativeQueryBuilder.js +66 -69
  22. package/dialects/mysql/MySqlSchemaHelper.d.ts +39 -39
  23. package/dialects/mysql/MySqlSchemaHelper.js +327 -319
  24. package/dialects/oracledb/OracleDialect.d.ts +81 -52
  25. package/dialects/oracledb/OracleDialect.js +155 -149
  26. package/dialects/oracledb/OracleNativeQueryBuilder.d.ts +12 -12
  27. package/dialects/oracledb/OracleNativeQueryBuilder.js +232 -236
  28. package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +108 -105
  29. package/dialects/postgresql/BasePostgreSqlPlatform.js +351 -350
  30. package/dialects/postgresql/FullTextType.d.ts +10 -6
  31. package/dialects/postgresql/FullTextType.js +51 -51
  32. package/dialects/postgresql/PostgreSqlExceptionConverter.d.ts +5 -5
  33. package/dialects/postgresql/PostgreSqlExceptionConverter.js +55 -43
  34. package/dialects/postgresql/PostgreSqlNativeQueryBuilder.d.ts +1 -1
  35. package/dialects/postgresql/PostgreSqlNativeQueryBuilder.js +4 -4
  36. package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +102 -82
  37. package/dialects/postgresql/PostgreSqlSchemaHelper.js +711 -683
  38. package/dialects/sqlite/BaseSqliteConnection.d.ts +3 -5
  39. package/dialects/sqlite/BaseSqliteConnection.js +21 -19
  40. package/dialects/sqlite/NodeSqliteDialect.d.ts +1 -1
  41. package/dialects/sqlite/NodeSqliteDialect.js +23 -23
  42. package/dialects/sqlite/SqliteDriver.d.ts +1 -1
  43. package/dialects/sqlite/SqliteDriver.js +3 -3
  44. package/dialects/sqlite/SqliteExceptionConverter.d.ts +6 -6
  45. package/dialects/sqlite/SqliteExceptionConverter.js +67 -51
  46. package/dialects/sqlite/SqliteNativeQueryBuilder.d.ts +2 -2
  47. package/dialects/sqlite/SqliteNativeQueryBuilder.js +7 -7
  48. package/dialects/sqlite/SqlitePlatform.d.ts +63 -72
  49. package/dialects/sqlite/SqlitePlatform.js +139 -139
  50. package/dialects/sqlite/SqliteSchemaHelper.d.ts +70 -60
  51. package/dialects/sqlite/SqliteSchemaHelper.js +533 -520
  52. package/package.json +4 -4
  53. package/plugin/index.d.ts +44 -35
  54. package/plugin/index.js +44 -36
  55. package/plugin/transformer.d.ts +117 -94
  56. package/plugin/transformer.js +890 -881
  57. package/query/ArrayCriteriaNode.d.ts +4 -4
  58. package/query/ArrayCriteriaNode.js +18 -18
  59. package/query/CriteriaNode.d.ts +35 -25
  60. package/query/CriteriaNode.js +133 -123
  61. package/query/CriteriaNodeFactory.d.ts +49 -6
  62. package/query/CriteriaNodeFactory.js +97 -94
  63. package/query/NativeQueryBuilder.d.ts +120 -117
  64. package/query/NativeQueryBuilder.js +484 -480
  65. package/query/ObjectCriteriaNode.d.ts +12 -12
  66. package/query/ObjectCriteriaNode.js +298 -282
  67. package/query/QueryBuilder.d.ts +1546 -904
  68. package/query/QueryBuilder.js +2270 -2145
  69. package/query/QueryBuilderHelper.d.ts +153 -72
  70. package/query/QueryBuilderHelper.js +1079 -1028
  71. package/query/ScalarCriteriaNode.d.ts +3 -3
  72. package/query/ScalarCriteriaNode.js +53 -46
  73. package/query/enums.d.ts +16 -14
  74. package/query/enums.js +16 -14
  75. package/query/raw.d.ts +16 -6
  76. package/query/raw.js +10 -10
  77. package/schema/DatabaseSchema.d.ts +73 -50
  78. package/schema/DatabaseSchema.js +331 -307
  79. package/schema/DatabaseTable.d.ts +96 -73
  80. package/schema/DatabaseTable.js +1012 -927
  81. package/schema/SchemaComparator.d.ts +58 -54
  82. package/schema/SchemaComparator.js +745 -719
  83. package/schema/SchemaHelper.d.ts +110 -80
  84. package/schema/SchemaHelper.js +676 -645
  85. package/schema/SqlSchemaGenerator.d.ts +79 -58
  86. package/schema/SqlSchemaGenerator.js +536 -501
  87. package/typings.d.ts +380 -266
@@ -2,529 +2,542 @@ import { Utils } from '@mikro-orm/core';
2
2
  import { SchemaHelper } from '../../schema/SchemaHelper.js';
3
3
  /** SpatiaLite system views that should be automatically ignored */
4
4
  const SPATIALITE_VIEWS = [
5
- 'geometry_columns',
6
- 'spatial_ref_sys',
7
- 'views_geometry_columns',
8
- 'virts_geometry_columns',
9
- 'geom_cols_ref_sys',
10
- 'spatial_ref_sys_aux',
11
- 'vector_layers',
12
- 'vector_layers_auth',
13
- 'vector_layers_field_infos',
14
- 'vector_layers_statistics',
15
- 'ElementaryGeometries',
5
+ 'geometry_columns',
6
+ 'spatial_ref_sys',
7
+ 'views_geometry_columns',
8
+ 'virts_geometry_columns',
9
+ 'geom_cols_ref_sys',
10
+ 'spatial_ref_sys_aux',
11
+ 'vector_layers',
12
+ 'vector_layers_auth',
13
+ 'vector_layers_field_infos',
14
+ 'vector_layers_statistics',
15
+ 'ElementaryGeometries',
16
16
  ];
17
17
  export class SqliteSchemaHelper extends SchemaHelper {
18
- disableForeignKeysSQL() {
19
- return 'pragma foreign_keys = off;';
20
- }
21
- enableForeignKeysSQL() {
22
- return 'pragma foreign_keys = on;';
23
- }
24
- supportsSchemaConstraints() {
25
- return false;
26
- }
27
- getCreateNamespaceSQL(name) {
28
- return '';
29
- }
30
- getDropNamespaceSQL(name) {
31
- return '';
32
- }
33
- getListTablesSQL() {
34
- return (`select name as table_name from sqlite_master where type = 'table' and name != 'sqlite_sequence' and name != 'geometry_columns' and name != 'spatial_ref_sys' ` +
35
- `union all select name as table_name from sqlite_temp_master where type = 'table' order by name`);
36
- }
37
- async getAllTables(connection, schemas) {
38
- const databases = await this.getDatabaseList(connection);
39
- const hasAttachedDbs = databases.length > 1; // More than just 'main'
40
- // If no attached databases, use original behavior
41
- if (!hasAttachedDbs && !schemas?.length) {
42
- return connection.execute(this.getListTablesSQL());
43
- }
44
- // With attached databases, query each one
45
- const targetSchemas = schemas?.length ? schemas : databases;
46
- const allTables = [];
47
- for (const dbName of targetSchemas) {
48
- const prefix = this.getSchemaPrefix(dbName);
49
- const tables = await connection.execute(`select name from ${prefix}sqlite_master where type = 'table' ` +
50
- `and name != 'sqlite_sequence' and name != 'geometry_columns' and name != 'spatial_ref_sys'`);
51
- for (const t of tables) {
52
- allTables.push({ table_name: t.name, schema_name: dbName });
53
- }
54
- }
55
- return allTables;
56
- }
57
- async getNamespaces(connection) {
58
- return this.getDatabaseList(connection);
59
- }
60
- getIgnoredViewsCondition() {
61
- return SPATIALITE_VIEWS.map(v => `name != '${v}'`).join(' and ');
62
- }
63
- getListViewsSQL() {
64
- return `select name as view_name, sql as view_definition from sqlite_master where type = 'view' and ${this.getIgnoredViewsCondition()} order by name`;
65
- }
66
- async loadViews(schema, connection, schemaName) {
67
- const databases = await this.getDatabaseList(connection);
68
- const hasAttachedDbs = databases.length > 1; // More than just 'main'
69
- // If no attached databases and no specific schema, use original behavior
70
- if (!hasAttachedDbs && !schemaName) {
71
- const views = await connection.execute(this.getListViewsSQL());
72
- for (const view of views) {
73
- schema.addView(view.view_name, schemaName, this.extractViewDefinition(view.view_definition));
74
- }
75
- return;
76
- }
77
- // With attached databases, query each one
78
- /* v8 ignore next - schemaName branch not commonly used */
79
- const targetDbs = schemaName ? [schemaName] : databases;
80
- for (const dbName of targetDbs) {
81
- const prefix = this.getSchemaPrefix(dbName);
82
- const views = await connection.execute(`select name as view_name, sql as view_definition from ${prefix}sqlite_master where type = 'view' and ${this.getIgnoredViewsCondition()} order by name`);
83
- for (const view of views) {
84
- schema.addView(view.view_name, dbName, this.extractViewDefinition(view.view_definition));
85
- }
86
- }
87
- }
88
- getDropDatabaseSQL(name) {
89
- if (name === ':memory:') {
90
- return '';
91
- }
18
+ disableForeignKeysSQL() {
19
+ return 'pragma foreign_keys = off;';
20
+ }
21
+ enableForeignKeysSQL() {
22
+ return 'pragma foreign_keys = on;';
23
+ }
24
+ supportsSchemaConstraints() {
25
+ return false;
26
+ }
27
+ getCreateNamespaceSQL(name) {
28
+ return '';
29
+ }
30
+ getDropNamespaceSQL(name) {
31
+ return '';
32
+ }
33
+ getListTablesSQL() {
34
+ return (
35
+ `select name as table_name from sqlite_master where type = 'table' and name != 'sqlite_sequence' and name != 'geometry_columns' and name != 'spatial_ref_sys' ` +
36
+ `union all select name as table_name from sqlite_temp_master where type = 'table' order by name`
37
+ );
38
+ }
39
+ async getAllTables(connection, schemas) {
40
+ const databases = await this.getDatabaseList(connection);
41
+ const hasAttachedDbs = databases.length > 1; // More than just 'main'
42
+ // If no attached databases, use original behavior
43
+ if (!hasAttachedDbs && !schemas?.length) {
44
+ return connection.execute(this.getListTablesSQL());
45
+ }
46
+ // With attached databases, query each one
47
+ const targetSchemas = schemas?.length ? schemas : databases;
48
+ const allTables = [];
49
+ for (const dbName of targetSchemas) {
50
+ const prefix = this.getSchemaPrefix(dbName);
51
+ const tables = await connection.execute(
52
+ `select name from ${prefix}sqlite_master where type = 'table' ` +
53
+ `and name != 'sqlite_sequence' and name != 'geometry_columns' and name != 'spatial_ref_sys'`,
54
+ );
55
+ for (const t of tables) {
56
+ allTables.push({ table_name: t.name, schema_name: dbName });
57
+ }
58
+ }
59
+ return allTables;
60
+ }
61
+ async getNamespaces(connection) {
62
+ return this.getDatabaseList(connection);
63
+ }
64
+ getIgnoredViewsCondition() {
65
+ return SPATIALITE_VIEWS.map(v => `name != '${v}'`).join(' and ');
66
+ }
67
+ getListViewsSQL() {
68
+ return `select name as view_name, sql as view_definition from sqlite_master where type = 'view' and ${this.getIgnoredViewsCondition()} order by name`;
69
+ }
70
+ async loadViews(schema, connection, schemaName) {
71
+ const databases = await this.getDatabaseList(connection);
72
+ const hasAttachedDbs = databases.length > 1; // More than just 'main'
73
+ // If no attached databases and no specific schema, use original behavior
74
+ if (!hasAttachedDbs && !schemaName) {
75
+ const views = await connection.execute(this.getListViewsSQL());
76
+ for (const view of views) {
77
+ schema.addView(view.view_name, schemaName, this.extractViewDefinition(view.view_definition));
78
+ }
79
+ return;
80
+ }
81
+ // With attached databases, query each one
82
+ /* v8 ignore next - schemaName branch not commonly used */
83
+ const targetDbs = schemaName ? [schemaName] : databases;
84
+ for (const dbName of targetDbs) {
85
+ const prefix = this.getSchemaPrefix(dbName);
86
+ const views = await connection.execute(
87
+ `select name as view_name, sql as view_definition from ${prefix}sqlite_master where type = 'view' and ${this.getIgnoredViewsCondition()} order by name`,
88
+ );
89
+ for (const view of views) {
90
+ schema.addView(view.view_name, dbName, this.extractViewDefinition(view.view_definition));
91
+ }
92
+ }
93
+ }
94
+ getDropDatabaseSQL(name) {
95
+ if (name === ':memory:') {
96
+ return '';
97
+ }
98
+ /* v8 ignore next */
99
+ return `drop database if exists ${this.quote(name)}`;
100
+ }
101
+ async loadInformationSchema(schema, connection, tables, schemas) {
102
+ for (const t of tables) {
103
+ const table = schema.addTable(t.table_name, t.schema_name, t.table_comment);
104
+ const cols = await this.getColumns(connection, table.name, table.schema);
105
+ const indexes = await this.getIndexes(connection, table.name, table.schema);
106
+ const checks = await this.getChecks(connection, table.name, table.schema);
107
+ const pks = await this.getPrimaryKeys(connection, indexes, table.name, table.schema);
108
+ const fks = await this.getForeignKeys(connection, table.name, table.schema);
109
+ const enums = await this.getEnumDefinitions(connection, table.name, table.schema);
110
+ table.init(cols, indexes, checks, pks, fks, enums);
111
+ }
112
+ }
113
+ createTable(table, alter) {
114
+ let sql = `create table ${table.getQuotedName()} (`;
115
+ const columns = table.getColumns();
116
+ const lastColumn = columns[columns.length - 1].name;
117
+ for (const column of columns) {
118
+ const col = this.createTableColumn(column, table);
119
+ if (col) {
120
+ const comma = column.name === lastColumn ? '' : ', ';
121
+ sql += col + comma;
122
+ }
123
+ }
124
+ const primaryKey = table.getPrimaryKey();
125
+ const createPrimary = primaryKey?.composite;
126
+ if (createPrimary && primaryKey) {
127
+ sql += `, primary key (${primaryKey.columnNames.map(c => this.quote(c)).join(', ')})`;
128
+ }
129
+ const parts = [];
130
+ for (const fk of Object.values(table.getForeignKeys())) {
131
+ parts.push(this.createForeignKey(table, fk, false));
132
+ }
133
+ for (const check of table.getChecks()) {
134
+ const sql = `constraint ${this.quote(check.name)} check (${check.expression})`;
135
+ parts.push(sql);
136
+ }
137
+ if (parts.length > 0) {
138
+ sql += ', ' + parts.join(', ');
139
+ }
140
+ sql += ')';
141
+ if (table.comment) {
142
+ sql += ` /* ${table.comment} */`;
143
+ }
144
+ const ret = [];
145
+ this.append(ret, sql);
146
+ for (const index of table.getIndexes()) {
147
+ this.append(ret, this.createIndex(index, table));
148
+ }
149
+ return ret;
150
+ }
151
+ createTableColumn(column, table, _changedProperties) {
152
+ const col = [this.quote(column.name)];
153
+ const checks = table.getChecks();
154
+ const check = checks.findIndex(check => check.columnName === column.name);
155
+ const useDefault = column.default != null && column.default !== 'null';
156
+ let columnType = column.type;
157
+ if (column.autoincrement) {
158
+ columnType = 'integer';
159
+ }
160
+ if (column.generated) {
161
+ columnType += ` generated always as ${column.generated}`;
162
+ }
163
+ col.push(columnType);
164
+ if (check !== -1) {
165
+ col.push(`check (${checks[check].expression})`);
166
+ checks.splice(check, 1);
167
+ }
168
+ Utils.runIfNotEmpty(() => col.push('null'), column.nullable);
169
+ Utils.runIfNotEmpty(() => col.push('not null'), !column.nullable && !column.generated);
170
+ Utils.runIfNotEmpty(() => col.push('primary key'), column.primary);
171
+ Utils.runIfNotEmpty(() => col.push('autoincrement'), column.autoincrement);
172
+ Utils.runIfNotEmpty(() => col.push(`default ${column.default}`), useDefault);
173
+ return col.join(' ');
174
+ }
175
+ getAddColumnsSQL(table, columns, diff) {
176
+ return columns.map(column => {
177
+ let sql = `alter table ${table.getQuotedName()} add column ${this.createTableColumn(column, table)}`;
178
+ const foreignKey = Object.values(diff.addedForeignKeys).find(
179
+ fk => fk.columnNames.length === 1 && fk.columnNames[0] === column.name,
180
+ );
181
+ if (foreignKey && this.options.createForeignKeyConstraints) {
182
+ delete diff.addedForeignKeys[foreignKey.constraintName];
183
+ sql += ' ' + this.createForeignKey(diff.toTable, foreignKey, false, true);
184
+ }
185
+ return sql;
186
+ });
187
+ }
188
+ dropForeignKey(tableName, constraintName) {
189
+ return '';
190
+ }
191
+ getDropColumnsSQL(tableName, columns, schemaName) {
192
+ /* v8 ignore next */
193
+ const name = this.quote(
194
+ (schemaName && schemaName !== this.platform.getDefaultSchemaName() ? schemaName + '.' : '') + tableName,
195
+ );
196
+ return columns
197
+ .map(column => {
198
+ return `alter table ${name} drop column ${this.quote(column.name)}`;
199
+ })
200
+ .join(';\n');
201
+ }
202
+ getCreateIndexSQL(tableName, index) {
203
+ /* v8 ignore next */
204
+ if (index.expression) {
205
+ return index.expression;
206
+ }
207
+ // SQLite requires: CREATE INDEX schema.index_name ON table_name (columns)
208
+ // NOT: CREATE INDEX index_name ON schema.table_name (columns)
209
+ const [schemaName, rawTableName] = this.splitTableName(tableName);
210
+ const quotedTableName = this.quote(rawTableName);
211
+ // If there's a schema, prefix the index name with it
212
+ let keyName;
213
+ if (schemaName && schemaName !== 'main') {
214
+ keyName = `${this.quote(schemaName)}.${this.quote(index.keyName)}`;
215
+ } else {
216
+ keyName = this.quote(index.keyName);
217
+ }
218
+ const sqlPrefix = `create ${index.unique ? 'unique ' : ''}index ${keyName} on ${quotedTableName}`;
219
+ /* v8 ignore next 4 */
220
+ if (index.columnNames.some(column => column.includes('.'))) {
221
+ // JSON columns can have unique index but not unique constraint, and we need to distinguish those, so we can properly drop them
222
+ const columns = this.platform.getJsonIndexDefinition(index);
223
+ return `${sqlPrefix} (${columns.join(', ')})`;
224
+ }
225
+ // Use getIndexColumns to support advanced options like sort order and collation
226
+ return `${sqlPrefix} (${this.getIndexColumns(index)})`;
227
+ }
228
+ parseTableDefinition(sql, cols) {
229
+ const columns = {};
230
+ const constraints = [];
231
+ // extract all columns definitions
232
+ let columnsDef = new RegExp(`create table [\`"']?.*?[\`"']? \\((.*)\\)`, 'i').exec(sql.replaceAll('\n', ''))?.[1];
233
+ /* v8 ignore next */
234
+ if (columnsDef) {
235
+ if (columnsDef.includes(', constraint ')) {
236
+ constraints.push(...columnsDef.substring(columnsDef.indexOf(', constraint') + 2).split(', '));
237
+ columnsDef = columnsDef.substring(0, columnsDef.indexOf(', constraint'));
238
+ }
239
+ for (let i = cols.length - 1; i >= 0; i--) {
240
+ const col = cols[i];
241
+ const re = ` *, *[\`"']?${col.name}[\`"']? (.*)`;
242
+ const columnDef = new RegExp(re, 'i').exec(columnsDef);
243
+ if (columnDef) {
244
+ columns[col.name] = { name: col.name, definition: columnDef[1] };
245
+ columnsDef = columnsDef.substring(0, columnDef.index);
246
+ }
247
+ }
248
+ }
249
+ return { columns, constraints };
250
+ }
251
+ /**
252
+ * Returns schema prefix for pragma and sqlite_master queries.
253
+ * Returns empty string for main database (no prefix needed).
254
+ */
255
+ getSchemaPrefix(schemaName) {
256
+ if (!schemaName || schemaName === 'main') {
257
+ return '';
258
+ }
259
+ return `${this.platform.quoteIdentifier(schemaName)}.`;
260
+ }
261
+ /**
262
+ * Returns all database names excluding 'temp'.
263
+ */
264
+ async getDatabaseList(connection) {
265
+ const databases = await connection.execute('pragma database_list');
266
+ return databases.filter(d => d.name !== 'temp').map(d => d.name);
267
+ }
268
+ /**
269
+ * Extracts the SELECT part from a CREATE VIEW statement.
270
+ */
271
+ extractViewDefinition(viewDefinition) {
272
+ const match = /create\s+view\s+[`"']?\w+[`"']?\s+as\s+(.*)/is.exec(viewDefinition);
273
+ /* v8 ignore next - fallback for non-standard view definitions */
274
+ return match ? match[1] : viewDefinition;
275
+ }
276
+ async getColumns(connection, tableName, schemaName) {
277
+ const prefix = this.getSchemaPrefix(schemaName);
278
+ const columns = await connection.execute(`pragma ${prefix}table_xinfo('${tableName}')`);
279
+ const sql = `select sql from ${prefix}sqlite_master where type = ? and name = ?`;
280
+ const tableDefinition = await connection.execute(sql, ['table', tableName], 'get');
281
+ const composite = columns.reduce((count, col) => count + (col.pk ? 1 : 0), 0) > 1;
282
+ // there can be only one, so naive check like this should be enough
283
+ const hasAutoincrement = tableDefinition.sql.toLowerCase().includes('autoincrement');
284
+ const { columns: columnDefinitions } = this.parseTableDefinition(tableDefinition.sql, columns);
285
+ return columns.map(col => {
286
+ const mappedType = connection.getPlatform().getMappedType(col.type);
287
+ let generated;
288
+ if (col.hidden > 1) {
92
289
  /* v8 ignore next */
93
- return `drop database if exists ${this.quote(name)}`;
94
- }
95
- async loadInformationSchema(schema, connection, tables, schemas) {
96
- for (const t of tables) {
97
- const table = schema.addTable(t.table_name, t.schema_name, t.table_comment);
98
- const cols = await this.getColumns(connection, table.name, table.schema);
99
- const indexes = await this.getIndexes(connection, table.name, table.schema);
100
- const checks = await this.getChecks(connection, table.name, table.schema);
101
- const pks = await this.getPrimaryKeys(connection, indexes, table.name, table.schema);
102
- const fks = await this.getForeignKeys(connection, table.name, table.schema);
103
- const enums = await this.getEnumDefinitions(connection, table.name, table.schema);
104
- table.init(cols, indexes, checks, pks, fks, enums);
105
- }
106
- }
107
- createTable(table, alter) {
108
- let sql = `create table ${table.getQuotedName()} (`;
109
- const columns = table.getColumns();
110
- const lastColumn = columns[columns.length - 1].name;
111
- for (const column of columns) {
112
- const col = this.createTableColumn(column, table);
113
- if (col) {
114
- const comma = column.name === lastColumn ? '' : ', ';
115
- sql += col + comma;
116
- }
117
- }
118
- const primaryKey = table.getPrimaryKey();
119
- const createPrimary = primaryKey?.composite;
120
- if (createPrimary && primaryKey) {
121
- sql += `, primary key (${primaryKey.columnNames.map(c => this.quote(c)).join(', ')})`;
122
- }
123
- const parts = [];
124
- for (const fk of Object.values(table.getForeignKeys())) {
125
- parts.push(this.createForeignKey(table, fk, false));
126
- }
127
- for (const check of table.getChecks()) {
128
- const sql = `constraint ${this.quote(check.name)} check (${check.expression})`;
129
- parts.push(sql);
130
- }
131
- if (parts.length > 0) {
132
- sql += ', ' + parts.join(', ');
133
- }
134
- sql += ')';
135
- if (table.comment) {
136
- sql += ` /* ${table.comment} */`;
137
- }
138
- const ret = [];
139
- this.append(ret, sql);
140
- for (const index of table.getIndexes()) {
141
- this.append(ret, this.createIndex(index, table));
142
- }
143
- return ret;
144
- }
145
- createTableColumn(column, table, _changedProperties) {
146
- const col = [this.quote(column.name)];
147
- const checks = table.getChecks();
148
- const check = checks.findIndex(check => check.columnName === column.name);
149
- const useDefault = column.default != null && column.default !== 'null';
150
- let columnType = column.type;
151
- if (column.autoincrement) {
152
- columnType = 'integer';
153
- }
154
- if (column.generated) {
155
- columnType += ` generated always as ${column.generated}`;
156
- }
157
- col.push(columnType);
158
- if (check !== -1) {
159
- col.push(`check (${checks[check].expression})`);
160
- checks.splice(check, 1);
161
- }
162
- Utils.runIfNotEmpty(() => col.push('null'), column.nullable);
163
- Utils.runIfNotEmpty(() => col.push('not null'), !column.nullable && !column.generated);
164
- Utils.runIfNotEmpty(() => col.push('primary key'), column.primary);
165
- Utils.runIfNotEmpty(() => col.push('autoincrement'), column.autoincrement);
166
- Utils.runIfNotEmpty(() => col.push(`default ${column.default}`), useDefault);
167
- return col.join(' ');
168
- }
169
- getAddColumnsSQL(table, columns, diff) {
170
- return columns.map(column => {
171
- let sql = `alter table ${table.getQuotedName()} add column ${this.createTableColumn(column, table)}`;
172
- const foreignKey = Object.values(diff.addedForeignKeys).find(fk => fk.columnNames.length === 1 && fk.columnNames[0] === column.name);
173
- if (foreignKey && this.options.createForeignKeyConstraints) {
174
- delete diff.addedForeignKeys[foreignKey.constraintName];
175
- sql += ' ' + this.createForeignKey(diff.toTable, foreignKey, false, true);
176
- }
177
- return sql;
290
+ const storage = col.hidden === 2 ? 'virtual' : 'stored';
291
+ const re = new RegExp(`(generated always)? as \\((.*)\\)( ${storage})?$`, 'i');
292
+ const match = columnDefinitions[col.name].definition.match(re);
293
+ if (match) {
294
+ generated = `${match[2]} ${storage}`;
295
+ }
296
+ }
297
+ return {
298
+ name: col.name,
299
+ type: col.type,
300
+ default: this.wrapExpressionDefault(col.dflt_value),
301
+ nullable: !col.notnull,
302
+ primary: !!col.pk,
303
+ mappedType,
304
+ unsigned: false,
305
+ autoincrement: !composite && col.pk && this.platform.isNumericColumn(mappedType) && hasAutoincrement,
306
+ generated,
307
+ };
308
+ });
309
+ }
310
+ /**
311
+ * SQLite strips outer parentheses from expression defaults (`DEFAULT (expr)` → `expr` in pragma).
312
+ * We need to add them back so they match what we generate in DDL.
313
+ */
314
+ wrapExpressionDefault(value) {
315
+ if (value == null) {
316
+ return null;
317
+ }
318
+ // simple values that are returned as-is from pragma (no wrapping needed)
319
+ if (
320
+ /^-?\d/.test(value) ||
321
+ /^[xX]'/.test(value) ||
322
+ value.startsWith("'") ||
323
+ value.startsWith('"') ||
324
+ value.startsWith('(')
325
+ ) {
326
+ return value;
327
+ }
328
+ const lower = value.toLowerCase();
329
+ if (['null', 'true', 'false', 'current_timestamp', 'current_date', 'current_time'].includes(lower)) {
330
+ return value;
331
+ }
332
+ // everything else is an expression that had its outer parens stripped
333
+ return `(${value})`;
334
+ }
335
+ async getEnumDefinitions(connection, tableName, schemaName) {
336
+ const prefix = this.getSchemaPrefix(schemaName);
337
+ const sql = `select sql from ${prefix}sqlite_master where type = ? and name = ?`;
338
+ const tableDefinition = await connection.execute(sql, ['table', tableName], 'get');
339
+ const checkConstraints = [...(tableDefinition.sql.match(/[`["'][^`\]"']+[`\]"'] text check \(.*?\)/gi) ?? [])];
340
+ return checkConstraints.reduce((o, item) => {
341
+ // check constraints are defined as (note that last closing paren is missing):
342
+ // `type` text check (`type` in ('local', 'global')
343
+ const match = /[`["']([^`\]"']+)[`\]"'] text check \(.* \((.*)\)/i.exec(item);
344
+ /* v8 ignore next */
345
+ if (match) {
346
+ o[match[1]] = match[2].split(',').map(item => /^\(?'(.*)'/.exec(item.trim())[1]);
347
+ }
348
+ return o;
349
+ }, {});
350
+ }
351
+ async getPrimaryKeys(connection, indexes, tableName, schemaName) {
352
+ const prefix = this.getSchemaPrefix(schemaName);
353
+ const sql = `pragma ${prefix}table_info(\`${tableName}\`)`;
354
+ const cols = await connection.execute(sql);
355
+ return cols.filter(col => !!col.pk).map(col => col.name);
356
+ }
357
+ async getIndexes(connection, tableName, schemaName) {
358
+ const prefix = this.getSchemaPrefix(schemaName);
359
+ const sql = `pragma ${prefix}table_info(\`${tableName}\`)`;
360
+ const cols = await connection.execute(sql);
361
+ const indexes = await connection.execute(`pragma ${prefix}index_list(\`${tableName}\`)`);
362
+ const ret = [];
363
+ for (const col of cols.filter(c => c.pk)) {
364
+ ret.push({
365
+ columnNames: [col.name],
366
+ keyName: 'primary',
367
+ constraint: true,
368
+ unique: true,
369
+ primary: true,
370
+ });
371
+ }
372
+ for (const index of indexes.filter(index => !this.isImplicitIndex(index.name))) {
373
+ const res = await connection.execute(`pragma ${prefix}index_info(\`${index.name}\`)`);
374
+ ret.push(
375
+ ...res.map(row => ({
376
+ columnNames: [row.name],
377
+ keyName: index.name,
378
+ unique: !!index.unique,
379
+ constraint: !!index.unique,
380
+ primary: false,
381
+ })),
382
+ );
383
+ }
384
+ return this.mapIndexes(ret);
385
+ }
386
+ async getChecks(connection, tableName, schemaName) {
387
+ const { columns, constraints } = await this.getColumnDefinitions(connection, tableName, schemaName);
388
+ const checks = [];
389
+ for (const key of Object.keys(columns)) {
390
+ const column = columns[key];
391
+ const expression = / (check \((.*)\))/i.exec(column.definition);
392
+ if (expression) {
393
+ checks.push({
394
+ name: this.platform.getConfig().getNamingStrategy().indexName(tableName, [column.name], 'check'),
395
+ definition: expression[1],
396
+ expression: expression[2],
397
+ columnName: column.name,
178
398
  });
179
- }
180
- dropForeignKey(tableName, constraintName) {
181
- return '';
182
- }
183
- getDropColumnsSQL(tableName, columns, schemaName) {
184
- /* v8 ignore next */
185
- const name = this.quote((schemaName && schemaName !== this.platform.getDefaultSchemaName() ? schemaName + '.' : '') + tableName);
186
- return columns
187
- .map(column => {
188
- return `alter table ${name} drop column ${this.quote(column.name)}`;
189
- })
190
- .join(';\n');
191
- }
192
- getCreateIndexSQL(tableName, index) {
193
- /* v8 ignore next */
194
- if (index.expression) {
195
- return index.expression;
196
- }
197
- // SQLite requires: CREATE INDEX schema.index_name ON table_name (columns)
198
- // NOT: CREATE INDEX index_name ON schema.table_name (columns)
199
- const [schemaName, rawTableName] = this.splitTableName(tableName);
200
- const quotedTableName = this.quote(rawTableName);
201
- // If there's a schema, prefix the index name with it
202
- let keyName;
203
- if (schemaName && schemaName !== 'main') {
204
- keyName = `${this.quote(schemaName)}.${this.quote(index.keyName)}`;
205
- }
206
- else {
207
- keyName = this.quote(index.keyName);
208
- }
209
- const sqlPrefix = `create ${index.unique ? 'unique ' : ''}index ${keyName} on ${quotedTableName}`;
210
- /* v8 ignore next 4 */
211
- if (index.columnNames.some(column => column.includes('.'))) {
212
- // JSON columns can have unique index but not unique constraint, and we need to distinguish those, so we can properly drop them
213
- const columns = this.platform.getJsonIndexDefinition(index);
214
- return `${sqlPrefix} (${columns.join(', ')})`;
215
- }
216
- // Use getIndexColumns to support advanced options like sort order and collation
217
- return `${sqlPrefix} (${this.getIndexColumns(index)})`;
218
- }
219
- parseTableDefinition(sql, cols) {
220
- const columns = {};
221
- const constraints = [];
222
- // extract all columns definitions
223
- let columnsDef = new RegExp(`create table [\`"']?.*?[\`"']? \\((.*)\\)`, 'i').exec(sql.replaceAll('\n', ''))?.[1];
224
- /* v8 ignore next */
225
- if (columnsDef) {
226
- if (columnsDef.includes(', constraint ')) {
227
- constraints.push(...columnsDef.substring(columnsDef.indexOf(', constraint') + 2).split(', '));
228
- columnsDef = columnsDef.substring(0, columnsDef.indexOf(', constraint'));
229
- }
230
- for (let i = cols.length - 1; i >= 0; i--) {
231
- const col = cols[i];
232
- const re = ` *, *[\`"']?${col.name}[\`"']? (.*)`;
233
- const columnDef = new RegExp(re, 'i').exec(columnsDef);
234
- if (columnDef) {
235
- columns[col.name] = { name: col.name, definition: columnDef[1] };
236
- columnsDef = columnsDef.substring(0, columnDef.index);
237
- }
238
- }
239
- }
240
- return { columns, constraints };
241
- }
242
- /**
243
- * Returns schema prefix for pragma and sqlite_master queries.
244
- * Returns empty string for main database (no prefix needed).
245
- */
246
- getSchemaPrefix(schemaName) {
247
- if (!schemaName || schemaName === 'main') {
248
- return '';
249
- }
250
- return `${this.platform.quoteIdentifier(schemaName)}.`;
251
- }
252
- /**
253
- * Returns all database names excluding 'temp'.
254
- */
255
- async getDatabaseList(connection) {
256
- const databases = await connection.execute('pragma database_list');
257
- return databases.filter(d => d.name !== 'temp').map(d => d.name);
258
- }
259
- /**
260
- * Extracts the SELECT part from a CREATE VIEW statement.
261
- */
262
- extractViewDefinition(viewDefinition) {
263
- const match = /create\s+view\s+[`"']?\w+[`"']?\s+as\s+(.*)/i.exec(viewDefinition);
264
- /* v8 ignore next - fallback for non-standard view definitions */
265
- return match ? match[1] : viewDefinition;
266
- }
267
- async getColumns(connection, tableName, schemaName) {
268
- const prefix = this.getSchemaPrefix(schemaName);
269
- const columns = await connection.execute(`pragma ${prefix}table_xinfo('${tableName}')`);
270
- const sql = `select sql from ${prefix}sqlite_master where type = ? and name = ?`;
271
- const tableDefinition = await connection.execute(sql, ['table', tableName], 'get');
272
- const composite = columns.reduce((count, col) => count + (col.pk ? 1 : 0), 0) > 1;
273
- // there can be only one, so naive check like this should be enough
274
- const hasAutoincrement = tableDefinition.sql.toLowerCase().includes('autoincrement');
275
- const { columns: columnDefinitions } = this.parseTableDefinition(tableDefinition.sql, columns);
276
- return columns.map(col => {
277
- const mappedType = connection.getPlatform().getMappedType(col.type);
278
- let generated;
279
- if (col.hidden > 1) {
280
- /* v8 ignore next */
281
- const storage = col.hidden === 2 ? 'virtual' : 'stored';
282
- const re = new RegExp(`(generated always)? as \\((.*)\\)( ${storage})?$`, 'i');
283
- const match = columnDefinitions[col.name].definition.match(re);
284
- if (match) {
285
- generated = `${match[2]} ${storage}`;
286
- }
287
- }
288
- return {
289
- name: col.name,
290
- type: col.type,
291
- default: this.wrapExpressionDefault(col.dflt_value),
292
- nullable: !col.notnull,
293
- primary: !!col.pk,
294
- mappedType,
295
- unsigned: false,
296
- autoincrement: !composite && col.pk && this.platform.isNumericColumn(mappedType) && hasAutoincrement,
297
- generated,
298
- };
399
+ }
400
+ }
401
+ for (const constraint of constraints) {
402
+ const expression = /constraint *[`"']?(.*?)[`"']? * (check \((.*)\))/i.exec(constraint);
403
+ if (expression) {
404
+ checks.push({
405
+ name: expression[1],
406
+ definition: expression[2],
407
+ expression: expression[3],
299
408
  });
300
- }
301
- /**
302
- * SQLite strips outer parentheses from expression defaults (`DEFAULT (expr)` → `expr` in pragma).
303
- * We need to add them back so they match what we generate in DDL.
304
- */
305
- wrapExpressionDefault(value) {
306
- if (value == null) {
307
- return null;
308
- }
309
- // simple values that are returned as-is from pragma (no wrapping needed)
310
- if (/^-?\d/.test(value) ||
311
- /^[xX]'/.test(value) ||
312
- value.startsWith("'") ||
313
- value.startsWith('"') ||
314
- value.startsWith('(')) {
315
- return value;
316
- }
317
- const lower = value.toLowerCase();
318
- if (['null', 'true', 'false', 'current_timestamp', 'current_date', 'current_time'].includes(lower)) {
319
- return value;
320
- }
321
- // everything else is an expression that had its outer parens stripped
322
- return `(${value})`;
323
- }
324
- async getEnumDefinitions(connection, tableName, schemaName) {
325
- const prefix = this.getSchemaPrefix(schemaName);
326
- const sql = `select sql from ${prefix}sqlite_master where type = ? and name = ?`;
327
- const tableDefinition = await connection.execute(sql, ['table', tableName], 'get');
328
- const checkConstraints = [...(tableDefinition.sql.match(/[`["'][^`\]"']+[`\]"'] text check \(.*?\)/gi) ?? [])];
329
- return checkConstraints.reduce((o, item) => {
330
- // check constraints are defined as (note that last closing paren is missing):
331
- // `type` text check (`type` in ('local', 'global')
332
- const match = /[`["']([^`\]"']+)[`\]"'] text check \(.* \((.*)\)/i.exec(item);
333
- /* v8 ignore next */
334
- if (match) {
335
- o[match[1]] = match[2].split(',').map((item) => /^\(?'(.*)'/.exec(item.trim())[1]);
336
- }
337
- return o;
338
- }, {});
339
- }
340
- async getPrimaryKeys(connection, indexes, tableName, schemaName) {
341
- const prefix = this.getSchemaPrefix(schemaName);
342
- const sql = `pragma ${prefix}table_info(\`${tableName}\`)`;
343
- const cols = await connection.execute(sql);
344
- return cols.filter(col => !!col.pk).map(col => col.name);
345
- }
346
- async getIndexes(connection, tableName, schemaName) {
347
- const prefix = this.getSchemaPrefix(schemaName);
348
- const sql = `pragma ${prefix}table_info(\`${tableName}\`)`;
349
- const cols = await connection.execute(sql);
350
- const indexes = await connection.execute(`pragma ${prefix}index_list(\`${tableName}\`)`);
351
- const ret = [];
352
- for (const col of cols.filter(c => c.pk)) {
353
- ret.push({
354
- columnNames: [col.name],
355
- keyName: 'primary',
356
- constraint: true,
357
- unique: true,
358
- primary: true,
359
- });
360
- }
361
- for (const index of indexes.filter(index => !this.isImplicitIndex(index.name))) {
362
- const res = await connection.execute(`pragma ${prefix}index_info(\`${index.name}\`)`);
363
- ret.push(...res.map(row => ({
364
- columnNames: [row.name],
365
- keyName: index.name,
366
- unique: !!index.unique,
367
- constraint: !!index.unique,
368
- primary: false,
369
- })));
370
- }
371
- return this.mapIndexes(ret);
372
- }
373
- async getChecks(connection, tableName, schemaName) {
374
- const { columns, constraints } = await this.getColumnDefinitions(connection, tableName, schemaName);
375
- const checks = [];
376
- for (const key of Object.keys(columns)) {
377
- const column = columns[key];
378
- const expression = / (check \((.*)\))/i.exec(column.definition);
379
- if (expression) {
380
- checks.push({
381
- name: this.platform.getConfig().getNamingStrategy().indexName(tableName, [column.name], 'check'),
382
- definition: expression[1],
383
- expression: expression[2],
384
- columnName: column.name,
385
- });
386
- }
387
- }
388
- for (const constraint of constraints) {
389
- const expression = /constraint *[`"']?(.*?)[`"']? * (check \((.*)\))/i.exec(constraint);
390
- if (expression) {
391
- checks.push({
392
- name: expression[1],
393
- definition: expression[2],
394
- expression: expression[3],
395
- });
396
- }
397
- }
398
- return checks;
399
- }
400
- async getColumnDefinitions(connection, tableName, schemaName) {
401
- const prefix = this.getSchemaPrefix(schemaName);
402
- const columns = await connection.execute(`pragma ${prefix}table_xinfo('${tableName}')`);
403
- const sql = `select sql from ${prefix}sqlite_master where type = ? and name = ?`;
404
- const tableDefinition = await connection.execute(sql, ['table', tableName], 'get');
405
- return this.parseTableDefinition(tableDefinition.sql, columns);
406
- }
407
- async getForeignKeys(connection, tableName, schemaName) {
408
- const { constraints } = await this.getColumnDefinitions(connection, tableName, schemaName);
409
- const prefix = this.getSchemaPrefix(schemaName);
410
- const fks = await connection.execute(`pragma ${prefix}foreign_key_list(\`${tableName}\`)`);
411
- const qualifiedTableName = schemaName ? `${schemaName}.${tableName}` : tableName;
412
- return fks.reduce((ret, fk) => {
413
- const constraintName = this.platform.getIndexName(tableName, [fk.from], 'foreign');
414
- const constraint = constraints?.find(c => c.includes(constraintName));
415
- ret[constraintName] = {
416
- constraintName,
417
- columnName: fk.from,
418
- columnNames: [fk.from],
419
- localTableName: qualifiedTableName,
420
- referencedTableName: fk.table,
421
- referencedColumnName: fk.to,
422
- referencedColumnNames: [fk.to],
423
- updateRule: fk.on_update.toLowerCase(),
424
- deleteRule: fk.on_delete.toLowerCase(),
425
- deferMode: constraint?.match(/ deferrable initially (deferred|immediate)/i)?.[1].toLowerCase(),
426
- };
427
- return ret;
428
- }, {});
429
- }
430
- getManagementDbName() {
431
- return '';
432
- }
433
- getCreateDatabaseSQL(name) {
434
- return '';
435
- }
436
- async databaseExists(connection, name) {
437
- const tables = await connection.execute(this.getListTablesSQL());
438
- return tables.length > 0;
439
- }
440
- /**
441
- * Implicit indexes will be ignored when diffing
442
- */
443
- isImplicitIndex(name) {
444
- // Ignore indexes with reserved names, e.g. autoindexes
445
- return name.startsWith('sqlite_');
446
- }
447
- dropIndex(table, index, oldIndexName = index.keyName) {
448
- return `drop index ${this.quote(oldIndexName)}`;
449
- }
450
- /**
451
- * SQLite does not support schema-qualified table names in REFERENCES clauses.
452
- * Foreign key references can only point to tables in the same database.
453
- */
454
- getReferencedTableName(referencedTableName, schema) {
455
- const [schemaName, tableName] = this.splitTableName(referencedTableName);
456
- // Strip any schema prefix - SQLite REFERENCES clause doesn't support it
457
- return tableName;
458
- }
459
- alterTable(diff, safe) {
460
- const ret = [];
461
- const [schemaName, tableName] = this.splitTableName(diff.name);
462
- if (Utils.hasObjectKeys(diff.removedChecks) ||
463
- Utils.hasObjectKeys(diff.changedChecks) ||
464
- Utils.hasObjectKeys(diff.changedForeignKeys) ||
465
- Utils.hasObjectKeys(diff.changedColumns)) {
466
- return this.getAlterTempTableSQL(diff);
467
- }
468
- for (const index of Object.values(diff.removedIndexes)) {
469
- this.append(ret, this.dropIndex(diff.name, index));
470
- }
471
- for (const index of Object.values(diff.changedIndexes)) {
472
- this.append(ret, this.dropIndex(diff.name, index));
473
- }
474
- /* v8 ignore next */
475
- if (!safe && Object.values(diff.removedColumns).length > 0) {
476
- this.append(ret, this.getDropColumnsSQL(tableName, Object.values(diff.removedColumns), schemaName));
477
- }
478
- if (Object.values(diff.addedColumns).length > 0) {
479
- this.append(ret, this.getAddColumnsSQL(diff.toTable, Object.values(diff.addedColumns), diff));
480
- }
481
- if (Utils.hasObjectKeys(diff.addedForeignKeys) || Utils.hasObjectKeys(diff.addedChecks)) {
482
- return this.getAlterTempTableSQL(diff);
483
- }
484
- for (const [oldColumnName, column] of Object.entries(diff.renamedColumns)) {
485
- this.append(ret, this.getRenameColumnSQL(tableName, oldColumnName, column, schemaName));
486
- }
487
- for (const index of Object.values(diff.addedIndexes)) {
488
- ret.push(this.createIndex(index, diff.toTable));
489
- }
490
- for (const index of Object.values(diff.changedIndexes)) {
491
- ret.push(this.createIndex(index, diff.toTable, true));
492
- }
493
- for (const [oldIndexName, index] of Object.entries(diff.renamedIndexes)) {
494
- if (index.unique) {
495
- this.append(ret, this.dropIndex(diff.name, index, oldIndexName));
496
- this.append(ret, this.createIndex(index, diff.toTable));
497
- }
498
- else {
499
- this.append(ret, this.getRenameIndexSQL(diff.name, index, oldIndexName));
500
- }
501
- }
502
- return ret;
503
- }
504
- getAlterTempTableSQL(changedTable) {
505
- const tempName = `${changedTable.toTable.name}__temp_alter`;
506
- const quotedName = this.quote(changedTable.toTable.name);
507
- const quotedTempName = this.quote(tempName);
508
- const [first, ...rest] = this.createTable(changedTable.toTable);
509
- const sql = [
510
- 'pragma foreign_keys = off;',
511
- first.replace(`create table ${quotedName}`, `create table ${quotedTempName}`),
512
- ];
513
- const columns = [];
514
- for (const column of changedTable.toTable.getColumns()) {
515
- const fromColumn = changedTable.fromTable.getColumn(column.name);
516
- if (fromColumn) {
517
- columns.push(this.quote(column.name));
518
- }
519
- else {
520
- columns.push(`null as ${this.quote(column.name)}`);
521
- }
522
- }
523
- sql.push(`insert into ${quotedTempName} select ${columns.join(', ')} from ${quotedName};`);
524
- sql.push(`drop table ${quotedName};`);
525
- sql.push(`alter table ${quotedTempName} rename to ${quotedName};`);
526
- sql.push(...rest);
527
- sql.push('pragma foreign_keys = on;');
528
- return sql;
529
- }
409
+ }
410
+ }
411
+ return checks;
412
+ }
413
+ async getColumnDefinitions(connection, tableName, schemaName) {
414
+ const prefix = this.getSchemaPrefix(schemaName);
415
+ const columns = await connection.execute(`pragma ${prefix}table_xinfo('${tableName}')`);
416
+ const sql = `select sql from ${prefix}sqlite_master where type = ? and name = ?`;
417
+ const tableDefinition = await connection.execute(sql, ['table', tableName], 'get');
418
+ return this.parseTableDefinition(tableDefinition.sql, columns);
419
+ }
420
+ async getForeignKeys(connection, tableName, schemaName) {
421
+ const { constraints } = await this.getColumnDefinitions(connection, tableName, schemaName);
422
+ const prefix = this.getSchemaPrefix(schemaName);
423
+ const fks = await connection.execute(`pragma ${prefix}foreign_key_list(\`${tableName}\`)`);
424
+ const qualifiedTableName = schemaName ? `${schemaName}.${tableName}` : tableName;
425
+ return fks.reduce((ret, fk) => {
426
+ const constraintName = this.platform.getIndexName(tableName, [fk.from], 'foreign');
427
+ const constraint = constraints?.find(c => c.includes(constraintName));
428
+ ret[constraintName] = {
429
+ constraintName,
430
+ columnName: fk.from,
431
+ columnNames: [fk.from],
432
+ localTableName: qualifiedTableName,
433
+ referencedTableName: fk.table,
434
+ referencedColumnName: fk.to,
435
+ referencedColumnNames: [fk.to],
436
+ updateRule: fk.on_update.toLowerCase(),
437
+ deleteRule: fk.on_delete.toLowerCase(),
438
+ deferMode: constraint?.match(/ deferrable initially (deferred|immediate)/i)?.[1].toLowerCase(),
439
+ };
440
+ return ret;
441
+ }, {});
442
+ }
443
+ getManagementDbName() {
444
+ return '';
445
+ }
446
+ getCreateDatabaseSQL(name) {
447
+ return '';
448
+ }
449
+ async databaseExists(connection, name) {
450
+ const tables = await connection.execute(this.getListTablesSQL());
451
+ return tables.length > 0;
452
+ }
453
+ /**
454
+ * Implicit indexes will be ignored when diffing
455
+ */
456
+ isImplicitIndex(name) {
457
+ // Ignore indexes with reserved names, e.g. autoindexes
458
+ return name.startsWith('sqlite_');
459
+ }
460
+ dropIndex(table, index, oldIndexName = index.keyName) {
461
+ return `drop index ${this.quote(oldIndexName)}`;
462
+ }
463
+ /**
464
+ * SQLite does not support schema-qualified table names in REFERENCES clauses.
465
+ * Foreign key references can only point to tables in the same database.
466
+ */
467
+ getReferencedTableName(referencedTableName, schema) {
468
+ const [schemaName, tableName] = this.splitTableName(referencedTableName);
469
+ // Strip any schema prefix - SQLite REFERENCES clause doesn't support it
470
+ return tableName;
471
+ }
472
+ alterTable(diff, safe) {
473
+ const ret = [];
474
+ const [schemaName, tableName] = this.splitTableName(diff.name);
475
+ if (
476
+ Utils.hasObjectKeys(diff.removedChecks) ||
477
+ Utils.hasObjectKeys(diff.changedChecks) ||
478
+ Utils.hasObjectKeys(diff.changedForeignKeys) ||
479
+ Utils.hasObjectKeys(diff.changedColumns)
480
+ ) {
481
+ return this.getAlterTempTableSQL(diff);
482
+ }
483
+ for (const index of Object.values(diff.removedIndexes)) {
484
+ this.append(ret, this.dropIndex(diff.name, index));
485
+ }
486
+ for (const index of Object.values(diff.changedIndexes)) {
487
+ this.append(ret, this.dropIndex(diff.name, index));
488
+ }
489
+ /* v8 ignore next */
490
+ if (!safe && Object.values(diff.removedColumns).length > 0) {
491
+ this.append(ret, this.getDropColumnsSQL(tableName, Object.values(diff.removedColumns), schemaName));
492
+ }
493
+ if (Object.values(diff.addedColumns).length > 0) {
494
+ this.append(ret, this.getAddColumnsSQL(diff.toTable, Object.values(diff.addedColumns), diff));
495
+ }
496
+ if (Utils.hasObjectKeys(diff.addedForeignKeys) || Utils.hasObjectKeys(diff.addedChecks)) {
497
+ return this.getAlterTempTableSQL(diff);
498
+ }
499
+ for (const [oldColumnName, column] of Object.entries(diff.renamedColumns)) {
500
+ this.append(ret, this.getRenameColumnSQL(tableName, oldColumnName, column, schemaName));
501
+ }
502
+ for (const index of Object.values(diff.addedIndexes)) {
503
+ ret.push(this.createIndex(index, diff.toTable));
504
+ }
505
+ for (const index of Object.values(diff.changedIndexes)) {
506
+ ret.push(this.createIndex(index, diff.toTable, true));
507
+ }
508
+ for (const [oldIndexName, index] of Object.entries(diff.renamedIndexes)) {
509
+ if (index.unique) {
510
+ this.append(ret, this.dropIndex(diff.name, index, oldIndexName));
511
+ this.append(ret, this.createIndex(index, diff.toTable));
512
+ } else {
513
+ this.append(ret, this.getRenameIndexSQL(diff.name, index, oldIndexName));
514
+ }
515
+ }
516
+ return ret;
517
+ }
518
+ getAlterTempTableSQL(changedTable) {
519
+ const tempName = `${changedTable.toTable.name}__temp_alter`;
520
+ const quotedName = this.quote(changedTable.toTable.name);
521
+ const quotedTempName = this.quote(tempName);
522
+ const [first, ...rest] = this.createTable(changedTable.toTable);
523
+ const sql = [
524
+ 'pragma foreign_keys = off;',
525
+ first.replace(`create table ${quotedName}`, `create table ${quotedTempName}`),
526
+ ];
527
+ const columns = [];
528
+ for (const column of changedTable.toTable.getColumns()) {
529
+ const fromColumn = changedTable.fromTable.getColumn(column.name);
530
+ if (fromColumn) {
531
+ columns.push(this.quote(column.name));
532
+ } else {
533
+ columns.push(`null as ${this.quote(column.name)}`);
534
+ }
535
+ }
536
+ sql.push(`insert into ${quotedTempName} select ${columns.join(', ')} from ${quotedName};`);
537
+ sql.push(`drop table ${quotedName};`);
538
+ sql.push(`alter table ${quotedTempName} rename to ${quotedName};`);
539
+ sql.push(...rest);
540
+ sql.push('pragma foreign_keys = on;');
541
+ return sql;
542
+ }
530
543
  }