uql-orm 0.34.0 → 0.35.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.
Files changed (32) hide show
  1. package/README.md +1 -1
  2. package/dist/dialect/abstractDialect.js +2 -3
  3. package/dist/dialect/abstractSqlDialect.d.ts +6 -0
  4. package/dist/dialect/abstractSqlDialect.js +10 -3
  5. package/dist/migrate/builder/expressions.d.ts +8 -28
  6. package/dist/migrate/builder/expressions.js +8 -40
  7. package/dist/migrate/builder/migrationBuilder.js +3 -1
  8. package/dist/migrate/builder/tableBuilder.js +5 -4
  9. package/dist/migrate/generator/definitionToNode.js +3 -3
  10. package/dist/migrate/generator/mongoSchemaGenerator.js +3 -2
  11. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +12 -1
  12. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +16 -2
  13. package/dist/migrate/introspection/baseSqlIntrospector.d.ts +7 -1
  14. package/dist/migrate/introspection/baseSqlIntrospector.js +11 -12
  15. package/dist/migrate/introspection/mongoIntrospector.js +0 -1
  16. package/dist/migrate/introspection/mysqlIntrospector.d.ts +1 -0
  17. package/dist/migrate/introspection/mysqlIntrospector.js +8 -6
  18. package/dist/migrate/introspection/postgresIntrospector.d.ts +1 -0
  19. package/dist/migrate/introspection/postgresIntrospector.js +6 -5
  20. package/dist/migrate/migrator.d.ts +14 -5
  21. package/dist/migrate/migrator.js +32 -10
  22. package/dist/migrate/schemaGenerator.d.ts +12 -8
  23. package/dist/migrate/schemaGenerator.js +44 -29
  24. package/dist/schema/schemaAST.d.ts +8 -1
  25. package/dist/schema/schemaAST.js +22 -13
  26. package/dist/schema/schemaASTBuilder.d.ts +4 -2
  27. package/dist/schema/schemaASTBuilder.js +17 -25
  28. package/dist/schema/types.d.ts +10 -3
  29. package/dist/type/migration.d.ts +15 -1
  30. package/dist/util/sql.util.d.ts +17 -0
  31. package/dist/util/sql.util.js +23 -0
  32. package/package.json +2 -2
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  </picture>
8
8
  </a>
9
9
 
10
- <h3>The JSON-native TypeScript ORM</h3>
10
+ <h3>The JSON-native ORM</h3>
11
11
 
12
12
  <p>Queries are plain JSON, typed to the leaf. Unified across SQL databases and MongoDB.</p>
13
13
 
@@ -1,4 +1,5 @@
1
1
  import { applyFilters, buildQueryWhereAsMap } from '../util/dialect.util.js';
2
+ import { qualifyName } from '../util/sql.util.js';
2
3
  /**
3
4
  * The dialect's share of a pool's {@link ExtraOptions}: what changes the SQL rather than the
4
5
  * connection. Every pool builds its dialect through this, so a new option lands here instead of in
@@ -52,9 +53,7 @@ export class AbstractDialect {
52
53
  * would point at one that does not exist.
53
54
  */
54
55
  resolveTableName(meta) {
55
- const name = this.resolveTableAlias(meta);
56
- const schema = this.resolveSchema(meta);
57
- return schema ? `${schema}.${name}` : name;
56
+ return qualifyName(this.resolveTableAlias(meta), this.resolveSchema(meta));
58
57
  }
59
58
  /**
60
59
  * Which schema an entity is read from: its own wins over the pool's default, the way its `name`
@@ -301,6 +301,12 @@ export declare abstract class AbstractSqlDialect extends IndexSqlDialect impleme
301
301
  protected getUpsertConflictPathsStr<E>(meta: EntityMeta<E>, conflictPaths: QueryConflictPaths<E>): string;
302
302
  delete<E>(ctx: QueryContext, entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions): void;
303
303
  escapeId(val: string | undefined, forbidQualified?: boolean, addDot?: boolean): string;
304
+ /**
305
+ * A name behind its schema, each part escaped on its own rather than as one dotted string taken
306
+ * apart again by {@link escapeId}. Tables and their indexes both use it, since an index lives in
307
+ * the schema of the table it is on.
308
+ */
309
+ escapeQualifiedId(name: string, schema: string | undefined): string;
304
310
  /**
305
311
  * Bind one persisted value, classifying its column on the spot. Dialects override
306
312
  * {@link appendJsonValue} and {@link appendVectorValue} rather than this, so the chain runs once per
@@ -195,9 +195,7 @@ export class AbstractSqlDialect extends IndexSqlDialect {
195
195
  * string taken apart again by {@link escapeId}.
196
196
  */
