uql-orm 0.45.1 → 0.47.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 +1 -1
- package/dist/bunSql/bunSqlQuerier.d.ts +7 -0
- package/dist/bunSql/bunSqlQuerier.js +7 -0
- package/dist/bunSql/bunSqlQuerierPool.d.ts +1 -0
- package/dist/bunSql/bunSqlQuerierPool.js +6 -1
- package/dist/bunSql/index.d.ts +1 -0
- package/dist/bunSql/index.js +1 -0
- package/dist/dialect/abstractSqlDialect.d.ts +22 -2
- package/dist/dialect/abstractSqlDialect.js +55 -18
- package/dist/dialect/aliases.d.ts +4 -0
- package/dist/dialect/aliases.js +4 -0
- package/dist/dialect/mysqlLikeSqlDialect.js +2 -1
- package/dist/dialect/pgLikeSqlDialect.d.ts +14 -0
- package/dist/dialect/pgLikeSqlDialect.js +42 -2
- package/dist/entity/metadata/definition.js +8 -1
- package/dist/migrate/builder/columnBuilder.d.ts +14 -0
- package/dist/migrate/builder/columnBuilder.js +28 -9
- package/dist/migrate/builder/tableBuilder.js +8 -19
- package/dist/migrate/builder/types.d.ts +16 -33
- package/dist/migrate/codegen/entityCodeGenerator.js +4 -5
- package/dist/migrate/codegen/fieldOptionsSource.d.ts +2 -0
- package/dist/migrate/codegen/fieldOptionsSource.js +49 -33
- package/dist/migrate/codegen/indexDecoratorSource.js +1 -9
- package/dist/migrate/codegen/sourceLiteral.d.ts +12 -0
- package/dist/migrate/codegen/sourceLiteral.js +17 -0
- package/dist/migrate/generator/definitionToNode.d.ts +21 -0
- package/dist/migrate/generator/definitionToNode.js +47 -25
- package/dist/migrate/introspection/baseSqlIntrospector.d.ts +4 -2
- package/dist/migrate/introspection/baseSqlIntrospector.js +11 -11
- package/dist/migrate/introspection/mongoIntrospector.d.ts +1 -1
- package/dist/migrate/introspection/mongoIntrospector.js +2 -2
- package/dist/migrate/introspection/sqliteIntrospector.d.ts +5 -0
- package/dist/migrate/introspection/sqliteIntrospector.js +6 -2
- package/dist/migrate/schemaGenerator.d.ts +48 -2
- package/dist/migrate/schemaGenerator.js +109 -36
- package/dist/mongo/mongoDialect.js +2 -1
- package/dist/mongo/mongodbQuerier.d.ts +16 -0
- package/dist/mongo/mongodbQuerier.js +50 -1
- package/dist/querier/abstractQuerier.d.ts +18 -1
- package/dist/querier/abstractQuerier.js +50 -14
- package/dist/querier/abstractSqlQuerier.d.ts +14 -0
- package/dist/querier/abstractSqlQuerier.js +17 -0
- package/dist/schema/schemaAST.d.ts +34 -3
- package/dist/schema/schemaAST.js +4 -9
- package/dist/schema/schemaASTBuilder.js +5 -2
- package/dist/schema/schemaASTDiffer.js +8 -4
- package/dist/schema/types.d.ts +2 -0
- package/dist/sqlite/sqliteDialect.js +2 -1
- package/dist/type/dialect.d.ts +21 -2
- package/dist/type/entity.d.ts +21 -0
- package/dist/type/migration.d.ts +11 -18
- package/dist/util/dialect.util.js +5 -4
- package/dist/util/field.util.d.ts +23 -0
- package/dist/util/field.util.js +28 -0
- package/dist/util/fieldOption.util.d.ts +16 -3
- package/dist/util/fieldOption.util.js +23 -4
- package/dist/util/relationQuery.util.d.ts +34 -1
- package/dist/util/relationQuery.util.js +40 -3
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Type definitions for the fluent migration builder API.
|
|
5
5
|
* Enables type-safe migrations without raw SQL.
|
|
6
6
|
*/
|
|
7
|
-
import type {
|
|
7
|
+
import type { ColumnNode, EnumValues, ForeignKeyAction } from '../../schema/types.js';
|
|
8
8
|
import type { IndexColumnInput, IndexOptions, IndexSchema } from '../../type/index.js';
|
|
9
9
|
import type { ForeignKeySchema } from '../../type/migration.js';
|
|
10
10
|
/**
|
|
@@ -68,41 +68,20 @@ export interface VectorColumnOptions extends BaseColumnOptions {
|
|
|
68
68
|
dimensions?: number;
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
|
-
*
|
|
71
|
+
* A column as the builder describes one: {@link ColumnNode} without the graph links a DTO cannot carry.
|
|
72
|
+
*
|
|
73
|
+
* Derived rather than restated, so the two cannot drift. A column gained `enum` and this shape was
|
|
74
|
+
* simply missing it, which is why a hand-written `createTable` could never constrain one. A field
|
|
75
|
+
* added to the node now reaches here, and failing to render it is a compile error rather than a
|
|
76
|
+
* column that quietly loses half its declaration.
|
|
72
77
|
*/
|
|
73
|
-
export
|
|
74
|
-
/** Column name */
|
|
75
|
-
name: string;
|
|
76
|
-
/** Canonical type */
|
|
77
|
-
type: CanonicalType;
|
|
78
|
-
/** Whether the column is nullable */
|
|
79
|
-
nullable: boolean;
|
|
80
|
-
/** Default value or expression */
|
|
81
|
-
defaultValue?: unknown;
|
|
82
|
-
/** Whether this is a primary key */
|
|
83
|
-
primaryKey: boolean;
|
|
84
|
-
/** Whether this column auto-increments */
|
|
85
|
-
autoIncrement: boolean;
|
|
86
|
-
/** Whether this column has a unique constraint */
|
|
87
|
-
unique: boolean;
|
|
88
|
-
/** Column comment */
|
|
89
|
-
comment?: string;
|
|
90
|
-
}
|
|
78
|
+
export type ColumnDefinition = Omit<ColumnNode, 'table' | 'referencedBy' | 'references'>;
|
|
91
79
|
/**
|
|
92
|
-
*
|
|
80
|
+
* The foreign key a single column declares: {@link ForeignKeySchema} without the local columns, which
|
|
81
|
+
* are the column itself. Derived for the reason {@link ColumnDefinition} is - restated, the two spelled
|
|
82
|
+
* their target differently and every hand-off between them had to translate.
|
|
93
83
|
*/
|
|
94
|
-
export
|
|
95
|
-
/** Constraint name */
|
|
96
|
-
name?: string;
|
|
97
|
-
/** Referenced table */
|
|
98
|
-
table: string;
|
|
99
|
-
/** Referenced column(s) */
|
|
100
|
-
columns: string[];
|
|
101
|
-
/** Action on delete */
|
|
102
|
-
onDelete: ForeignKeyAction;
|
|
103
|
-
/** Action on update */
|
|
104
|
-
onUpdate: ForeignKeyAction;
|
|
105
|
-
}
|
|
84
|
+
export type ForeignKeyDefinition = Omit<ForeignKeySchema, 'columns'>;
|
|
106
85
|
/**
|
|
107
86
|
* Full column definition including foreign key.
|
|
108
87
|
*/
|
|
@@ -259,6 +238,10 @@ export interface IColumnBuilder {
|
|
|
259
238
|
autoIncrement(): this;
|
|
260
239
|
/** Add unique constraint */
|
|
261
240
|
unique(): this;
|
|
241
|
+
/** Constrain the column to these values, as a `CHECK (col IN (...))`. */
|
|
242
|
+
enum(values: EnumValues): this;
|
|
243
|
+
/** Make the column one the database computes, as `GENERATED ALWAYS AS (<sql>) STORED`. */
|
|
244
|
+
computed(sql: string): this;
|
|
262
245
|
/** Add comment */
|
|
263
246
|
comment(text: string): this;
|
|
264
247
|
/** Add index */
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { canonicalToTypeScript } from '../../schema/canonicalType.js';
|
|
13
13
|
import { DEFAULT_FOREIGN_KEY_ACTION, } from '../../schema/types.js';
|
|
14
14
|
import { camelCase, pascalCase, singularize } from '../../util/string.util.js';
|
|
15
|
-
import { buildFieldOptionsSource } from './fieldOptionsSource.js';
|
|
15
|
+
import { buildFieldOptionsSource, fieldNeedsRaw } from './fieldOptionsSource.js';
|
|
16
16
|
import { buildIndexDecoratorSource, indexNeedsRaw, isPlainFieldIndex } from './indexDecoratorSource.js';
|
|
17
17
|
/**
|
|
18
18
|
* Generates TypeScript entity code from SchemaAST.
|
|
@@ -75,11 +75,13 @@ export class EntityCodeGenerator {
|
|
|
75
75
|
buildImports(table) {
|
|
76
76
|
const uqlImports = new Set(['Entity', 'Field']);
|
|
77
77
|
const relatedImports = [];
|
|
78
|
-
// Check for Id decorator
|
|
79
78
|
for (const col of table.columns.values()) {
|
|
80
79
|
if (col.isPrimaryKey) {
|
|
81
80
|
uqlImports.add('Id');
|
|
82
81
|
}
|
|
82
|
+
if (fieldNeedsRaw(col)) {
|
|
83
|
+
uqlImports.add('raw');
|
|
84
|
+
}
|
|
83
85
|
}
|
|
84
86
|
// Check for relation decorators
|
|
85
87
|
if (this.options.includeRelations) {
|
|
@@ -145,9 +147,6 @@ export class EntityCodeGenerator {
|
|
|
145
147
|
lines.push(' /**');
|
|
146
148
|
lines.push(` * @sync-added ${new Date().toISOString().split('T')[0]}`);
|
|
147
149
|
lines.push(` * Column: ${col.name} (${this.formatTypeDescription(col.type)})`);
|
|
148
|
-
if (col.comment) {
|
|
149
|
-
lines.push(` * ${col.comment}`);
|
|
150
|
-
}
|
|
151
150
|
lines.push(' */');
|
|
152
151
|
}
|
|
153
152
|
// Decorator
|
|
@@ -8,3 +8,5 @@ import type { ColumnNode } from '../../schema/types.js';
|
|
|
8
8
|
* file from scratch.
|
|
9
9
|
*/
|
|
10
10
|
export declare function buildFieldOptionsSource(col: ColumnNode, propertyName: string, indexName?: string): string;
|
|
11
|
+
/** Whether the field's decorator needs `raw` imported, the way {@link indexNeedsRaw} does for an index. */
|
|
12
|
+
export declare function fieldNeedsRaw(col: ColumnNode): boolean;
|
|
@@ -1,4 +1,41 @@
|
|
|
1
1
|
import { canonicalToColumnType } from '../../schema/canonicalType.js';
|
|
2
|
+
import { quoted, rawTag } from './sourceLiteral.js';
|
|
3
|
+
/**
|
|
4
|
+
* What each field of a {@link ColumnNode} contributes to `@Field({ ... })`, in emit order, and `null`
|
|
5
|
+
* where nothing does.
|
|
6
|
+
*
|
|
7
|
+
* The `satisfies` is the point: a field the node gains cannot reach here without someone answering
|
|
8
|
+
* whether an entity generated from a database keeps it. Written as a hand-rolled `if` chain, this had
|
|
9
|
+
* already dropped `comment` - introspection reads one on Postgres and MySQL, and regenerating an
|
|
10
|
+
* entity threw it away.
|
|
11
|
+
*/
|
|
12
|
+
const OPTION_SOURCE = {
|
|
13
|
+
// Without this the entity maps to a column named after the property, which for anything the
|
|
14
|
+
// transformer rewrote - every `user_id` - is a column the database does not have.
|
|
15
|
+
name: (col, { propertyName }) => (propertyName === col.name ? [] : [`name: ${quoted(col.name)}`]),
|
|
16
|
+
type: (col) => {
|
|
17
|
+
const columnType = canonicalToColumnType(col.type);
|
|
18
|
+
return [
|
|
19
|
+
...(columnType ? [`columnType: ${quoted(columnType)}`] : []),
|
|
20
|
+
...(col.type.length && col.type.category === 'string' ? [`length: ${col.type.length}`] : []),
|
|
21
|
+
...(col.type.precision === undefined ? [] : [`precision: ${col.type.precision}`]),
|
|
22
|
+
...(col.type.precision !== undefined && col.type.scale !== undefined ? [`scale: ${col.type.scale}`] : []),
|
|
23
|
+
];
|
|
24
|
+
},
|
|
25
|
+
nullable: (col) => (col.nullable ? ['nullable: true'] : []),
|
|
26
|
+
isUnique: (col) => (col.isUnique ? ['unique: true'] : []),
|
|
27
|
+
enum: (col) => col.enum ? [`enum: [${col.enum.map((it) => (typeof it === 'number' ? it : quoted(it))).join(', ')}]`] : [],
|
|
28
|
+
defaultValue: (col) => col.defaultValue === undefined ? [] : [`defaultValue: ${defaultValueSource(col.defaultValue)}`],
|
|
29
|
+
generatedAs: (col) => (col.generatedAs ? [`computed: ${rawTag(col.generatedAs)}`, 'stored: true'] : []),
|
|
30
|
+
comment: (col) => (col.comment ? [`comment: ${quoted(col.comment)}`] : []),
|
|
31
|
+
// A key is `@Id`, and a numeric one generates by that alone. Neither is an option to write out.
|
|
32
|
+
isPrimaryKey: null,
|
|
33
|
+
isAutoIncrement: null,
|
|
34
|
+
// Graph links. A foreign key becomes a relation decorator, emitted beside the field rather than in it.
|
|
35
|
+
table: null,
|
|
36
|
+
references: null,
|
|
37
|
+
referencedBy: null,
|
|
38
|
+
};
|
|
2
39
|
/**
|
|
3
40
|
* A column's `@Field({ ... })` options as source, or `''` when it needs none.
|
|
4
41
|
*
|
|
@@ -8,47 +45,26 @@ import { canonicalToColumnType } from '../../schema/canonicalType.js';
|
|
|
8
45
|
* file from scratch.
|
|
9
46
|
*/
|
|
10
47
|
export function buildFieldOptionsSource(col, propertyName, indexName) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const columnType = canonicalToColumnType(col.type);
|
|
18
|
-
if (columnType) {
|
|
19
|
-
options.push(`columnType: '${columnType}'`);
|
|
20
|
-
}
|
|
21
|
-
if (col.type.length && col.type.category === 'string') {
|
|
22
|
-
options.push(`length: ${col.type.length}`);
|
|
23
|
-
}
|
|
24
|
-
if (col.type.precision !== undefined) {
|
|
25
|
-
options.push(`precision: ${col.type.precision}`);
|
|
26
|
-
if (col.type.scale !== undefined) {
|
|
27
|
-
options.push(`scale: ${col.type.scale}`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
if (col.nullable) {
|
|
31
|
-
options.push('nullable: true');
|
|
32
|
-
}
|
|
33
|
-
if (col.isUnique) {
|
|
34
|
-
options.push('unique: true');
|
|
35
|
-
}
|
|
36
|
-
if (col.defaultValue !== undefined) {
|
|
37
|
-
options.push(`defaultValue: ${formatDefaultValueSource(col.defaultValue)}`);
|
|
38
|
-
}
|
|
39
|
-
if (indexName) {
|
|
40
|
-
options.push(`index: '${indexName}'`);
|
|
41
|
-
}
|
|
48
|
+
const context = { propertyName, indexName };
|
|
49
|
+
const options = [
|
|
50
|
+
...Object.values(OPTION_SOURCE).flatMap((source) => source?.(col, context) ?? []),
|
|
51
|
+
// Not a column field: the index is a table-level object the field only borrows.
|
|
52
|
+
...(indexName ? [`index: ${quoted(indexName)}`] : []),
|
|
53
|
+
];
|
|
42
54
|
return options.length > 0 ? `{ ${options.join(', ')} }` : '';
|
|
43
55
|
}
|
|
56
|
+
/** Whether the field's decorator needs `raw` imported, the way {@link indexNeedsRaw} does for an index. */
|
|
57
|
+
export function fieldNeedsRaw(col) {
|
|
58
|
+
return col.generatedAs !== undefined;
|
|
59
|
+
}
|
|
44
60
|
/**
|
|
45
61
|
* A default value as source. Strings stay single-quoted, expressions included: `defaultValue: 'now()'`
|
|
46
62
|
* is what reaches the DDL. The generator used to branch on `CURRENT_TIMESTAMP`/`NEXTVAL`/`(` first, but
|
|
47
63
|
* both branches emitted a quoted string and only the fallthrough escaped embedded quotes.
|
|
48
64
|
*/
|
|
49
|
-
function
|
|
65
|
+
function defaultValueSource(value) {
|
|
50
66
|
if (typeof value === 'string') {
|
|
51
|
-
return
|
|
67
|
+
return quoted(value);
|
|
52
68
|
}
|
|
53
69
|
if (typeof value === 'boolean' || typeof value === 'number') {
|
|
54
70
|
return value.toString();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { rawTag } from './sourceLiteral.js';
|
|
1
2
|
/**
|
|
2
3
|
* A vector index carries its metric in the operator class pgvector names after it
|
|
3
4
|
* (`vector_cosine_ops`), which is the only place introspection can recover it from. `@Index` requires
|
|
@@ -94,12 +95,3 @@ function indexEntrySource(entry, propertyName) {
|
|
|
94
95
|
}
|
|
95
96
|
return `{ column: '${propertyName(entry.column)}', ${modifiers.join(', ')} }`;
|
|
96
97
|
}
|
|
97
|
-
/**
|
|
98
|
-
* SQL as a `raw` tagged template. A database reprints an expression as arbitrary text, and exactly
|
|
99
|
-
* three sequences can end or interpolate a template literal, so escaping those is the whole job.
|
|
100
|
-
* Newlines need none, which keeps a multi-line expression readable in the generated entity.
|
|
101
|
-
*/
|
|
102
|
-
function rawTag(sql) {
|
|
103
|
-
const escaped = sql.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
|
104
|
-
return `raw\`${escaped}\``;
|
|
105
|
-
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A string as single-quoted source. Introspected text is arbitrary - a comment or a default
|
|
3
|
+
* expression can hold a quote or a backslash - and only escaping both keeps the generated file
|
|
4
|
+
* parsing.
|
|
5
|
+
*/
|
|
6
|
+
export declare function quoted(text: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* SQL as a `raw` tagged template. A database reprints an expression as arbitrary text, and exactly
|
|
9
|
+
* three sequences can end or interpolate a template literal, so escaping those is the whole job.
|
|
10
|
+
* Newlines need none, which keeps a multi-line expression readable in the generated entity.
|
|
11
|
+
*/
|
|
12
|
+
export declare function rawTag(sql: string): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A string as single-quoted source. Introspected text is arbitrary - a comment or a default
|
|
3
|
+
* expression can hold a quote or a backslash - and only escaping both keeps the generated file
|
|
4
|
+
* parsing.
|
|
5
|
+
*/
|
|
6
|
+
export function quoted(text) {
|
|
7
|
+
return `'${text.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* SQL as a `raw` tagged template. A database reprints an expression as arbitrary text, and exactly
|
|
11
|
+
* three sequences can end or interpolate a template literal, so escaping those is the whole job.
|
|
12
|
+
* Newlines need none, which keeps a multi-line expression readable in the generated entity.
|
|
13
|
+
*/
|
|
14
|
+
export function rawTag(sql) {
|
|
15
|
+
const escaped = sql.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
|
16
|
+
return `raw\`${escaped}\``;
|
|
17
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ColumnNode, TableNode } from '../../schema/types.js';
|
|
2
|
+
import type { ForeignKeySchema, IndexSchema } from '../../type/migration.js';
|
|
2
3
|
import type { FullColumnDefinition, TableDefinition } from '../builder/types.js';
|
|
3
4
|
/**
|
|
4
5
|
* A migration builder's table definition as the AST nodes the generators render from, so a hand-written
|
|
@@ -6,4 +7,24 @@ import type { FullColumnDefinition, TableDefinition } from '../builder/types.js'
|
|
|
6
7
|
* not generator methods: nothing here consults the dialect.
|
|
7
8
|
*/
|
|
8
9
|
export declare function tableDefinitionToNode(def: TableDefinition): TableNode;
|
|
10
|
+
/**
|
|
11
|
+
* A builder's column as the AST node the generators render from.
|
|
12
|
+
*
|
|
13
|
+
* The shared half is spread, not copied field by field: `ColumnDefinition` *is* a `ColumnNode` minus
|
|
14
|
+
* the graph links, so spreading it and adding those back is a node by construction. Listed one by one,
|
|
15
|
+
* the copy silently dropped whatever the node gained next - `enum` first, and the type had no way to
|
|
16
|
+
* say so. The two builder-only keys are destructured off: `index` and `foreignKey` are lifted onto the
|
|
17
|
+
* table by `columnIndex`/`columnForeignKey`, which is the path that renders them.
|
|
18
|
+
*
|
|
19
|
+
* No `references` node either: `SchemaAST.addRelationship` sets that one.
|
|
20
|
+
*/
|
|
9
21
|
export declare function fullColumnDefinitionToNode(col: FullColumnDefinition, tableName: string): ColumnNode;
|
|
22
|
+
/**
|
|
23
|
+
* The index a column-level `index` declares, or nothing.
|
|
24
|
+
*
|
|
25
|
+
* Shared with `TableBuilder.build`, which lifts these into the table it is creating: written twice,
|
|
26
|
+
* `addColumn` had no lift at all and silently emitted a column with no index.
|
|
27
|
+
*/
|
|
28
|
+
export declare function columnIndex(tableName: string, col: FullColumnDefinition): IndexSchema | undefined;
|
|
29
|
+
/** The foreign key a column-level `references` declares, or nothing. Shared for the same reason. */
|
|
30
|
+
export declare function columnForeignKey(col: FullColumnDefinition): ForeignKeySchema | undefined;
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import { derivedForeignKeyName } from '../../util/sql.util.js';
|
|
1
|
+
import { derivedForeignKeyName, derivedIndexName } from '../../util/sql.util.js';
|
|
2
|
+
/**
|
|
3
|
+
* A table the builder names but has not seen.
|
|
4
|
+
*
|
|
5
|
+
* A `RelationshipNode` points at a whole `TableNode` because the AST wires `incomingRelations` through
|
|
6
|
+
* it; a builder creating one table has no node for the table its foreign key targets, and the
|
|
7
|
+
* generator reads only the name. Stated once, so the three casts it replaces cannot be mistaken for a
|
|
8
|
+
* node that was resolved and lost.
|
|
9
|
+
*/
|
|
10
|
+
function unresolvedTable(name) {
|
|
11
|
+
return { name };
|
|
12
|
+
}
|
|
2
13
|
/**
|
|
3
14
|
* A migration builder's table definition as the AST nodes the generators render from, so a hand-written
|
|
4
15
|
* `createTable` and an entity reach `generateCreateTableFromNode` in the same shape. Free functions and
|
|
@@ -40,7 +51,7 @@ export function tableDefinitionToNode(def) {
|
|
|
40
51
|
columns: fkDef.columns.map((name) => columns.get(name)).filter((c) => c !== undefined),
|
|
41
52
|
},
|
|
42
53
|
to: {
|
|
43
|
-
table:
|
|
54
|
+
table: unresolvedTable(fkDef.references.table),
|
|
44
55
|
columns: fkDef.references.columns.map((name) => ({ name })),
|
|
45
56
|
},
|
|
46
57
|
onDelete: fkDef.onDelete,
|
|
@@ -50,30 +61,41 @@ export function tableDefinitionToNode(def) {
|
|
|
50
61
|
}
|
|
51
62
|
return table;
|
|
52
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* A builder's column as the AST node the generators render from.
|
|
66
|
+
*
|
|
67
|
+
* The shared half is spread, not copied field by field: `ColumnDefinition` *is* a `ColumnNode` minus
|
|
68
|
+
* the graph links, so spreading it and adding those back is a node by construction. Listed one by one,
|
|
69
|
+
* the copy silently dropped whatever the node gained next - `enum` first, and the type had no way to
|
|
70
|
+
* say so. The two builder-only keys are destructured off: `index` and `foreignKey` are lifted onto the
|
|
71
|
+
* table by `columnIndex`/`columnForeignKey`, which is the path that renders them.
|
|
72
|
+
*
|
|
73
|
+
* No `references` node either: `SchemaAST.addRelationship` sets that one.
|
|
74
|
+
*/
|
|
53
75
|
export function fullColumnDefinitionToNode(col, tableName) {
|
|
76
|
+
const { index: _index, foreignKey: _foreignKey, ...column } = col;
|
|
77
|
+
return { ...column, table: unresolvedTable(tableName), referencedBy: [] };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The index a column-level `index` declares, or nothing.
|
|
81
|
+
*
|
|
82
|
+
* Shared with `TableBuilder.build`, which lifts these into the table it is creating: written twice,
|
|
83
|
+
* `addColumn` had no lift at all and silently emitted a column with no index.
|
|
84
|
+
*/
|
|
85
|
+
export function columnIndex(tableName, col) {
|
|
86
|
+
if (!col.index) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
54
89
|
return {
|
|
55
|
-
name: col.name,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
defaultValue: col.defaultValue,
|
|
59
|
-
isPrimaryKey: col.primaryKey,
|
|
60
|
-
isAutoIncrement: col.autoIncrement,
|
|
61
|
-
isUnique: col.unique,
|
|
62
|
-
comment: col.comment,
|
|
63
|
-
table: { name: tableName },
|
|
64
|
-
referencedBy: [],
|
|
65
|
-
references: col.foreignKey
|
|
66
|
-
? {
|
|
67
|
-
name: derivedForeignKeyName(tableName, [col.name]),
|
|
68
|
-
type: 'ManyToOne',
|
|
69
|
-
from: { table: { name: tableName }, columns: [] },
|
|
70
|
-
to: {
|
|
71
|
-
table: { name: col.foreignKey.table },
|
|
72
|
-
columns: col.foreignKey.columns.map((name) => ({ name })),
|
|
73
|
-
},
|
|
74
|
-
onDelete: col.foreignKey.onDelete,
|
|
75
|
-
onUpdate: col.foreignKey.onUpdate,
|
|
76
|
-
}
|
|
77
|
-
: undefined,
|
|
90
|
+
name: typeof col.index === 'string' ? col.index : derivedIndexName(tableName, [col.name]),
|
|
91
|
+
entries: [{ column: col.name }],
|
|
92
|
+
unique: col.isUnique,
|
|
78
93
|
};
|
|
79
94
|
}
|
|
95
|
+
/** The foreign key a column-level `references` declares, or nothing. Shared for the same reason. */
|
|
96
|
+
export function columnForeignKey(col) {
|
|
97
|
+
if (!col.foreignKey) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
return { ...col.foreignKey, columns: [col.name] };
|
|
101
|
+
}
|
|
@@ -18,9 +18,11 @@ export declare abstract class BaseSqlIntrospector {
|
|
|
18
18
|
constructor(dialect: AbstractSqlDialect, schema?: string | undefined);
|
|
19
19
|
protected escapeId(identifier: string): string;
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
21
|
+
* The database as a {@link SchemaAST}, or just the tables named. A name nothing matches is left out
|
|
22
|
+
* rather than raised: the point of naming them is to read a database other things are still
|
|
23
|
+
* changing, where scanning every table is both wasted work and a relation that can vanish mid-scan.
|
|
22
24
|
*/
|
|
23
|
-
introspect(): Promise<SchemaAST>;
|
|
25
|
+
introspect(tables?: readonly string[]): Promise<SchemaAST>;
|
|
24
26
|
abstract getTableNames(): Promise<string[]>;
|
|
25
27
|
abstract getTableSchema(tableName: string): Promise<TableSchema | undefined>;
|
|
26
28
|
/**
|
|
@@ -23,10 +23,12 @@ export class BaseSqlIntrospector {
|
|
|
23
23
|
return escapeSqlId(identifier, this.dialect.escapeIdChar);
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* The database as a {@link SchemaAST}, or just the tables named. A name nothing matches is left out
|
|
27
|
+
* rather than raised: the point of naming them is to read a database other things are still
|
|
28
|
+
* changing, where scanning every table is both wasted work and a relation that can vanish mid-scan.
|
|
27
29
|
*/
|
|
28
|
-
async introspect() {
|
|
29
|
-
const tableNames = await this.getTableNames();
|
|
30
|
+
async introspect(tables) {
|
|
31
|
+
const tableNames = tables ?? (await this.getTableNames());
|
|
30
32
|
const tableSchemas = [];
|
|
31
33
|
for (const tableName of tableNames) {
|
|
32
34
|
const schema = await this.getTableSchema(tableName);
|
|
@@ -52,15 +54,13 @@ export class BaseSqlIntrospector {
|
|
|
52
54
|
const table = createTableNode(schema.name, this.schema);
|
|
53
55
|
const { columns } = table;
|
|
54
56
|
for (const col of schema.columns) {
|
|
57
|
+
// Spread, not field by field: a `ColumnSchema` is a `ColumnNode` minus the graph links, so
|
|
58
|
+
// everything but the type crosses unchanged and a field either shape gains cannot be dropped
|
|
59
|
+
// here. Listed by hand this had already lost `enum` and `generatedAs`.
|
|
60
|
+
const { type, length: _length, precision: _precision, scale: _scale, ...rest } = col;
|
|
55
61
|
const column = {
|
|
56
|
-
|
|
57
|
-
type: canonicalColumnType(
|
|
58
|
-
nullable: col.nullable,
|
|
59
|
-
defaultValue: col.defaultValue,
|
|
60
|
-
isPrimaryKey: col.isPrimaryKey,
|
|
61
|
-
isAutoIncrement: col.isAutoIncrement,
|
|
62
|
-
isUnique: col.isUnique,
|
|
63
|
-
comment: col.comment,
|
|
62
|
+
...rest,
|
|
63
|
+
type: canonicalColumnType(type, col),
|
|
64
64
|
table,
|
|
65
65
|
referencedBy: [],
|
|
66
66
|
};
|
|
@@ -10,7 +10,7 @@ export declare class MongoSchemaIntrospector implements SchemaIntrospector {
|
|
|
10
10
|
/** `listIndexes` reports keys and uniqueness; a `partialFilterExpression` is no SQL predicate. */
|
|
11
11
|
readonly indexFacets: ReadonlySet<IndexFacet>;
|
|
12
12
|
constructor(pool: QuerierPool);
|
|
13
|
-
introspect(): Promise<SchemaAST>;
|
|
13
|
+
introspect(tables?: readonly string[]): Promise<SchemaAST>;
|
|
14
14
|
getTableSchema(tableName: string): Promise<TableSchema | undefined>;
|
|
15
15
|
getTableNames(): Promise<string[]>;
|
|
16
16
|
tableExists(tableName: string): Promise<boolean>;
|
|
@@ -10,8 +10,8 @@ export class MongoSchemaIntrospector {
|
|
|
10
10
|
constructor(pool) {
|
|
11
11
|
this.pool = pool;
|
|
12
12
|
}
|
|
13
|
-
async introspect() {
|
|
14
|
-
const tableNames = await this.getTableNames();
|
|
13
|
+
async introspect(tables) {
|
|
14
|
+
const tableNames = tables ?? (await this.getTableNames());
|
|
15
15
|
const ast = new SchemaAST();
|
|
16
16
|
for (const name of tableNames) {
|
|
17
17
|
const schema = await this.getTableSchema(name);
|
|
@@ -7,6 +7,11 @@ export declare class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospec
|
|
|
7
7
|
protected getTableNamesQuery(): string;
|
|
8
8
|
protected tableExistsQuery(): string;
|
|
9
9
|
protected parseTableExistsResult(results: SqliteCountRow[]): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* `table_xinfo`, not `table_info`: the latter omits generated columns entirely, so a table carrying
|
|
12
|
+
* one read back without it and every sync offered to add a column that was already there - which
|
|
13
|
+
* SQLite cannot do to an existing table anyway. PRAGMA takes no bound parameters, hence the splice.
|
|
14
|
+
*/
|
|
10
15
|
protected getColumnsQuery(tableName: string): string;
|
|
11
16
|
protected getIndexesQuery(tableName: string): string;
|
|
12
17
|
protected getForeignKeysQuery(tableName: string): string;
|
|
@@ -28,9 +28,13 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
28
28
|
}
|
|
29
29
|
return false;
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
/**
|
|
32
|
+
* `table_xinfo`, not `table_info`: the latter omits generated columns entirely, so a table carrying
|
|
33
|
+
* one read back without it and every sync offered to add a column that was already there - which
|
|
34
|
+
* SQLite cannot do to an existing table anyway. PRAGMA takes no bound parameters, hence the splice.
|
|
35
|
+
*/
|
|
32
36
|
getColumnsQuery(tableName) {
|
|
33
|
-
return /*sql*/ `PRAGMA
|
|
37
|
+
return /*sql*/ `PRAGMA table_xinfo(${this.escapeId(tableName)})`;
|
|
34
38
|
}
|
|
35
39
|
getIndexesQuery(tableName) {
|
|
36
40
|
return /*sql*/ `PRAGMA index_list(${this.escapeId(tableName)})`;
|
|
@@ -36,6 +36,15 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
36
36
|
* pointing at it emitted `INT`, and every engine refuses that constraint.
|
|
37
37
|
*/
|
|
38
38
|
protected serialType(type: CanonicalType): string;
|
|
39
|
+
/**
|
|
40
|
+
* The SQL type a column is spelled with: the engine's generated-key form for an auto-increment key,
|
|
41
|
+
* the canonical type otherwise.
|
|
42
|
+
*
|
|
43
|
+
* One method because both paths that spell a column need the same answer - written twice, with a
|
|
44
|
+
* comment asking the two to stay in sync, is how the generated key and the column referencing it
|
|
45
|
+
* came to disagree in the first place.
|
|
46
|
+
*/
|
|
47
|
+
protected columnSqlType(col: ColumnNode): string;
|
|
39
48
|
protected canonicalTypeToSql(type: CanonicalType): string;
|
|
40
49
|
/** The entity side as an AST, carrying this generator's default referential action. */
|
|
41
50
|
buildAST(entities: readonly Type<unknown>[]): SchemaAST;
|
|
@@ -101,10 +110,26 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
101
110
|
* Generate ALTER COLUMN statements (database-specific)
|
|
102
111
|
*/
|
|
103
112
|
generateAlterColumnStatements(tableName: string, column: ColumnSchema, newDefinition: string): string[];
|
|
113
|
+
/** The inline ` COMMENT '...'` a column declaration carries, where the engine takes one there. */
|
|
114
|
+
generateColumnComment(comment: string): string;
|
|
115
|
+
/**
|
|
116
|
+
* The `COMMENT ON` statements a table and its columns need, on an engine that carries a comment
|
|
117
|
+
* that way. Empty on the others: MySQL writes them inline, SQLite has no comments at all.
|
|
118
|
+
*
|
|
119
|
+
* Emitted after the `CREATE TABLE` rather than folded into it, which is what `COMMENT ON` requires -
|
|
120
|
+
* and what makes a comment reach Postgres, where it was previously read as unsupported and dropped.
|
|
121
|
+
*/
|
|
122
|
+
protected generateCommentStatements(table: TableNode): string[];
|
|
104
123
|
/**
|
|
105
|
-
*
|
|
124
|
+
* The `COMMENT ON COLUMN` one column needs, on an engine that carries a comment that way.
|
|
125
|
+
*
|
|
126
|
+
* Shared by `CREATE TABLE` and every path that adds a column: written only for the former, a column
|
|
127
|
+
* added later reached the database undocumented, the way its enum `CHECK` used to.
|
|
106
128
|
*/
|
|
107
|
-
|
|
129
|
+
protected generateColumnCommentStatement(tableName: string, column: {
|
|
130
|
+
name: string;
|
|
131
|
+
comment?: string;
|
|
132
|
+
}, schema?: string): string[];
|
|
108
133
|
/**
|
|
109
134
|
* Compare an entity with a database table node and return the differences.
|
|
110
135
|
*/
|
|
@@ -131,6 +156,7 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
131
156
|
*/
|
|
132
157
|
private missingIndexes;
|
|
133
158
|
protected diffOptions(): DiffOptions;
|
|
159
|
+
/** Spread, not copied field by field, so a field the node gains cannot go missing here. */
|
|
134
160
|
private columnNodeToSchema;
|
|
135
161
|
/**
|
|
136
162
|
* Compare two default values for equality
|
|
@@ -155,10 +181,25 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
155
181
|
ifNotExists?: boolean;
|
|
156
182
|
}): string[];
|
|
157
183
|
generateRenameTableSql(oldName: string, newName: string): string;
|
|
184
|
+
/**
|
|
185
|
+
* `ADD COLUMN`, plus the constraint and index the column declares.
|
|
186
|
+
*
|
|
187
|
+
* `CREATE TABLE` lifts a column's `references` and `index` onto the table it is building; this had
|
|
188
|
+
* no lift, so a hand-written `addColumn(...).references(...).index()` emitted the column alone and
|
|
189
|
+
* dropped both without a word. Several statements in one string is what `generateAlterColumnSql`
|
|
190
|
+
* already returns, and `execute` splits them.
|
|
191
|
+
*/
|
|
158
192
|
generateAddColumnSql(tableName: string, column: FullColumnDefinition): string;
|
|
159
193
|
generateAlterColumnSql(tableName: string, columnName: string, column: FullColumnDefinition): string;
|
|
160
194
|
generateDropColumnSql(tableName: string, columnName: string): string;
|
|
161
195
|
generateRenameColumnSql(tableName: string, oldName: string, newName: string): string;
|
|
196
|
+
/**
|
|
197
|
+
* `CONSTRAINT <name> FOREIGN KEY (...) REFERENCES ... ON DELETE ... ON UPDATE ...`.
|
|
198
|
+
*
|
|
199
|
+
* One spelling for the two places that need it - inline in a `CREATE TABLE`, and after `ADD` in an
|
|
200
|
+
* `ALTER`. Written twice, the two drifted over which end they qualified with a schema.
|
|
201
|
+
*/
|
|
202
|
+
protected foreignKeyConstraint(tableName: string, foreignKey: ForeignKeySchema, refTableSql: string): string;
|
|
162
203
|
generateAddForeignKeySql(tableName: string, foreignKey: ForeignKeySchema): string;
|
|
163
204
|
generateDropForeignKeySql(tableName: string, constraintName: string): string;
|
|
164
205
|
/**
|
|
@@ -176,6 +217,11 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
176
217
|
* no name at all - a table's key is always `PRIMARY` there.
|
|
177
218
|
*/
|
|
178
219
|
generateDropPrimaryKeySql(tableName: string, constraintName?: string): string;
|
|
220
|
+
/**
|
|
221
|
+
* A column an `ALTER` can carry. Only a generated one is ever refused, and only where the engine
|
|
222
|
+
* takes it in a `CREATE TABLE` but not afterwards.
|
|
223
|
+
*/
|
|
224
|
+
private assertColumnAddable;
|
|
179
225
|
private assertPrimaryKeyAlterable;
|
|
180
226
|
}
|
|
181
227
|
/**
|