uql-orm 0.54.0 → 0.56.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 +2 -2
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +4 -4
- package/dist/bunSql/bunSql.util.d.ts +3 -14
- package/dist/bunSql/bunSql.util.js +33 -56
- package/dist/bunSql/bunSqlQuerier.d.ts +3 -6
- package/dist/bunSql/bunSqlQuerier.js +7 -13
- package/dist/bunSql/bunSqlQuerierPool.d.ts +10 -5
- package/dist/bunSql/bunSqlQuerierPool.js +25 -10
- package/dist/cockroachdb/crdbQuerierPool.d.ts +1 -3
- package/dist/cockroachdb/crdbQuerierPool.js +0 -4
- package/dist/cockroachdb/index.d.ts +0 -1
- package/dist/cockroachdb/index.js +0 -1
- package/dist/d1/d1SqliteDialect.d.ts +5 -0
- package/dist/d1/d1SqliteDialect.js +7 -0
- package/dist/dialect/abstractSqlDialect.d.ts +9 -9
- package/dist/dialect/abstractSqlDialect.js +38 -51
- package/dist/dialect/hydrateColumn.js +2 -2
- package/dist/dialect/mergeSqlDialect.d.ts +2 -2
- package/dist/dialect/mergeSqlDialect.js +0 -4
- package/dist/dialect/mysqlLikeSqlDialect.js +2 -3
- package/dist/dialect/pgLikeSqlDialect.d.ts +5 -0
- package/dist/dialect/pgLikeSqlDialect.js +14 -4
- package/dist/dialect/queryJoins.js +2 -4
- package/dist/entity/index.d.ts +1 -1
- package/dist/entity/index.js +1 -1
- package/dist/entity/metadata/definition.d.ts +5 -1
- package/dist/entity/metadata/definition.js +32 -26
- package/dist/libsql/index.d.ts +0 -1
- package/dist/libsql/index.js +0 -1
- package/dist/libsql/libsqlQuerierPool.d.ts +3 -5
- package/dist/libsql/libsqlQuerierPool.js +2 -5
- package/dist/maria/mariadbQuerier.d.ts +0 -3
- package/dist/maria/mariadbQuerier.js +6 -7
- package/dist/maria/mariadbQuerierPool.js +4 -7
- package/dist/migrate/builder/splitSqlStatements.js +2 -2
- package/dist/migrate/cli.js +2 -9
- package/dist/migrate/codegen/fieldOptionsSource.js +1 -1
- package/dist/migrate/ddl/index.d.ts +3 -3
- package/dist/migrate/ddl/index.js +3 -3
- package/dist/migrate/ddl/pgIndexDdl.d.ts +5 -0
- package/dist/migrate/ddl/pgIndexDdl.js +9 -0
- package/dist/migrate/drift/driftDetector.js +21 -8
- package/dist/migrate/generator/mongoSchemaGenerator.js +1 -1
- package/dist/migrate/introspection/baseSqlIntrospector.d.ts +1 -1
- package/dist/migrate/introspection/baseSqlIntrospector.js +65 -76
- package/dist/migrate/introspection/mssqlIntrospector.js +2 -1
- package/dist/migrate/introspection/mysqlIntrospector.js +5 -8
- package/dist/migrate/introspection/sqliteIntrospector.js +2 -5
- package/dist/migrate/migrator.d.ts +5 -2
- package/dist/migrate/migrator.js +9 -14
- package/dist/migrate/schemaGenerator.js +2 -6
- package/dist/mongo/index.d.ts +0 -1
- package/dist/mongo/index.js +0 -1
- package/dist/mongo/mongoDialect.d.ts +1 -2
- package/dist/mongo/mongoDialect.js +7 -21
- package/dist/mongo/mongodbQuerier.js +12 -11
- package/dist/mongo/mongodbQuerierPool.d.ts +2 -2
- package/dist/mongo/mongodbQuerierPool.js +2 -2
- package/dist/mssql/mssqlDialect.d.ts +3 -5
- package/dist/mssql/mssqlDialect.js +9 -8
- package/dist/mssql/mssqlQuerier.d.ts +13 -6
- package/dist/mssql/mssqlQuerier.js +30 -75
- package/dist/mssql/mssqlQuerierPool.js +2 -0
- package/dist/mssql/mssqlWireTypes.d.ts +3 -4
- package/dist/mssql/mssqlWireTypes.js +5 -8
- package/dist/mysql/index.d.ts +0 -1
- package/dist/mysql/index.js +0 -1
- package/dist/mysql/mysql2Querier.d.ts +1 -4
- package/dist/mysql/mysql2Querier.js +0 -3
- package/dist/mysql/mysql2QuerierPool.d.ts +2 -2
- package/dist/mysql/mysql2QuerierPool.js +5 -3
- package/dist/neon/index.d.ts +0 -2
- package/dist/neon/index.js +0 -2
- package/dist/neon/neonQuerierPool.d.ts +2 -4
- package/dist/neon/neonQuerierPool.js +2 -6
- package/dist/pglite/index.d.ts +0 -1
- package/dist/pglite/index.js +0 -1
- package/dist/pglite/pgliteQuerier.d.ts +3 -3
- package/dist/pglite/pgliteQuerier.js +1 -1
- package/dist/pglite/pgliteQuerierPool.d.ts +7 -2
- package/dist/pglite/pgliteQuerierPool.js +16 -6
- package/dist/postgres/abstractPgQuerierPool.d.ts +8 -12
- package/dist/postgres/abstractPgQuerierPool.js +8 -6
- package/dist/postgres/index.d.ts +0 -1
- package/dist/postgres/index.js +0 -1
- package/dist/postgres/pgNumericTypes.d.ts +5 -5
- package/dist/postgres/pgNumericTypes.js +11 -7
- package/dist/postgres/pgQuerier.d.ts +22 -4
- package/dist/postgres/pgQuerier.js +29 -2
- package/dist/postgres/pgQuerierPool.d.ts +2 -4
- package/dist/postgres/pgQuerierPool.js +2 -6
- package/dist/postgres/postgresDialect.d.ts +5 -5
- package/dist/postgres/postgresDialect.js +5 -5
- package/dist/postgres/postgresWireDriverCapabilities.d.ts +2 -2
- package/dist/postgres/postgresWireDriverCapabilities.js +2 -2
- package/dist/querier/abstractPoolQuerier.d.ts +1 -1
- package/dist/querier/abstractPoolQuerier.js +1 -1
- package/dist/querier/abstractQuerier.js +17 -22
- package/dist/querier/abstractSqlQuerier.d.ts +21 -12
- package/dist/querier/abstractSqlQuerier.js +61 -43
- package/dist/schema/canonicalType.js +0 -2
- package/dist/schema/dependencyGraph.js +2 -4
- package/dist/schema/indexDifferences.js +2 -2
- package/dist/schema/schemaASTBuilder.js +3 -10
- package/dist/schema/schemaASTDiffer.d.ts +10 -2
- package/dist/schema/schemaASTDiffer.js +12 -11
- package/dist/schema/types.d.ts +1 -1
- package/dist/sqlite/hranaQuerier.d.ts +6 -8
- package/dist/sqlite/hranaQuerier.js +13 -30
- package/dist/sqlite/hranaQuerierPool.d.ts +3 -4
- package/dist/sqlite/hranaQuerierPool.js +2 -1
- package/dist/sqlite/localSqliteQuerierPool.d.ts +3 -3
- package/dist/sqlite/localSqliteQuerierPool.js +4 -2
- package/dist/sqlite/nodeSqliteAdapter.d.ts +0 -1
- package/dist/sqlite/nodeSqliteQuerierPool.js +0 -2
- package/dist/sqlite/sqliteDialect.js +2 -1
- package/dist/sqlite/sqlitePragmas.d.ts +12 -0
- package/dist/sqlite/sqlitePragmas.js +15 -0
- package/dist/sqlite/sqliteQuerierPool.d.ts +0 -5
- package/dist/sqlite/sqliteQuerierPool.js +2 -13
- package/dist/turso/index.d.ts +0 -1
- package/dist/turso/index.js +0 -1
- package/dist/turso/tursoLocalQuerier.d.ts +0 -1
- package/dist/turso/tursoLocalQuerierPool.js +2 -2
- package/dist/turso/tursoQuerierPool.d.ts +1 -3
- package/dist/turso/tursoQuerierPool.js +0 -4
- package/dist/type/dialect.d.ts +1 -1
- package/dist/util/dialect.util.d.ts +8 -2
- package/dist/util/dialect.util.js +20 -1
- package/dist/util/logger.d.ts +17 -9
- package/dist/util/logger.js +36 -11
- package/dist/util/object.util.d.ts +2 -0
- package/dist/util/object.util.js +4 -0
- package/dist/util/raw.js +3 -3
- package/dist/util/relationQuery.util.d.ts +3 -3
- package/dist/util/relationQuery.util.js +8 -2
- package/dist/util/sqlLiteral.js +3 -8
- package/dist/util/string.util.js +2 -6
- package/dist/util/wideNumber.d.ts +14 -0
- package/dist/util/wideNumber.js +24 -0
- package/package.json +1 -1
- package/dist/cockroachdb/crdbQuerier.d.ts +0 -8
- package/dist/cockroachdb/crdbQuerier.js +0 -6
- package/dist/libsql/libsqlQuerier.d.ts +0 -10
- package/dist/libsql/libsqlQuerier.js +0 -10
- package/dist/mongo/mongodbNativeDialect.d.ts +0 -9
- package/dist/mongo/mongodbNativeDialect.js +0 -9
- package/dist/mysql/mysql2Dialect.d.ts +0 -9
- package/dist/mysql/mysql2Dialect.js +0 -9
- package/dist/neon/neonDialect.d.ts +0 -10
- package/dist/neon/neonDialect.js +0 -10
- package/dist/neon/neonQuerier.d.ts +0 -5
- package/dist/neon/neonQuerier.js +0 -3
- package/dist/pglite/pgliteDialect.d.ts +0 -14
- package/dist/pglite/pgliteDialect.js +0 -14
- package/dist/postgres/abstractPgQuerier.d.ts +0 -24
- package/dist/postgres/abstractPgQuerier.js +0 -32
- package/dist/postgres/pgDialect.d.ts +0 -10
- package/dist/postgres/pgDialect.js +0 -10
- package/dist/turso/tursoQuerier.d.ts +0 -10
- package/dist/turso/tursoQuerier.js +0 -10
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
import { AbstractPoolQuerier } from '../querier/abstractPoolQuerier.js';
|
|
2
|
+
import { decodeBigInts } from '../util/wideNumber.js';
|
|
2
3
|
export class MariadbQuerier extends AbstractPoolQuerier {
|
|
3
|
-
constructor(connect, dialect, extra) {
|
|
4
|
-
super(dialect, connect, extra);
|
|
5
|
-
}
|
|
6
4
|
async internalAll(query, values) {
|
|
7
|
-
const
|
|
8
|
-
return
|
|
5
|
+
const rows = await this.getConn().query(query, values);
|
|
6
|
+
return Array.from(rows, decodeBigInts);
|
|
9
7
|
}
|
|
10
8
|
async internalRun(query, values) {
|
|
11
9
|
const res = await this.getConn().query(query, values);
|
|
12
10
|
// MariaDB may not set `affectedRows` when RETURNING is used; fall back to row count.
|
|
13
11
|
const changes = res.affectedRows ?? res.length ?? 0;
|
|
14
|
-
|
|
12
|
+
const rows = res.length ? Array.from(res, decodeBigInts) : [];
|
|
13
|
+
return this.buildUpdateResult({ rows, changes, upsertStatus: res.affectedRows });
|
|
15
14
|
}
|
|
16
15
|
async *internalStream(query, values) {
|
|
17
16
|
const stream = this.getConn().queryStream(query, values);
|
|
18
17
|
try {
|
|
19
18
|
for await (const row of stream) {
|
|
20
|
-
yield row;
|
|
19
|
+
yield decodeBigInts(row);
|
|
21
20
|
}
|
|
22
21
|
}
|
|
23
22
|
finally {
|
|
@@ -8,19 +8,16 @@ export class MariadbQuerierPool extends AbstractSqlQuerierPool {
|
|
|
8
8
|
pool;
|
|
9
9
|
constructor(opts, extra) {
|
|
10
10
|
super(new MariaDialect(dialectOptionsFrom(extra)), extra);
|
|
11
|
-
//
|
|
12
|
-
// (
|
|
13
|
-
|
|
14
|
-
// caller who needs more. This belongs to the pool, not to the suites - it lived in
|
|
15
|
-
// `mariadbQuerier.test.ts`, which meant the tests passed on behaviour the library never shipped.
|
|
16
|
-
this.pool = createPool({ bigIntAsNumber: true, ...opts });
|
|
11
|
+
// BIGINT stays the driver's `bigint`, which `MariadbQuerier` decodes by the rule every driver here
|
|
12
|
+
// shares (`decodeWideNumber`) - not `bigIntAsNumber`, which rounds past 2^53 without a word.
|
|
13
|
+
this.pool = createPool(opts);
|
|
17
14
|
// `mariadb`'s own `createPool` already attaches a silent no-op 'error'
|
|
18
15
|
// listener (so a dropped connection can't crash the process), but its
|
|
19
16
|
// `Pool` type only declares `on` for 'acquire' | 'connection' | 'enqueue'
|
|
20
17
|
// | 'release' - 'error' genuinely fires at runtime (see `lib/pool.js`)
|
|
21
18
|
// but isn't in the declaration, hence the cast. Re-attaching our own
|
|
22
19
|
// listener here just makes the error visible instead of a silent no-op.
|
|
23
|
-
attachPoolErrorHandler(this.pool, 'Idle MariaDB pool connection encountered an error');
|
|
20
|
+
attachPoolErrorHandler(this.pool, 'Idle MariaDB pool connection encountered an error', extra?.logger);
|
|
24
21
|
}
|
|
25
22
|
async getQuerier() {
|
|
26
23
|
return new MariadbQuerier(() => this.pool.getConnection(), this.dialect, this.extra);
|
|
@@ -26,11 +26,11 @@ export function splitSqlStatements(sql) {
|
|
|
26
26
|
const masterRegex = /'(?:''|\\['\\]|[^'])*(?:'|(?=$))|"(?:""|\\["\\]|[^"])*(?:"|(?=$))|`(?:``|\\[`\\]|[^`])*(?:`|(?=$))|\$(?<tag>[a-zA-Z0-9_]*)\$[\s\S]*?(?:\$\k<tag>|(?=$))|--.*|\/\*[\s\S]*?(?:\*\/|(?=$))|;/g;
|
|
27
27
|
for (const match of sql.matchAll(masterRegex)) {
|
|
28
28
|
if (match[0] === ';') {
|
|
29
|
-
const stmt = sql.substring(lastIndex, match.index
|
|
29
|
+
const stmt = sql.substring(lastIndex, match.index).trim();
|
|
30
30
|
if (stmt) {
|
|
31
31
|
statements.push(stmt);
|
|
32
32
|
}
|
|
33
|
-
lastIndex =
|
|
33
|
+
lastIndex = match.index + match[0].length;
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
const lastStmt = sql.substring(lastIndex).trim();
|
package/dist/migrate/cli.js
CHANGED
|
@@ -34,7 +34,6 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
34
34
|
try {
|
|
35
35
|
const config = await loadConfig(customPath);
|
|
36
36
|
assertCliConfig(config);
|
|
37
|
-
const dialectName = config.pool.dialect.dialectName ?? 'postgres';
|
|
38
37
|
const options = {
|
|
39
38
|
migrationsPath: config.migrationsPath ?? './migrations',
|
|
40
39
|
tableName: config.tableName,
|
|
@@ -43,13 +42,7 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
43
42
|
defaultForeignKeyAction: config.defaultForeignKeyAction,
|
|
44
43
|
};
|
|
45
44
|
const migrator = new Migrator(config.pool, options);
|
|
46
|
-
|
|
47
|
-
const generator = await createSchemaGeneratorAsync(config.pool.dialect, config.defaultForeignKeyAction);
|
|
48
|
-
if (!generator) {
|
|
49
|
-
throw new TypeError(`Could not find a schema generator for dialect: ${dialectName}`);
|
|
50
|
-
}
|
|
51
|
-
migrator.setSchemaGenerator(generator);
|
|
52
|
-
}
|
|
45
|
+
await migrator.ensureSchemaGenerator();
|
|
53
46
|
switch (command) {
|
|
54
47
|
case 'up':
|
|
55
48
|
await runUp(migrator, filteredArgs.slice(1));
|
|
@@ -314,7 +307,7 @@ function printDriftGroup(title, drifts, icon, showSuggestion) {
|
|
|
314
307
|
if (drift.expected && drift.actual) {
|
|
315
308
|
console.log(` Expected: ${drift.expected}, Actual: ${drift.actual}`);
|
|
316
309
|
}
|
|
317
|
-
if (showSuggestion
|
|
310
|
+
if (showSuggestion) {
|
|
318
311
|
console.log(` → ${drift.suggestion}`);
|
|
319
312
|
}
|
|
320
313
|
}
|
|
@@ -16,7 +16,7 @@ const OPTION_SOURCE = {
|
|
|
16
16
|
type: (col) => {
|
|
17
17
|
const columnType = canonicalToColumnType(col.type);
|
|
18
18
|
return [
|
|
19
|
-
|
|
19
|
+
`columnType: ${quoted(columnType)}`,
|
|
20
20
|
...(col.type.length && col.type.category === 'string' ? [`length: ${col.type.length}`] : []),
|
|
21
21
|
...(col.type.precision === undefined ? [] : [`precision: ${col.type.precision}`]),
|
|
22
22
|
...(col.type.precision !== undefined && col.type.scale !== undefined ? [`scale: ${col.type.scale}`] : []),
|
|
@@ -7,9 +7,9 @@ export { MariaIndexDdl, MySqlIndexDdl, MysqlLikeIndexDdl } from './mysqlIndexDdl
|
|
|
7
7
|
export { CockroachIndexDdl, PgIndexDdl } from './pgIndexDdl.js';
|
|
8
8
|
export { TableDdl } from './tableDdl.js';
|
|
9
9
|
/**
|
|
10
|
-
* The index DDL a dialect gets, most specific first. `instanceof` rather than `dialectName`
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* The index DDL a dialect gets, most specific first. `instanceof` rather than the `dialectName`
|
|
11
|
+
* {@link tableDdlFor} reads, because each family's index DDL is typed to its dialect and the narrowing
|
|
12
|
+
* is what hands it one. Anything else gets the portable form, which is SQLite's.
|
|
13
13
|
*/
|
|
14
14
|
export declare function indexDdlFor(dialect: AbstractSqlDialect): IndexDdl;
|
|
15
15
|
/**
|
|
@@ -14,9 +14,9 @@ export { MariaIndexDdl, MySqlIndexDdl, MysqlLikeIndexDdl } from './mysqlIndexDdl
|
|
|
14
14
|
export { CockroachIndexDdl, PgIndexDdl } from './pgIndexDdl.js';
|
|
15
15
|
export { TableDdl } from './tableDdl.js';
|
|
16
16
|
/**
|
|
17
|
-
* The index DDL a dialect gets, most specific first. `instanceof` rather than `dialectName`
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* The index DDL a dialect gets, most specific first. `instanceof` rather than the `dialectName`
|
|
18
|
+
* {@link tableDdlFor} reads, because each family's index DDL is typed to its dialect and the narrowing
|
|
19
|
+
* is what hands it one. Anything else gets the portable form, which is SQLite's.
|
|
20
20
|
*/
|
|
21
21
|
export function indexDdlFor(dialect) {
|
|
22
22
|
if (dialect instanceof CockroachDialect) {
|
|
@@ -6,6 +6,11 @@ export declare class PgIndexDdl extends IndexDdl<PgLikeSqlDialect> {
|
|
|
6
6
|
protected readonly indexFeatures: Set<"expression" | "include" | "jsonArray" | "jsonPath" | "nullsOrder" | "opsClass" | "partial" | "prefixLength">;
|
|
7
7
|
/** pgvector's own index types; CockroachDB's native one widens this. */
|
|
8
8
|
protected isVectorIndex(index: IndexSchema): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* ` USING <method>`. `fulltext` is refused rather than compiled into a ` USING fulltext` the server
|
|
11
|
+
* can only answer with a syntax error: `$text` computes its `TO_TSVECTOR` per row, which no index
|
|
12
|
+
* over the raw columns serves.
|
|
13
|
+
*/
|
|
9
14
|
protected indexAccessMethod(index: IndexSchema): string;
|
|
10
15
|
/**
|
|
11
16
|
* A vector index's operator class is named `{type}_{metric}_ops`: an index on a `halfvec` column
|
|
@@ -14,7 +14,16 @@ export class PgIndexDdl extends IndexDdl {
|
|
|
14
14
|
isVectorIndex(index) {
|
|
15
15
|
return index.type === 'hnsw' || index.type === 'ivfflat';
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* ` USING <method>`. `fulltext` is refused rather than compiled into a ` USING fulltext` the server
|
|
19
|
+
* can only answer with a syntax error: `$text` computes its `TO_TSVECTOR` per row, which no index
|
|
20
|
+
* over the raw columns serves.
|
|
21
|
+
*/
|
|
17
22
|
indexAccessMethod(index) {
|
|
23
|
+
if (index.type === 'fulltext') {
|
|
24
|
+
throw new TypeError(`${this.dialect.dialectName} has no fulltext index (index "${index.name}"). $text needs none there; ` +
|
|
25
|
+
'name the columns it searches with $fields.');
|
|
26
|
+
}
|
|
18
27
|
return index.type ? ` USING ${index.type}` : '';
|
|
19
28
|
}
|
|
20
29
|
/**
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* actual database schema.
|
|
6
6
|
*/
|
|
7
7
|
import { canonicalToSql, engineType } from '../../schema/canonicalType.js';
|
|
8
|
-
import { diffSchemas } from '../../schema/schemaASTDiffer.js';
|
|
8
|
+
import { diffSchemas, referentialActions } from '../../schema/schemaASTDiffer.js';
|
|
9
9
|
function resolveOptions(options) {
|
|
10
10
|
return {
|
|
11
11
|
checkTypes: options.checkTypes ?? true,
|
|
@@ -114,7 +114,7 @@ function detectColumnDrifts(diff, opts) {
|
|
|
114
114
|
suggestion: 'Add to entity or create migration to drop',
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
|
-
else
|
|
117
|
+
else {
|
|
118
118
|
addAlterColumnDrifts(colDiff, drifts, opts);
|
|
119
119
|
}
|
|
120
120
|
}
|
|
@@ -124,10 +124,6 @@ function detectColumnDrifts(diff, opts) {
|
|
|
124
124
|
* Add drifts for column alterations (type/nullable mismatches).
|
|
125
125
|
*/
|
|
126
126
|
function addAlterColumnDrifts(colDiff, drifts, opts) {
|
|
127
|
-
// Every check below compares the two sides, so there is nothing to report without both.
|
|
128
|
-
if (!colDiff.expected || !colDiff.actual) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
127
|
// An auto-increment key is created through the dialect's `serialPrimaryKey`, whose spelling the
|
|
132
128
|
// entity never states - `BIGINT UNSIGNED AUTO_INCREMENT` on the MySQL family, where the column then
|
|
133
129
|
// reads back as `BIGINT UNSIGNED` against an entity that can only say `BIGINT`. Comparing the two
|
|
@@ -206,7 +202,7 @@ function detectIndexDrifts(diff) {
|
|
|
206
202
|
suggestion: 'Add @Field({ index }) or create migration to drop',
|
|
207
203
|
});
|
|
208
204
|
}
|
|
209
|
-
else
|
|
205
|
+
else {
|
|
210
206
|
// No `expected`/`actual` here: the CLI prints those by interpolation, where an `IndexNode`
|
|
211
207
|
// renders as `[object Object]`. What differs is already spelled out in `description`.
|
|
212
208
|
drifts.push({
|
|
@@ -247,9 +243,26 @@ function detectRelationshipDrifts(diff) {
|
|
|
247
243
|
suggestion: 'Add relation to entity or drop FK',
|
|
248
244
|
});
|
|
249
245
|
}
|
|
246
|
+
else {
|
|
247
|
+
drifts.push({
|
|
248
|
+
type: 'relationship_mismatch',
|
|
249
|
+
severity: 'warning',
|
|
250
|
+
table: relDiff.fromTable,
|
|
251
|
+
relationship: relDiff.name,
|
|
252
|
+
expected: formatActions(relDiff.expected),
|
|
253
|
+
actual: formatActions(relDiff.actual),
|
|
254
|
+
details: `FK "${relDiff.name}" has other referential actions in the database than in the entity`,
|
|
255
|
+
suggestion: 'Generate a migration, which drops and re-adds the constraint',
|
|
256
|
+
});
|
|
257
|
+
}
|
|
250
258
|
}
|
|
251
259
|
return drifts;
|
|
252
260
|
}
|
|
261
|
+
/** Both actions spelled out, so a side that left one unstated reads the same as one that stated the default. */
|
|
262
|
+
function formatActions(rel) {
|
|
263
|
+
const { onDelete, onUpdate } = referentialActions(rel);
|
|
264
|
+
return `ON DELETE ${onDelete} ON UPDATE ${onUpdate}`;
|
|
265
|
+
}
|
|
253
266
|
/**
|
|
254
267
|
* Calculate overall status based on drifts.
|
|
255
268
|
*/
|
|
@@ -275,7 +288,7 @@ function createSummary(drifts) {
|
|
|
275
288
|
* Format type for display.
|
|
276
289
|
*/
|
|
277
290
|
function formatType(type, dialect) {
|
|
278
|
-
if (!
|
|
291
|
+
if (!dialect)
|
|
279
292
|
return 'unknown';
|
|
280
293
|
return canonicalToSql(type, dialect);
|
|
281
294
|
}
|
|
@@ -147,7 +147,7 @@ export class MongoSchemaGenerator extends AbstractDialect {
|
|
|
147
147
|
if (!currentTable) {
|
|
148
148
|
return { tableName: collectionName, type: 'create' };
|
|
149
149
|
}
|
|
150
|
-
const existingIndexes = new Set(currentTable.indexes
|
|
150
|
+
const existingIndexes = new Set(currentTable.indexes.map((i) => i.name));
|
|
151
151
|
const indexesToAdd = this.fieldIndexes(meta, collectionName).filter((index) => !existingIndexes.has(index.name));
|
|
152
152
|
if (indexesToAdd.length === 0) {
|
|
153
153
|
return undefined;
|
|
@@ -29,7 +29,7 @@ export declare abstract class BaseSqlIntrospector {
|
|
|
29
29
|
* Build SchemaAST from table schemas.
|
|
30
30
|
*/
|
|
31
31
|
protected buildAST(tableSchemas: TableSchema[]): SchemaAST;
|
|
32
|
-
private
|
|
32
|
+
private buildTable;
|
|
33
33
|
private buildRelationships;
|
|
34
34
|
private buildIndexes;
|
|
35
35
|
}
|
|
@@ -43,90 +43,79 @@ export class BaseSqlIntrospector {
|
|
|
43
43
|
*/
|
|
44
44
|
buildAST(tableSchemas) {
|
|
45
45
|
const ast = new SchemaAST();
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
// Every table first, so a foreign key reaches a table introspected after the one it leaves from.
|
|
47
|
+
const tables = tableSchemas.map((schema) => ({ schema, table: this.buildTable(schema) }));
|
|
48
|
+
const tableNodes = new Map(tables.map(({ schema, table }) => [schema.name, table]));
|
|
49
|
+
for (const { table } of tables) {
|
|
50
|
+
ast.addTable(table);
|
|
51
|
+
}
|
|
52
|
+
for (const { schema, table } of tables) {
|
|
53
|
+
this.buildRelationships(ast, tableNodes, schema, table);
|
|
54
|
+
this.buildIndexes(ast, schema, table);
|
|
55
|
+
}
|
|
50
56
|
return ast;
|
|
51
57
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
columns.set(col.name, column);
|
|
68
|
-
}
|
|
69
|
-
// From the ordered list the query returned, not from the per-column flags: `(a, b)` is a
|
|
70
|
-
// different key from `(b, a)`, and a flag says only that a column is *in* the key. Falls back
|
|
71
|
-
// to the flags for an introspector that reports no key of its own.
|
|
72
|
-
const keyColumns = schema.primaryKey ?? schema.columns.filter((col) => col.isPrimaryKey).map((col) => col.name);
|
|
73
|
-
table.primaryKey.push(...keyColumns.flatMap((name) => columns.get(name) ?? []));
|
|
74
|
-
table.primaryKeyName = schema.primaryKeyName;
|
|
75
|
-
tableNodes.set(schema.name, table);
|
|
76
|
-
ast.addTable(table);
|
|
58
|
+
buildTable(schema) {
|
|
59
|
+
const table = createTableNode(schema.name, this.schema);
|
|
60
|
+
const { columns } = table;
|
|
61
|
+
for (const col of schema.columns) {
|
|
62
|
+
// Spread, not field by field: a `ColumnSchema` is a `ColumnNode` minus the graph links, so
|
|
63
|
+
// everything but the type crosses unchanged and a field either shape gains cannot be dropped
|
|
64
|
+
// here. Listed by hand this had already lost `enum` and `generatedAs`.
|
|
65
|
+
const { type, length: _length, precision: _precision, scale: _scale, ...rest } = col;
|
|
66
|
+
const column = {
|
|
67
|
+
...rest,
|
|
68
|
+
type: canonicalColumnType(type, col),
|
|
69
|
+
table,
|
|
70
|
+
referencedBy: [],
|
|
71
|
+
};
|
|
72
|
+
columns.set(col.name, column);
|
|
77
73
|
}
|
|
74
|
+
// From the ordered list the query returned, not from the per-column flags: `(a, b)` is a
|
|
75
|
+
// different key from `(b, a)`, and a flag says only that a column is *in* the key. Falls back
|
|
76
|
+
// to the flags for an introspector that reports no key of its own.
|
|
77
|
+
const keyColumns = schema.primaryKey ?? schema.columns.filter((col) => col.isPrimaryKey).map((col) => col.name);
|
|
78
|
+
table.primaryKey.push(...keyColumns.flatMap((name) => columns.get(name) ?? []));
|
|
79
|
+
table.primaryKeyName = schema.primaryKeyName;
|
|
80
|
+
return table;
|
|
78
81
|
}
|
|
79
|
-
buildRelationships(ast, tableNodes,
|
|
80
|
-
for (const
|
|
81
|
-
|
|
82
|
+
buildRelationships(ast, tableNodes, schema, fromTable) {
|
|
83
|
+
for (const fk of schema.foreignKeys ?? []) {
|
|
84
|
+
const toTable = tableNodes.get(fk.references.table);
|
|
85
|
+
if (!toTable)
|
|
82
86
|
continue;
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
type: fromColumns[0].isUnique ? 'OneToOne' : 'ManyToOne',
|
|
96
|
-
from: { table: fromTable, columns: fromColumns },
|
|
97
|
-
to: { table: toTable, columns: toColumns },
|
|
98
|
-
onDelete: fk.onDelete || 'NO ACTION',
|
|
99
|
-
onUpdate: fk.onUpdate || 'NO ACTION',
|
|
100
|
-
};
|
|
101
|
-
ast.addRelationship(rel);
|
|
102
|
-
}
|
|
87
|
+
const fromColumns = fk.columns.flatMap((name) => fromTable.columns.get(name) ?? []);
|
|
88
|
+
const toColumns = fk.references.columns.flatMap((name) => toTable.columns.get(name) ?? []);
|
|
89
|
+
if (fromColumns.length > 0 && toColumns.length > 0) {
|
|
90
|
+
const rel = {
|
|
91
|
+
name: fk.name ?? derivedForeignKeyName(schema.name, fk.columns),
|
|
92
|
+
type: fromColumns[0].isUnique ? 'OneToOne' : 'ManyToOne',
|
|
93
|
+
from: { table: fromTable, columns: fromColumns },
|
|
94
|
+
to: { table: toTable, columns: toColumns },
|
|
95
|
+
onDelete: fk.onDelete || 'NO ACTION',
|
|
96
|
+
onUpdate: fk.onUpdate || 'NO ACTION',
|
|
97
|
+
};
|
|
98
|
+
ast.addRelationship(rel);
|
|
103
99
|
}
|
|
104
100
|
}
|
|
105
101
|
}
|
|
106
|
-
buildIndexes(ast,
|
|
107
|
-
for (const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
type: idx.type,
|
|
124
|
-
where: idx.where,
|
|
125
|
-
include: idx.include,
|
|
126
|
-
source: 'database',
|
|
127
|
-
};
|
|
128
|
-
ast.addIndex(index);
|
|
129
|
-
}
|
|
102
|
+
buildIndexes(ast, schema, table) {
|
|
103
|
+
for (const idx of schema.indexes ?? []) {
|
|
104
|
+
// An expression has no column to resolve. Dropping the entries that name a column this table
|
|
105
|
+
// does not have, and the index if that leaves none, is what the entity side does too.
|
|
106
|
+
const entries = idx.entries.filter((entry) => entry.expression || table.columns.has(entry.column));
|
|
107
|
+
if (entries.length > 0) {
|
|
108
|
+
const index = {
|
|
109
|
+
name: idx.name,
|
|
110
|
+
table,
|
|
111
|
+
entries,
|
|
112
|
+
unique: idx.unique,
|
|
113
|
+
type: idx.type,
|
|
114
|
+
where: idx.where,
|
|
115
|
+
include: idx.include,
|
|
116
|
+
source: 'database',
|
|
117
|
+
};
|
|
118
|
+
ast.addIndex(index);
|
|
130
119
|
}
|
|
131
120
|
}
|
|
132
121
|
}
|
|
@@ -27,7 +27,8 @@ export class MsSqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
27
27
|
`;
|
|
28
28
|
}
|
|
29
29
|
parseTableExistsResult(results) {
|
|
30
|
-
|
|
30
|
+
// No row, or no count in it, reads as `NaN`, which is not above zero.
|
|
31
|
+
return Number(results[0]?.count) > 0;
|
|
31
32
|
}
|
|
32
33
|
/**
|
|
33
34
|
* From `sys` rather than `INFORMATION_SCHEMA`, which has no identity flag and no per-column view of
|
|
@@ -24,11 +24,8 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
24
24
|
`;
|
|
25
25
|
}
|
|
26
26
|
parseTableExistsResult(results) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return (this.toNumber(row.count) ?? 0) > 0;
|
|
30
|
-
}
|
|
31
|
-
return false;
|
|
27
|
+
// No row, or no count in it, reads as `NaN`, which is not above zero.
|
|
28
|
+
return Number(results[0]?.count) > 0;
|
|
32
29
|
}
|
|
33
30
|
getColumnsQuery(_tableName) {
|
|
34
31
|
return /*sql*/ `
|
|
@@ -97,7 +94,7 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
97
94
|
async mapColumnsResult(_read, _tableName, results) {
|
|
98
95
|
return results.map((row) => ({
|
|
99
96
|
name: row.column_name,
|
|
100
|
-
type:
|
|
97
|
+
type: row.column_type.toUpperCase(),
|
|
101
98
|
nullable: row.is_nullable === 'YES',
|
|
102
99
|
defaultValue: this.parseDefaultValue(row.column_default),
|
|
103
100
|
isPrimaryKey: row.column_key === 'PRI',
|
|
@@ -122,8 +119,8 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
122
119
|
async mapForeignKeysResult(_read, _tableName, results) {
|
|
123
120
|
return results.map((row) => ({
|
|
124
121
|
name: row.constraint_name,
|
|
125
|
-
columns:
|
|
126
|
-
references: { table: row.referenced_table, columns:
|
|
122
|
+
columns: row.columns.split(','),
|
|
123
|
+
references: { table: row.referenced_table, columns: row.referenced_columns.split(',') },
|
|
127
124
|
onDelete: this.normalizeReferentialAction(row.delete_rule),
|
|
128
125
|
onUpdate: this.normalizeReferentialAction(row.update_rule),
|
|
129
126
|
}));
|
|
@@ -22,11 +22,8 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
22
22
|
`;
|
|
23
23
|
}
|
|
24
24
|
parseTableExistsResult(results) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return (this.toNumber(row.count) ?? 0) > 0;
|
|
28
|
-
}
|
|
29
|
-
return false;
|
|
25
|
+
// No row, or no count in it, reads as `NaN`, which is not above zero.
|
|
26
|
+
return Number(results[0]?.count) > 0;
|
|
30
27
|
}
|
|
31
28
|
/**
|
|
32
29
|
* `table_xinfo`, not `table_info`: the latter omits generated columns entirely, so a table carrying
|
|
@@ -19,8 +19,11 @@ export declare class Migrator {
|
|
|
19
19
|
private readonly _defaultForeignKeyAction?;
|
|
20
20
|
private _mongoSchemaLoadPromise?;
|
|
21
21
|
constructor(pool: QuerierPool, options?: MigratorOptions);
|
|
22
|
-
/**
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Loads MongoDB's schema generator on first use, so the optional `mongodb` peer loads only then. SQL
|
|
24
|
+
* generators are set in the constructor (or via {@link setSchemaGenerator}).
|
|
25
|
+
*/
|
|
26
|
+
ensureSchemaGenerator(): Promise<void>;
|
|
24
27
|
/**
|
|
25
28
|
* Set the schema generator for DDL operations
|
|
26
29
|
*/
|
package/dist/migrate/migrator.js
CHANGED
|
@@ -10,7 +10,6 @@ import { buildSqlQuerierMigrationModule, EMPTY_MANUAL_MIGRATION_DOWN_INNER, EMPT
|
|
|
10
10
|
import { runMongoCommand } from './generator/mongoCommand.js';
|
|
11
11
|
import { introspectorFor } from './introspection/registry.js';
|
|
12
12
|
import { createSchemaGenerator } from './schemaGenerator.js';
|
|
13
|
-
import { createSchemaGeneratorAsync } from './schemaGeneratorAsync.js';
|
|
14
13
|
import { DatabaseMigrationStorage } from './storage/databaseStorage.js';
|
|
15
14
|
/**
|
|
16
15
|
* Main class for managing database migrations
|
|
@@ -37,7 +36,7 @@ export class Migrator {
|
|
|
37
36
|
_mongoSchemaLoadPromise;
|
|
38
37
|
constructor(pool, options = {}) {
|
|
39
38
|
this.pool = pool;
|
|
40
|
-
this.dialectName = pool.dialect.dialectName
|
|
39
|
+
this.dialectName = pool.dialect.dialectName;
|
|
41
40
|
this._defaultForeignKeyAction = options.defaultForeignKeyAction;
|
|
42
41
|
this.storage =
|
|
43
42
|
options.storage ??
|
|
@@ -51,18 +50,17 @@ export class Migrator {
|
|
|
51
50
|
this.schemaGenerator =
|
|
52
51
|
options.schemaGenerator ?? (this.dialectName === 'mongodb' ? undefined : this.createGenerator());
|
|
53
52
|
}
|
|
54
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Loads MongoDB's schema generator on first use, so the optional `mongodb` peer loads only then. SQL
|
|
55
|
+
* generators are set in the constructor (or via {@link setSchemaGenerator}).
|
|
56
|
+
*/
|
|
55
57
|
async ensureSchemaGenerator() {
|
|
56
58
|
if (this.schemaGenerator || this.dialectName !== 'mongodb') {
|
|
57
59
|
return;
|
|
58
60
|
}
|
|
59
|
-
|
|
60
|
-
this.
|
|
61
|
-
|
|
62
|
-
this.schemaGenerator = gen;
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
}
|
|
61
|
+
this._mongoSchemaLoadPromise ??= import('./generator/mongoSchemaGenerator.js').then(({ MongoSchemaGenerator }) => {
|
|
62
|
+
this.schemaGenerator = new MongoSchemaGenerator(this.pool.dialect.namingStrategy, this._defaultForeignKeyAction);
|
|
63
|
+
});
|
|
66
64
|
await this._mongoSchemaLoadPromise;
|
|
67
65
|
}
|
|
68
66
|
/**
|
|
@@ -302,12 +300,9 @@ export class Migrator {
|
|
|
302
300
|
}
|
|
303
301
|
async findEntityForTable(tableName) {
|
|
304
302
|
await this.ensureSchemaGenerator();
|
|
305
|
-
if (!this.schemaGenerator) {
|
|
306
|
-
return undefined;
|
|
307
|
-
}
|
|
308
303
|
for (const entity of this.entities) {
|
|
309
304
|
const meta = getMeta(entity);
|
|
310
|
-
const name = this.
|
|
305
|
+
const name = this.generator.resolveTableName(meta);
|
|
311
306
|
if (name === tableName) {
|
|
312
307
|
return entity;
|
|
313
308
|
}
|
|
@@ -474,8 +474,6 @@ export class SqlSchemaGenerator {
|
|
|
474
474
|
if (current == null || desired == null)
|
|
475
475
|
return current == null && desired == null;
|
|
476
476
|
const normalize = (value) => {
|
|
477
|
-
if (value === null)
|
|
478
|
-
return 'null';
|
|
479
477
|
// Render first: the desired side may be a symbolic expression, the current side is always the
|
|
480
478
|
// engine's own text, and `{"kind":"now"}` matches no spelling of `CURRENT_TIMESTAMP`.
|
|
481
479
|
const val = SqlExpression.isExpression(value) ? formatDefaultValue(value, this.dialect) : value;
|
|
@@ -518,10 +516,8 @@ export class SqlSchemaGenerator {
|
|
|
518
516
|
constraints.push(`CONSTRAINT ${this.escapeId(name)} CHECK (${check.expression})`);
|
|
519
517
|
});
|
|
520
518
|
for (const rel of table.outgoingRelations) {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
constraints.push(this.foreignKeyConstraint(table.name, foreignKeyOf(rel), refTable));
|
|
524
|
-
}
|
|
519
|
+
const refTable = this.dialect.escapeQualifiedId(rel.to.table.name, rel.to.table.schema);
|
|
520
|
+
constraints.push(this.foreignKeyConstraint(table.name, foreignKeyOf(rel), refTable));
|
|
525
521
|
}
|
|
526
522
|
const ifNotExists = options.ifNotExists && this.features.ifNotExists ? 'IF NOT EXISTS ' : '';
|
|
527
523
|
let createSql = `CREATE TABLE ${ifNotExists}${this.dialect.escapeQualifiedId(table.name, table.schema)} (\n`;
|
package/dist/mongo/index.d.ts
CHANGED
package/dist/mongo/index.js
CHANGED
|
@@ -20,7 +20,6 @@ export declare class MongoDialect extends AbstractDialect {
|
|
|
20
20
|
private static readonly ID_KEY;
|
|
21
21
|
/** Atlas rejects a `$vectorSearch` asking for more candidates than this. */
|
|
22
22
|
private static readonly MAX_NUM_CANDIDATES;
|
|
23
|
-
private static readonly AGGREGATE_OP_MAP;
|
|
24
23
|
/**
|
|
25
24
|
* MongoDB stores the primary key as `_id`; everything else resolves as usual. Projections, sorts,
|
|
26
25
|
* `$group` refs and `$where` keys all map through here, so no read path can address a property name
|
|
@@ -188,7 +187,7 @@ export declare class MongoDialect extends AbstractDialect {
|
|
|
188
187
|
/** `[column, key]` for the fields whose stored name differs from their property name, memoized per entity. */
|
|
189
188
|
private renamedColumns;
|
|
190
189
|
private referenceKeys;
|
|
191
|
-
normalizeIds<E extends Document>(meta: EntityMeta<E>, docs: Document[]
|
|
190
|
+
normalizeIds<E extends Document>(meta: EntityMeta<E>, docs: Document[]): E[];
|
|
192
191
|
/** `doc` is the wire shape - `_id`, stored names, `ObjectId`s - and what comes back is the code's. */
|
|
193
192
|
normalizeId<E extends Document>(meta: EntityMeta<E>, doc: Document | undefined): E | undefined;
|
|
194
193
|
/**
|