197
197
  escapedTableName(meta) {
198
- const alias = this.escapeId(this.resolveTableAlias(meta), true);
199
- const schema = this.resolveSchema(meta);
200
- return schema ? `${this.escapeId(schema, true)}.${alias}` : alias;
198
+ return this.escapeQualifiedId(this.resolveTableAlias(meta), this.resolveSchema(meta));
201
199
  }
202
200
  /**
203
201
  * A FROM or JOIN operand plus the alias to prefix its columns by, aliased only once a schema puts
@@ -1065,6 +1063,15 @@ export class AbstractSqlDialect extends IndexSqlDialect {
1065
1063
  escapeId(val, forbidQualified, addDot) {
1066
1064
  return escapeSqlId(val, this.escapeIdChar, forbidQualified, addDot);
1067
1065
  }
1066
+ /**
1067
+ * A name behind its schema, each part escaped on its own rather than as one dotted string taken
1068
+ * apart again by {@link escapeId}. Tables and their indexes both use it, since an index lives in
1069
+ * the schema of the table it is on.
1070
+ */
1071
+ escapeQualifiedId(name, schema) {
1072
+ const escaped = this.escapeId(name, true);
1073
+ return schema ? `${this.escapeId(schema, true)}.${escaped}` : escaped;
1074
+ }
1068
1075
  /**
1069
1076
  * Bind one persisted value, classifying its column on the spot. Dialects override
1070
1077
  * {@link appendJsonValue} and {@link appendVectorValue} rather than this, so the chain runs once per
@@ -2,7 +2,7 @@
2
2
  * SQL Expressions
3
3
  *
4
4
  * Type-safe SQL expressions for default values and other uses.
5
- * Provides a `t` helper with common expressions.
5
+ * Provides an `expr` helper with common expressions.
6
6
  */
7
7
  /**
8
8
  * Represents a raw SQL expression (not a literal value).
@@ -18,11 +18,11 @@ export declare class SqlExpression {
18
18
  }
19
19
  /**
20
20
  * Helper object for common SQL expressions.
21
- * Use in migrations: `table.timestamp('createdAt').defaultValue(t.now())`
21
+ * Use in migrations: `t.timestamp('createdAt').defaultValue(expr.now())`
22
22
  */
