@mikro-orm/sql 7.0.0-dev.196 → 7.0.0-dev.198

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.
@@ -3,4 +3,5 @@ export declare abstract class BaseSqliteConnection extends AbstractSqlConnection
3
3
  connect(options?: {
4
4
  skipOnConnect?: boolean;
5
5
  }): Promise<void>;
6
+ protected attachDatabases(): Promise<void>;
6
7
  }
@@ -4,5 +4,18 @@ export class BaseSqliteConnection extends AbstractSqlConnection {
4
4
  async connect(options) {
5
5
  await super.connect(options);
6
6
  await this.getClient().executeQuery(CompiledQuery.raw('pragma foreign_keys = on'));
7
+ await this.attachDatabases();
8
+ }
9
+ async attachDatabases() {
10
+ const attachDatabases = this.config.get('attachDatabases');
11
+ if (!attachDatabases?.length) {
12
+ return;
13
+ }
14
+ const { fs } = await import('@mikro-orm/core/fs-utils');
15
+ const baseDir = this.config.get('baseDir');
16
+ for (const db of attachDatabases) {
17
+ const path = fs.absolutePath(db.path, baseDir);
18
+ await this.execute(`attach database '${path}' as ${this.platform.quoteIdentifier(db.name)}`);
19
+ }
7
20
  }
8
21
  }
