@mikro-orm/sql 7.0.4 → 7.0.5-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AbstractSqlConnection.d.ts +58 -94
- package/AbstractSqlConnection.js +238 -235
- package/AbstractSqlDriver.d.ts +155 -410
- package/AbstractSqlDriver.js +1941 -2064
- package/AbstractSqlPlatform.d.ts +73 -83
- package/AbstractSqlPlatform.js +158 -162
- package/PivotCollectionPersister.d.ts +15 -33
- package/PivotCollectionPersister.js +160 -158
- package/README.md +1 -1
- package/SqlEntityManager.d.ts +22 -67
- package/SqlEntityManager.js +38 -54
- package/SqlEntityRepository.d.ts +14 -14
- package/SqlEntityRepository.js +23 -23
- package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +12 -12
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +194 -192
- package/dialects/mysql/BaseMySqlPlatform.d.ts +45 -64
- package/dialects/mysql/BaseMySqlPlatform.js +131 -134
- package/dialects/mysql/MySqlExceptionConverter.d.ts +6 -6
- package/dialects/mysql/MySqlExceptionConverter.js +77 -91
- package/dialects/mysql/MySqlNativeQueryBuilder.d.ts +3 -3
- package/dialects/mysql/MySqlNativeQueryBuilder.js +69 -66
- package/dialects/mysql/MySqlSchemaHelper.d.ts +39 -39
- package/dialects/mysql/MySqlSchemaHelper.js +319 -327
- package/dialects/oracledb/OracleDialect.d.ts +52 -81
- package/dialects/oracledb/OracleDialect.js +149 -155
- package/dialects/oracledb/OracleNativeQueryBuilder.d.ts +12 -12
- package/dialects/oracledb/OracleNativeQueryBuilder.js +236 -232
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +106 -109
- package/dialects/postgresql/BasePostgreSqlPlatform.js +353 -354
- package/dialects/postgresql/FullTextType.d.ts +6 -10
- package/dialects/postgresql/FullTextType.js +51 -51
- package/dialects/postgresql/PostgreSqlExceptionConverter.d.ts +5 -5
- package/dialects/postgresql/PostgreSqlExceptionConverter.js +43 -55
- package/dialects/postgresql/PostgreSqlNativeQueryBuilder.d.ts +1 -1
- package/dialects/postgresql/PostgreSqlNativeQueryBuilder.js +4 -4
- package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +82 -102
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +705 -733
- package/dialects/sqlite/BaseSqliteConnection.d.ts +5 -3
- package/dialects/sqlite/BaseSqliteConnection.js +19 -21
- package/dialects/sqlite/NodeSqliteDialect.d.ts +1 -1
- package/dialects/sqlite/NodeSqliteDialect.js +23 -23
- package/dialects/sqlite/SqliteDriver.d.ts +1 -1
- package/dialects/sqlite/SqliteDriver.js +3 -3
- package/dialects/sqlite/SqliteExceptionConverter.d.ts +6 -6
- package/dialects/sqlite/SqliteExceptionConverter.js +51 -67
- package/dialects/sqlite/SqliteNativeQueryBuilder.d.ts +2 -2
- package/dialects/sqlite/SqliteNativeQueryBuilder.js +7 -7
- package/dialects/sqlite/SqlitePlatform.d.ts +72 -63
- package/dialects/sqlite/SqlitePlatform.js +139 -139
- package/dialects/sqlite/SqliteSchemaHelper.d.ts +60 -70
- package/dialects/sqlite/SqliteSchemaHelper.js +520 -533
- package/package.json +2 -2
- package/plugin/index.d.ts +35 -42
- package/plugin/index.js +36 -43
- package/plugin/transformer.d.ts +94 -117
- package/plugin/transformer.js +881 -890
- package/query/ArrayCriteriaNode.d.ts +4 -4
- package/query/ArrayCriteriaNode.js +18 -18
- package/query/CriteriaNode.d.ts +25 -35
- package/query/CriteriaNode.js +123 -133
- package/query/CriteriaNodeFactory.d.ts +6 -49
- package/query/CriteriaNodeFactory.js +94 -97
- package/query/NativeQueryBuilder.d.ts +118 -118
- package/query/NativeQueryBuilder.js +480 -484
- package/query/ObjectCriteriaNode.d.ts +12 -12
- package/query/ObjectCriteriaNode.js +282 -298
- package/query/QueryBuilder.d.ts +905 -1557
- package/query/QueryBuilder.js +2192 -2322
- package/query/QueryBuilderHelper.d.ts +72 -153
- package/query/QueryBuilderHelper.js +1028 -1079
- package/query/ScalarCriteriaNode.d.ts +3 -3
- package/query/ScalarCriteriaNode.js +46 -53
- package/query/enums.d.ts +14 -14
- package/query/enums.js +14 -14
- package/query/raw.d.ts +6 -16
- package/query/raw.js +10 -10
- package/schema/DatabaseSchema.d.ts +50 -73
- package/schema/DatabaseSchema.js +307 -331
- package/schema/DatabaseTable.d.ts +73 -96
- package/schema/DatabaseTable.js +927 -1012
- package/schema/SchemaComparator.d.ts +66 -70
- package/schema/SchemaComparator.js +740 -766
- package/schema/SchemaHelper.d.ts +95 -109
- package/schema/SchemaHelper.js +659 -675
- package/schema/SqlSchemaGenerator.d.ts +58 -78
- package/schema/SqlSchemaGenerator.js +501 -535
- package/typings.d.ts +266 -380
|
@@ -2,542 +2,529 @@ 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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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) {
|
|
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
|
+
}
|
|
289
92
|
/* v8 ignore next */
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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,
|
|
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;
|
|
398
178
|
});
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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+(.*)/is.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
|
+
};
|
|
408
299
|
});
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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
|
+
}
|
|
543
530
|
}
|