23
- export declare const t: {
23
+ export declare const expr: {
24
24
  /**
25
- * Current timestamp (NOW() or CURRENT_TIMESTAMP depending on dialect).
25
+ * Current timestamp (CURRENT_TIMESTAMP).
26
26
  */
27
27
  now(): SqlExpression;
28
28
  /**
@@ -34,23 +34,11 @@ export declare const t: {
34
34
  */
35
35
  currentTime(): SqlExpression;
36
36
  /**
37
- * NULL value.
38
- */
39
- null(): SqlExpression;
40
- /**
41
- * Boolean true.
42
- */
43
- true(): SqlExpression;
44
- /**
45
- * Boolean false.
46
- */
47
- false(): SqlExpression;
48
- /**
49
- * Generate UUID (Postgres: gen_random_uuid(), MySQL: UUID()).
37
+ * Generate a UUID. Postgres only - use `expr.mysqlUuid()` on MySQL/MariaDB.
50
38
  */
51
39
  uuid(): SqlExpression;
52
40
  /**
53
- * Auto-generate UUID for MySQL.
41
+ * Generate a UUID on MySQL/MariaDB.
54
42
  */
55
43
  mysqlUuid(): SqlExpression;
56
44
  /**
@@ -59,19 +47,11 @@ export declare const t: {
59
47
  */
60
48
  raw(sql: string): SqlExpression;
61
49
  /**
62
- * String literal with proper escaping.
63
- */
64
- literal(value: string): SqlExpression;
65
- /**
66
- * Numeric literal.
67
- */
68
- number(value: number): SqlExpression;
69
- /**
70
- * Empty JSON object.
50
+ * Empty JSON object. Postgres only - the `::jsonb` cast is not portable.
71
51
  */
72
52
  emptyObject(): SqlExpression;
73
53
  /**
74
- * Empty JSON array.
54
+ * Empty JSON array. Postgres only - the `::jsonb` cast is not portable.
75
55
  */
76
56
  emptyArray(): SqlExpression;
77
57
  /**
@@ -2,7 +2,7 @@
2
2
  * SQL Expressions
3
3
  *
4
4
  * Type-safe SQL expressions for default values and other uses.
5
- * Provides a `t` helper with common expressions.
5
+ * Provides an `expr` helper with common expressions.
6
6
  */
7
7
  /**
8
8
  * Represents a raw SQL expression (not a literal value).
@@ -24,11 +24,11 @@ export class SqlExpression {
24
24
  }
25
25
  /**
26
26
  * Helper object for common SQL expressions.
27
- * Use in migrations: `table.timestamp('createdAt').defaultValue(t.now())`
27
+ * Use in migrations: `t.timestamp('createdAt').defaultValue(expr.now())`
28
28
  */
29
- export const t = {
29
+ export const expr = {
30
30
  /**
31
- * Current timestamp (NOW() or CURRENT_TIMESTAMP depending on dialect).
31
+ * Current timestamp (CURRENT_TIMESTAMP).
32
32
  */
33
33
  now() {
34
34
  return new SqlExpression('CURRENT_TIMESTAMP');
@@ -46,31 +46,13 @@ export const t = {
46
46
  return new SqlExpression('CURRENT_TIME');
47
47
  },
48
48
  /**
49
- * NULL value.
50
- */
51
- null() {
52
- return new SqlExpression('NULL');
53
- },
54
- /**
55
- * Boolean true.
56
- */
57
- true() {
58
- return new SqlExpression('TRUE');
59
- },
60
- /**
61
- * Boolean false.
62
- */
63
- false() {
64
- return new SqlExpression('FALSE');
65
- },
66
- /**
67
- * Generate UUID (Postgres: gen_random_uuid(), MySQL: UUID()).
49
+ * Generate a UUID. Postgres only - use `expr.mysqlUuid()` on MySQL/MariaDB.
68
50
  */
69
51
  uuid() {
70
52
  return new SqlExpression('gen_random_uuid()');
71
53
  },
72
54
  /**
73
- * Auto-generate UUID for MySQL.
55
+ * Generate a UUID on MySQL/MariaDB.
74
56
  */
75
57
  mysqlUuid() {
76
58
  return new SqlExpression('UUID()');
@@ -83,27 +65,13 @@ export const t = {
83
65
  return new SqlExpression(sql);
84
66
  },
85
67
  /**
86
- * String literal with proper escaping.
87
- */
88
- literal(value) {
89
- // Escape single quotes by doubling them
90
- const escaped = value.replace(/'/g, "''");
91
- return new SqlExpression(`'${escaped}'`);
92
- },
93
- /**
94
- * Numeric literal.
95
- */
96
- number(value) {
97
- return new SqlExpression(String(value));
98
- },
99
- /**
100
- * Empty JSON object.
68
+ * Empty JSON object. Postgres only - the `::jsonb` cast is not portable.
101
69
  */
102
70
  emptyObject() {
103
71
  return new SqlExpression("'{}'::jsonb");
104
72
  },
105
73
  /**
106
- * Empty JSON array.
74
+ * Empty JSON array. Postgres only - the `::jsonb` cast is not portable.
107
75
  */
108
76
  emptyArray() {
109
77
  return new SqlExpression("'[]'::jsonb");
@@ -6,6 +6,7 @@
6
6
  * - MigrationBuilder: Execute DDL operations (for integration tests/runtime)
7
7
  */
8
8
  import { normalizeIndexColumn } from '../../util/index.js';
9
+ import { derivedIndexName } from '../../util/sql.util.js';
9
10
  import { createSchemaGenerator } from '../schemaGenerator.js';
10
11
  import { splitSqlStatements } from './splitSqlStatements.js';
11
12
  import { TableBuilder } from './tableBuilder.js';
@@ -26,7 +27,8 @@ function createIndexOperation(tableName, columns, options = {}) {
26
27
  tableName,
27
28
  index: {
28
29
  ...index,
29
- name: name ?? `idx_${tableName}_${entries.map((entry) => entry.column).join('_')}`,
30
+ name: name ??
31
+ derivedIndexName(tableName, entries.map((entry) => entry.column)),
30
32
  entries,
31
33
  unique: unique ?? false,
32
34
  },
@@ -4,8 +4,9 @@
4
4
  * Fluent API for defining tables in migrations.
5
5
  */
6
6
  import { normalizeIndexColumn } from '../../util/index.js';
7
+ import { derivedIndexName } from '../../util/sql.util.js';
7
8
  import { ColumnBuilder } from './columnBuilder.js';
8
- import { t } from './expressions.js';
9
+ import { expr } from './expressions.js';
9
10
  /**
10
11
  * Builder for table-level foreign keys.
11
12
  */
@@ -136,10 +137,10 @@ export class TableBuilder {
136
137
  return column;
137
138
  }
138
139
  createdAt() {
139
- return this.add('createdAt', { category: 'timestamp' }, { defaultValue: t.now() });
140
+ return this.add('createdAt', { category: 'timestamp' }, { defaultValue: expr.now() });
140
141
  }
141
142
  updatedAt() {
142
- return this.add('updatedAt', { category: 'timestamp' }, { defaultValue: t.now() });
143
+ return this.add('updatedAt', { category: 'timestamp' }, { defaultValue: expr.now() });
143
144
  }
144
145
  timestamps() {
145
146
  this.createdAt();
@@ -190,7 +191,7 @@ export class TableBuilder {
190
191
  // Collect column-level indexes
191
192
  for (const col of columns) {
192
193
  if (col.index) {
193
- const indexName = typeof col.index === 'string' ? col.index : `idx_${this._name}_${col.name}`;
194
+ const indexName = typeof col.index === 'string' ? col.index : derivedIndexName(this._name, [col.name]);
194
195
  // Only add if not already in table-level indexes
195
196
  if (!this._indexes.some((idx) => idx.name === indexName)) {
196
197
  this._indexes.push({
@@ -1,3 +1,4 @@
1
+ import { derivedForeignKeyName } from '../../util/sql.util.js';
1
2
  /**
2
3
  * A migration builder's table definition as the AST nodes the generators render from, so a hand-written
3
4
  * `createTable` and an entity reach `generateCreateTableFromNode` in the same shape. Free functions and
@@ -11,7 +12,6 @@ export function tableDefinitionToNode(def) {
11
12
  columns,
12
13
  primaryKey: [], // placeholder
13
14
  indexes: [],
14
- schema: { tables: new Map(), relationships: [], indexes: [] },
15
15
  incomingRelations: [],
16
16
  outgoingRelations: [],
17
17
  comment: def.comment,
@@ -33,7 +33,7 @@ export function tableDefinitionToNode(def) {
33
33
  }
34
34
  for (const fkDef of def.foreignKeys) {
35
35
  const relNode = {
36
- name: fkDef.name ?? `fk_${def.name}_${fkDef.columns.join('_')}`,
36
+ name: fkDef.name ?? derivedForeignKeyName(def.name, fkDef.columns),
37
37
  type: 'ManyToOne', // Builder default
38
38
  from: {
39
39
  table,
@@ -64,7 +64,7 @@ export function fullColumnDefinitionToNode(col, tableName) {
64
64
  referencedBy: [],
65
65
  references: col.foreignKey
66
66
  ? {
67
- name: `fk_${tableName}_${col.name}`,
67
+ name: derivedForeignKeyName(tableName, [col.name]),
68
68
  type: 'ManyToOne',
69
69
  from: { table: { name: tableName }, columns: [] },
70
70
  to: {
@@ -2,6 +2,7 @@ import { AbstractDialect } from '../../dialect/abstractDialect.js';
2
2
  import { getMeta } from '../../entity/index.js';
3
3
  import { mongoDialectFeatures } from '../../mongo/mongoDialect.js';
4
4
  import { getKeys } from '../../util/index.js';
5
+ import { derivedIndexName } from '../../util/sql.util.js';
5
6
  import { indexNodeToSchema } from './indexNodeToSchema.js';
6
7
  import { serializeMongoCommand } from './mongoCommand.js';
7
8
  export class MongoSchemaGenerator extends AbstractDialect {
@@ -39,7 +40,7 @@ export class MongoSchemaGenerator extends AbstractDialect {
39
40
  const field = meta.fields[key];
40
41
  if (field?.index) {
41
42
  const columnName = this.resolveColumnName(key, field);
42
- const indexName = typeof field.index === 'string' ? field.index : `idx_${collectionName}_${columnName}`;
43
+ const indexName = typeof field.index === 'string' ? field.index : derivedIndexName(collectionName, [columnName]);
43
44
  indexes.push({
44
45
  name: indexName,
45
46
  entries: [{ column: columnName }],
@@ -142,7 +143,7 @@ export class MongoSchemaGenerator extends AbstractDialect {
142
143
  const field = meta.fields[key];
143
144
  if (field?.index) {
144
145
  const columnName = this.resolveColumnName(key, field);
145
- const indexName = typeof field.index === 'string' ? field.index : `idx_${collectionName}_${columnName}`;
146
+ const indexName = typeof field.index === 'string' ? field.index : derivedIndexName(collectionName, [columnName]);
146
147
  if (!existingIndexes.has(indexName)) {
147
148
  indexesToAdd.push({
148
149
  name: indexName,
@@ -36,7 +36,18 @@ export type TableRowReader = <T extends RawRow>(sql: string, params?: unknown[])
36
36
  */
37
37
  export declare abstract class AbstractSqlSchemaIntrospector extends BaseSqlIntrospector implements SchemaIntrospector {
38
38
  protected readonly pool: QuerierPool;
39
- constructor(pool: QuerierPool);
39
+ constructor(pool: QuerierPool, schema?: string);
40
+ /**
41
+ * The schema every catalogue query filters on, as SQL: the one that was asked for, or the engine's
42
+ * expression for the connection's default. A literal rather than a bind parameter because these
43
+ * queries are assembled as text and several use it more than once.
44
+ */
45
+ protected get schemaExpr(): string;
46
+ /**
47
+ * How this engine names the connection's current schema (Postgres) or database (MySQL). Empty on
48
+ * an engine with no schemas, whose catalogue queries never reference one.
49
+ */
50
+ protected readonly defaultSchemaExpr: string;
40
51
  getTableSchema(tableName: string): Promise<TableSchema | undefined>;
41
52
  getTableNames(): Promise<string[]>;
42
53
  tableExists(tableName: string): Promise<boolean>;
@@ -1,4 +1,5 @@
1
1
  import { isSqlQuerier } from '../../type/index.js';
2
+ import { escapeAnsiSqlLiteral } from '../../util/sqlLiteral.js';
2
3
  import { BaseSqlIntrospector } from './baseSqlIntrospector.js';
3
4
  /**
4
5
  * Abstract base class for SQL schema introspectors.
@@ -21,10 +22,23 @@ import { BaseSqlIntrospector } from './baseSqlIntrospector.js';
21
22
  */
22
23
  export class AbstractSqlSchemaIntrospector extends BaseSqlIntrospector {
23
24
  pool;
24
- constructor(pool) {
25
- super(pool.dialect);
25
+ constructor(pool, schema) {
26
+ super(pool.dialect, schema);
26
27
  this.pool = pool;
27
28
  }
29
+ /**
30
+ * The schema every catalogue query filters on, as SQL: the one that was asked for, or the engine's
31
+ * expression for the connection's default. A literal rather than a bind parameter because these
32
+ * queries are assembled as text and several use it more than once.
33
+ */
34
+ get schemaExpr() {
35
+ return this.schema === undefined ? this.defaultSchemaExpr : escapeAnsiSqlLiteral(this.schema);
36
+ }
37
+ /**
38
+ * How this engine names the connection's current schema (Postgres) or database (MySQL). Empty on
39
+ * an engine with no schemas, whose catalogue queries never reference one.
40
+ */
41
+ defaultSchemaExpr = '';
28
42
  async getTableSchema(tableName) {
29
43
  return this.withSqlQuerier(async (querier) => {
30
44
  const read = createTableRowReader(querier);
@@ -7,9 +7,15 @@ import type { TableSchema } from '../../type/migration.js';
7
7
  */
8
8
  export declare abstract class BaseSqlIntrospector {
9
9
  protected readonly dialect: AbstractSqlDialect;
10
+ readonly schema?: string | undefined;
10
11
  /** Columns and uniqueness only; each introspector opts in to what its catalogue queries report. */
11
12
  readonly indexFacets: ReadonlySet<IndexFacet>;
12
- constructor(dialect: AbstractSqlDialect);
13
+ /**
14
+ * The one schema these queries read, `undefined` for the connection's own default. Every table
15
+ * reported is stamped with it, so a diff compares like with like: entity and database both say
16
+ * `undefined` for "wherever the connection points", and name a schema only when one was asked for.
17
+ */
18
+ constructor(dialect: AbstractSqlDialect, schema?: string | undefined);
13
19
  protected escapeId(identifier: string): string;
14
20
  /**
15
21
  * Introspect entire database schema and return SchemaAST.
@@ -1,15 +1,22 @@
1
1
  import { canonicalColumnType } from '../../schema/canonicalType.js';
2
- import { SchemaAST } from '../../schema/schemaAST.js';
2
+ import { createTableNode, SchemaAST } from '../../schema/schemaAST.js';
3
3
  import { escapeSqlId } from '../../util/index.js';
4
4
  /**
5
5
  * Base class for SQL introspectors with shared AST building logic.
6
6
  */
7
7
  export class BaseSqlIntrospector {
8
8
  dialect;
9
+ schema;
9
10
  /** Columns and uniqueness only; each introspector opts in to what its catalogue queries report. */
10
11
  indexFacets = new Set();
11
- constructor(dialect) {
12
+ /**
13
+ * The one schema these queries read, `undefined` for the connection's own default. Every table
14
+ * reported is stamped with it, so a diff compares like with like: entity and database both say
15
+ * `undefined` for "wherever the connection points", and name a schema only when one was asked for.
16
+ */
17
+ constructor(dialect, schema) {
12
18
  this.dialect = dialect;
19
+ this.schema = schema;
13
20
  }
14
21
  escapeId(identifier) {
15
22
  return escapeSqlId(identifier, this.dialect.escapeIdChar);
@@ -41,16 +48,8 @@ export class BaseSqlIntrospector {
41
48
  }
42
49
  buildTables(ast, tableNodes, tableSchemas) {
43
50
  for (const schema of tableSchemas) {
44
- const columns = new Map();
45
- const table = {
46
- name: schema.name,
47
- columns,
48
- primaryKey: [],
49
- indexes: [],
50
- schema: ast,
51
- incomingRelations: [],
52
- outgoingRelations: [],
53
- };
51
+ const table = createTableNode(schema.name, this.schema);
52
+ const { columns } = table;
54
53
  for (const col of schema.columns) {
55
54
  const column = {
56
55
  name: col.name,
@@ -22,7 +22,6 @@ export class MongoSchemaIntrospector {
22
22
  columns,
23
23
  primaryKey: [],
24
24
  indexes: [],
25
- schema: ast,
26
25
  incomingRelations: [],
27
26
  outgoingRelations: [],
28
27
  };
@@ -5,6 +5,7 @@ import { AbstractSqlSchemaIntrospector, type TableRowReader } from './abstractSq
5
5
  * Works with both MySQL and MariaDB as they share the same information_schema structure.
6
6
  */
7
7
  export declare class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
8
+ protected readonly defaultSchemaExpr = "DATABASE()";
8
9
  protected getTableNamesQuery(): string;
9
10
  protected tableExistsQuery(): string;
10
11
  protected parseTableExistsResult(results: {
@@ -4,11 +4,13 @@ import { AbstractSqlSchemaIntrospector } from './abstractSqlSchemaIntrospector.j
4
4
  * Works with both MySQL and MariaDB as they share the same information_schema structure.
5
5
  */
6
6
  export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
7
+ // A MySQL "schema" is a database, so the connection's own is what `DATABASE()` reports.
8
+ defaultSchemaExpr = 'DATABASE()';
7
9
  getTableNamesQuery() {
8
10
  return /*sql*/ `
9
11
  SELECT TABLE_NAME as table_name
10
12
  FROM information_schema.TABLES
11
- WHERE TABLE_SCHEMA = DATABASE()
13
+ WHERE TABLE_SCHEMA = ${this.schemaExpr}
12
14
  AND TABLE_TYPE = 'BASE TABLE'
13
15
  ORDER BY TABLE_NAME
14
16
  `;
@@ -17,7 +19,7 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
17
19
  return /*sql*/ `
18
20
  SELECT COUNT(*) as count
19
21
  FROM information_schema.TABLES
20
- WHERE TABLE_SCHEMA = DATABASE()
22
+ WHERE TABLE_SCHEMA = ${this.schemaExpr}
21
23
  AND TABLE_NAME = ?
22
24
  `;
23
25
  }
@@ -43,7 +45,7 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
43
45
  EXTRA as extra,
44
46
  COLUMN_COMMENT as column_comment
45
47
  FROM information_schema.COLUMNS
46
- WHERE TABLE_SCHEMA = DATABASE()
48
+ WHERE TABLE_SCHEMA = ${this.schemaExpr}
47
49
  AND TABLE_NAME = ?
48
50
  ORDER BY ORDINAL_POSITION
49
51
  `;
@@ -55,7 +57,7 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
55
57
  GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) as columns,
56
58
  NOT NON_UNIQUE as is_unique
57
59
  FROM information_schema.STATISTICS
58
- WHERE TABLE_SCHEMA = DATABASE()
60
+ WHERE TABLE_SCHEMA = ${this.schemaExpr}
59
61
  AND TABLE_NAME = ?
60
62
  AND INDEX_NAME != 'PRIMARY'
61
63
  GROUP BY INDEX_NAME, NON_UNIQUE
@@ -75,7 +77,7 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
75
77
  JOIN information_schema.REFERENTIAL_CONSTRAINTS rc
76
78
  ON kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
77
79
  AND kcu.TABLE_SCHEMA = rc.CONSTRAINT_SCHEMA
78
- WHERE kcu.TABLE_SCHEMA = DATABASE()
80
+ WHERE kcu.TABLE_SCHEMA = ${this.schemaExpr}
79
81
  AND kcu.TABLE_NAME = ?
80
82
  AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
81
83
  GROUP BY kcu.CONSTRAINT_NAME, kcu.REFERENCED_TABLE_NAME, rc.DELETE_RULE, rc.UPDATE_RULE
@@ -86,7 +88,7 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
86
88
  return /*sql*/ `
87
89
  SELECT COLUMN_NAME as column_name
88
90
  FROM information_schema.KEY_COLUMN_USAGE
89
- WHERE TABLE_SCHEMA = DATABASE()
91
+ WHERE TABLE_SCHEMA = ${this.schemaExpr}
90
92
  AND TABLE_NAME = ?
91
93
  AND CONSTRAINT_NAME = 'PRIMARY'
92
94
  ORDER BY ORDINAL_POSITION
@@ -5,6 +5,7 @@ import { AbstractSqlSchemaIntrospector, type TableRowReader } from './abstractSq
5
5
  * PostgreSQL schema introspector
6
6
  */
7
7
  export declare class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
8
+ protected readonly defaultSchemaExpr = "current_schema()";
8
9
  /**
9
10
  * Expressions and predicates are read back too, for `generate:from-db`, but they are text the
10
11
  * database reprints in its own words, so they are not comparable and are not claimed here.
@@ -4,6 +4,7 @@ import { AbstractSqlSchemaIntrospector } from './abstractSqlSchemaIntrospector.j
4
4
  * PostgreSQL schema introspector
5
5
  */
6
6
  export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
7
+ defaultSchemaExpr = 'current_schema()';
7
8
  /**
8
9
  * Expressions and predicates are read back too, for `generate:from-db`, but they are text the
9
10
  * database reprints in its own words, so they are not comparable and are not claimed here.
@@ -19,7 +20,7 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
19
20
  return /*sql*/ `
20
21
  SELECT table_name
21
22
  FROM information_schema.tables
22
- WHERE table_schema = 'public'
23
+ WHERE table_schema = ${this.schemaExpr}
23
24
  AND table_type = 'BASE TABLE'
24
25
  ORDER BY table_name
25
26
  `;
@@ -28,7 +29,7 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
28
29
  return /*sql*/ `
29
30
  SELECT EXISTS (
30
31
  SELECT FROM information_schema.tables
31
- WHERE table_schema = 'public'
32
+ WHERE table_schema = ${this.schemaExpr}
32
33
  AND table_name = $1
33
34
  ) AS exists
34
35
  `;
@@ -74,7 +75,7 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
74
75
  c.ordinal_position
75
76
  ) AS column_comment
76
77
  FROM information_schema.columns c
77
- WHERE c.table_schema = 'public'
78
+ WHERE c.table_schema = ${this.schemaExpr}
78
79
  AND c.table_name = $1
79
80
  ORDER BY c.ordinal_position
80
81
  `;
@@ -152,7 +153,7 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
152
153
  AND rc.constraint_schema = tc.table_schema
153
154
  WHERE tc.constraint_type = 'FOREIGN KEY'
154
155
  AND tc.table_name = $1
155
- AND tc.table_schema = 'public'
156
+ AND tc.table_schema = ${this.schemaExpr}
156
157
  GROUP BY tc.constraint_name, ccu.table_name, rc.delete_rule, rc.update_rule
157
158
  ORDER BY tc.constraint_name
158
159
  `;
@@ -166,7 +167,7 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
166
167
  AND tc.table_schema = kcu.table_schema
167
168
  WHERE tc.constraint_type = 'PRIMARY KEY'
168
169
  AND tc.table_name = $1
169
- AND tc.table_schema = 'public'
170
+ AND tc.table_schema = ${this.schemaExpr}
170
171
  ORDER BY kcu.ordinal_position
171
172
  `;
172
173
  }
@@ -25,7 +25,8 @@ export declare class Migrator {
25
25
  * Set the schema generator for DDL operations
26
26
  */
27
27
  setSchemaGenerator(generator: SchemaGenerator): void;
28
- protected createIntrospector(): SchemaIntrospector | undefined;
28
+ /** `schema` reads one namespace instead of the connection's own; see {@link BaseSqlIntrospector.schema}. */
29
+ protected createIntrospector(schema?: string): SchemaIntrospector | undefined;
29
30
  protected createGenerator(): SchemaGenerator | undefined;
30
31
  /**
31
32
  * Get all discovered migrations from the migrations directory
@@ -77,6 +78,14 @@ export declare class Migrator {
77
78
  * Get all schema differences between entities and database
78
79
  */
79
80
  getDiffs(): Promise<SchemaDiff[]>;
81
+ /**
82
+ * One AST spanning every schema the entities claim, each table stamped with the schema it was read
83
+ * from. Read per schema rather than all at once, so a table comes back keyed exactly as the entity
84
+ * that wants it spells the key: `undefined` on both sides for the connection's default, a name on
85
+ * both sides otherwise. Ordinarily that is one schema and one pass, as before, and that pass keeps
86
+ * {@link schemaIntrospector} so a caller that replaced it still wins.
87
+ */
88
+ private introspectClaimedSchemas;
80
89
  findEntityForTable(tableName: string): Promise<Type<unknown> | undefined>;
81
90
  /**
82
91
  * Sync schema directly (for development only - not for production!)
@@ -189,10 +198,10 @@ export interface BuilderMigrationDefinition {
189
198
  * ```ts
190
199
  * export default defineBuilderMigration({
191
200
  * async up(m) {
192
- * await m.createTable('users', (table) => {
193
- * table.id();
194
- * table.string('email', 255).unique();
195
- * table.timestamps();
201
+ * await m.createTable('users', (t) => {
202
+ * t.id();
203
+ * t.string('email', { length: 255 }).unique();
204
+ * t.timestamps();
196
205
  * });
197
206
  * },
198
207
  * async down(m) {