uql-orm 0.25.0 → 0.26.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/README.md +9 -8
- package/dist/browser/uql-browser.min.js.map +1 -1
- package/dist/cockroachdb/cockroachDialect.js +1 -1
- package/dist/dialect/indexSqlDialect.d.ts +3 -2
- package/dist/dialect/indexSqlDialect.js +10 -8
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +0 -2
- package/dist/dialect/mysqlLikeSqlDialect.js +0 -7
- package/dist/dialect/pgLikeSqlDialect.js +1 -0
- package/dist/maria/mariaDialect.js +1 -1
- package/dist/migrate/bin.js +0 -0
- package/dist/migrate/builder/migrationBuilder.js +1 -1
- package/dist/migrate/builder/tableBuilder.js +2 -2
- package/dist/migrate/cli.js +4 -6
- package/dist/migrate/codegen/entityCodeGenerator.d.ts +5 -0
- package/dist/migrate/codegen/entityCodeGenerator.js +32 -27
- package/dist/migrate/codegen/fieldOptionsSource.d.ts +1 -1
- package/dist/migrate/codegen/fieldOptionsSource.js +6 -1
- package/dist/migrate/codegen/indexDecoratorSource.d.ts +14 -0
- package/dist/migrate/codegen/indexDecoratorSource.js +105 -0
- package/dist/migrate/drift/driftDetector.d.ts +5 -50
- package/dist/migrate/drift/driftDetector.js +215 -224
- package/dist/migrate/drift/index.d.ts +1 -1
- package/dist/migrate/drift/index.js +1 -1
- package/dist/migrate/generator/definitionToNode.d.ts +9 -0
- package/dist/migrate/generator/definitionToNode.js +79 -0
- package/dist/migrate/generator/indexNodeToSchema.js +4 -2
- package/dist/migrate/generator/mongoSchemaGenerator.js +3 -3
- package/dist/migrate/introspection/baseSqlIntrospector.d.ts +3 -0
- package/dist/migrate/introspection/baseSqlIntrospector.js +13 -5
- package/dist/migrate/introspection/mongoIntrospector.d.ts +3 -0
- package/dist/migrate/introspection/mongoIntrospector.js +5 -10
- package/dist/migrate/introspection/mysqlIntrospector.js +1 -1
- package/dist/migrate/introspection/postgresIntrospector.d.ts +57 -5
- package/dist/migrate/introspection/postgresIntrospector.js +93 -10
- package/dist/migrate/introspection/sqliteIntrospector.js +1 -1
- package/dist/migrate/migrator.js +3 -2
- package/dist/migrate/schemaGenerator.d.ts +26 -3
- package/dist/migrate/schemaGenerator.js +53 -92
- package/dist/schema/index.d.ts +3 -3
- package/dist/schema/index.js +2 -2
- package/dist/schema/indexColumns.d.ts +10 -0
- package/dist/schema/indexColumns.js +11 -0
- package/dist/schema/indexDifferences.d.ts +22 -0
- package/dist/schema/indexDifferences.js +49 -0
- package/dist/schema/schemaAST.js +2 -8
- package/dist/schema/schemaASTBuilder.d.ts +6 -59
- package/dist/schema/schemaASTBuilder.js +208 -233
- package/dist/schema/schemaASTDiffer.d.ts +9 -56
- package/dist/schema/schemaASTDiffer.js +229 -393
- package/dist/schema/types.d.ts +5 -12
- package/dist/schema/types.js +15 -0
- package/dist/type/dialect.d.ts +7 -2
- package/dist/type/dialect.js +1 -0
- package/dist/type/entity.d.ts +12 -10
- package/dist/type/migration.d.ts +14 -1
- package/dist/util/string.util.js +6 -1
- package/package.json +3 -4
- package/LICENSE.md +0 -22
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { AbstractSqlDialect } from '../dialect/index.js';
|
|
2
2
|
import { getMeta } from '../entity/index.js';
|
|
3
3
|
import { areTypesEqual, canonicalToSql, fieldOptionsToCanonical, isVectorCategory, sqlToCanonical, } from '../schema/canonicalType.js';
|
|
4
|
-
import {
|
|
4
|
+
import { buildSchemaAST } from '../schema/schemaASTBuilder.js';
|
|
5
5
|
import { escapeSqlId, getKeys, isAutoIncrement } from '../util/index.js';
|
|
6
6
|
import { formatDefaultValue } from './builder/expressions.js';
|
|
7
|
+
import { fullColumnDefinitionToNode, tableDefinitionToNode } from './generator/definitionToNode.js';
|
|
7
8
|
import { indexNodeToSchema } from './generator/indexNodeToSchema.js';
|
|
8
9
|
/**
|
|
9
10
|
* Unified SQL schema generator.
|
|
@@ -90,7 +91,7 @@ export class SqlSchemaGenerator {
|
|
|
90
91
|
* resolves instead of being silently dropped.
|
|
91
92
|
*/
|
|
92
93
|
orderedTables(entities, direction, only) {
|
|
93
|
-
const ast =
|
|
94
|
+
const ast = buildEntityAST(this, entities, this.defaultForeignKeyAction);
|
|
94
95
|
const tables = direction === 'create' ? ast.getCreateOrder() : ast.getDropOrder();
|
|
95
96
|
if (!only) {
|
|
96
97
|
return tables;
|
|
@@ -331,7 +332,11 @@ export class SqlSchemaGenerator {
|
|
|
331
332
|
for (const [name] of currentColumns) {
|
|
332
333
|
columnsToDrop.push(name);
|
|
333
334
|
}
|
|
334
|
-
|
|
335
|
+
const indexesToAdd = this.missingIndexes(entity, currentTable);
|
|
336
|
+
if (columnsToAdd.length === 0 &&
|
|
337
|
+
columnsToAlter.length === 0 &&
|
|
338
|
+
columnsToDrop.length === 0 &&
|
|
339
|
+
indexesToAdd.length === 0) {
|
|
335
340
|
return undefined;
|
|
336
341
|
}
|
|
337
342
|
return {
|
|
@@ -340,8 +345,32 @@ export class SqlSchemaGenerator {
|
|
|
340
345
|
columnsToAdd: columnsToAdd.length > 0 ? columnsToAdd : undefined,
|
|
341
346
|
columnsToAlter: columnsToAlter.length > 0 ? columnsToAlter : undefined,
|
|
342
347
|
columnsToDrop: columnsToDrop.length > 0 ? columnsToDrop : undefined,
|
|
348
|
+
indexesToAdd: indexesToAdd.length > 0 ? indexesToAdd : undefined,
|
|
343
349
|
};
|
|
344
350
|
}
|
|
351
|
+
/**
|
|
352
|
+
* Indexes the entity declares that the table does not have, matched by name and built the same way
|
|
353
|
+
* `CREATE TABLE` builds them, so adding an `@Index` to an entity already in the database is picked
|
|
354
|
+
* up rather than waiting for the table to be created from scratch somewhere else.
|
|
355
|
+
*
|
|
356
|
+
* Only ever additive. An index the entity does not name is left alone: it may well have been
|
|
357
|
+
* created deliberately outside the ORM, and dropping it is a decision for a reviewed migration.
|
|
358
|
+
*/
|
|
359
|
+
missingIndexes(entity, currentTable) {
|
|
360
|
+
const desired = buildEntityAST(this, [entity]).getTable(currentTable.name)?.indexes ?? [];
|
|
361
|
+
const present = new Set(currentTable.indexes.map((index) => index.name));
|
|
362
|
+
return desired
|
|
363
|
+
.filter((index) => !present.has(index.name) && !this.isInlineVectorIndex(index))
|
|
364
|
+
.map(indexNodeToSchema);
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* A vector index this dialect declares inside `CREATE TABLE` rather than as a statement of its own,
|
|
368
|
+
* which MariaDB is alone in doing. It has no `CREATE INDEX` form, so it can only ever be created
|
|
369
|
+
* with its table, never added to one.
|
|
370
|
+
*/
|
|
371
|
+
isInlineVectorIndex(index) {
|
|
372
|
+
return this.features.inlineVectorIndex && index.type === 'vector';
|
|
373
|
+
}
|
|
345
374
|
columnNodeToSchema(col) {
|
|
346
375
|
return {
|
|
347
376
|
name: col.name,
|
|
@@ -430,12 +459,11 @@ export class SqlSchemaGenerator {
|
|
|
430
459
|
generateCreateTableFromNode(table, options = {}) {
|
|
431
460
|
const columns = [];
|
|
432
461
|
const constraints = [];
|
|
433
|
-
const
|
|
434
|
-
const
|
|
435
|
-
const regularIndexes = isInlineVectorIdx ? table.indexes.filter((idx) => idx.type !== 'vector') : table.indexes;
|
|
462
|
+
const vectorIndexes = table.indexes.filter((index) => this.isInlineVectorIndex(index));
|
|
463
|
+
const regularIndexes = table.indexes.filter((index) => !this.isInlineVectorIndex(index));
|
|
436
464
|
// MariaDB rejects a `VECTOR INDEX` whose column is nullable ("All parts of a VECTOR index must
|
|
437
465
|
// be NOT NULL"), so being indexed decides it rather than the entity's own nullability.
|
|
438
|
-
const indexedVectorColumns = new Set(vectorIndexes.flatMap((idx) => idx.
|
|
466
|
+
const indexedVectorColumns = new Set(vectorIndexes.flatMap((idx) => idx.entries.map((entry) => entry.column)));
|
|
439
467
|
for (const col of table.columns.values()) {
|
|
440
468
|
const colDef = this.generateColumnFromNode(indexedVectorColumns.has(col.name) ? { ...col, nullable: false } : col);
|
|
441
469
|
columns.push(colDef);
|
|
@@ -503,7 +531,7 @@ export class SqlSchemaGenerator {
|
|
|
503
531
|
return this.generateCreateIndex(index.table.name, indexNodeToSchema(index), options);
|
|
504
532
|
}
|
|
505
533
|
generateCreateTableFromDefinition(table, options = {}) {
|
|
506
|
-
const tableNode =
|
|
534
|
+
const tableNode = tableDefinitionToNode(table);
|
|
507
535
|
return this.generateCreateTableFromNode(tableNode, options);
|
|
508
536
|
}
|
|
509
537
|
generateRenameTableSql(oldName, newName) {
|
|
@@ -513,11 +541,11 @@ export class SqlSchemaGenerator {
|
|
|
513
541
|
return `ALTER TABLE ${this.escapeId(oldName)} RENAME TO ${this.escapeId(newName)};`;
|
|
514
542
|
}
|
|
515
543
|
generateAddColumnSql(tableName, column) {
|
|
516
|
-
const colSql = this.generateColumnFromNode(
|
|
544
|
+
const colSql = this.generateColumnFromNode(fullColumnDefinitionToNode(column, tableName));
|
|
517
545
|
return `ALTER TABLE ${this.escapeId(tableName)} ADD COLUMN ${colSql};`;
|
|
518
546
|
}
|
|
519
547
|
generateAlterColumnSql(tableName, columnName, column) {
|
|
520
|
-
const node =
|
|
548
|
+
const node = fullColumnDefinitionToNode(column, tableName);
|
|
521
549
|
return this.generateAlterColumnStatements(tableName, { ...this.columnNodeToSchema(node), name: columnName }, this.generateColumnFromNode(node)).join('\n');
|
|
522
550
|
}
|
|
523
551
|
generateDropColumnSql(tableName, columnName) {
|
|
@@ -542,88 +570,21 @@ export class SqlSchemaGenerator {
|
|
|
542
570
|
generateDropForeignKeySql(tableName, constraintName) {
|
|
543
571
|
return `ALTER TABLE ${this.escapeId(tableName)} ${this.dialect.dropForeignKeySyntax} ${this.escapeId(constraintName)};`;
|
|
544
572
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
node.table = table;
|
|
561
|
-
columns.set(node.name, node);
|
|
562
|
-
if (node.isPrimaryKey) {
|
|
563
|
-
pkNodes.push(node);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
const finalPrimaryKey = def.primaryKey
|
|
567
|
-
? def.primaryKey.map((name) => columns.get(name)).filter((c) => c !== undefined)
|
|
568
|
-
: pkNodes;
|
|
569
|
-
table.primaryKey = finalPrimaryKey;
|
|
570
|
-
for (const idxDef of def.indexes) {
|
|
571
|
-
const indexNode = {
|
|
572
|
-
...idxDef,
|
|
573
|
-
table,
|
|
574
|
-
columns: idxDef.columns
|
|
575
|
-
.map((entry) => (entry.expression ? undefined : columns.get(entry.column)))
|
|
576
|
-
.filter((c) => c !== undefined),
|
|
577
|
-
entries: idxDef.columns,
|
|
578
|
-
};
|
|
579
|
-
table.indexes.push(indexNode);
|
|
580
|
-
}
|
|
581
|
-
for (const fkDef of def.foreignKeys) {
|
|
582
|
-
const relNode = {
|
|
583
|
-
name: fkDef.name ?? `fk_${def.name}_${fkDef.columns.join('_')}`,
|
|
584
|
-
type: 'ManyToOne', // Builder default
|
|
585
|
-
from: {
|
|
586
|
-
table,
|
|
587
|
-
columns: fkDef.columns.map((name) => columns.get(name)).filter((c) => c !== undefined),
|
|
588
|
-
},
|
|
589
|
-
to: {
|
|
590
|
-
table: { name: fkDef.referencesTable },
|
|
591
|
-
columns: fkDef.referencesColumns.map((name) => ({ name })),
|
|
592
|
-
},
|
|
593
|
-
onDelete: fkDef.onDelete,
|
|
594
|
-
onUpdate: fkDef.onUpdate,
|
|
595
|
-
};
|
|
596
|
-
table.outgoingRelations.push(relNode);
|
|
597
|
-
}
|
|
598
|
-
return table;
|
|
599
|
-
}
|
|
600
|
-
fullColumnDefinitionToNode(col, tableName) {
|
|
601
|
-
return {
|
|
602
|
-
name: col.name,
|
|
603
|
-
type: col.type,
|
|
604
|
-
nullable: col.nullable,
|
|
605
|
-
defaultValue: col.defaultValue,
|
|
606
|
-
isPrimaryKey: col.primaryKey,
|
|
607
|
-
isAutoIncrement: col.autoIncrement,
|
|
608
|
-
isUnique: col.unique,
|
|
609
|
-
comment: col.comment,
|
|
610
|
-
table: { name: tableName },
|
|
611
|
-
referencedBy: [],
|
|
612
|
-
references: col.foreignKey
|
|
613
|
-
? {
|
|
614
|
-
name: `fk_${tableName}_${col.name}`,
|
|
615
|
-
type: 'ManyToOne',
|
|
616
|
-
from: { table: { name: tableName }, columns: [] },
|
|
617
|
-
to: {
|
|
618
|
-
table: { name: col.foreignKey.table },
|
|
619
|
-
columns: col.foreignKey.columns.map((name) => ({ name })),
|
|
620
|
-
},
|
|
621
|
-
onDelete: col.foreignKey.onDelete,
|
|
622
|
-
onUpdate: col.foreignKey.onUpdate,
|
|
623
|
-
}
|
|
624
|
-
: undefined,
|
|
625
|
-
};
|
|
626
|
-
}
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* The entities as an AST, named the way `generator` names things.
|
|
576
|
+
*
|
|
577
|
+
* Its resolvers rather than a naming strategy, because the two disagree: a strategy renames whatever
|
|
578
|
+
* it is handed, while a generator leaves an explicit `@Entity({ name })` alone. Build the AST the
|
|
579
|
+
* other way and the table is created under one name and compared under another, which reports every
|
|
580
|
+
* table of a project using a naming strategy as both missing and unexpected.
|
|
581
|
+
*/
|
|
582
|
+
export function buildEntityAST(generator, entities, defaultForeignKeyAction) {
|
|
583
|
+
return buildSchemaAST(entities, {
|
|
584
|
+
resolveTableName: (entity, meta) => generator.resolveTableName(entity, meta),
|
|
585
|
+
resolveColumnName: (key, field) => generator.resolveColumnName(key, field),
|
|
586
|
+
defaultForeignKeyAction,
|
|
587
|
+
});
|
|
627
588
|
}
|
|
628
589
|
/**
|
|
629
590
|
* Synchronous factory for SQL schema generators only.
|
package/dist/schema/index.d.ts
CHANGED
|
@@ -18,8 +18,8 @@ export { areTypesEqual, canonicalToColumnType, canonicalToSql, canonicalToTypeSc
|
|
|
18
18
|
*/
|
|
19
19
|
export declare function introspectSchema(introspector: SchemaIntrospector): Promise<SchemaAST>;
|
|
20
20
|
export { SchemaAST } from './schemaAST.js';
|
|
21
|
-
export type {
|
|
22
|
-
export {
|
|
21
|
+
export type { BuildSchemaASTOptions } from './schemaASTBuilder.js';
|
|
22
|
+
export { buildSchemaAST } from './schemaASTBuilder.js';
|
|
23
23
|
export type { DiffOptions } from './schemaASTDiffer.js';
|
|
24
|
-
export { diffSchemas
|
|
24
|
+
export { diffSchemas } from './schemaASTDiffer.js';
|
|
25
25
|
export type { CanonicalType, ColumnDiff, ColumnNode, Drift, DriftReport, DriftSeverity, DriftStatus, DriftType, ForeignKeyAction, IndexDiff, IndexNode, IndexSource, IndexSyncStatus, IndexType, RelationshipDiff, RelationshipNode, RelationshipSource, RelationshipType, SchemaAST as ISchemaAST, SchemaDiffResult, SizeVariant, TableDiff, TableNode, TypeCategory, ValidationError, ValidationErrorType, } from './types.js';
|
package/dist/schema/index.js
CHANGED
|
@@ -21,6 +21,6 @@ export async function introspectSchema(introspector) {
|
|
|
21
21
|
// SchemaAST class
|
|
22
22
|
export { SchemaAST } from './schemaAST.js';
|
|
23
23
|
// Builder
|
|
24
|
-
export {
|
|
24
|
+
export { buildSchemaAST } from './schemaASTBuilder.js';
|
|
25
25
|
// Differ
|
|
26
|
-
export { diffSchemas
|
|
26
|
+
export { diffSchemas } from './schemaASTDiffer.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ColumnNode, IndexNode } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The table columns an index resolves to, in order.
|
|
4
|
+
*
|
|
5
|
+
* Derived rather than stored: an index is defined by its entries, and a second field repeating them
|
|
6
|
+
* as columns is a second thing to keep in step. It went out of step - one introspector rebuilt the
|
|
7
|
+
* entries from the columns it had just built from the entries, and every fixture had to write both.
|
|
8
|
+
* An expression entry resolves to no column at all, which is why the two were never the same list.
|
|
9
|
+
*/
|
|
10
|
+
export declare function indexColumns(index: IndexNode): ColumnNode[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The table columns an index resolves to, in order.
|
|
3
|
+
*
|
|
4
|
+
* Derived rather than stored: an index is defined by its entries, and a second field repeating them
|
|
5
|
+
* as columns is a second thing to keep in step. It went out of step - one introspector rebuilt the
|
|
6
|
+
* entries from the columns it had just built from the entries, and every fixture had to write both.
|
|
7
|
+
* An expression entry resolves to no column at all, which is why the two were never the same list.
|
|
8
|
+
*/
|
|
9
|
+
export function indexColumns(index) {
|
|
10
|
+
return index.entries.flatMap((entry) => (entry.expression ? [] : (index.table.columns.get(entry.column) ?? [])));
|
|
11
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { IndexNode } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* What an introspector can report about an index, and so all that diffing may compare. Anything it
|
|
4
|
+
* cannot report is skipped: the entity declares it, the database never mentions it, and no migration
|
|
5
|
+
* could ever make the two agree.
|
|
6
|
+
*
|
|
7
|
+
* Deliberately separate from the dialect's `IndexFeature`, which says what an engine can *emit* and
|
|
8
|
+
* rejects outright. The two look alike and are not: Postgres emits an expression index and reads one
|
|
9
|
+
* back, MySQL emits one it cannot describe afterwards.
|
|
10
|
+
*/
|
|
11
|
+
export type IndexFacet = 'order' | 'nulls' | 'opsClass' | 'accessMethod' | 'include';
|
|
12
|
+
/**
|
|
13
|
+
* Everything an index differs by, named, or nothing when the two match.
|
|
14
|
+
*
|
|
15
|
+
* Only what both sides can state *structurally* is compared. SQL text is not: a database reprints an
|
|
16
|
+
* expression and a predicate from its parse tree, so `status IN ('a','b')` reads back as
|
|
17
|
+
* `status = ANY (ARRAY['a'::text, 'b'::text])`, `LIKE` as `~~`, and a date literal with its time zone
|
|
18
|
+
* spelled out. Folding that back needs a SQL parser, and every near-miss reports drift that no
|
|
19
|
+
* migration can settle. So a partial index's predicate is never compared, and an index over an
|
|
20
|
+
* expression has its entries left alone while the rest of it still compares.
|
|
21
|
+
*/
|
|
22
|
+
export declare function describeIndexDifferences(source: IndexNode, target: IndexNode, facets: ReadonlySet<IndexFacet>): string[];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything an index differs by, named, or nothing when the two match.
|
|
3
|
+
*
|
|
4
|
+
* Only what both sides can state *structurally* is compared. SQL text is not: a database reprints an
|
|
5
|
+
* expression and a predicate from its parse tree, so `status IN ('a','b')` reads back as
|
|
6
|
+
* `status = ANY (ARRAY['a'::text, 'b'::text])`, `LIKE` as `~~`, and a date literal with its time zone
|
|
7
|
+
* spelled out. Folding that back needs a SQL parser, and every near-miss reports drift that no
|
|
8
|
+
* migration can settle. So a partial index's predicate is never compared, and an index over an
|
|
9
|
+
* expression has its entries left alone while the rest of it still compares.
|
|
10
|
+
*/
|
|
11
|
+
export function describeIndexDifferences(source, target, facets) {
|
|
12
|
+
const differences = [];
|
|
13
|
+
const comparableEntries = ![...source.entries, ...target.entries].some((entry) => entry.expression);
|
|
14
|
+
if (comparableEntries) {
|
|
15
|
+
const [sourceColumns, targetColumns] = [source.entries, target.entries].map((entries) => entries.map((entry) => entrySignature(entry, facets)).join(', '));
|
|
16
|
+
if (sourceColumns !== targetColumns) {
|
|
17
|
+
differences.push(`columns: (${targetColumns}) → (${sourceColumns})`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if ((source.unique ?? false) !== (target.unique ?? false)) {
|
|
21
|
+
differences.push(`unique: ${target.unique ?? false} → ${source.unique ?? false}`);
|
|
22
|
+
}
|
|
23
|
+
if (facets.has('accessMethod') && (source.type ?? 'btree') !== (target.type ?? 'btree')) {
|
|
24
|
+
differences.push(`type: ${target.type ?? 'btree'} → ${source.type ?? 'btree'}`);
|
|
25
|
+
}
|
|
26
|
+
if (facets.has('include')) {
|
|
27
|
+
// Order carries no meaning in an `INCLUDE` list, so it is compared as a set.
|
|
28
|
+
const [sourceInclude, targetInclude] = [source.include ?? [], target.include ?? []].map((columns) => [...columns].sort().join(', '));
|
|
29
|
+
if (sourceInclude !== targetInclude) {
|
|
30
|
+
differences.push(`include: (${targetInclude}) → (${sourceInclude})`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return differences;
|
|
34
|
+
}
|
|
35
|
+
function entrySignature(entry, facets) {
|
|
36
|
+
const parts = [entry.column];
|
|
37
|
+
if (facets.has('order')) {
|
|
38
|
+
parts.push(entry.order ?? 'asc');
|
|
39
|
+
}
|
|
40
|
+
if (facets.has('nulls')) {
|
|
41
|
+
// Postgres states this on every entry, so an entity that omits it has asked for Postgres's own
|
|
42
|
+
// default: nulls sort opposite to the direction.
|
|
43
|
+
parts.push(`nulls ${entry.nulls ?? ((entry.order ?? 'asc') === 'desc' ? 'first' : 'last')}`);
|
|
44
|
+
}
|
|
45
|
+
if (facets.has('opsClass') && entry.opsClass) {
|
|
46
|
+
parts.push(entry.opsClass);
|
|
47
|
+
}
|
|
48
|
+
return parts.join(' ');
|
|
49
|
+
}
|
package/dist/schema/schemaAST.js
CHANGED
|
@@ -400,13 +400,7 @@ export class SchemaAST {
|
|
|
400
400
|
const table = clone.tables.get(idx.table.name);
|
|
401
401
|
if (!table)
|
|
402
402
|
continue;
|
|
403
|
-
|
|
404
|
-
const clonedIdx = {
|
|
405
|
-
...idx,
|
|
406
|
-
table,
|
|
407
|
-
columns,
|
|
408
|
-
};
|
|
409
|
-
clone.addIndex(clonedIdx);
|
|
403
|
+
clone.addIndex({ ...idx, table });
|
|
410
404
|
}
|
|
411
405
|
return clone;
|
|
412
406
|
}
|
|
@@ -442,7 +436,7 @@ export class SchemaAST {
|
|
|
442
436
|
})),
|
|
443
437
|
indexes: t.indexes.map((i) => ({
|
|
444
438
|
name: i.name,
|
|
445
|
-
columns: i.
|
|
439
|
+
columns: i.entries.map((entry) => entry.column),
|
|
446
440
|
unique: i.unique,
|
|
447
441
|
})),
|
|
448
442
|
})),
|
|
@@ -12,7 +12,7 @@ import type { ForeignKeyAction } from './types.js';
|
|
|
12
12
|
/**
|
|
13
13
|
* Options for building SchemaAST from entities.
|
|
14
14
|
*/
|
|
15
|
-
export interface
|
|
15
|
+
export interface BuildSchemaASTOptions {
|
|
16
16
|
/** Custom table name resolver */
|
|
17
17
|
resolveTableName?: (entity: Type<unknown>, meta: EntityMeta<unknown>) => string;
|
|
18
18
|
/** Custom column name resolver */
|
|
@@ -23,62 +23,9 @@ export interface BuildFromEntitiesOptions {
|
|
|
23
23
|
defaultForeignKeyAction?: ForeignKeyAction;
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* Build a SchemaAST from entity classes (decorated with `@Entity`, `@Field`, etc.).
|
|
27
|
+
*
|
|
28
|
+
* Three passes, because each needs the one before it to have finished for *every* entity: a relation
|
|
29
|
+
* resolves against a table another entity declares, and an index against the columns of its own.
|
|
27
30
|
*/
|
|
28
|
-
export declare
|
|
29
|
-
private readonly namingStrategy?;
|
|
30
|
-
private readonly defaultForeignKeyAction;
|
|
31
|
-
private ast;
|
|
32
|
-
constructor(namingStrategy?: NamingStrategy | undefined, defaultForeignKeyAction?: ForeignKeyAction);
|
|
33
|
-
/**
|
|
34
|
-
* Reset the builder for a new schema.
|
|
35
|
-
*/
|
|
36
|
-
reset(): this;
|
|
37
|
-
/**
|
|
38
|
-
* Get the built AST.
|
|
39
|
-
*/
|
|
40
|
-
getAST(): SchemaAST;
|
|
41
|
-
/**
|
|
42
|
-
* Build AST from entity classes (decorated with @Entity, @Field, etc.)
|
|
43
|
-
*/
|
|
44
|
-
fromEntities(entities: readonly Type<unknown>[], options?: BuildFromEntitiesOptions): SchemaAST;
|
|
45
|
-
/**
|
|
46
|
-
* Resolve the canonical type for a field, inheriting from the referenced
|
|
47
|
-
* entity's primary key when the field is a foreign-key reference
|
|
48
|
-
* (`@Field({ references: () => SomeEntity })`) with no explicit type of its
|
|
49
|
-
* own.
|
|
50
|
-
*
|
|
51
|
-
* Without this, a field like `creatorId?: UUID` (a bare TypeScript alias for
|
|
52
|
-
* `string`, erased at runtime) falls back to the generic string inference in
|
|
53
|
-
* {@link fieldOptionsToCanonical} and gets typed as TEXT/VARCHAR - producing a
|
|
54
|
-
* foreign key column whose type doesn't match the UUID primary key it
|
|
55
|
-
* references, which Postgres (and most databases) reject outright.
|
|
56
|
-
*
|
|
57
|
-
* `field.typeFromReference` (set by `defineField`, see entity/metadata/definition.ts)
|
|
58
|
-
* is what distinguishes "no type was given" from "the decorator explicitly set
|
|
59
|
-
* a type" - including explicit constructor overrides like `type: BigInt`, which
|
|
60
|
-
* a value-based check (e.g. `typeof field.type === 'string'`) would miss since
|
|
61
|
-
* reflection also produces constructor values like `String`/`Number`.
|
|
62
|
-
* `columnType` remains the unambiguous, always-respected explicit override.
|
|
63
|
-
*/
|
|
64
|
-
private resolveColumnCanonicalType;
|
|
65
|
-
/**
|
|
66
|
-
* Add a table from entity metadata.
|
|
67
|
-
*/
|
|
68
|
-
private addTableFromEntity;
|
|
69
|
-
/**
|
|
70
|
-
* Add relationships from entity relation decorators.
|
|
71
|
-
*/
|
|
72
|
-
private addRelationshipsFromEntity;
|
|
73
|
-
/**
|
|
74
|
-
* Add indexes from field options (`@Field({ index })`) and from `@Index([...])`, which have nothing
|
|
75
|
-
* in common beyond their target table.
|
|
76
|
-
*/
|
|
77
|
-
private addIndexesFromEntity;
|
|
78
|
-
/**
|
|
79
|
-
* One `@Index([...])`. Its entries keep the authored form (expression, prefix length, order) with
|
|
80
|
-
* names resolved, so the generator renders exactly what was declared; `columns` is the resolvable
|
|
81
|
-
* subset, which is what diffing and introspection compare.
|
|
82
|
-
*/
|
|
83
|
-
private addCompositeIndex;
|
|
84
|
-
}
|
|
31
|
+
export declare function buildSchemaAST(entities: readonly Type<unknown>[], options?: BuildSchemaASTOptions): SchemaAST;
|