uql-orm 0.24.6 → 0.24.7
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/cockroachdb/crdbQuerierPool.d.ts +1 -1
- package/dist/cockroachdb/crdbQuerierPool.js +5 -4
- package/dist/dialect/abstractSqlDialect.d.ts +78 -27
- package/dist/dialect/abstractSqlDialect.js +189 -129
- package/dist/dialect/hydrateColumn.d.ts +16 -0
- package/dist/dialect/hydrateColumn.js +66 -0
- package/dist/dialect/jsonSql.d.ts +24 -0
- package/dist/dialect/jsonSql.js +39 -0
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +5 -0
- package/dist/dialect/mysqlLikeSqlDialect.js +10 -1
- package/dist/dialect/pgLikeSqlDialect.d.ts +3 -7
- package/dist/dialect/pgLikeSqlDialect.js +2 -14
- package/dist/dialect/vectorCast.d.ts +15 -0
- package/dist/dialect/vectorCast.js +58 -0
- package/dist/entity/metadata/definition.d.ts +0 -1
- package/dist/entity/metadata/definition.js +1 -1
- package/dist/maria/mariaDialect.d.ts +3 -2
- package/dist/maria/mariaDialect.js +3 -18
- package/dist/maria/mariadbQuerierPool.js +6 -1
- package/dist/migrate/builder/migrationBuilder.d.ts +12 -16
- package/dist/migrate/builder/migrationBuilder.js +24 -59
- package/dist/migrate/builder/tableBuilder.js +0 -12
- package/dist/migrate/cli.d.ts +0 -1
- package/dist/migrate/cli.js +1 -1
- package/dist/migrate/codegen/entityCodeGenerator.js +0 -3
- package/dist/migrate/drift/driftDetector.js +17 -15
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +8 -2
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +10 -9
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +0 -3
- package/dist/migrate/introspection/mysqlIntrospector.js +0 -9
- package/dist/migrate/introspection/postgresIntrospector.d.ts +0 -3
- package/dist/migrate/introspection/postgresIntrospector.js +0 -12
- package/dist/migrate/introspection/sqliteIntrospector.d.ts +1 -0
- package/dist/migrate/introspection/sqliteIntrospector.js +1 -9
- package/dist/migrate/migrator.d.ts +8 -0
- package/dist/migrate/migrator.js +19 -29
- package/dist/migrate/schemaGenerator.js +0 -12
- package/dist/neon/neonQuerierPool.d.ts +1 -1
- package/dist/neon/neonQuerierPool.js +6 -4
- package/dist/postgres/abstractPgQuerierPool.d.ts +6 -0
- package/dist/postgres/abstractPgQuerierPool.js +3 -0
- package/dist/postgres/pgNumericTypes.d.ts +41 -0
- package/dist/postgres/pgNumericTypes.js +35 -0
- package/dist/postgres/pgQuerierPool.d.ts +1 -1
- package/dist/postgres/pgQuerierPool.js +5 -4
- package/dist/querier/abstractSqlQuerier.d.ts +9 -2
- package/dist/querier/abstractSqlQuerier.js +31 -24
- package/dist/schema/canonicalType.js +2 -12
- package/dist/schema/schemaAST.js +0 -24
- package/dist/schema/schemaASTBuilder.js +0 -3
- package/dist/type/queryAggregate.d.ts +3 -0
- package/dist/util/field.util.d.ts +4 -0
- package/dist/util/field.util.js +12 -0
- package/dist/util/sqlLiteral.js +18 -15
- package/package.json +4 -4
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getMeta } from '../entity/index.js';
|
|
2
1
|
import { QueryRaw, } from '../type/index.js';
|
|
3
2
|
import { escapeSingleQuotes } from '../util/sqlLiteral.js';
|
|
4
3
|
import { AbstractSqlDialect } from './abstractSqlDialect.js';
|
|
@@ -41,6 +40,8 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
41
40
|
commitTransactionCommand = 'COMMIT';
|
|
42
41
|
rollbackTransactionCommand = 'ROLLBACK';
|
|
43
42
|
alterColumnStrategy = 'separate-clauses';
|
|
43
|
+
/** `$N` placeholders carry their own index, so the upsert's assignments need no scratch context. */
|
|
44
|
+
upsertUpdateBindsInPlace = true;
|
|
44
45
|
insertIdSource = 'returning';
|
|
45
46
|
maxBindValues = 65535;
|
|
46
47
|
/**
|
|
@@ -115,19 +116,6 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
115
116
|
placeholder(index) {
|
|
116
117
|
return `$${index}`;
|
|
117
118
|
}
|
|
118
|
-
/**
|
|
119
|
-
* The same statement as the base, binding the assignments into the main context instead of a second
|
|
120
|
-
* one: `$N` placeholders carry their own index, so the values need not be in statement order, and
|
|
121
|
-
* computing them first is what keeps `appendInsertValues`' `onInsert` fields out of the update set.
|
|
122
|
-
*/
|
|
123
|
-
upsert(ctx, entity, conflictPaths, payload, extraReturning = '') {
|
|
124
|
-
const meta = getMeta(entity);
|
|
125
|
-
const update = this.getUpsertUpdateAssignments(ctx, meta, conflictPaths, payload, this.upsertExcluded);
|
|
126
|
-
const keys = this.getUpsertConflictPathsStr(meta, conflictPaths);
|
|
127
|
-
const onConflict = update ? `DO UPDATE SET ${update}` : 'DO NOTHING';
|
|
128
|
-
this.appendInsertValues(ctx, entity, payload);
|
|
129
|
-
ctx.append(` ON CONFLICT (${keys}) ${onConflict} ${this.returningId(entity)}${extraReturning}`);
|
|
130
|
-
}
|
|
131
119
|
/**
|
|
132
120
|
* `to_tsvector(...) @@ websearch_to_tsquery(...)`. `websearch_to_tsquery` takes free-form user input
|
|
133
121
|
* (quoted phrases, `or`, `-negation`) and never raises a syntax error, unlike `to_tsquery`, which
|
|
@@ -30,3 +30,18 @@ export declare function resolveVectorCast(field: {
|
|
|
30
30
|
* declaring `type: 'sparsevec'` still hands UQL the dense array its field type promises.
|
|
31
31
|
*/
|
|
32
32
|
export declare function toSparsevecLiteral(values: readonly unknown[]): string;
|
|
33
|
+
/**
|
|
34
|
+
* The inverse of the two literals above. pgvector hands a vector column back as **text**, so a read
|
|
35
|
+
* that did not parse it returned a string from a field whose declared type is `number[]`: invisible
|
|
36
|
+
* to the compiler, and invisible to any mocked test, because a mock returns the array the entity
|
|
37
|
+
* promises. It surfaces only as arithmetic quietly producing nonsense on real rows.
|
|
38
|
+
*
|
|
39
|
+
* Driven by `cast`, never by the shape of the text, so this is the exact mirror of the write side:
|
|
40
|
+
* a `sparsevec` column is read as `{1:1,3:2}/3` because that is what it was written as, and a dense
|
|
41
|
+
* one as `[1,2,3]`. Both return the dense array the field type promises, whichever width the column
|
|
42
|
+
* has. Sniffing the string instead would guess at a type the caller already knows.
|
|
43
|
+
*
|
|
44
|
+
* Returns `undefined` when the text does not match the column's own format, so a caller can keep the
|
|
45
|
+
* raw value rather than replace it with something invented.
|
|
46
|
+
*/
|
|
47
|
+
export declare function parseVectorLiteral(raw: string, cast: VectorCast): number[] | undefined;
|
|
@@ -39,3 +39,61 @@ export function toSparsevecLiteral(values) {
|
|
|
39
39
|
.join(',');
|
|
40
40
|
return `{${pairs}}/${values.length}`;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* The inverse of the two literals above. pgvector hands a vector column back as **text**, so a read
|
|
44
|
+
* that did not parse it returned a string from a field whose declared type is `number[]`: invisible
|
|
45
|
+
* to the compiler, and invisible to any mocked test, because a mock returns the array the entity
|
|
46
|
+
* promises. It surfaces only as arithmetic quietly producing nonsense on real rows.
|
|
47
|
+
*
|
|
48
|
+
* Driven by `cast`, never by the shape of the text, so this is the exact mirror of the write side:
|
|
49
|
+
* a `sparsevec` column is read as `{1:1,3:2}/3` because that is what it was written as, and a dense
|
|
50
|
+
* one as `[1,2,3]`. Both return the dense array the field type promises, whichever width the column
|
|
51
|
+
* has. Sniffing the string instead would guess at a type the caller already knows.
|
|
52
|
+
*
|
|
53
|
+
* Returns `undefined` when the text does not match the column's own format, so a caller can keep the
|
|
54
|
+
* raw value rather than replace it with something invented.
|
|
55
|
+
*/
|
|
56
|
+
export function parseVectorLiteral(raw, cast) {
|
|
57
|
+
const text = raw.trim();
|
|
58
|
+
return cast === 'sparsevec' ? parseSparse(text) : parseDense(text);
|
|
59
|
+
}
|
|
60
|
+
const SPARSE_LITERAL = /^\{(.*)\}\/(\d+)$/;
|
|
61
|
+
/** `{1:1,3:2}/3` expanded to the dense array the field type promises, zeros included. */
|
|
62
|
+
function parseSparse(text) {
|
|
63
|
+
const sparse = SPARSE_LITERAL.exec(text);
|
|
64
|
+
if (!sparse)
|
|
65
|
+
return undefined;
|
|
66
|
+
const dense = new Array(Number(sparse[2])).fill(0);
|
|
67
|
+
if (!sparse[1])
|
|
68
|
+
return dense;
|
|
69
|
+
for (const pair of sparse[1].split(',')) {
|
|
70
|
+
// Split into exactly two non-empty parts before converting: `Number('')` is 0, not NaN, so a
|
|
71
|
+
// truncated `{1:}/3` would otherwise decode to a confident zero instead of being refused.
|
|
72
|
+
const parts = pair.split(':');
|
|
73
|
+
if (parts.length !== 2 || !parts[0] || !parts[1])
|
|
74
|
+
return undefined;
|
|
75
|
+
const [index, value] = parts.map(Number);
|
|
76
|
+
if (!Number.isInteger(index) || index < 1 || index > dense.length || Number.isNaN(value))
|
|
77
|
+
return undefined;
|
|
78
|
+
dense[index - 1] = value;
|
|
79
|
+
}
|
|
80
|
+
return dense;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* `[1,0,2]`, whatever width the column has.
|
|
84
|
+
*
|
|
85
|
+
* A dense literal is valid JSON by construction, so parsing it as JSON is both stricter and cheaper
|
|
86
|
+
* than splitting: `[1,,2]` throws here, where `split(',').map(Number)` would have turned the hole
|
|
87
|
+
* into a 0.
|
|
88
|
+
*/
|
|
89
|
+
function parseDense(text) {
|
|
90
|
+
if (!text.startsWith('[') || !text.endsWith(']'))
|
|
91
|
+
return undefined;
|
|
92
|
+
try {
|
|
93
|
+
const dense = JSON.parse(text);
|
|
94
|
+
return Array.isArray(dense) && dense.every((n) => typeof n === 'number') ? dense : undefined;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -26,6 +26,5 @@ type MemberSpecs = {
|
|
|
26
26
|
export declare function applyMembers<E>(entity: Type<E>, specs: MemberSpecs | undefined): void;
|
|
27
27
|
export declare function defineEntity<E>(entity: Type<E>, opts?: EntityOptions<E>): EntityMeta<E>;
|
|
28
28
|
export declare function getEntities(): Type<unknown>[];
|
|
29
|
-
export declare function ensureMeta<E>(entity: Type<E>): EntityMeta<E>;
|
|
30
29
|
export declare function getMeta<E>(entity: Type<E>): EntityMeta<E>;
|
|
31
30
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MysqlLikeSqlDialect } from '../dialect/mysqlLikeSqlDialect.js';
|
|
2
|
-
import type { DialectFeatures, FieldOptions, IndexFeature, IndexSchema,
|
|
2
|
+
import type { DialectFeatures, FieldOptions, IndexFeature, IndexSchema, QueryContext, Type, VectorDistance } from '../type/index.js';
|
|
3
3
|
export declare class MariaDialect extends MysqlLikeSqlDialect {
|
|
4
4
|
readonly dialectName = "mariadb";
|
|
5
5
|
readonly insertIdSource = "returning";
|
|
@@ -11,7 +11,8 @@ export declare class MariaDialect extends MysqlLikeSqlDialect {
|
|
|
11
11
|
protected readonly indexFeatures: Set<IndexFeature>;
|
|
12
12
|
/** Unlike MySQL: `VECTOR(n)` takes its dimension, and its vector index is declared inline. */
|
|
13
13
|
protected readonly featureOverrides: Partial<DialectFeatures>;
|
|
14
|
-
|
|
14
|
+
/** MariaDB 10.5+ supports `INSERT ... RETURNING`, so the ids are exact per row. */
|
|
15
|
+
protected upsertReturning<E>(entity: Type<E>): string;
|
|
15
16
|
/**
|
|
16
17
|
* MariaDB supports neither MySQL's `->`/`->>` shorthand nor the base's chained form. `JSON_VALUE`
|
|
17
18
|
* reads a scalar and `JSON_EXTRACT` the subtree that the array operators need.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { jsonPath } from '../dialect/jsonSql.js';
|
|
2
2
|
import { MysqlLikeSqlDialect } from '../dialect/mysqlLikeSqlDialect.js';
|
|
3
3
|
import { isVectorFieldType } from '../dialect/vectorCast.js';
|
|
4
|
-
import { getMeta } from '../entity/index.js';
|
|
5
4
|
export class MariaDialect extends MysqlLikeSqlDialect {
|
|
6
5
|
dialectName = 'mariadb';
|
|
7
6
|
// MariaDB 10.5+ supports `INSERT ... RETURNING` (see `insert` below), so IDs are exact per row.
|
|
@@ -17,23 +16,9 @@ export class MariaDialect extends MysqlLikeSqlDialect {
|
|
|
17
16
|
vectorSupportsLength: true,
|
|
18
17
|
inlineVectorIndex: true,
|
|
19
18
|
};
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const update = this.getUpsertUpdateAssignments(updateCtx, meta, conflictPaths, payload, (name) => `VALUES(${name})`);
|
|
24
|
-
const returning = this.returningId(entity);
|
|
25
|
-
if (update) {
|
|
26
|
-
this.appendInsertValues(ctx, entity, payload);
|
|
27
|
-
ctx.append(` ON DUPLICATE KEY UPDATE ${update} ${returning}`);
|
|
28
|
-
ctx.pushValue(...updateCtx.values);
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
const insertCtx = this.createContext();
|
|
32
|
-
this.appendInsertValues(insertCtx, entity, payload);
|
|
33
|
-
ctx.append(insertCtx.sql.replace(/^INSERT/, 'INSERT IGNORE'));
|
|
34
|
-
ctx.append(' ' + returning);
|
|
35
|
-
ctx.pushValue(...insertCtx.values);
|
|
36
|
-
}
|
|
19
|
+
/** MariaDB 10.5+ supports `INSERT ... RETURNING`, so the ids are exact per row. */
|
|
20
|
+
upsertReturning(entity) {
|
|
21
|
+
return ` ${this.returningId(entity)}`;
|
|
37
22
|
}
|
|
38
23
|
/**
|
|
39
24
|
* MariaDB supports neither MySQL's `->`/`->>` shorthand nor the base's chained form. `JSON_VALUE`
|
|
@@ -7,7 +7,12 @@ export class MariadbQuerierPool extends AbstractSqlQuerierPool {
|
|
|
7
7
|
pool;
|
|
8
8
|
constructor(opts, extra) {
|
|
9
9
|
super(new MariaDialect({ namingStrategy: extra?.namingStrategy }), extra);
|
|
10
|
-
|
|
10
|
+
// `mariadb` defaults to handing BIGINT back as a BigInt, and uql maps `type: Number` to BIGINT
|
|
11
|
+
// (see `schema/canonicalType.ts`), so every auto-increment id reached a field declared `number`
|
|
12
|
+
// as `9n` without this. Same trade as the pg pools: exact to 2^53, and `...opts` wins for a
|
|
13
|
+
// caller who needs more. This belongs to the pool, not to the suites - it lived in
|
|
14
|
+
// `mariadbQuerier.test.ts`, which meant the tests passed on behaviour the library never shipped.
|
|
15
|
+
this.pool = createPool({ bigIntAsNumber: true, ...opts });
|
|
11
16
|
// `mariadb`'s own `createPool` already attaches a silent no-op 'error'
|
|
12
17
|
// listener (so a dropped connection can't crash the process), but its
|
|
13
18
|
// `Pool` type only declares `on` for 'acquire' | 'connection' | 'enqueue'
|
|
@@ -9,6 +9,15 @@ import type { ForeignKeyAction } from '../../schema/types.js';
|
|
|
9
9
|
import type { IndexColumnInput, IndexOptions } from '../../type/index.js';
|
|
10
10
|
import type { SqlQuerier } from '../../type/querier.js';
|
|
11
11
|
import type { AnyMigrationOperation, IAlterTableBuilder, IColumnBuilder, IColumnFactory, IMigrationBuilder, ITableBuilder } from './types.js';
|
|
12
|
+
type ForeignKeyTarget = {
|
|
13
|
+
table: string;
|
|
14
|
+
columns: string[];
|
|
15
|
+
};
|
|
16
|
+
type ForeignKeyOptions = {
|
|
17
|
+
name?: string;
|
|
18
|
+
onDelete?: ForeignKeyAction;
|
|
19
|
+
onUpdate?: ForeignKeyAction;
|
|
20
|
+
};
|
|
12
21
|
/**
|
|
13
22
|
* Records migration operations without executing them.
|
|
14
23
|
* Use for migration code generation and dry-run scenarios.
|
|
@@ -28,14 +37,7 @@ export declare class OperationRecorder implements IMigrationBuilder {
|
|
|
28
37
|
renameColumn(tableName: string, oldName: string, newName: string): Promise<void>;
|
|
29
38
|
createIndex(tableName: string, columns: readonly IndexColumnInput[], options?: IndexOptions): Promise<void>;
|
|
30
39
|
dropIndex(tableName: string, indexName: string): Promise<void>;
|
|
31
|
-
addForeignKey(tableName: string, columns: string[], target:
|
|
32
|
-
table: string;
|
|
33
|
-
columns: string[];
|
|
34
|
-
}, options?: {
|
|
35
|
-
name?: string;
|
|
36
|
-
onDelete?: ForeignKeyAction;
|
|
37
|
-
onUpdate?: ForeignKeyAction;
|
|
38
|
-
}): Promise<void>;
|
|
40
|
+
addForeignKey(tableName: string, columns: string[], target: ForeignKeyTarget, options?: ForeignKeyOptions): Promise<void>;
|
|
39
41
|
dropForeignKey(tableName: string, constraintName: string): Promise<void>;
|
|
40
42
|
raw(sql: string): Promise<void>;
|
|
41
43
|
getOperations(): AnyMigrationOperation[];
|
|
@@ -74,16 +76,10 @@ export declare class MigrationBuilder extends OperationRecorder {
|
|
|
74
76
|
renameColumn(tableName: string, oldName: string, newName: string): Promise<void>;
|
|
75
77
|
createIndex(tableName: string, columns: readonly IndexColumnInput[], options?: IndexOptions): Promise<void>;
|
|
76
78
|
dropIndex(tableName: string, indexName: string): Promise<void>;
|
|
77
|
-
addForeignKey(tableName: string, columns: string[], target:
|
|
78
|
-
table: string;
|
|
79
|
-
columns: string[];
|
|
80
|
-
}, options?: {
|
|
81
|
-
name?: string;
|
|
82
|
-
onDelete?: ForeignKeyAction;
|
|
83
|
-
onUpdate?: ForeignKeyAction;
|
|
84
|
-
}): Promise<void>;
|
|
79
|
+
addForeignKey(tableName: string, columns: string[], target: ForeignKeyTarget, options?: ForeignKeyOptions): Promise<void>;
|
|
85
80
|
dropForeignKey(tableName: string, constraintName: string): Promise<void>;
|
|
86
81
|
private getCreateTableStatements;
|
|
87
82
|
private execute;
|
|
88
83
|
private operationToSql;
|
|
89
84
|
}
|
|
85
|
+
export {};
|
|
@@ -32,6 +32,27 @@ function createIndexOperation(tableName, columns, options = {}) {
|
|
|
32
32
|
},
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* The one shape of an `addForeignKey` operation, `NO ACTION` defaults included.
|
|
37
|
+
*
|
|
38
|
+
* Three callers build it and differ only in what they do with the result: the table builder records
|
|
39
|
+
* it through its parent, the recorder records it directly, and the executing builder also runs it.
|
|
40
|
+
* Spelled out three times, a changed default would have had to be found in all three.
|
|
41
|
+
*/
|
|
42
|
+
function addForeignKeyOperation(tableName, columns, target, options = {}) {
|
|
43
|
+
return {
|
|
44
|
+
type: 'addForeignKey',
|
|
45
|
+
tableName,
|
|
46
|
+
foreignKey: {
|
|
47
|
+
name: options.name,
|
|
48
|
+
columns,
|
|
49
|
+
referencesTable: target.table,
|
|
50
|
+
referencesColumns: target.columns,
|
|
51
|
+
onDelete: options.onDelete ?? 'NO ACTION',
|
|
52
|
+
onUpdate: options.onUpdate ?? 'NO ACTION',
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
35
56
|
/**
|
|
36
57
|
* Declare one column through the same vocabulary `createTable` uses. A throwaway {@link TableBuilder}
|
|
37
58
|
* is that vocabulary: `addColumn`/`alterColumn` used to take a bare {@link IColumnBuilder}, which
|
|
@@ -95,18 +116,7 @@ class AlterTableBuilder {
|
|
|
95
116
|
return this;
|
|
96
117
|
}
|
|
97
118
|
addForeignKey(columns, target, options) {
|
|
98
|
-
this.parentBuilder.recordOperationSync(
|
|
99
|
-
type: 'addForeignKey',
|
|
100
|
-
tableName: this.tableName,
|
|
101
|
-
foreignKey: {
|
|
102
|
-
name: options?.name,
|
|
103
|
-
columns,
|
|
104
|
-
referencesTable: target.table,
|
|
105
|
-
referencesColumns: target.columns,
|
|
106
|
-
onDelete: options?.onDelete ?? 'NO ACTION',
|
|
107
|
-
onUpdate: options?.onUpdate ?? 'NO ACTION',
|
|
108
|
-
},
|
|
109
|
-
});
|
|
119
|
+
this.parentBuilder.recordOperationSync(addForeignKeyOperation(this.tableName, columns, target, options));
|
|
110
120
|
return this;
|
|
111
121
|
}
|
|
112
122
|
dropForeignKey(name) {
|
|
@@ -124,9 +134,6 @@ class AlterTableBuilder {
|
|
|
124
134
|
*/
|
|
125
135
|
export class OperationRecorder {
|
|
126
136
|
operations = [];
|
|
127
|
-
// ============================================================================
|
|
128
|
-
// Table Operations
|
|
129
|
-
// ============================================================================
|
|
130
137
|
async createTable(name, callback) {
|
|
131
138
|
const builder = new TableBuilder(name);
|
|
132
139
|
callback(builder);
|
|
@@ -154,9 +161,6 @@ export class OperationRecorder {
|
|
|
154
161
|
const builder = new AlterTableBuilder(name, this);
|
|
155
162
|
callback(builder);
|
|
156
163
|
}
|
|
157
|
-
// ============================================================================
|
|
158
|
-
// Column Operations
|
|
159
|
-
// ============================================================================
|
|
160
164
|
async addColumn(tableName, callback) {
|
|
161
165
|
this.recordOperationSync({
|
|
162
166
|
type: 'addColumn',
|
|
@@ -188,9 +192,6 @@ export class OperationRecorder {
|
|
|
188
192
|
newName,
|
|
189
193
|
});
|
|
190
194
|
}
|
|
191
|
-
// ============================================================================
|
|
192
|
-
// Index Operations
|
|
193
|
-
// ============================================================================
|
|
194
195
|
async createIndex(tableName, columns, options) {
|
|
195
196
|
this.recordOperationSync(createIndexOperation(tableName, columns, options));
|
|
196
197
|
}
|
|
@@ -201,22 +202,8 @@ export class OperationRecorder {
|
|
|
201
202
|
indexName,
|
|
202
203
|
});
|
|
203
204
|
}
|
|
204
|
-
// ============================================================================
|
|
205
|
-
// Foreign Key Operations
|
|
206
|
-
// ============================================================================
|
|
207
205
|
async addForeignKey(tableName, columns, target, options = {}) {
|
|
208
|
-
this.recordOperationSync(
|
|
209
|
-
type: 'addForeignKey',
|
|
210
|
-
tableName,
|
|
211
|
-
foreignKey: {
|
|
212
|
-
name: options.name,
|
|
213
|
-
columns,
|
|
214
|
-
referencesTable: target.table,
|
|
215
|
-
referencesColumns: target.columns,
|
|
216
|
-
onDelete: options.onDelete ?? 'NO ACTION',
|
|
217
|
-
onUpdate: options.onUpdate ?? 'NO ACTION',
|
|
218
|
-
},
|
|
219
|
-
});
|
|
206
|
+
this.recordOperationSync(addForeignKeyOperation(tableName, columns, target, options));
|
|
220
207
|
}
|
|
221
208
|
async dropForeignKey(tableName, constraintName) {
|
|
222
209
|
this.recordOperationSync({
|
|
@@ -225,18 +212,12 @@ export class OperationRecorder {
|
|
|
225
212
|
constraintName,
|
|
226
213
|
});
|
|
227
214
|
}
|
|
228
|
-
// ============================================================================
|
|
229
|
-
// Raw SQL
|
|
230
|
-
// ============================================================================
|
|
231
215
|
async raw(sql) {
|
|
232
216
|
this.recordOperationSync({
|
|
233
217
|
type: 'raw',
|
|
234
218
|
sql,
|
|
235
219
|
});
|
|
236
220
|
}
|
|
237
|
-
// ============================================================================
|
|
238
|
-
// Operation Access
|
|
239
|
-
// ============================================================================
|
|
240
221
|
getOperations() {
|
|
241
222
|
return [...this.operations];
|
|
242
223
|
}
|
|
@@ -284,9 +265,7 @@ export class MigrationBuilder extends OperationRecorder {
|
|
|
284
265
|
this.operations.push(operation);
|
|
285
266
|
await this.querier.run(sql);
|
|
286
267
|
}
|
|
287
|
-
// ============================================================================
|
|
288
268
|
// Override async methods to execute immediately
|
|
289
|
-
// ============================================================================
|
|
290
269
|
async createTable(name, callback) {
|
|
291
270
|
const builder = new TableBuilder(name);
|
|
292
271
|
callback(builder);
|
|
@@ -370,18 +349,7 @@ export class MigrationBuilder extends OperationRecorder {
|
|
|
370
349
|
await this.execute(operation);
|
|
371
350
|
}
|
|
372
351
|
async addForeignKey(tableName, columns, target, options = {}) {
|
|
373
|
-
const operation =
|
|
374
|
-
type: 'addForeignKey',
|
|
375
|
-
tableName,
|
|
376
|
-
foreignKey: {
|
|
377
|
-
name: options.name,
|
|
378
|
-
columns,
|
|
379
|
-
referencesTable: target.table,
|
|
380
|
-
referencesColumns: target.columns,
|
|
381
|
-
onDelete: options.onDelete ?? 'NO ACTION',
|
|
382
|
-
onUpdate: options.onUpdate ?? 'NO ACTION',
|
|
383
|
-
},
|
|
384
|
-
};
|
|
352
|
+
const operation = addForeignKeyOperation(tableName, columns, target, options);
|
|
385
353
|
this.operations.push(operation);
|
|
386
354
|
await this.execute(operation);
|
|
387
355
|
}
|
|
@@ -394,9 +362,6 @@ export class MigrationBuilder extends OperationRecorder {
|
|
|
394
362
|
this.operations.push(operation);
|
|
395
363
|
await this.execute(operation);
|
|
396
364
|
}
|
|
397
|
-
// ============================================================================
|
|
398
|
-
// Private Methods
|
|
399
|
-
// ============================================================================
|
|
400
365
|
getCreateTableStatements(operation) {
|
|
401
366
|
return this.sqlGenerator.generateCreateTableFromDefinition(operation.table);
|
|
402
367
|
}
|
|
@@ -65,9 +65,6 @@ export class TableBuilder {
|
|
|
65
65
|
constructor(name) {
|
|
66
66
|
this._name = name;
|
|
67
67
|
}
|
|
68
|
-
// ============================================================================
|
|
69
|
-
// Numeric Types
|
|
70
|
-
// ============================================================================
|
|
71
68
|
id(name = 'id', options = {}) {
|
|
72
69
|
return this.add(name, { category: 'integer' }, { ...options, primaryKey: true, autoIncrement: true });
|
|
73
70
|
}
|
|
@@ -138,9 +135,6 @@ export class TableBuilder {
|
|
|
138
135
|
this._columnBuilders.push(column);
|
|
139
136
|
return column;
|
|
140
137
|
}
|
|
141
|
-
// ============================================================================
|
|
142
|
-
// Convenience Methods
|
|
143
|
-
// ============================================================================
|
|
144
138
|
createdAt() {
|
|
145
139
|
return this.add('createdAt', { category: 'timestamp' }, { defaultValue: t.now() });
|
|
146
140
|
}
|
|
@@ -151,9 +145,6 @@ export class TableBuilder {
|
|
|
151
145
|
this.createdAt();
|
|
152
146
|
this.updatedAt();
|
|
153
147
|
}
|
|
154
|
-
// ============================================================================
|
|
155
|
-
// Indexes & Constraints
|
|
156
|
-
// ============================================================================
|
|
157
148
|
primaryKey(columns) {
|
|
158
149
|
this._primaryKey = columns;
|
|
159
150
|
return this;
|
|
@@ -186,9 +177,6 @@ export class TableBuilder {
|
|
|
186
177
|
this._foreignKeyBuilders.push(fk);
|
|
187
178
|
return fk;
|
|
188
179
|
}
|
|
189
|
-
// ============================================================================
|
|
190
|
-
// Utilities
|
|
191
|
-
// ============================================================================
|
|
192
180
|
comment(text) {
|
|
193
181
|
this._comment = text;
|
|
194
182
|
return this;
|
package/dist/migrate/cli.d.ts
CHANGED
|
@@ -17,4 +17,3 @@ export declare function runGenerateFromEntities(migrator: Migrator, args: string
|
|
|
17
17
|
export declare function runSync(migrator: Migrator, args: string[], config: Partial<Config>): Promise<void>;
|
|
18
18
|
export declare function runGenerateFromDb(migrator: Migrator, args: string[], config: Partial<Config>): Promise<void>;
|
|
19
19
|
export declare function runDriftCheck(migrator: Migrator, config: Partial<Config>): Promise<void>;
|
|
20
|
-
export declare function printHelp(): void;
|
package/dist/migrate/cli.js
CHANGED
|
@@ -268,9 +268,6 @@ export class EntityCodeGenerator {
|
|
|
268
268
|
}
|
|
269
269
|
return lines.join('\n');
|
|
270
270
|
}
|
|
271
|
-
// ============================================================================
|
|
272
|
-
// Helper Methods
|
|
273
|
-
// ============================================================================
|
|
274
271
|
/**
|
|
275
272
|
* Get decorator name for relation type.
|
|
276
273
|
*/
|
|
@@ -110,7 +110,11 @@ export class DriftDetector {
|
|
|
110
110
|
* Add drifts for column alterations (type/nullable mismatches).
|
|
111
111
|
*/
|
|
112
112
|
addAlterColumnDrifts(colDiff, drifts) {
|
|
113
|
-
|
|
113
|
+
// Every check below compares the two sides, so there is nothing to report without both.
|
|
114
|
+
if (!colDiff.expected || !colDiff.actual) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (this.options.checkTypes) {
|
|
114
118
|
const expectedType = this.formatType(colDiff.expected.type);
|
|
115
119
|
const actualType = this.formatType(colDiff.actual.type);
|
|
116
120
|
if (expectedType !== actualType) {
|
|
@@ -128,21 +132,19 @@ export class DriftDetector {
|
|
|
128
132
|
});
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
|
-
if (this.options.checkNullable && colDiff.expected
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
});
|
|
143
|
-
}
|
|
135
|
+
if (this.options.checkNullable && colDiff.expected.nullable !== colDiff.actual.nullable) {
|
|
136
|
+
drifts.push({
|
|
137
|
+
type: 'constraint_mismatch',
|
|
138
|
+
severity: 'warning',
|
|
139
|
+
table: colDiff.table,
|
|
140
|
+
column: colDiff.column,
|
|
141
|
+
expected: colDiff.expected.nullable ? 'NULLABLE' : 'NOT NULL',
|
|
142
|
+
actual: colDiff.actual.nullable ? 'NULLABLE' : 'NOT NULL',
|
|
143
|
+
details: `Nullable mismatch for "${colDiff.column}"`,
|
|
144
|
+
suggestion: 'Align nullable setting in entity or database',
|
|
145
|
+
});
|
|
144
146
|
}
|
|
145
|
-
if (this.options.checkDefaults
|
|
147
|
+
if (this.options.checkDefaults) {
|
|
146
148
|
const expected = String(colDiff.expected.defaultValue ?? 'NULL');
|
|
147
149
|
const actual = String(colDiff.actual.defaultValue ?? 'NULL');
|
|
148
150
|
if (expected !== actual) {
|
|
@@ -77,8 +77,14 @@ export declare abstract class AbstractSqlSchemaIntrospector extends BaseSqlIntro
|
|
|
77
77
|
protected abstract getForeignKeysQuery(tableName: string): string;
|
|
78
78
|
/** SQL query to get primary key columns. Parameter: tableName (for PRAGMA-style). */
|
|
79
79
|
protected abstract getPrimaryKeyQuery(tableName: string): string;
|
|
80
|
-
/**
|
|
81
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Extract table name from a row returned by getTableNamesQuery.
|
|
82
|
+
*
|
|
83
|
+
* Defaults to `information_schema`'s own column, which is what every engine with an
|
|
84
|
+
* `information_schema` returns and what Postgres and MySQL both restated identically. SQLite reads
|
|
85
|
+
* `sqlite_master` instead and overrides.
|
|
86
|
+
*/
|
|
87
|
+
protected mapTableNameRow(row: RawRow): string;
|
|
82
88
|
/** Map column query results to ColumnSchema array. Allows async for SQLite's unique column check. */
|
|
83
89
|
protected abstract mapColumnsResult(read: TableRowReader, tableName: string, results: RawRow[]): Promise<ColumnSchema[]>;
|
|
84
90
|
/** Map index query results to IndexSchema array. Allows async for SQLite's index_info calls. */
|
|
@@ -25,9 +25,6 @@ export class AbstractSqlSchemaIntrospector extends BaseSqlIntrospector {
|
|
|
25
25
|
super(pool.dialect);
|
|
26
26
|
this.pool = pool;
|
|
27
27
|
}
|
|
28
|
-
// ============================================================================
|
|
29
|
-
// Template Methods (shared control flow)
|
|
30
|
-
// ============================================================================
|
|
31
28
|
async getTableSchema(tableName) {
|
|
32
29
|
return this.withSqlQuerier(async (querier) => {
|
|
33
30
|
const read = createTableRowReader(querier);
|
|
@@ -91,9 +88,6 @@ export class AbstractSqlSchemaIntrospector extends BaseSqlIntrospector {
|
|
|
91
88
|
const results = await read(this.getPrimaryKeyQuery(tableName), this.getPrimaryKeyParams(tableName));
|
|
92
89
|
return this.mapPrimaryKeyResult(results);
|
|
93
90
|
}
|
|
94
|
-
// ============================================================================
|
|
95
|
-
// Parameter Methods (can be overridden by dialects)
|
|
96
|
-
// ============================================================================
|
|
97
91
|
tableExistsParams(tableName) {
|
|
98
92
|
return [tableName];
|
|
99
93
|
}
|
|
@@ -109,9 +103,6 @@ export class AbstractSqlSchemaIntrospector extends BaseSqlIntrospector {
|
|
|
109
103
|
getPrimaryKeyParams(tableName) {
|
|
110
104
|
return [tableName];
|
|
111
105
|
}
|
|
112
|
-
// ============================================================================
|
|
113
|
-
// Shared Utilities
|
|
114
|
-
// ============================================================================
|
|
115
106
|
/**
|
|
116
107
|
* Normalize referential action string to standard type.
|
|
117
108
|
*/
|
|
@@ -138,6 +129,16 @@ export class AbstractSqlSchemaIntrospector extends BaseSqlIntrospector {
|
|
|
138
129
|
}
|
|
139
130
|
return Number(value);
|
|
140
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Extract table name from a row returned by getTableNamesQuery.
|
|
134
|
+
*
|
|
135
|
+
* Defaults to `information_schema`'s own column, which is what every engine with an
|
|
136
|
+
* `information_schema` returns and what Postgres and MySQL both restated identically. SQLite reads
|
|
137
|
+
* `sqlite_master` instead and overrides.
|
|
138
|
+
*/
|
|
139
|
+
mapTableNameRow(row) {
|
|
140
|
+
return row['table_name'];
|
|
141
|
+
}
|
|
141
142
|
/**
|
|
142
143
|
* Map primary key query results to column names, in key order. `information_schema` gives every SQL
|
|
143
144
|
* engine here a `column_name` per row; SQLite reads its key off `PRAGMA table_info` instead and
|
|
@@ -14,9 +14,6 @@ export declare class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospect
|
|
|
14
14
|
protected getIndexesQuery(_tableName: string): string;
|
|
15
15
|
protected getForeignKeysQuery(_tableName: string): string;
|
|
16
16
|
protected getPrimaryKeyQuery(_tableName: string): string;
|
|
17
|
-
protected mapTableNameRow(row: {
|
|
18
|
-
table_name: string;
|
|
19
|
-
}): string;
|
|
20
17
|
protected mapColumnsResult(_read: TableRowReader, _tableName: string, results: MysqlColumnRow[]): Promise<ColumnSchema[]>;
|
|
21
18
|
protected mapIndexesResult(_read: TableRowReader, _tableName: string, results: {
|
|
22
19
|
index_name: string;
|
|
@@ -4,9 +4,6 @@ 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
|
-
// ============================================================================
|
|
8
|
-
// SQL Queries (dialect-specific)
|
|
9
|
-
// ============================================================================
|
|
10
7
|
getTableNamesQuery() {
|
|
11
8
|
return /*sql*/ `
|
|
12
9
|
SELECT TABLE_NAME as table_name
|
|
@@ -95,12 +92,6 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
95
92
|
ORDER BY ORDINAL_POSITION
|
|
96
93
|
`;
|
|
97
94
|
}
|
|
98
|
-
// ============================================================================
|
|
99
|
-
// Internal Types
|
|
100
|
-
// ============================================================================
|
|
101
|
-
mapTableNameRow(row) {
|
|
102
|
-
return row.table_name;
|
|
103
|
-
}
|
|
104
95
|
async mapColumnsResult(_read, _tableName, results) {
|
|
105
96
|
return results.map((row) => ({
|
|
106
97
|
name: row.column_name,
|
|
@@ -11,9 +11,6 @@ export declare class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrosp
|
|
|
11
11
|
protected getIndexesQuery(_tableName: string): string;
|
|
12
12
|
protected getForeignKeysQuery(_tableName: string): string;
|
|
13
13
|
protected getPrimaryKeyQuery(_tableName: string): string;
|
|
14
|
-
protected mapTableNameRow(row: {
|
|
15
|
-
table_name: string;
|
|
16
|
-
}): string;
|
|
17
14
|
protected mapColumnsResult(_read: TableRowReader, _tableName: string, results: PostgresColumnRow[]): Promise<ColumnSchema[]>;
|
|
18
15
|
protected mapIndexesResult(_read: TableRowReader, _tableName: string, results: {
|
|
19
16
|
index_name: string;
|