@@ -64,6 +64,12 @@ export declare abstract class BaseSqlitePlatform extends AbstractSqlPlatform {
64
64
  processDateProperty(value: unknown): string | number | Date;
65
65
  getIndexName(tableName: string, columns: string[], type: 'index' | 'unique' | 'foreign' | 'primary' | 'sequence'): string;
66
66
  supportsDeferredUniqueConstraints(): boolean;
67
+ /**
68
+ * SQLite supports schemas via ATTACH DATABASE. Returns true when there are
69
+ * attached databases configured.
70
+ */
71
+ supportsSchemas(): boolean;
72
+ getDefaultSchemaName(): string | undefined;
67
73
  getFullTextWhereClause(): string;
68
74
  quoteVersionValue(value: Date | number, prop: EntityProperty): Date | string | number;
69
75
  quoteValue(value: any): string;
@@ -86,6 +86,18 @@ export class BaseSqlitePlatform extends AbstractSqlPlatform {
86
86
  supportsDeferredUniqueConstraints() {
87
87
  return false;
88
88
  }
89
+ /**
90
+ * SQLite supports schemas via ATTACH DATABASE. Returns true when there are
91
+ * attached databases configured.
92
+ */
93
+ supportsSchemas() {
94
+ const attachDatabases = this.config.get('attachDatabases');
95
+ return !!attachDatabases?.length;
96
+ }
97
+ getDefaultSchemaName() {
98
+ // Return 'main' only when schema support is active (i.e., databases are attached)
99
+ return this.supportsSchemas() ? 'main' : undefined;
100
+ }
89
101
  getFullTextWhereClause() {
90
102
  return `:column: match :query`;
91
103
  }
@@ -8,7 +8,11 @@ export declare class SqliteSchemaHelper extends SchemaHelper {
8
8
  disableForeignKeysSQL(): string;
9
9
  enableForeignKeysSQL(): string;
10
10
  supportsSchemaConstraints(): boolean;
11
+ getCreateNamespaceSQL(name: string): string;
12
+ getDropNamespaceSQL(name: string): string;
11
13
  getListTablesSQL(): string;
14
+ getAllTables(connection: AbstractSqlConnection, schemas?: string[]): Promise<Table[]>;
15
+ getNamespaces(connection: AbstractSqlConnection): Promise<string[]>;
12
16
  getListViewsSQL(): string;
13
17
  loadViews(schema: DatabaseSchema, connection: AbstractSqlConnection, schemaName?: string): Promise<void>;
14
18
  getDropDatabaseSQL(name: string): string;
@@ -20,6 +24,19 @@ export declare class SqliteSchemaHelper extends SchemaHelper {
20
24
  getDropColumnsSQL(tableName: string, columns: Column[], schemaName?: string): string;
21
25
  getCreateIndexSQL(tableName: string, index: IndexDef): string;
22
26
  private parseTableDefinition;
27
+ /**
28
+ * Returns schema prefix for pragma and sqlite_master queries.
29
+ * Returns empty string for main database (no prefix needed).
30
+ */
31
+ private getSchemaPrefix;
32
+ /**
33
+ * Returns all database names excluding 'temp'.
34
+ */
35
+ private getDatabaseList;
36
+ /**
37
+ * Extracts the SELECT part from a CREATE VIEW statement.
38
+ */
39
+ private extractViewDefinition;
23
40
  private getColumns;
24
41
  private getEnumDefinitions;
25
42
  getPrimaryKeys(connection: AbstractSqlConnection, indexes: IndexDef[], tableName: string, schemaName?: string): Promise<string[]>;
@@ -35,6 +52,11 @@ export declare class SqliteSchemaHelper extends SchemaHelper {
35
52
  */
36
53
  isImplicitIndex(name: string): boolean;
37
54
  dropIndex(table: string, index: IndexDef, oldIndexName?: string): string;
55
+ /**
56
+ * SQLite does not support schema-qualified table names in REFERENCES clauses.
57
+ * Foreign key references can only point to tables in the same database.
58
+ */
59
+ getReferencedTableName(referencedTableName: string, schema?: string): string;
38
60
  alterTable(diff: TableDifference, safe?: boolean): string[];
39
61
  private getAlterTempTableSQL;
40
62
  }
@@ -10,20 +10,62 @@ export class SqliteSchemaHelper extends SchemaHelper {
10
10
  supportsSchemaConstraints() {
11
11
  return false;
12
12
  }
13
+ getCreateNamespaceSQL(name) {
14
+ return '';
15
+ }
16
+ getDropNamespaceSQL(name) {
17
+ return '';
18
+ }
13
19
  getListTablesSQL() {
14
20
  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' `
15
21
  + `union all select name as table_name from sqlite_temp_master where type = 'table' order by name`;
16
22
  }
23
+ async getAllTables(connection, schemas) {
24
+ const databases = await this.getDatabaseList(connection);
25
+ const hasAttachedDbs = databases.length > 1; // More than just 'main'
26
+ // If no attached databases, use original behavior
27
+ if (!hasAttachedDbs && !schemas?.length) {
28
+ return connection.execute(this.getListTablesSQL());
29
+ }
30
+ // With attached databases, query each one
31
+ const targetSchemas = schemas?.length ? schemas : databases;
32
+ const allTables = [];
33
+ for (const dbName of targetSchemas) {
34
+ const prefix = this.getSchemaPrefix(dbName);
35
+ const tables = await connection.execute(`select name from ${prefix}sqlite_master where type = 'table' ` +
36
+ `and name != 'sqlite_sequence' and name != 'geometry_columns' and name != 'spatial_ref_sys'`);
37
+ for (const t of tables) {
38
+ allTables.push({ table_name: t.name, schema_name: dbName });
39
+ }
40
+ }
41
+ return allTables;
42
+ }
43
+ async getNamespaces(connection) {
44
+ return this.getDatabaseList(connection);
45
+ }
17
46
  getListViewsSQL() {
18
47
  return `select name as view_name, sql as view_definition from sqlite_master where type = 'view' order by name`;
19
48
  }
20
49
  async loadViews(schema, connection, schemaName) {
21
- const views = await connection.execute(this.getListViewsSQL());
22
- for (const view of views) {
23
- // Extract the definition from CREATE VIEW statement
24
- const match = view.view_definition.match(/create\s+view\s+[`"']?\w+[`"']?\s+as\s+(.*)/i);
25
- const definition = match ? match[1] : view.view_definition;
26
- schema.addView(view.view_name, schemaName, definition);
50
+ const databases = await this.getDatabaseList(connection);
51
+ const hasAttachedDbs = databases.length > 1; // More than just 'main'
52
+ // If no attached databases and no specific schema, use original behavior
53
+ if (!hasAttachedDbs && !schemaName) {
54
+ const views = await connection.execute(this.getListViewsSQL());
55
+ for (const view of views) {
56
+ schema.addView(view.view_name, schemaName, this.extractViewDefinition(view.view_definition));
57
+ }
58
+ return;
59
+ }
60
+ // With attached databases, query each one
61
+ /* v8 ignore next - schemaName branch not commonly used */
62
+ const targetDbs = schemaName ? [schemaName] : databases;
63
+ for (const dbName of targetDbs) {
64
+ const prefix = this.getSchemaPrefix(dbName);
65
+ const views = await connection.execute(`select name as view_name, sql as view_definition from ${prefix}sqlite_master where type = 'view' order by name`);
66
+ for (const view of views) {
67
+ schema.addView(view.view_name, dbName, this.extractViewDefinition(view.view_definition));
68
+ }
27
69
  }
28
70
  }
29
71
  getDropDatabaseSQL(name) {
@@ -41,7 +83,7 @@ export class SqliteSchemaHelper extends SchemaHelper {
41
83
  const checks = await this.getChecks(connection, table.name, table.schema);
42
84
  const pks = await this.getPrimaryKeys(connection, indexes, table.name, table.schema);
43
85
  const fks = await this.getForeignKeys(connection, table.name, table.schema);
44
- const enums = await this.getEnumDefinitions(connection, table.name);
86
+ const enums = await this.getEnumDefinitions(connection, table.name, table.schema);
45
87
  table.init(cols, indexes, checks, pks, fks, enums);
46
88
  }
47
89
  }
@@ -73,6 +115,9 @@ export class SqliteSchemaHelper extends SchemaHelper {
73
115
  sql += ', ' + parts.join(', ');
74
116
  }
75
117
  sql += ')';
118
+ if (table.comment) {
119
+ sql += ` /* ${table.comment} */`;
120
+ }
76
121
  const ret = [];
77
122
  this.append(ret, sql);
78
123
  for (const index of table.getIndexes()) {
@@ -130,16 +175,26 @@ export class SqliteSchemaHelper extends SchemaHelper {
130
175
  if (index.expression) {
131
176
  return index.expression;
132
177
  }
133
- tableName = this.quote(tableName);
134
- const keyName = this.quote(index.keyName);
135
- const sql = `create ${index.unique ? 'unique ' : ''}index ${keyName} on ${tableName} `;
178
+ // SQLite requires: CREATE INDEX schema.index_name ON table_name (columns)
179
+ // NOT: CREATE INDEX index_name ON schema.table_name (columns)
180
+ const [schemaName, rawTableName] = this.splitTableName(tableName);
181
+ const quotedTableName = this.quote(rawTableName);
182
+ // If there's a schema, prefix the index name with it
183
+ let keyName;
184
+ if (schemaName && schemaName !== 'main') {
185
+ keyName = `${this.quote(schemaName)}.${this.quote(index.keyName)}`;
186
+ }
187
+ else {
188
+ keyName = this.quote(index.keyName);
189
+ }
190
+ const sqlPrefix = `create ${index.unique ? 'unique ' : ''}index ${keyName} on ${quotedTableName}`;
191
+ /* v8 ignore next 4 */
136
192
  if (index.columnNames.some(column => column.includes('.'))) {
137
193
  // JSON columns can have unique index but not unique constraint, and we need to distinguish those, so we can properly drop them
138
- const sql = `create ${index.unique ? 'unique ' : ''}index ${keyName} on ${tableName} `;
139
194
  const columns = this.platform.getJsonIndexDefinition(index);
140
- return `${sql}(${columns.join(', ')})`;
195
+ return `${sqlPrefix} (${columns.join(', ')})`;
141
196
  }
142
- return `${sql}(${index.columnNames.map(c => this.quote(c)).join(', ')})`;
197
+ return `${sqlPrefix} (${index.columnNames.map(c => this.quote(c)).join(', ')})`;
143
198
  }
144
199
  parseTableDefinition(sql, cols) {
145
200
  const columns = {};
@@ -164,9 +219,35 @@ export class SqliteSchemaHelper extends SchemaHelper {
164
219
  }
165
220
  return { columns, constraints };
166
221
  }
222
+ /**
223
+ * Returns schema prefix for pragma and sqlite_master queries.
224
+ * Returns empty string for main database (no prefix needed).
225
+ */
226
+ getSchemaPrefix(schemaName) {
227
+ if (!schemaName || schemaName === 'main') {
228
+ return '';
229
+ }
230
+ return `${this.platform.quoteIdentifier(schemaName)}.`;
231
+ }
232
+ /**
233
+ * Returns all database names excluding 'temp'.
234
+ */
235
+ async getDatabaseList(connection) {
236
+ const databases = await connection.execute('pragma database_list');
237
+ return databases.filter(d => d.name !== 'temp').map(d => d.name);
238
+ }
239
+ /**
240
+ * Extracts the SELECT part from a CREATE VIEW statement.
241
+ */
242
+ extractViewDefinition(viewDefinition) {
243
+ const match = viewDefinition?.match(/create\s+view\s+[`"']?\w+[`"']?\s+as\s+(.*)/i);
244
+ /* v8 ignore next - fallback for non-standard view definitions */
245
+ return match ? match[1] : viewDefinition;
246
+ }
167
247
  async getColumns(connection, tableName, schemaName) {
168
- const columns = await connection.execute(`pragma table_xinfo('${tableName}')`);
169
- const sql = `select sql from sqlite_master where type = ? and name = ?`;
248
+ const prefix = this.getSchemaPrefix(schemaName);
249
+ const columns = await connection.execute(`pragma ${prefix}table_xinfo('${tableName}')`);
250
+ const sql = `select sql from ${prefix}sqlite_master where type = ? and name = ?`;
170
251
  const tableDefinition = await connection.execute(sql, ['table', tableName], 'get');
171
252
  const composite = columns.reduce((count, col) => count + (col.pk ? 1 : 0), 0) > 1;
172
253
  // there can be only one, so naive check like this should be enough
@@ -197,8 +278,9 @@ export class SqliteSchemaHelper extends SchemaHelper {
197
278
  };
198
279
  });
199
280
  }
200
- async getEnumDefinitions(connection, tableName) {
201
- const sql = `select sql from sqlite_master where type = ? and name = ?`;
281
+ async getEnumDefinitions(connection, tableName, schemaName) {
282
+ const prefix = this.getSchemaPrefix(schemaName);
283
+ const sql = `select sql from ${prefix}sqlite_master where type = ? and name = ?`;
202
284
  const tableDefinition = await connection.execute(sql, ['table', tableName], 'get');
203
285
  const checkConstraints = [...(tableDefinition.sql.match(/[`["'][^`\]"']+[`\]"'] text check \(.*?\)/gi) ?? [])];
204
286
  return checkConstraints.reduce((o, item) => {
@@ -213,14 +295,16 @@ export class SqliteSchemaHelper extends SchemaHelper {
213
295
  }, {});
214
296
  }
215
297
  async getPrimaryKeys(connection, indexes, tableName, schemaName) {
216
- const sql = `pragma table_info(\`${tableName}\`)`;
298
+ const prefix = this.getSchemaPrefix(schemaName);
299
+ const sql = `pragma ${prefix}table_info(\`${tableName}\`)`;
217
300
  const cols = await connection.execute(sql);
218
301
  return cols.filter(col => !!col.pk).map(col => col.name);
219
302
  }
220
303
  async getIndexes(connection, tableName, schemaName) {
221
- const sql = `pragma table_info(\`${tableName}\`)`;
304
+ const prefix = this.getSchemaPrefix(schemaName);
305
+ const sql = `pragma ${prefix}table_info(\`${tableName}\`)`;
222
306
  const cols = await connection.execute(sql);
223
- const indexes = await connection.execute(`pragma index_list(\`${tableName}\`)`);
307
+ const indexes = await connection.execute(`pragma ${prefix}index_list(\`${tableName}\`)`);
224
308
  const ret = [];
225
309
  for (const col of cols.filter(c => c.pk)) {
226
310
  ret.push({
@@ -232,7 +316,7 @@ export class SqliteSchemaHelper extends SchemaHelper {
232
316
  });
233
317
  }
234
318
  for (const index of indexes.filter(index => !this.isImplicitIndex(index.name))) {
235
- const res = await connection.execute(`pragma index_info(\`${index.name}\`)`);
319
+ const res = await connection.execute(`pragma ${prefix}index_info(\`${index.name}\`)`);
236
320
  ret.push(...res.map(row => ({
237
321
  columnNames: [row.name],
238
322
  keyName: index.name,
@@ -271,14 +355,17 @@ export class SqliteSchemaHelper extends SchemaHelper {
271
355
  return checks;
272
356
  }
273
357
  async getColumnDefinitions(connection, tableName, schemaName) {
274
- const columns = await connection.execute(`pragma table_xinfo('${tableName}')`);
275
- const sql = `select sql from sqlite_master where type = ? and name = ?`;
358
+ const prefix = this.getSchemaPrefix(schemaName);
359
+ const columns = await connection.execute(`pragma ${prefix}table_xinfo('${tableName}')`);
360
+ const sql = `select sql from ${prefix}sqlite_master where type = ? and name = ?`;
276
361
  const tableDefinition = await connection.execute(sql, ['table', tableName], 'get');
277
362
  return this.parseTableDefinition(tableDefinition.sql, columns);
278
363
  }
279
364
  async getForeignKeys(connection, tableName, schemaName) {
280
365
  const { constraints } = await this.getColumnDefinitions(connection, tableName, schemaName);
281
- const fks = await connection.execute(`pragma foreign_key_list(\`${tableName}\`)`);
366
+ const prefix = this.getSchemaPrefix(schemaName);
367
+ const fks = await connection.execute(`pragma ${prefix}foreign_key_list(\`${tableName}\`)`);
368
+ const qualifiedTableName = schemaName ? `${schemaName}.${tableName}` : tableName;
282
369
  return fks.reduce((ret, fk) => {
283
370
  const constraintName = this.platform.getIndexName(tableName, [fk.from], 'foreign');
284
371
  const constraint = constraints?.find(c => c.includes(constraintName));
@@ -286,7 +373,7 @@ export class SqliteSchemaHelper extends SchemaHelper {
286
373
  constraintName,
287
374
  columnName: fk.from,
288
375
  columnNames: [fk.from],
289
- localTableName: tableName,
376
+ localTableName: qualifiedTableName,
290
377
  referencedTableName: fk.table,
291
378
  referencedColumnName: fk.to,
292
379
  referencedColumnNames: [fk.to],
@@ -317,6 +404,15 @@ export class SqliteSchemaHelper extends SchemaHelper {
317
404
  dropIndex(table, index, oldIndexName = index.keyName) {
318
405
  return `drop index ${this.quote(oldIndexName)}`;
319
406
  }
407
+ /**
408
+ * SQLite does not support schema-qualified table names in REFERENCES clauses.
409
+ * Foreign key references can only point to tables in the same database.
410
+ */
411
+ getReferencedTableName(referencedTableName, schema) {
412
+ const [schemaName, tableName] = this.splitTableName(referencedTableName);
413
+ // Strip any schema prefix - SQLite REFERENCES clause doesn't support it
414
+ return tableName;
415
+ }
320
416
  alterTable(diff, safe) {
321
417
  const ret = [];
322
418
  const [schemaName, tableName] = this.splitTableName(diff.name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/sql",
3
- "version": "7.0.0-dev.196",
3
+ "version": "7.0.0-dev.198",
4
4
  "description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -56,6 +56,6 @@
56
56
  "@mikro-orm/core": "^6.6.4"
57
57
  },
58
58
  "peerDependencies": {
59
- "@mikro-orm/core": "7.0.0-dev.196"
59
+ "@mikro-orm/core": "7.0.0-dev.198"
60
60
  }
61
61
  }
@@ -77,7 +77,7 @@ export class DatabaseSchema {
77
77
  }
78
78
  static async create(connection, platform, config, schemaName, schemas, takeTables, skipTables) {
79
79
  const schema = new DatabaseSchema(platform, schemaName ?? config.get('schema') ?? platform.getDefaultSchemaName());
80
- const allTables = await connection.execute(platform.getSchemaHelper().getListTablesSQL());
80
+ const allTables = await platform.getSchemaHelper().getAllTables(connection, schemas);
81
81
  const parts = config.get('migrations').tableName.split('.');
82
82
  const migrationsTableName = parts[1] ?? parts[0];
83
83
  const migrationsSchemaName = parts.length > 1 ? parts[0] : config.get('schema', platform.getDefaultSchemaName());
@@ -22,6 +22,7 @@ export declare abstract class SchemaHelper {
22
22
  getAlterNativeEnumSQL(name: string, schema?: string, value?: string, items?: string[], oldItems?: string[]): string;
23
23
  abstract loadInformationSchema(schema: DatabaseSchema, connection: AbstractSqlConnection, tables: Table[], schemas?: string[]): Promise<void>;
24
24
  getListTablesSQL(): string;
25
+ getAllTables(connection: AbstractSqlConnection, schemas?: string[]): Promise<Table[]>;
25
26
  getListViewsSQL(): string;
26
27
  loadViews(schema: DatabaseSchema, connection: AbstractSqlConnection): Promise<void>;
27
28
  getRenameColumnSQL(tableName: string, oldColumnName: string, to: Column, schemaName?: string): string;
@@ -65,6 +65,9 @@ export class SchemaHelper {
65
65
  getListTablesSQL() {
66
66
  throw new Error('Not supported by given driver');
67
67
  }
68
+ async getAllTables(connection, schemas) {
69
+ return connection.execute(this.getListTablesSQL());
70
+ }
68
71
  getListViewsSQL() {
69
72
  throw new Error('Not supported by given driver');
70
73
  }