uql-orm 0.55.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/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +3 -3
- package/dist/bunSql/bunSql.util.js +1 -1
- package/dist/dialect/mysqlLikeSqlDialect.js +2 -3
- package/dist/dialect/pgLikeSqlDialect.js +2 -2
- 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 +4 -2
- package/dist/entity/metadata/definition.js +24 -26
- 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/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/mongoDialect.d.ts +1 -1
- package/dist/mongo/mongoDialect.js +4 -7
- package/dist/mongo/mongodbQuerier.js +6 -6
- package/dist/querier/abstractQuerier.js +17 -22
- package/dist/querier/abstractSqlQuerier.d.ts +10 -8
- package/dist/querier/abstractSqlQuerier.js +33 -27
- 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/sqliteDialect.js +2 -1
- package/dist/util/dialect.util.d.ts +7 -1
- package/dist/util/dialect.util.js +19 -0
- package/dist/util/logger.d.ts +10 -1
- package/dist/util/logger.js +18 -0
- package/dist/util/object.util.d.ts +2 -0
- package/dist/util/object.util.js +4 -0
- package/dist/util/relationQuery.util.d.ts +3 -3
- package/dist/util/relationQuery.util.js +8 -2
- package/package.json +1 -1
|
@@ -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`;
|
|
@@ -187,7 +187,7 @@ export declare class MongoDialect extends AbstractDialect {
|
|
|
187
187
|
/** `[column, key]` for the fields whose stored name differs from their property name, memoized per entity. */
|
|
188
188
|
private renamedColumns;
|
|
189
189
|
private referenceKeys;
|
|
190
|
-
normalizeIds<E extends Document>(meta: EntityMeta<E>, docs: Document[]
|
|
190
|
+
normalizeIds<E extends Document>(meta: EntityMeta<E>, docs: Document[]): E[];
|
|
191
191
|
/** `doc` is the wire shape - `_id`, stored names, `ObjectId`s - and what comes back is the code's. */
|
|
192
192
|
normalizeId<E extends Document>(meta: EntityMeta<E>, doc: Document | undefined): E | undefined;
|
|
193
193
|
/**
|
|
@@ -2,7 +2,7 @@ import { ObjectId } from 'mongodb';
|
|
|
2
2
|
import { AbstractDialect } from '../dialect/abstractDialect.js';
|
|
3
3
|
import { COUNT_ALIAS, REL_NESTED_KEY, REL_TEMP_PREFIX, sortCountField } from '../dialect/aliases.js';
|
|
4
4
|
import { resolveQueryJoins, resolveSortableJoin } from '../dialect/queryJoins.js';
|
|
5
|
-
import { assertSoleId, getMeta, soleIdOf } from '../entity/index.js';
|
|
5
|
+
import { assertSoleId, fieldOf, getMeta, relationOf, soleIdOf } from '../entity/index.js';
|
|
6
6
|
import { QueryRaw } from '../type/queryRaw.js';
|
|
7
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. */
|
|
@@ -722,7 +722,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
722
722
|
#renamedColumns = new WeakMap();
|
|
723
723
|
#referenceKeys = new WeakMap();
|
|
724
724
|
normalizeIds(meta, docs) {
|
|
725
|
-
return docs
|
|
725
|
+
return docs.map((doc) => this.normalizeId(meta, doc));
|
|
726
726
|
}
|
|
727
727
|
/** `doc` is the wire shape - `_id`, stored names, `ObjectId`s - and what comes back is the code's. */
|
|
728
728
|
normalizeId(meta, doc) {
|
|
@@ -755,10 +755,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
755
755
|
}
|
|
756
756
|
const relKeys = getKeys(meta.relations).filter((key) => res[key]);
|
|
757
757
|
for (const relKey of relKeys) {
|
|
758
|
-
const
|
|
759
|
-
if (!relOpts)
|
|
760
|
-
continue;
|
|
761
|
-
const relMeta = getMeta(relOpts.entity());
|
|
758
|
+
const relMeta = getMeta(relationOf(meta, relKey).entity());
|
|
762
759
|
res[relKey] = Array.isArray(res[relKey])
|
|
763
760
|
? this.normalizeIds(relMeta, res[relKey])
|
|
764
761
|
: this.normalizeId(relMeta, res[relKey]);
|
|
@@ -894,7 +891,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
894
891
|
const named = inserting && it[idKey] != null;
|
|
895
892
|
if (inserting && !named) {
|
|
896
893
|
// Nothing named the key, so the database is being asked to mint one.
|
|
897
|
-
this.assertMintableKey(meta, meta
|
|
894
|
+
this.assertMintableKey(meta, fieldOf(meta, idKey));
|
|
898
895
|
}
|
|
899
896
|
const doc = named ? { [MongoDialect.ID_KEY]: this.toWireId(it[idKey]) } : {};
|
|
900
897
|
for (const key of filterFieldKeys(meta, it, callbackKey)) {
|
|
@@ -63,7 +63,7 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
63
63
|
else {
|
|
64
64
|
const cursor = this.buildFindCursor(entity, q, opts);
|
|
65
65
|
documents = await this.execute(() => cursor.toArray());
|
|
66
|
-
documents = this.dialect.normalizeIds(meta, documents)
|
|
66
|
+
documents = this.dialect.normalizeIds(meta, documents);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
return documents;
|
|
@@ -92,7 +92,7 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
92
92
|
const pipelines = queries.map((it) => this.dialect.aggregationPipeline(entity, it));
|
|
93
93
|
// Counted, not estimated: the leading branch's own length grows with every `$lookup` a populate
|
|
94
94
|
// adds, so a fixed parent budget would let a richer query overflow at the server instead.
|
|
95
|
-
const stages =
|
|
95
|
+
const stages = pipelines[0].length + pipelines.length - 1;
|
|
96
96
|
return stages > MAX_PIPELINE_STAGES
|
|
97
97
|
? this.readEachInTurn(entity, queries)
|
|
98
98
|
: this.readInOnePipeline(entity, q, pipelines);
|
|
@@ -139,7 +139,7 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
139
139
|
const cursor = this.buildFindCursor(entity, q, opts);
|
|
140
140
|
try {
|
|
141
141
|
for await (const doc of cursor) {
|
|
142
|
-
const [normalized] = this.dialect.normalizeIds(meta, [doc])
|
|
142
|
+
const [normalized] = this.dialect.normalizeIds(meta, [doc]);
|
|
143
143
|
yield normalized;
|
|
144
144
|
}
|
|
145
145
|
}
|
|
@@ -177,7 +177,7 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
177
177
|
/** Execute an aggregation pipeline and normalize `_id` → `id`. */
|
|
178
178
|
async runPipeline(entity, meta, pipeline) {
|
|
179
179
|
const documents = await this.execute((session) => this.collection(entity).aggregate(pipeline, { session }).toArray());
|
|
180
|
-
return this.dialect.normalizeIds(meta, documents)
|
|
180
|
+
return this.dialect.normalizeIds(meta, documents);
|
|
181
181
|
}
|
|
182
182
|
/**
|
|
183
183
|
* Build an aggregation pipeline for vector similarity search.
|
|
@@ -257,7 +257,7 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
257
257
|
const founds = await this.execute((session) => this.collection(entity).aggregate(pipeline, { session }).toArray());
|
|
258
258
|
// `normalizeIds` has already spread a compound `_id` back into its columns, so the settled rows
|
|
259
259
|
// are named the same way every other driver names them.
|
|
260
|
-
return
|
|
260
|
+
return this.dialect.normalizeIds(meta, founds).map((found) => idOf(meta, found));
|
|
261
261
|
}
|
|
262
262
|
async internalInsertMany(entity, rows) {
|
|
263
263
|
return this.timed('internalInsertMany', undefined, async () => {
|
|
@@ -356,7 +356,7 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
356
356
|
};
|
|
357
357
|
});
|
|
358
358
|
const res = await this.execute((session) => this.collection(entity).bulkWrite(operations, { session }));
|
|
359
|
-
const changes =
|
|
359
|
+
const changes = res.upsertedCount + res.modifiedCount;
|
|
360
360
|
// `upsertedIds` names only the documents inserted, keyed by operation index, so each lands on
|
|
361
361
|
// its own row; an updated document's `_id` is read back by the conflict fields instead.
|
|
362
362
|
const reported = payload.map((_, index) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { assertSoleId, getMeta, idOf, namesKey, soleIdOf } from '../entity/index.js';
|
|
2
|
-
import { asSelectMap, childrenOf, clone, dataKeyed, fillOnFields, filterPersistableRelationKeys, forEachRequestedRelation, getKeys, getRelationRequestSummary, idOnlyQuery, isScalarId, joinedColumns, keyColumns, LoggerWrapper, isBoundedPerParent, parentJoins, queryChildrenOfAll, parseRelationAtKey, parseRelationQueryValue, rowKey, runHooks, someKey, targetKeyColumns, whereIds, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
1
|
+
import { assertSoleId, getMeta, idOf, namesKey, relationOf, soleIdOf } from '../entity/index.js';
|
|
2
|
+
import { asSelectMap, childrenOf, clone, dataKeyed, entityName, fillOnFields, filterPersistableRelationKeys, forEachRequestedRelation, getKeys, getRelationRequestSummary, idOnlyQuery, isScalarId, joinedColumns, keyColumns, LoggerWrapper, isBoundedPerParent, parentJoins, queryChildrenOfAll, queryLoggerFor, parseRelationAtKey, parseRelationQueryValue, rowKey, runHooks, someKey, targetKeyColumns, whereIds, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
3
3
|
import { enrichError } from './queryError.js';
|
|
4
4
|
import { fillRelationCounts, withIdForCounts } from './relationCount.js';
|
|
5
5
|
/**
|
|
@@ -78,13 +78,10 @@ export class AbstractQuerier {
|
|
|
78
78
|
logger;
|
|
79
79
|
constructor(extra) {
|
|
80
80
|
this.extra = extra;
|
|
81
|
-
this.logger =
|
|
82
|
-
logValues: extra?.logValues,
|
|
83
|
-
slowQuery: extra?.slowQuery,
|
|
84
|
-
});
|
|
81
|
+
this.logger = queryLoggerFor(extra);
|
|
85
82
|
}
|
|
86
83
|
validateProjectionQuery(entity, q) {
|
|
87
|
-
this.validateProjectionQueryRecursive(entity, q, getMeta(entity)
|
|
84
|
+
this.validateProjectionQueryRecursive(entity, q, entityName(getMeta(entity)));
|
|
88
85
|
}
|
|
89
86
|
validateProjectionQueryRecursive(entity, q, path) {
|
|
90
87
|
const meta = getMeta(entity);
|
|
@@ -96,9 +93,7 @@ export class AbstractQuerier {
|
|
|
96
93
|
}
|
|
97
94
|
}
|
|
98
95
|
forEachRequestedRelation(meta, q.$populate, (relKey, relValue) => {
|
|
99
|
-
const relOpts = meta
|
|
100
|
-
if (!relOpts)
|
|
101
|
-
return;
|
|
96
|
+
const relOpts = relationOf(meta, relKey);
|
|
102
97
|
const relEntity = relOpts.entity();
|
|
103
98
|
const parsed = parseRelationQueryValue(relValue);
|
|
104
99
|
if (parsed.nested) {
|
|
@@ -136,8 +131,14 @@ export class AbstractQuerier {
|
|
|
136
131
|
const [entity, q, opts] = this.resolveEntityQuery(entityOrQuery, maybeQueryOrOpts, maybeOpts);
|
|
137
132
|
this.validateProjectionQuery(entity, q);
|
|
138
133
|
const founds = await this.internalFindMany(entity, withIdForCounts(entity, q), opts);
|
|
139
|
-
|
|
140
|
-
|
|
134
|
+
// Guarded here rather than only inside: awaiting a call that returns at once still costs every read
|
|
135
|
+
// a promise and a turn of the microtask queue, and most reads count nothing and hook nothing.
|
|
136
|
+
if (q.$count) {
|
|
137
|
+
await fillRelationCounts(this, entity, founds, q.$count);
|
|
138
|
+
}
|
|
139
|
+
if (this.hasHook(entity, 'afterLoad')) {
|
|
140
|
+
await this.emitHook(entity, 'afterLoad', founds);
|
|
141
|
+
}
|
|
141
142
|
return founds;
|
|
142
143
|
}
|
|
143
144
|
findManyStream(entityOrQuery, maybeQueryOrOpts, maybeOpts) {
|
|
@@ -348,9 +349,7 @@ export class AbstractQuerier {
|
|
|
348
349
|
const meta = getMeta(entity);
|
|
349
350
|
const relKeys = getRelationRequestSummary(meta, populate).toManyKeys;
|
|
350
351
|
for (const relKey of relKeys) {
|
|
351
|
-
const relOpts = meta
|
|
352
|
-
if (!relOpts)
|
|
353
|
-
continue;
|
|
352
|
+
const relOpts = relationOf(meta, relKey);
|
|
354
353
|
const relEntity = relOpts.entity();
|
|
355
354
|
const relationQuery = clone(parseRelationAtKey(relKey, populate).query);
|
|
356
355
|
if (relOpts.through) {
|
|
@@ -498,9 +497,7 @@ export class AbstractQuerier {
|
|
|
498
497
|
const relKeys = filterPersistableRelationKeys(meta, meta.relations, 'delete');
|
|
499
498
|
// Cascade forwards `opts` (including `hardDelete`); each child soft-deletes only if it can.
|
|
500
499
|
for (const relKey of relKeys) {
|
|
501
|
-
const relOpts = meta
|
|
502
|
-
if (!relOpts)
|
|
503
|
-
continue;
|
|
500
|
+
const relOpts = relationOf(meta, relKey);
|
|
504
501
|
const relEntity = relOpts.entity();
|
|
505
502
|
const target = relOpts.through ? relOpts.through() : relEntity;
|
|
506
503
|
const where = childrenOf(parentJoins(relOpts, meta.ids.length), ids);
|
|
@@ -514,9 +511,7 @@ export class AbstractQuerier {
|
|
|
514
511
|
*/
|
|
515
512
|
async saveRelation(entity, ids, relValue, relKey, isUpdate) {
|
|
516
513
|
const meta = getMeta(entity);
|
|
517
|
-
const relOpts = meta
|
|
518
|
-
if (!relOpts)
|
|
519
|
-
return;
|
|
514
|
+
const relOpts = relationOf(meta, relKey);
|
|
520
515
|
// Here rather than only in the callers below: writing the parent's key into a child is one column
|
|
521
516
|
// per key, so a composite takes a statement per parent. `soleParentColumn` and the sole
|
|
522
517
|
// `targetKeyColumns` under it read the *first* pair, which is a real column of a wrong pairing
|
|
@@ -684,7 +679,7 @@ export class AbstractQuerier {
|
|
|
684
679
|
* another one would wait for a task queued behind itself. Callers below keep their `serialize` calls
|
|
685
680
|
* sequential rather than nested.
|
|
686
681
|
*/
|
|
687
|
-
|
|
682
|
+
serialize(task) {
|
|
688
683
|
const res = this.taskQueue.then(task);
|
|
689
684
|
this.taskQueue = res.catch(() => { });
|
|
690
685
|
return res;
|
|
@@ -100,14 +100,16 @@ export declare abstract class AbstractSqlQuerier extends AbstractQuerier impleme
|
|
|
100
100
|
*/
|
|
101
101
|
protected internalStream<T>(query: string, values?: unknown[]): AsyncIterable<T>;
|
|
102
102
|
/**
|
|
103
|
-
* Turn what a driver returned back into the types the entity declares, for
|
|
104
|
-
* populated under
|
|
105
|
-
* `decodeColumn`. Both live with the dialect, because a `sparsevec` is
|
|
106
|
-
*
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
*
|
|
103
|
+
* Turn what a driver returned back into the types the entity declares, for every row and everything
|
|
104
|
+
* populated under them. Which columns, and as what, is `hydratableFields`, resolved once for all the
|
|
105
|
+
* rows; the per-cell decode is `decodeColumn`. Both live with the dialect, because a `sparsevec` is
|
|
106
|
+
* only sparse on Postgres.
|
|
107
|
+
*/
|
|
108
|
+
private hydrateAll;
|
|
109
|
+
/**
|
|
110
|
+
* One row of {@link hydrateAll}. `visited` guards a populated graph that points back at itself, and
|
|
111
|
+
* makes a node two paths reach decode once. Only a populated relation can lead the walk back, so the
|
|
112
|
+
* guard is created at the first one a row carries: rows that populated nothing never allocate one.
|
|
111
113
|
*/
|
|
112
114
|
private hydrateFields;
|
|
113
115
|
/**
|