uql-orm 0.52.0 → 0.54.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/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +5 -5
- package/dist/cockroachdb/cockroachDialect.d.ts +2 -5
- package/dist/cockroachdb/cockroachDialect.js +2 -5
- package/dist/dialect/abstractDialect.d.ts +5 -5
- package/dist/dialect/abstractDialect.js +7 -6
- package/dist/dialect/abstractSqlDialect.d.ts +8 -3
- package/dist/dialect/abstractSqlDialect.js +14 -11
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +0 -4
- package/dist/dialect/mysqlLikeSqlDialect.js +0 -6
- package/dist/dialect/pgLikeSqlDialect.js +0 -2
- package/dist/dialect/vectorSqlDialect.js +2 -1
- package/dist/entity/decorator/members.d.ts +3 -10
- package/dist/entity/metadata/definition.js +0 -4
- package/dist/http/handler.js +3 -12
- package/dist/http/query.js +4 -1
- package/dist/migrate/builder/migrationBuilder.js +0 -4
- package/dist/migrate/ddl/index.d.ts +8 -0
- package/dist/migrate/ddl/index.js +11 -0
- package/dist/migrate/ddl/mssqlTableDdl.d.ts +23 -0
- package/dist/migrate/ddl/mssqlTableDdl.js +61 -0
- package/dist/migrate/ddl/tableDdl.d.ts +34 -0
- package/dist/migrate/ddl/tableDdl.js +71 -0
- package/dist/migrate/introspection/mssqlIntrospector.d.ts +6 -9
- package/dist/migrate/introspection/mssqlIntrospector.js +40 -33
- package/dist/migrate/migrator.d.ts +3 -7
- package/dist/migrate/migrator.js +3 -7
- package/dist/migrate/schemaGenerator.d.ts +8 -23
- package/dist/migrate/schemaGenerator.js +29 -87
- package/dist/migrate/storage/databaseStorage.d.ts +2 -2
- package/dist/migrate/storage/databaseStorage.js +2 -2
- package/dist/mongo/mongoDialect.js +9 -14
- package/dist/mongo/mongodbQuerier.d.ts +3 -5
- package/dist/mongo/mongodbQuerier.js +18 -19
- package/dist/mssql/mssqlDialect.d.ts +14 -1
- package/dist/mssql/mssqlDialect.js +22 -7
- package/dist/querier/abstractQuerier.d.ts +17 -22
- package/dist/querier/abstractQuerier.js +92 -67
- package/dist/querier/abstractSqlQuerier.d.ts +9 -9
- package/dist/querier/abstractSqlQuerier.js +71 -87
- package/dist/schema/canonicalType.js +2 -2
- package/dist/schema/schemaASTBuilder.js +7 -7
- package/dist/sqlite/sqliteDialect.js +0 -2
- package/dist/type/dialect.d.ts +0 -8
- package/dist/type/entity.d.ts +6 -11
- package/dist/type/migration.d.ts +0 -3
- package/dist/type/query.d.ts +13 -13
- package/dist/type/query.js +0 -6
- package/dist/type/queryWhere.d.ts +7 -19
- package/dist/type/universalQuerier.d.ts +3 -3
- package/dist/type/vector.d.ts +3 -1
- package/dist/util/dialect.util.d.ts +12 -8
- package/dist/util/dialect.util.js +17 -29
- package/dist/util/field.util.d.ts +4 -16
- package/dist/util/field.util.js +6 -19
- package/dist/util/fieldOption.util.d.ts +1 -4
- package/dist/util/fieldOption.util.js +0 -2
- package/dist/util/logger.d.ts +3 -3
- package/dist/util/logger.js +3 -0
- package/dist/util/object.util.d.ts +2 -0
- package/dist/util/object.util.js +4 -0
- package/dist/util/raw.d.ts +3 -10
- package/dist/util/sql.util.js +2 -2
- package/package.json +2 -2
|
@@ -4,10 +4,11 @@ import { areTypesEqual, canonicalToSql, engineType, fieldOptionsToCanonical, isV
|
|
|
4
4
|
import { indexSignature } from '../schema/indexDifferences.js';
|
|
5
5
|
import { buildSchemaAST } from '../schema/schemaASTBuilder.js';
|
|
6
6
|
import { diffRelationshipNodes, diffTable } from '../schema/schemaASTDiffer.js';
|
|
7
|
-
import {
|
|
7
|
+
import { isAutoIncrement, qualifyName } from '../util/index.js';
|
|
8
8
|
import { derivedCheckName, derivedForeignKeyName, derivedPrimaryKeyName } from '../util/sql.util.js';
|
|
9
9
|
import { formatDefaultValue, SqlExpression } from './builder/expressions.js';
|
|
10
|
-
import { indexDdlFor } from './ddl/index.js';
|
|
10
|
+
import { indexDdlFor, tableDdlFor } from './ddl/index.js';
|
|
11
|
+
import { sizedType } from './ddl/tableDdl.js';
|
|
11
12
|
import { columnForeignKey, columnIndex, fullColumnDefinitionToNode, tableDefinitionToNode, } from './generator/definitionToNode.js';
|
|
12
13
|
import { indexNodeToSchema } from './generator/indexNodeToSchema.js';
|
|
13
14
|
/**
|
|
@@ -19,10 +20,13 @@ export class SqlSchemaGenerator {
|
|
|
19
20
|
defaultForeignKeyAction;
|
|
20
21
|
/** `CREATE INDEX` for this dialect: the migrator's, so a runtime import carries none of it. */
|
|
21
22
|
indexDdl;
|
|
23
|
+
/** The `ALTER TABLE` statements this dialect spells its own way, the migrator's for the same reason. */
|
|
24
|
+
tableDdl;
|
|
22
25
|
constructor(dialect, defaultForeignKeyAction = 'NO ACTION') {
|
|
23
26
|
this.dialect = dialect;
|
|
24
27
|
this.defaultForeignKeyAction = defaultForeignKeyAction;
|
|
25
28
|
this.indexDdl = indexDdlFor(dialect);
|
|
29
|
+
this.tableDdl = tableDdlFor(dialect);
|
|
26
30
|
}
|
|
27
31
|
get namingStrategy() {
|
|
28
32
|
return this.dialect.namingStrategy;
|
|
@@ -46,9 +50,6 @@ export class SqlSchemaGenerator {
|
|
|
46
50
|
escapeId(identifier) {
|
|
47
51
|
return this.dialect.escapeId(identifier);
|
|
48
52
|
}
|
|
49
|
-
/**
|
|
50
|
-
* Primary key type for auto-increment integer IDs
|
|
51
|
-
*/
|
|
52
53
|
/**
|
|
53
54
|
* How an auto-increment key of `type` is spelled: the type as any other column renders it, plus what
|
|
54
55
|
* the engine appends to make it generated.
|
|
@@ -138,7 +139,6 @@ export class SqlSchemaGenerator {
|
|
|
138
139
|
}
|
|
139
140
|
generateAlterTable(diff) {
|
|
140
141
|
const statements = [];
|
|
141
|
-
const tableName = this.escapeId(diff.tableName);
|
|
142
142
|
// Before the columns, because a key column being added cannot be part of the old key, and after
|
|
143
143
|
// it is dropped the table is free to take the new one below.
|
|
144
144
|
if (diff.primaryKey?.from.length) {
|
|
@@ -154,7 +154,7 @@ export class SqlSchemaGenerator {
|
|
|
154
154
|
if (diff.columnsToAdd?.length) {
|
|
155
155
|
for (const column of diff.columnsToAdd) {
|
|
156
156
|
this.assertColumnAddable(diff.tableName, column);
|
|
157
|
-
statements.push(
|
|
157
|
+
statements.push(this.tableDdl.addColumn(diff.tableName, this.generateColumnDefinitionFromSchema(column)));
|
|
158
158
|
statements.push(...this.generateColumnCommentStatement(diff.tableName, column, diff.schema));
|
|
159
159
|
}
|
|
160
160
|
}
|
|
@@ -169,7 +169,7 @@ export class SqlSchemaGenerator {
|
|
|
169
169
|
// Drop columns
|
|
170
170
|
if (diff.columnsToDrop?.length) {
|
|
171
171
|
for (const columnName of diff.columnsToDrop) {
|
|
172
|
-
statements.push(
|
|
172
|
+
statements.push(...this.tableDdl.dropColumn(diff.tableName, columnName));
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
// Add indexes
|
|
@@ -206,7 +206,6 @@ export class SqlSchemaGenerator {
|
|
|
206
206
|
}
|
|
207
207
|
generateAlterTableDown(diff) {
|
|
208
208
|
const statements = [];
|
|
209
|
-
const tableName = this.escapeId(diff.tableName);
|
|
210
209
|
// Constraints first, mirroring the up direction: the up added them last, so the down drops them
|
|
211
210
|
// first, and a column it is about to drop is then free of anything naming it.
|
|
212
211
|
statements.push(...this.dropForeignKeyStatements(diff.tableName, [
|
|
@@ -222,7 +221,7 @@ export class SqlSchemaGenerator {
|
|
|
222
221
|
// Reverse column additions by dropping them
|
|
223
222
|
if (diff.columnsToAdd?.length) {
|
|
224
223
|
for (const column of diff.columnsToAdd) {
|
|
225
|
-
statements.push(
|
|
224
|
+
statements.push(...this.tableDdl.dropColumn(diff.tableName, column.name));
|
|
226
225
|
}
|
|
227
226
|
}
|
|
228
227
|
// Reverse column alterations by restoring original schema
|
|
@@ -273,53 +272,35 @@ export class SqlSchemaGenerator {
|
|
|
273
272
|
* {@link renderColumn}, which is the part that must not be written twice.
|
|
274
273
|
*/
|
|
275
274
|
generateColumnDefinitionFromSchema(column) {
|
|
276
|
-
|
|
277
|
-
if (!type.includes('(')) {
|
|
278
|
-
if (column.precision !== undefined) {
|
|
279
|
-
type += column.scale === undefined ? `(${column.precision})` : `(${column.precision}, ${column.scale})`;
|
|
280
|
-
}
|
|
281
|
-
else if (column.length !== undefined) {
|
|
282
|
-
type += `(${column.length})`;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
return this.renderColumn({ ...column, type });
|
|
275
|
+
return this.renderColumn({ ...column, type: sizedType(column) });
|
|
286
276
|
}
|
|
287
277
|
/**
|
|
288
|
-
* The one place a column definition is spelled
|
|
289
|
-
*
|
|
290
|
-
*
|
|
278
|
+
* The one place a column definition is spelled, so the `ColumnSchema` and `ColumnNode` paths cannot
|
|
279
|
+
* drift. A key column states `NOT NULL` rather than leave it to the key: SQLite lets a key column hold
|
|
280
|
+
* NULL otherwise, and SQL Server adds no key over a nullable column. `UNIQUE` is left to the key. An
|
|
281
|
+
* enum's `CHECK` comes last, the only place MariaDB takes it.
|
|
291
282
|
*/
|
|
292
283
|
renderColumn(column) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (column.
|
|
298
|
-
def += ` GENERATED ALWAYS AS (${column.generatedAs}) STORED`;
|
|
299
|
-
}
|
|
300
|
-
if (!column.nullable && !column.isPrimaryKey) {
|
|
284
|
+
const type = column.generatedAs
|
|
285
|
+
? this.tableDdl.storedGeneratedColumn(column.type, column.generatedAs)
|
|
286
|
+
: column.type;
|
|
287
|
+
let def = `${this.escapeId(column.name)} ${type}`;
|
|
288
|
+
if (!column.nullable) {
|
|
301
289
|
def += ' NOT NULL';
|
|
302
290
|
}
|
|
303
291
|
if (column.isUnique && !column.isPrimaryKey) {
|
|
304
292
|
def += ' UNIQUE';
|
|
305
293
|
}
|
|
294
|
+
def += this.tableDdl.defaultClause(column);
|
|
295
|
+
if (column.comment) {
|
|
296
|
+
def += this.generateColumnComment(column.comment);
|
|
297
|
+
}
|
|
306
298
|
if (column.enum?.length) {
|
|
307
299
|
const values = column.enum.map((value) => this.dialect.escape(value)).join(', ');
|
|
308
300
|
def += ` CHECK (${this.escapeId(column.name)} IN (${values}))`;
|
|
309
301
|
}
|
|
310
|
-
def += this.defaultClause(column);
|
|
311
|
-
if (column.comment) {
|
|
312
|
-
def += this.generateColumnComment(column.comment);
|
|
313
|
-
}
|
|
314
302
|
return def;
|
|
315
303
|
}
|
|
316
|
-
/** ` DEFAULT <sql>`, or nothing where the column declares none. Empty rather than `DEFAULT NULL`
|
|
317
|
-
* so an absent default stays absent - `defaultValue: null` is the way to ask for one. */
|
|
318
|
-
defaultClause(column) {
|
|
319
|
-
return column.defaultValue === undefined
|
|
320
|
-
? ''
|
|
321
|
-
: ` DEFAULT ${formatDefaultValue(column.defaultValue, this.dialect, column.type)}`;
|
|
322
|
-
}
|
|
323
304
|
getSqlType(field) {
|
|
324
305
|
// A foreign key takes the type of the key it points at. A `referencedKey` the target does not
|
|
325
306
|
// have falls through to this column's own options, as the AST builder does with the same case.
|
|
@@ -338,35 +319,9 @@ export class SqlSchemaGenerator {
|
|
|
338
319
|
}
|
|
339
320
|
return this.canonicalTypeToSql(canonical);
|
|
340
321
|
}
|
|
341
|
-
/**
|
|
342
|
-
* Generate ALTER COLUMN statements (database-specific)
|
|
343
|
-
*/
|
|
322
|
+
/** The statements that alter `column` in place, as this dialect spells them. */
|
|
344
323
|
generateAlterColumnStatements(tableName, column, newDefinition) {
|
|
345
|
-
|
|
346
|
-
const colName = this.escapeId(column.name);
|
|
347
|
-
if (this.dialect.alterColumnSyntax === 'none') {
|
|
348
|
-
throw new TypeError(`${this.dialect}: Cannot alter column "${column.name}" - you must recreate the table. ` +
|
|
349
|
-
`This database does not support ALTER COLUMN.`);
|
|
350
|
-
}
|
|
351
|
-
if (this.dialect.alterColumnStrategy === 'separate-clauses') {
|
|
352
|
-
const statements = [];
|
|
353
|
-
// Separate ALTER COLUMN clauses for different changes (Postgres)
|
|
354
|
-
statements.push(`ALTER TABLE ${table} ALTER COLUMN ${colName} TYPE ${column.type};`);
|
|
355
|
-
if (column.nullable) {
|
|
356
|
-
statements.push(`ALTER TABLE ${table} ALTER COLUMN ${colName} DROP NOT NULL;`);
|
|
357
|
-
}
|
|
358
|
-
else {
|
|
359
|
-
statements.push(`ALTER TABLE ${table} ALTER COLUMN ${colName} SET NOT NULL;`);
|
|
360
|
-
}
|
|
361
|
-
if (column.defaultValue !== undefined) {
|
|
362
|
-
statements.push(`ALTER TABLE ${table} ALTER COLUMN ${colName} SET${this.defaultClause(column)};`);
|
|
363
|
-
}
|
|
364
|
-
else {
|
|
365
|
-
statements.push(`ALTER TABLE ${table} ALTER COLUMN ${colName} DROP DEFAULT;`);
|
|
366
|
-
}
|
|
367
|
-
return statements;
|
|
368
|
-
}
|
|
369
|
-
return [`ALTER TABLE ${table} ${this.dialect.alterColumnSyntax} ${newDefinition};`];
|
|
324
|
+
return this.tableDdl.alterColumn(tableName, column, newDefinition);
|
|
370
325
|
}
|
|
371
326
|
/** The inline ` COMMENT '...'` a column declaration carries, where the engine takes one there. */
|
|
372
327
|
generateColumnComment(comment) {
|
|
@@ -403,9 +358,6 @@ export class SqlSchemaGenerator {
|
|
|
403
358
|
const tableRef = this.dialect.escapeQualifiedId(tableName, schema);
|
|
404
359
|
return [`COMMENT ON COLUMN ${tableRef}.${this.escapeId(column.name)} IS ${this.dialect.escape(column.comment)};`];
|
|
405
360
|
}
|
|
406
|
-
/**
|
|
407
|
-
* Compare an entity with a database table node and return the differences.
|
|
408
|
-
*/
|
|
409
361
|
/**
|
|
410
362
|
* How this entity differs from the table the database reported, as the migrator's `SchemaDiff`.
|
|
411
363
|
*
|
|
@@ -489,13 +441,6 @@ export class SqlSchemaGenerator {
|
|
|
489
441
|
foreignKeysToAlter: foreignKeysToAlter.length ? foreignKeysToAlter : undefined,
|
|
490
442
|
};
|
|
491
443
|
}
|
|
492
|
-
/**
|
|
493
|
-
* What the shared differ needs from a dialect: a type as this engine would actually store it.
|
|
494
|
-
*
|
|
495
|
-
* `boolean` is `TINYINT(1)` on MySQL and `INTEGER` on SQLite, so two canonical types that differ on
|
|
496
|
-
* paper can be one column in the database. Round-tripping through the engine's own spelling is what
|
|
497
|
-
* stops every such column reporting an alteration on every sync.
|
|
498
|
-
*/
|
|
499
444
|
/**
|
|
500
445
|
* Indexes the entity declares that the table does not already have, in any shape.
|
|
501
446
|
*
|
|
@@ -627,10 +572,7 @@ export class SqlSchemaGenerator {
|
|
|
627
572
|
return this.generateCreateTableFromNode(tableNode, options);
|
|
628
573
|
}
|
|
629
574
|
generateRenameTableSql(oldName, newName) {
|
|
630
|
-
|
|
631
|
-
return `RENAME TABLE ${this.escapeId(oldName)} TO ${this.escapeId(newName)};`;
|
|
632
|
-
}
|
|
633
|
-
return `ALTER TABLE ${this.escapeId(oldName)} RENAME TO ${this.escapeId(newName)};`;
|
|
575
|
+
return this.tableDdl.renameTable(oldName, newName);
|
|
634
576
|
}
|
|
635
577
|
/**
|
|
636
578
|
* `ADD COLUMN`, plus the constraint and index the column declares.
|
|
@@ -643,7 +585,7 @@ export class SqlSchemaGenerator {
|
|
|
643
585
|
generateAddColumnSql(tableName, column) {
|
|
644
586
|
this.assertColumnAddable(tableName, column);
|
|
645
587
|
const colSql = this.generateColumnFromNode(fullColumnDefinitionToNode(column, tableName));
|
|
646
|
-
const statements = [
|
|
588
|
+
const statements = [this.tableDdl.addColumn(tableName, colSql)];
|
|
647
589
|
const foreignKey = columnForeignKey(column);
|
|
648
590
|
if (foreignKey) {
|
|
649
591
|
statements.push(...this.addForeignKeyStatements(tableName, [foreignKey]));
|
|
@@ -660,10 +602,10 @@ export class SqlSchemaGenerator {
|
|
|
660
602
|
return this.generateAlterColumnStatements(tableName, { ...this.columnNodeToSchema(node), name: columnName }, this.generateColumnFromNode(node)).join('\n');
|
|
661
603
|
}
|
|
662
604
|
generateDropColumnSql(tableName, columnName) {
|
|
663
|
-
return
|
|
605
|
+
return this.tableDdl.dropColumn(tableName, columnName).join('\n');
|
|
664
606
|
}
|
|
665
607
|
generateRenameColumnSql(tableName, oldName, newName) {
|
|
666
|
-
return
|
|
608
|
+
return this.tableDdl.renameColumn(tableName, oldName, newName);
|
|
667
609
|
}
|
|
668
610
|
/**
|
|
669
611
|
* `CONSTRAINT <name> FOREIGN KEY (...) REFERENCES ... ON DELETE ... ON UPDATE ...`.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { MigrationStorage, QuerierPool, SqlQuerier } from '../../type/index.js';
|
|
2
|
+
/** Where executed migrations are recorded when the config does not name a table. */
|
|
3
|
+
export declare const DEFAULT_MIGRATIONS_TABLE = "uql_migrations";
|
|
2
4
|
/**
|
|
3
5
|
* Stores migration state in a database table.
|
|
4
6
|
* Uses the querier's dialect for escaping and placeholders.
|
|
5
7
|
*/
|
|
6
|
-
/** Where executed migrations are recorded when the config does not name a table. */
|
|
7
|
-
export declare const DEFAULT_MIGRATIONS_TABLE = "uql_migrations";
|
|
8
8
|
export declare class DatabaseMigrationStorage implements MigrationStorage {
|
|
9
9
|
private readonly pool;
|
|
10
10
|
private readonly tableName;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { withSqlQuerierForMigrations } from '../acquireQuerierForMigrations.js';
|
|
2
|
+
/** Where executed migrations are recorded when the config does not name a table. */
|
|
3
|
+
export const DEFAULT_MIGRATIONS_TABLE = 'uql_migrations';
|
|
2
4
|
/**
|
|
3
5
|
* Stores migration state in a database table.
|
|
4
6
|
* Uses the querier's dialect for escaping and placeholders.
|
|
5
7
|
*/
|
|
6
|
-
/** Where executed migrations are recorded when the config does not name a table. */
|
|
7
|
-
export const DEFAULT_MIGRATIONS_TABLE = 'uql_migrations';
|
|
8
8
|
export class DatabaseMigrationStorage {
|
|
9
9
|
pool;
|
|
10
10
|
tableName;
|
|
@@ -4,7 +4,7 @@ import { COUNT_ALIAS, REL_NESTED_KEY, REL_TEMP_PREFIX, sortCountField } from '..
|
|
|
4
4
|
import { resolveQueryJoins, resolveSortableJoin } from '../dialect/queryJoins.js';
|
|
5
5
|
import { assertSoleId, getMeta, soleIdOf } from '../entity/index.js';
|
|
6
6
|
import { QueryRaw } from '../type/queryRaw.js';
|
|
7
|
-
import { asSelectMap, assertAggregateColumns, assertNonNegativeInteger,
|
|
7
|
+
import { asSelectMap, assertAggregateColumns, assertNonNegativeInteger, columnFamily, entityName, fillOnFields, filterFieldKeys, findVectorIndex, findVectorSort, getKeys, getRelationRequestSummary, hasKeys, isJsonUpdateOp, isOperatorMap, isOperatorObject, isVectorSearch, normalizeScalarFieldSelection, parentJoins, parseGroupMap, parseRelationSize, parseSortByCount, someKey, targetKeyColumns, } from '../util/index.js';
|
|
8
8
|
/** Default {@link DialectFeatures} for MongoDB; shared by {@link MongoDialect} and its schema generator. */
|
|
9
9
|
export const mongoDialectFeatures = {
|
|
10
10
|
explicitJsonCast: false,
|
|
@@ -14,7 +14,6 @@ export const mongoDialectFeatures = {
|
|
|
14
14
|
indexIfNotExists: false,
|
|
15
15
|
schemas: false, // the connection picks the database, and a collection name takes no dot
|
|
16
16
|
dropTableCascade: false,
|
|
17
|
-
renameColumn: false,
|
|
18
17
|
foreignKeyAlter: false,
|
|
19
18
|
primaryKeyAlter: false,
|
|
20
19
|
generatedColumnAdd: false,
|
|
@@ -24,7 +23,6 @@ export const mongoDialectFeatures = {
|
|
|
24
23
|
supportsTimestamptz: false,
|
|
25
24
|
stringSizing: 'bounded-text',
|
|
26
25
|
supportsUnsigned: false,
|
|
27
|
-
multipleCascadePaths: true,
|
|
28
26
|
serverSideCursors: false,
|
|
29
27
|
};
|
|
30
28
|
/** What `toWireId` converts: the hex spelling of an `ObjectId`, and nothing looser. */
|
|
@@ -70,7 +68,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
70
68
|
where(entity, where = {}, opts = {}) {
|
|
71
69
|
const meta = getMeta(entity);
|
|
72
70
|
// Filters are applied once, here at the scope entry point; recursion uses `renderFilter`.
|
|
73
|
-
return this.renderFilter(entity, this.
|
|
71
|
+
return this.renderFilter(entity, this.scopedWhere(meta, where, opts));
|
|
74
72
|
}
|
|
75
73
|
/**
|
|
76
74
|
* A `$where` that may constrain relations, split into the `$lookup` stages it needs and the `$match`
|
|
@@ -82,7 +80,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
82
80
|
whereWithRelations(entity, where = {}, opts = {}) {
|
|
83
81
|
const meta = getMeta(entity);
|
|
84
82
|
const lookups = { stages: [], temps: [] };
|
|
85
|
-
const filter = this.renderFilter(entity, this.
|
|
83
|
+
const filter = this.renderFilter(entity, this.scopedWhere(meta, where, opts), opts, lookups);
|
|
86
84
|
return { stages: lookups.stages, filter, unset: lookups.temps };
|
|
87
85
|
}
|
|
88
86
|
/** Whether a `$where` constrains any relation, and so needs the aggregation path rather than a cursor. */
|
|
@@ -91,9 +89,9 @@ export class MongoDialect extends AbstractDialect {
|
|
|
91
89
|
return false;
|
|
92
90
|
}
|
|
93
91
|
const meta = getMeta(entity);
|
|
94
|
-
const whereMap =
|
|
92
|
+
const whereMap = where;
|
|
95
93
|
return someKey(whereMap, (key) => MongoDialect.isGroupOp(key)
|
|
96
|
-
? (whereMap[key] ?? []).some((it) => this.constrainsRelations(entity, it))
|
|
94
|
+
? (whereMap[key] ?? []).some((it) => !(it instanceof QueryRaw) && this.constrainsRelations(entity, it))
|
|
97
95
|
: Boolean(meta.relations[key]));
|
|
98
96
|
}
|
|
99
97
|
/**
|
|
@@ -103,9 +101,8 @@ export class MongoDialect extends AbstractDialect {
|
|
|
103
101
|
*/
|
|
104
102
|
renderFilter(entity, where = {}, opts, lookups) {
|
|
105
103
|
const meta = getMeta(entity);
|
|
106
|
-
const whereMap = buildQueryWhereAsMap(meta, where);
|
|
107
104
|
const filter = {};
|
|
108
|
-
for (const [rawKey, rawVal] of Object.entries(
|
|
105
|
+
for (const [rawKey, rawVal] of Object.entries(where)) {
|
|
109
106
|
let key = rawKey;
|
|
110
107
|
let val = rawVal;
|
|
111
108
|
if (MongoDialect.isGroupOp(key)) {
|
|
@@ -155,8 +152,6 @@ export class MongoDialect extends AbstractDialect {
|
|
|
155
152
|
const { join, negate } = MongoDialect.GROUP_OPS[key];
|
|
156
153
|
const parts = MongoDialect.groupClauses(key, val)
|
|
157
154
|
.map((filterIt) => {
|
|
158
|
-
// A `QueryRaw` here would recurse forever: `buildQueryWhereAsMap` re-wraps it as
|
|
159
|
-
// `{ $and: [raw] }`, which lands back on this branch.
|
|
160
155
|
this.assertNoRaw(filterIt);
|
|
161
156
|
return this.renderFilter(entity, filterIt, opts, lookups);
|
|
162
157
|
})
|
|
@@ -190,7 +185,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
190
185
|
// `withDeleted()` or `hardDelete` on the parent must not un-hide trashed rows of the target, the
|
|
191
186
|
// same rule the SQL dialects' relation subqueries follow.
|
|
192
187
|
const targetCondition = (sizeVal === undefined ? val : {});
|
|
193
|
-
const targetScope = this.renderFilter(relEntity, this.
|
|
188
|
+
const targetScope = this.renderFilter(relEntity, this.scopedWhere(relMeta, targetCondition), opts);
|
|
194
189
|
lookups.temps.push(temp);
|
|
195
190
|
lookups.stages.push(this.relationLookup(meta, relOpts, relMeta, relEntity, targetScope, temp, tail, opts));
|
|
196
191
|
return sizeVal === undefined
|
|
@@ -221,7 +216,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
221
216
|
junctionLookup(meta, relOpts, relMeta, targetScope, temp, tail, opts) {
|
|
222
217
|
const throughEntity = relOpts.through();
|
|
223
218
|
const throughMeta = getMeta(throughEntity);
|
|
224
|
-
const junctionScope = this.renderFilter(throughEntity, this.
|
|
219
|
+
const junctionScope = this.renderFilter(throughEntity, this.scopedWhere(throughMeta, {}), opts);
|
|
225
220
|
const nested = REL_NESTED_KEY;
|
|
226
221
|
// Both ends are one column here - each `$lookup` matches one field against `_id` - so both sides
|
|
227
222
|
// must be sole-keyed. Sliced rather than indexed positionally: `references[1]` is the parent's
|
|
@@ -493,7 +488,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
493
488
|
const relEntity = relOpts.entity();
|
|
494
489
|
const relMeta = getMeta(relEntity);
|
|
495
490
|
const temp = sortCountField(key);
|
|
496
|
-
const targetScope = this.renderFilter(relEntity, this.
|
|
491
|
+
const targetScope = this.renderFilter(relEntity, this.scopedWhere(relMeta, {}), opts);
|
|
497
492
|
const tail = [{ $count: COUNT_ALIAS }];
|
|
498
493
|
stages.push(this.relationLookup(meta, relOpts, relMeta, relEntity, targetScope, temp, tail, opts), {
|
|
499
494
|
$addFields: { [temp]: { $ifNull: [{ $arrayElemAt: [`$${temp}.${COUNT_ALIAS}`, 0] }, 0] } },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Document, MongoClient } from 'mongodb';
|
|
2
2
|
import { AbstractQuerier } from '../querier/index.js';
|
|
3
|
-
import type { EntityData, ExtraOptions,
|
|
3
|
+
import type { EntityData, ExtraOptions, PrimaryKey, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryFilter, QueryGroupMap, QueryOptions, QuerySearch, TransactionOptions, Type, UpdatePayload } from '../type/index.js';
|
|
4
4
|
import { type ParentPartition } from '../util/index.js';
|
|
5
5
|
import type { MongoDialect } from './mongoDialect.js';
|
|
6
6
|
export declare class MongodbQuerier extends AbstractQuerier {
|
|
@@ -56,7 +56,7 @@ export declare class MongodbQuerier extends AbstractQuerier {
|
|
|
56
56
|
* the floor, and skipped the relation-sort rejection {@link MongoDialect.sort} raises.
|
|
57
57
|
*/
|
|
58
58
|
private settleIds;
|
|
59
|
-
internalInsertMany<E extends Document>(entity: Type<E>,
|
|
59
|
+
internalInsertMany<E extends Document>(entity: Type<E>, rows: EntityData<E>[]): Promise<void>;
|
|
60
60
|
internalUpdateMany<E extends Document>(entity: Type<E>, qm: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
|
|
61
61
|
/**
|
|
62
62
|
* `_id` is immutable, so a key the payload names can only be written on the insert branch of an
|
|
@@ -65,18 +65,16 @@ export declare class MongodbQuerier extends AbstractQuerier {
|
|
|
65
65
|
private upsertUpdate;
|
|
66
66
|
private buildConflictFilter;
|
|
67
67
|
protected internalUpsertOne<E extends Document>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<{
|
|
68
|
-
|
|
68
|
+
ids: (PrimaryKey | undefined)[];
|
|
69
69
|
changes: number;
|
|
70
70
|
created: boolean;
|
|
71
71
|
}>;
|
|
72
72
|
protected internalUpsertMany<E extends Document>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<{
|
|
73
73
|
changes: number;
|
|
74
74
|
ids?: undefined;
|
|
75
|
-
firstId?: undefined;
|
|
76
75
|
} | {
|
|
77
76
|
changes: number;
|
|
78
77
|
ids: (PrimaryKey | undefined)[];
|
|
79
|
-
firstId: PrimaryKey | undefined;
|
|
80
78
|
}>;
|
|
81
79
|
protected internalDeleteMany<E extends Document>(entity: Type<E>, qm: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
|
|
82
80
|
get hasOpenTransaction(): boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { COUNT_ALIAS } from '../dialect/aliases.js';
|
|
2
2
|
import { hasRequiredJoin } from '../dialect/queryJoins.js';
|
|
3
|
-
import { getMeta, idOf, soleIdOf } from '../entity/index.js';
|
|
3
|
+
import { getMeta, idOf, namesKey, soleIdOf } from '../entity/index.js';
|
|
4
4
|
import { AbstractQuerier, enrichError } from '../querier/index.js';
|
|
5
5
|
import { clone, getKeys, getRelationRequestSummary, getSoftDeleteValue, hasKeys, idOnlyQuery, isPagedQuery, populatesRelations, queryChildrenOf, throwNoPendingTransaction, throwPendingTransaction, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
6
6
|
/**
|
|
@@ -259,22 +259,17 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
259
259
|
// are named the same way every other driver names them.
|
|
260
260
|
return (this.dialect.normalizeIds(meta, founds) || []).map((found) => idOf(meta, found));
|
|
261
261
|
}
|
|
262
|
-
async internalInsertMany(entity,
|
|
262
|
+
async internalInsertMany(entity, rows) {
|
|
263
263
|
return this.timed('internalInsertMany', undefined, async () => {
|
|
264
|
-
if (!payloads?.length) {
|
|
265
|
-
return [];
|
|
266
|
-
}
|
|
267
|
-
payloads = clone(payloads);
|
|
268
264
|
const meta = getMeta(entity);
|
|
269
|
-
const persistables = this.dialect.getPersistables(meta,
|
|
265
|
+
const persistables = this.dialect.getPersistables(meta, rows, 'onInsert');
|
|
270
266
|
const { insertedIds } = await this.execute((session) => this.collection(entity).insertMany(persistables, { session }));
|
|
271
267
|
const ids = Object.values(insertedIds).map((id) => this.dialect.fromWireId(id));
|
|
272
268
|
const idKey = soleIdOf(meta, 'insert');
|
|
273
|
-
for (
|
|
274
|
-
|
|
269
|
+
for (let index = 0; index < rows.length; index++) {
|
|
270
|
+
rows[index][idKey] = ids[index];
|
|
275
271
|
}
|
|
276
|
-
await this.insertRelations(entity,
|
|
277
|
-
return ids;
|
|
272
|
+
await this.insertRelations(entity, rows);
|
|
278
273
|
});
|
|
279
274
|
}
|
|
280
275
|
async internalUpdateMany(entity, qm, payload, opts) {
|
|
@@ -332,10 +327,11 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
332
327
|
includeResultMetadata: true,
|
|
333
328
|
session,
|
|
334
329
|
}));
|
|
335
|
-
|
|
330
|
+
// Read off the document as written, which carries its `_id` on either branch.
|
|
331
|
+
const id = this.dialect.fromWireId(res?.value?._id);
|
|
336
332
|
// `updatedExisting` is false when a new document was inserted (upserted).
|
|
337
333
|
const created = res?.lastErrorObject?.['updatedExisting'] === false;
|
|
338
|
-
return {
|
|
334
|
+
return { ids: [id], changes: id === undefined ? 0 : 1, created };
|
|
339
335
|
});
|
|
340
336
|
}
|
|
341
337
|
async internalUpsertMany(entity, conflictPaths, payload) {
|
|
@@ -343,8 +339,10 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
343
339
|
if (!payload?.length) {
|
|
344
340
|
return { changes: 0 };
|
|
345
341
|
}
|
|
346
|
-
payload = clone(payload);
|
|
347
342
|
const meta = getMeta(entity);
|
|
343
|
+
// Asked before `getPersistable` fills an `onInsert` key into rows it may only update.
|
|
344
|
+
const unnamed = payload.map((row) => !namesKey(meta, row));
|
|
345
|
+
payload = clone(payload);
|
|
348
346
|
const operations = payload.map((item) => {
|
|
349
347
|
const persistable = this.dialect.getPersistable(meta, item, 'onInsert');
|
|
350
348
|
const filter = this.buildConflictFilter(entity, conflictPaths, item);
|
|
@@ -359,14 +357,15 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
359
357
|
});
|
|
360
358
|
const res = await this.execute((session) => this.collection(entity).bulkWrite(operations, { session }));
|
|
361
359
|
const changes = (res.upsertedCount ?? 0) + (res.modifiedCount ?? 0);
|
|
362
|
-
// `upsertedIds` only
|
|
363
|
-
// updated document's `_id` is
|
|
364
|
-
|
|
365
|
-
const ids = payload.map((_, index) => {
|
|
360
|
+
// `upsertedIds` names only the documents inserted, keyed by operation index, so each lands on
|
|
361
|
+
// its own row; an updated document's `_id` is read back by the conflict fields instead.
|
|
362
|
+
const reported = payload.map((_, index) => {
|
|
366
363
|
const id = res.upsertedIds[index];
|
|
367
364
|
return id === undefined ? undefined : this.dialect.fromWireId(id);
|
|
368
365
|
});
|
|
369
|
-
|
|
366
|
+
const unplaced = reported.some((id, index) => id === undefined && unnamed[index]);
|
|
367
|
+
const found = unplaced ? await this.idsByConflict(entity, conflictPaths, payload) : [];
|
|
368
|
+
return { changes, ids: reported.map((id, index) => id ?? found[index]) };
|
|
370
369
|
});
|
|
371
370
|
}
|
|
372
371
|
async internalDeleteMany(entity, qm, opts = {}) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MergeSqlDialect } from '../dialect/mergeSqlDialect.js';
|
|
2
|
-
import type { DialectFeatures, EntityMeta, FieldOptions, InsertIdSource, Query, QueryContext, QueryOptions, QueryPager, QuerySizeComparisonOps, Type } from '../type/index.js';
|
|
2
|
+
import type { DialectFeatures, EntityMeta, FieldOptions, InsertIdSource, Query, QueryContext, QueryOptions, QueryPager, QuerySizeComparisonOps, Type, VectorDistance, VectorMetric } from '../type/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* Microsoft SQL Server 2017 and up - the floor `STRING_AGG` sets, every other construct here being
|
|
5
5
|
* 2016 or older.
|
|
@@ -30,6 +30,8 @@ export declare class MsSqlDialect extends MergeSqlDialect {
|
|
|
30
30
|
readonly maxBindValues = 2100;
|
|
31
31
|
/** `OUTPUT` has no trailing form: it sits between the column list and `VALUES`. */
|
|
32
32
|
readonly returningPosition = "after-target";
|
|
33
|
+
/** Microsoft documents no row order for a `MERGE ... OUTPUT`. */
|
|
34
|
+
readonly upsertReturningOrdered = false;
|
|
33
35
|
readonly insertIdSource: InsertIdSource;
|
|
34
36
|
/** Holds the update key lock across the insert; without it two concurrent upserts of one key race. */
|
|
35
37
|
protected readonly mergeTargetHint = " WITH (HOLDLOCK)";
|
|
@@ -89,6 +91,17 @@ export declare class MsSqlDialect extends MergeSqlDialect {
|
|
|
89
91
|
* `uuidv7()` is emitted on. It is a predicate rather than a value, so it stands alone.
|
|
90
92
|
*/
|
|
91
93
|
protected regexCondition(operand: string, placeholder: string): string;
|
|
94
|
+
/**
|
|
95
|
+
* `VECTOR_DISTANCE('cosine', a, b)`, one function taking the metric by name; `dot` is the negated
|
|
96
|
+
* inner product, pgvector's `<#>` convention. Exact search, as on sqlite-vec: 2025's DiskANN index
|
|
97
|
+
* is a preview feature, and only `VECTOR_SEARCH` reads it, never an `ORDER BY VECTOR_DISTANCE`.
|
|
98
|
+
*/
|
|
99
|
+
readonly vectorMetrics: ReadonlyMap<VectorDistance, VectorMetric>;
|
|
100
|
+
/**
|
|
101
|
+
* `VECTOR_DISTANCE` refuses the `nvarchar` a vector binds as, so it is cast - to the value's own
|
|
102
|
+
* length, which is its dimension. A write would convert implicitly, and shares the cast anyway.
|
|
103
|
+
*/
|
|
104
|
+
protected appendVectorValue(ctx: QueryContext, value: readonly unknown[]): void;
|
|
92
105
|
/** There is no `CREATE SCHEMA IF NOT EXISTS`, and `CREATE SCHEMA` has to be alone in its batch. */
|
|
93
106
|
createSchemaSql(schema: string): string;
|
|
94
107
|
/** The estimate the engine already keeps per partition, live without a stats refresh. */
|
|
@@ -27,22 +27,16 @@ export class MsSqlDialect extends MergeSqlDialect {
|
|
|
27
27
|
indexIfNotExists: false,
|
|
28
28
|
schemas: true,
|
|
29
29
|
dropTableCascade: false,
|
|
30
|
-
// `sp_rename` is a stored procedure, not DDL, so a rename is refused by name rather than emitted
|
|
31
|
-
// as an `ALTER TABLE` the parser rejects.
|
|
32
|
-
renameColumn: false,
|
|
33
30
|
foreignKeyAlter: true,
|
|
34
31
|
primaryKeyAlter: true,
|
|
35
32
|
generatedColumnAdd: true,
|
|
36
33
|
// Extended properties are out-of-band metadata with their own procedures, not comments.
|
|
37
34
|
commentSyntax: 'none',
|
|
38
35
|
vectorIndexRequiresNotNull: false,
|
|
39
|
-
vectorSupportsLength:
|
|
36
|
+
vectorSupportsLength: true,
|
|
40
37
|
supportsTimestamptz: false,
|
|
41
38
|
stringSizing: 'varchar',
|
|
42
39
|
supportsUnsigned: false,
|
|
43
|
-
// Error 1785: the constraint is refused at create time, so any diamond-shaped schema would fail
|
|
44
|
-
// to build at all rather than misbehave on write.
|
|
45
|
-
multipleCascadePaths: false,
|
|
46
40
|
serverSideCursors: false,
|
|
47
41
|
};
|
|
48
42
|
dialectName = 'mssql';
|
|
@@ -70,6 +64,8 @@ export class MsSqlDialect extends MergeSqlDialect {
|
|
|
70
64
|
maxBindValues = 2100;
|
|
71
65
|
/** `OUTPUT` has no trailing form: it sits between the column list and `VALUES`. */
|
|
72
66
|
returningPosition = 'after-target';
|
|
67
|
+
/** Microsoft documents no row order for a `MERGE ... OUTPUT`. */
|
|
68
|
+
upsertReturningOrdered = false;
|
|
73
69
|
insertIdSource = 'returning';
|
|
74
70
|
/** Holds the update key lock across the insert; without it two concurrent upserts of one key race. */
|
|
75
71
|
mergeTargetHint = ' WITH (HOLDLOCK)';
|
|
@@ -188,6 +184,25 @@ export class MsSqlDialect extends MergeSqlDialect {
|
|
|
188
184
|
regexCondition(operand, placeholder) {
|
|
189
185
|
return `REGEXP_LIKE(${operand}, ${placeholder})`;
|
|
190
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* `VECTOR_DISTANCE('cosine', a, b)`, one function taking the metric by name; `dot` is the negated
|
|
189
|
+
* inner product, pgvector's `<#>` convention. Exact search, as on sqlite-vec: 2025's DiskANN index
|
|
190
|
+
* is a preview feature, and only `VECTOR_SEARCH` reads it, never an `ORDER BY VECTOR_DISTANCE`.
|
|
191
|
+
*/
|
|
192
|
+
vectorMetrics = new Map([
|
|
193
|
+
['cosine', { fn: 'VECTOR_DISTANCE', metricArg: 'cosine' }],
|
|
194
|
+
['l2', { fn: 'VECTOR_DISTANCE', metricArg: 'euclidean' }],
|
|
195
|
+
['inner', { fn: 'VECTOR_DISTANCE', metricArg: 'dot' }],
|
|
196
|
+
]);
|
|
197
|
+
/**
|
|
198
|
+
* `VECTOR_DISTANCE` refuses the `nvarchar` a vector binds as, so it is cast - to the value's own
|
|
199
|
+
* length, which is its dimension. A write would convert implicitly, and shares the cast anyway.
|
|
200
|
+
*/
|
|
201
|
+
appendVectorValue(ctx, value) {
|
|
202
|
+
ctx.append('CAST(');
|
|
203
|
+
super.appendVectorValue(ctx, value);
|
|
204
|
+
ctx.append(` AS VECTOR(${value.length}))`);
|
|
205
|
+
}
|
|
191
206
|
/** There is no `CREATE SCHEMA IF NOT EXISTS`, and `CREATE SCHEMA` has to be alone in its batch. */
|
|
192
207
|
createSchemaSql(schema) {
|
|
193
208
|
const literal = escapeSingleQuotes(schema);
|