uql-orm 0.69.0 → 0.71.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 -0
- package/dist/browser/querier/httpQuerier.d.ts +7 -7
- package/dist/browser/type/clientQuerier.d.ts +5 -5
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +4 -4
- package/dist/cockroachdb/cockroachDialect.d.ts +6 -0
- package/dist/cockroachdb/cockroachDialect.js +10 -2
- package/dist/d1/d1SqliteDialect.d.ts +3 -0
- package/dist/d1/d1SqliteDialect.js +3 -1
- package/dist/dialect/abstractDialect.d.ts +8 -2
- package/dist/dialect/abstractDialect.js +17 -1
- package/dist/dialect/abstractSqlDialect.d.ts +21 -5
- package/dist/dialect/abstractSqlDialect.js +91 -46
- package/dist/dialect/aliases.d.ts +10 -7
- package/dist/dialect/aliases.js +10 -7
- package/dist/dialect/hydrateColumn.d.ts +3 -2
- package/dist/dialect/hydrateColumn.js +10 -1
- package/dist/dialect/mysqlLikeSqlDialect.js +5 -3
- package/dist/dialect/pgLikeSqlDialect.d.ts +17 -5
- package/dist/dialect/pgLikeSqlDialect.js +34 -15
- package/dist/dialect/queryJoins.d.ts +19 -1
- package/dist/dialect/queryJoins.js +54 -11
- package/dist/dialect/vectorCast.d.ts +2 -0
- package/dist/dialect/vectorCast.js +7 -0
- package/dist/dialect/vectorSqlDialect.d.ts +7 -3
- package/dist/dialect/vectorSqlDialect.js +15 -10
- package/dist/entity/decorator/members.d.ts +25 -11
- package/dist/entity/metadata/definition.d.ts +8 -3
- package/dist/libsql/libsqlDialect.d.ts +1 -1
- package/dist/libsql/libsqlDialect.js +3 -3
- package/dist/maria/mariaDialect.d.ts +3 -9
- package/dist/maria/mariaDialect.js +4 -13
- package/dist/maria/mariadbQuerier.js +9 -3
- package/dist/migrate/codegen/entityCodeGenerator.js +4 -2
- package/dist/migrate/ddl/index.d.ts +1 -0
- package/dist/migrate/ddl/index.js +4 -2
- package/dist/migrate/ddl/indexDdl.d.ts +2 -0
- package/dist/migrate/ddl/indexDdl.js +10 -2
- package/dist/migrate/ddl/pgIndexDdl.d.ts +3 -4
- package/dist/migrate/ddl/pgIndexDdl.js +11 -11
- package/dist/migrate/ddl/sqliteIndexDdl.d.ts +11 -0
- package/dist/migrate/ddl/sqliteIndexDdl.js +38 -0
- package/dist/migrate/generator/mongoCommand.d.ts +28 -0
- package/dist/migrate/generator/mongoCommand.js +8 -0
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +2 -0
- package/dist/migrate/generator/mongoSchemaGenerator.js +50 -6
- package/dist/migrate/introspection/mongoIntrospector.js +33 -7
- package/dist/migrate/introspection/postgresIntrospector.js +19 -0
- package/dist/migrate/introspection/sqliteIntrospector.d.ts +6 -0
- package/dist/migrate/introspection/sqliteIntrospector.js +29 -3
- package/dist/migrate/migrator.d.ts +2 -1
- package/dist/migrate/migrator.js +5 -3
- package/dist/mongo/mongoDialect.d.ts +33 -27
- package/dist/mongo/mongoDialect.js +178 -114
- package/dist/mongo/mongodbQuerier.d.ts +0 -2
- package/dist/mongo/mongodbQuerier.js +14 -13
- package/dist/mssql/mssqlDialect.js +4 -2
- package/dist/postgres/postgresDialect.js +2 -2
- package/dist/querier/abstractQuerier.d.ts +16 -11
- package/dist/querier/abstractQuerier.js +28 -8
- package/dist/querier/abstractQuerierPool.d.ts +9 -9
- package/dist/querier/abstractSqlQuerier.d.ts +1 -1
- package/dist/querier/abstractSqlQuerier.js +3 -3
- package/dist/schema/canonicalType.js +10 -4
- package/dist/schema/indexDifferences.d.ts +5 -2
- package/dist/schema/indexDifferences.js +5 -0
- package/dist/schema/schemaASTBuilder.js +3 -2
- package/dist/sqlite/localSqliteQuerierPool.d.ts +10 -11
- package/dist/sqlite/localSqliteQuerierPool.js +8 -11
- package/dist/sqlite/nodeSqliteQuerierPool.d.ts +4 -2
- package/dist/sqlite/nodeSqliteQuerierPool.js +7 -4
- package/dist/sqlite/sqliteDialect.d.ts +9 -1
- package/dist/sqlite/sqliteDialect.js +43 -3
- package/dist/sqlite/sqliteQuerierPool.d.ts +6 -5
- package/dist/sqlite/sqliteQuerierPool.js +10 -7
- package/dist/turso/tursoDialect.d.ts +1 -1
- package/dist/turso/tursoDialect.js +6 -2
- package/dist/turso/tursoLocalDialect.d.ts +1 -1
- package/dist/turso/tursoLocalDialect.js +1 -1
- package/dist/turso/tursoLocalQuerierPool.d.ts +4 -5
- package/dist/turso/tursoLocalQuerierPool.js +3 -10
- package/dist/type/dialect.d.ts +11 -0
- package/dist/type/entity.d.ts +45 -5
- package/dist/type/migration.d.ts +14 -9
- package/dist/type/query.d.ts +6 -0
- package/dist/type/queryAggregate.d.ts +73 -42
- package/dist/type/queryAggregate.js +4 -21
- package/dist/type/universalQuerier.d.ts +9 -9
- package/dist/type/vector.d.ts +17 -0
- package/dist/type/vector.js +6 -0
- package/dist/util/ddlExpression.util.d.ts +2 -0
- package/dist/util/ddlExpression.util.js +5 -0
- package/dist/util/dialect.util.d.ts +17 -3
- package/dist/util/dialect.util.js +40 -6
- package/package.json +5 -3
- package/skills/uql-orm/SKILL.md +142 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { ObjectId } from 'mongodb';
|
|
2
2
|
import { AbstractDialect } from '../dialect/abstractDialect.js';
|
|
3
|
-
import { AGGREGATE_VALUE_ALIAS, REL_NESTED_KEY, REL_TEMP_PREFIX, sortCountField } from '../dialect/aliases.js';
|
|
4
|
-
import { resolveQueryJoins, resolveSortableJoin } from '../dialect/queryJoins.js';
|
|
3
|
+
import { AGGREGATE_VALUE_ALIAS, REL_NESTED_KEY, REL_TEMP_PREFIX, SUM_COUNT_ALIAS, sortCountField, } from '../dialect/aliases.js';
|
|
4
|
+
import { groupPathField, resolveGroupJoins, resolveQueryJoins, resolveSortableJoin, } from '../dialect/queryJoins.js';
|
|
5
5
|
import { assertSoleId, fieldOf, getMeta, relationOf, soleIdOf } from '../entity/index.js';
|
|
6
6
|
import { COUNT_RESULT_KEY } from '../type/query.js';
|
|
7
|
-
import { QueryRaw
|
|
8
|
-
import { asSelectMap, assertAggregateColumns, assertNonNegativeInteger, columnFamily, countedRelations, entityName, fillOnFields, filterFieldKeys, findVectorIndex, findVectorSort, getKeys, getRelationRequestSummary, hasKeys, isJsonObject, isJsonUpdateOp, isOperatorMap, isOperatorObject, isRecord, isVectorSearch, normalizeScalarFieldSelection, parentJoins, parseGroupMap, parseRelationAtKey, parseRelationSize, parseSortByCount, someKey, targetKeyColumns, } from '../util/index.js';
|
|
7
|
+
import { QueryRaw } from '../type/queryRaw.js';
|
|
8
|
+
import { aggregateOf, asSelectMap, assertAggregateColumns, assertNonNegativeInteger, columnFamily, countedRelations, entityName, fillOnFields, filterFieldKeys, findVectorIndex, findVectorSort, getKeys, getRelationRequestSummary, hasKeys, isJsonObject, isJsonUpdateOp, isOperatorMap, isOperatorObject, isRecord, isVectorSearch, normalizeScalarFieldSelection, parentJoins, parseGroupMap, parseRelationAtKey, parseRelationSize, parseSortByCount, someKey, targetKeyColumns, } from '../util/index.js';
|
|
9
9
|
/** Default {@link DialectFeatures} for MongoDB. */
|
|
10
10
|
export const mongoDialectFeatures = {
|
|
11
11
|
ifNotExists: false,
|
|
@@ -18,10 +18,12 @@ export const mongoDialectFeatures = {
|
|
|
18
18
|
commentSyntax: 'none',
|
|
19
19
|
vectorIndexRequiresNotNull: false,
|
|
20
20
|
vectorSupportsLength: false,
|
|
21
|
+
vectorBytes: false,
|
|
21
22
|
supportsTimestamptz: false,
|
|
22
23
|
stringSizing: 'bounded-text',
|
|
23
24
|
supportsUnsigned: false,
|
|
24
25
|
serverSideCursors: false,
|
|
26
|
+
correlatedWrites: false,
|
|
25
27
|
};
|
|
26
28
|
/** What `toWireId` converts: the hex spelling of an `ObjectId`, and nothing looser. */
|
|
27
29
|
const HEX_24 = /^[0-9a-f]{24}$/i;
|
|
@@ -61,30 +63,27 @@ export class MongoDialect extends AbstractDialect {
|
|
|
61
63
|
return this.renderFilter(entity, this.scopedWhere(meta, where, opts));
|
|
62
64
|
}
|
|
63
65
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
+
* The stages every pipeline starts with: the `$lookup` each relation condition needs, into a temporary
|
|
67
|
+
* field so `$or` keeps its meaning, the `$match` reading them, and the temporaries taken back out. Each
|
|
68
|
+
* relation aggregate the `$where` or `named` reads is left on the document, once, under its column.
|
|
66
69
|
*/
|
|
67
|
-
|
|
70
|
+
matchStages(entity, where = {}, opts = {}, named = []) {
|
|
68
71
|
const meta = getMeta(entity);
|
|
69
72
|
const lookups = { stages: [], temps: [] };
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
/** Whether a `$where` constrains any relation, and so needs the aggregation path rather than a cursor. */
|
|
74
|
-
constrainsRelations(entity, where) {
|
|
75
|
-
if (!where) {
|
|
76
|
-
return false;
|
|
73
|
+
for (const key of named) {
|
|
74
|
+
this.appendAggregateField(meta, key, lookups);
|
|
77
75
|
}
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
:
|
|
76
|
+
const filter = this.renderFilter(entity, this.scopedWhere(meta, where, opts), lookups);
|
|
77
|
+
return [
|
|
78
|
+
...lookups.stages,
|
|
79
|
+
...(hasKeys(filter) ? [{ $match: filter }] : []),
|
|
80
|
+
...(lookups.temps.length ? [{ $unset: lookups.temps }] : []),
|
|
81
|
+
];
|
|
83
82
|
}
|
|
84
83
|
/**
|
|
85
84
|
* Renders a `$where` tree without applying entity filters (used for same-scope group-operator
|
|
86
|
-
* recursion).
|
|
87
|
-
* given - a plain `find`/`updateMany` filter has nowhere to put them.
|
|
85
|
+
* recursion). A relation, or a relation aggregate, needs `$lookup` stages, so it is only accepted
|
|
86
|
+
* when `lookups` is given - a plain `find`/`updateMany` filter has nowhere to put them.
|
|
88
87
|
*/
|
|
89
88
|
renderFilter(entity, where = {}, lookups) {
|
|
90
89
|
const meta = getMeta(entity);
|
|
@@ -110,6 +109,12 @@ export class MongoDialect extends AbstractDialect {
|
|
|
110
109
|
else {
|
|
111
110
|
this.assertNoRaw(val);
|
|
112
111
|
this.assertKnownPathRoot(meta, key);
|
|
112
|
+
if (aggregateOf(meta.fields[key])) {
|
|
113
|
+
if (!lookups) {
|
|
114
|
+
throw new TypeError(`filtering by relation aggregate '${key}' is not supported here on MongoDB`);
|
|
115
|
+
}
|
|
116
|
+
this.appendAggregateField(meta, key, lookups);
|
|
117
|
+
}
|
|
113
118
|
const isReference = !!meta.fields[key]?.references;
|
|
114
119
|
key = this.pathOf(meta, key);
|
|
115
120
|
if ((key === MongoDialect.ID_KEY || isReference) && !isOperatorObject(val)) {
|
|
@@ -423,16 +428,11 @@ export class MongoDialect extends AbstractDialect {
|
|
|
423
428
|
const selectMap = asSelectMap(select);
|
|
424
429
|
// Projected by column, not by field key; `normalizeId` maps them back on the way out.
|
|
425
430
|
const projection = normalizeScalarFieldSelection(meta, selectMap, exclude).reduce((acc, key) => {
|
|
426
|
-
//
|
|
427
|
-
//
|
|
431
|
+
// A computed field writing SQL leaves the document nothing to project: refused asked for by
|
|
432
|
+
// name, skipped swept in with the rest. A relation aggregate is on it by now, like any column.
|
|
428
433
|
const field = meta.fields[key];
|
|
429
|
-
if (field?.computed) {
|
|
430
|
-
|
|
431
|
-
// is refused where it was asked for by name, and skipped where it was swept in with the rest.
|
|
432
|
-
if (aggregateOf(field)) {
|
|
433
|
-
acc[key] = 1;
|
|
434
|
-
}
|
|
435
|
-
else if (selectMap && key in selectMap) {
|
|
434
|
+
if (field?.computed && !aggregateOf(field)) {
|
|
435
|
+
if (selectMap && key in selectMap) {
|
|
436
436
|
assertReadable(meta, key);
|
|
437
437
|
}
|
|
438
438
|
return acc;
|
|
@@ -521,36 +521,25 @@ export class MongoDialect extends AbstractDialect {
|
|
|
521
521
|
readsAggregates(entity, q) {
|
|
522
522
|
return this.aggregateKeys(entity, q).length > 0;
|
|
523
523
|
}
|
|
524
|
-
/**
|
|
525
|
-
* The relation aggregates one query reads: the ones its projection carries, plus any its `$where` or
|
|
526
|
-
* `$sort` names, which a read materializes whether or not it answers with them.
|
|
527
|
-
*/
|
|
524
|
+
/** The relation aggregates a read projects or sorts by; its `$where` puts its own on the document. */
|
|
528
525
|
aggregateKeys(entity, q) {
|
|
529
526
|
const meta = getMeta(entity);
|
|
530
527
|
const projected = normalizeScalarFieldSelection(meta, asSelectMap(q.$select), q.$exclude);
|
|
531
|
-
|
|
532
|
-
return [...new Set([...projected, ...named])].flatMap((key) => {
|
|
533
|
-
const spec = aggregateOf(meta.fields[key]);
|
|
534
|
-
return spec ? [[key, spec]] : [];
|
|
535
|
-
});
|
|
528
|
+
return [...projected, ...Object.keys(q.$sort ?? {})].filter((key) => aggregateOf(meta.fields[key]));
|
|
536
529
|
}
|
|
537
530
|
/**
|
|
538
|
-
* The
|
|
539
|
-
*
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
* The same spec the SQL dialects render as a correlated subquery: a relation aggregate is data, so a
|
|
543
|
-
* document engine builds it out of stages rather than being refused a language it does not speak.
|
|
531
|
+
* The relation aggregate `key` computes, put on the document under its column by the stages
|
|
532
|
+
* {@link aggregateStages} builds from the same spec SQL renders as a subquery. Once however many
|
|
533
|
+
* clauses read it; a key computing none adds nothing.
|
|
544
534
|
*/
|
|
545
|
-
|
|
546
|
-
const
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
stages.push(...this.aggregateStages(meta, spec, temp, key));
|
|
551
|
-
temps.push(temp);
|
|
535
|
+
appendAggregateField(meta, key, lookups) {
|
|
536
|
+
const spec = aggregateOf(meta.fields[key]);
|
|
537
|
+
const temp = `${REL_TEMP_PREFIX}${key}`;
|
|
538
|
+
if (!spec || lookups.temps.includes(temp)) {
|
|
539
|
+
return;
|
|
552
540
|
}
|
|
553
|
-
|
|
541
|
+
lookups.temps.push(temp);
|
|
542
|
+
lookups.stages.push(...this.aggregateStages(meta, spec, temp, this.columnOf(meta, key)));
|
|
554
543
|
}
|
|
555
544
|
/**
|
|
556
545
|
* One relation aggregate on the document under `field`: the correlated lookup that reads the related
|
|
@@ -651,7 +640,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
651
640
|
return false;
|
|
652
641
|
}
|
|
653
642
|
const meta = getMeta(entity);
|
|
654
|
-
return someKey(sort, (key) =>
|
|
643
|
+
return someKey(sort, (key) => !!meta.relations[key]);
|
|
655
644
|
}
|
|
656
645
|
/**
|
|
657
646
|
* Aggregate results are keyed by `$group`/`$select` alias rather than by column, so an aggregate
|
|
@@ -677,22 +666,12 @@ export class MongoDialect extends AbstractDialect {
|
|
|
677
666
|
return this.columnOf(meta, key.slice(0, dot)) + key.slice(dot);
|
|
678
667
|
}
|
|
679
668
|
aggregationPipeline(entity, q, opts) {
|
|
680
|
-
// Lookups that a relation condition needs come first, then the match that reads them, then the
|
|
681
|
-
// temporary fields are dropped so they never reach the caller.
|
|
682
|
-
// Every relation aggregate the query reads comes first: a `$match`, a `$sort` and the projection all
|
|
683
|
-
// name it as a field of the document, which is what these stages make true.
|
|
684
|
-
const aggregates = this.aggregateFieldStages(getMeta(entity), this.aggregateKeys(entity, q));
|
|
685
|
-
const { stages, filter, unset } = this.whereWithRelations(entity, q.$where, opts);
|
|
686
669
|
return [
|
|
687
|
-
...
|
|
688
|
-
...stages,
|
|
689
|
-
...(hasKeys(filter) ? [{ $match: filter }] : []),
|
|
690
|
-
...(unset.length ? [{ $unset: unset }] : []),
|
|
670
|
+
...this.matchStages(entity, q.$where, opts, this.aggregateKeys(entity, q)),
|
|
691
671
|
...this.readStages(entity, q, {
|
|
692
672
|
sort: this.sort(entity, q.$sort, q.$populate),
|
|
693
673
|
pager: this.pagerStages(q),
|
|
694
674
|
}),
|
|
695
|
-
...(aggregates.temps.length ? [{ $unset: aggregates.temps }] : []),
|
|
696
675
|
];
|
|
697
676
|
}
|
|
698
677
|
/** The `$skip`/`$limit` stages of a page, each checked: `/http` hands a page over untyped. */
|
|
@@ -1037,33 +1016,19 @@ export class MongoDialect extends AbstractDialect {
|
|
|
1037
1016
|
* Build MongoDB aggregation pipeline stages from a QueryAggregate.
|
|
1038
1017
|
*/
|
|
1039
1018
|
buildAggregateStages(entity, q, opts) {
|
|
1040
|
-
const
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
pipeline.push({ $group: { _id: hasKeys(groupId) ? groupId : null, ...groupAccumulators } });
|
|
1051
|
-
// Project stage - rename _id fields back to their original names, and reduce collected distinct
|
|
1052
|
-
// sets. Needed whenever there are group keys OR any distinct alias.
|
|
1053
|
-
if (hasKeys(groupId) || distinctReducers.size) {
|
|
1054
|
-
const project = { _id: 0 };
|
|
1055
|
-
for (const alias of Object.keys(groupId)) {
|
|
1056
|
-
project[alias] = `$_id.${alias}`;
|
|
1057
|
-
}
|
|
1058
|
-
for (const alias of Object.keys(groupAccumulators)) {
|
|
1059
|
-
const reduceOp = distinctReducers.get(alias);
|
|
1060
|
-
project[alias] = reduceOp ? { [reduceOp]: `$${alias}` } : 1;
|
|
1061
|
-
}
|
|
1062
|
-
pipeline.push({ $project: project });
|
|
1063
|
-
}
|
|
1019
|
+
const meta = getMeta(entity);
|
|
1020
|
+
const { joins, where } = resolveGroupJoins(meta, q);
|
|
1021
|
+
const { groupId, accumulators, columns, named } = this.buildGroupSpec(meta, parseGroupMap(q.$group, q.$select), joins);
|
|
1022
|
+
const pipeline = [
|
|
1023
|
+
...this.matchStages(entity, where, opts, named),
|
|
1024
|
+
...this.lookupStages(meta, joins),
|
|
1025
|
+
{ $group: { _id: hasKeys(groupId) ? groupId : null, ...accumulators } },
|
|
1026
|
+
// `$group` answers with `_id` even when grouping by nothing, and with what `columns` read to the end.
|
|
1027
|
+
{ $project: { _id: 0, ...columns } },
|
|
1028
|
+
];
|
|
1064
1029
|
// Everything the pipeline emits, which is all `$having` and `$sort` may name. The `$project`
|
|
1065
1030
|
// above has already dropped the rest, so an unchecked key matched nothing or ordered by nothing.
|
|
1066
|
-
const emitted = new Set(
|
|
1031
|
+
const emitted = new Set(Object.keys(columns));
|
|
1067
1032
|
// $match stage for HAVING (post-group filtering)
|
|
1068
1033
|
if (q.$having) {
|
|
1069
1034
|
assertAggregateColumns(q.$having, emitted, '$having');
|
|
@@ -1084,39 +1049,139 @@ export class MongoDialect extends AbstractDialect {
|
|
|
1084
1049
|
return pipeline;
|
|
1085
1050
|
}
|
|
1086
1051
|
/**
|
|
1087
|
-
* Resolve parsed group entries into the `_id` keys and accumulators of a `$group` stage
|
|
1088
|
-
* `
|
|
1089
|
-
*
|
|
1052
|
+
* Resolve parsed group entries into the `_id` keys and accumulators of a `$group` stage, and the
|
|
1053
|
+
* `columns` its `$project` reads each result from: a group key out of `_id`, a DISTINCT set by its
|
|
1054
|
+
* size, and a `$sum` as null where it read no value, as SQL answers it. `named` is every field it reads,
|
|
1055
|
+
* filters included, so each relation aggregate among them is put on the document first.
|
|
1090
1056
|
*/
|
|
1091
|
-
buildGroupSpec(meta, groupEntries) {
|
|
1057
|
+
buildGroupSpec(meta, groupEntries, joins) {
|
|
1092
1058
|
if (!groupEntries.length) {
|
|
1093
1059
|
throw new TypeError('aggregate requires at least one $group column or $select function');
|
|
1094
1060
|
}
|
|
1095
1061
|
const groupId = {};
|
|
1096
|
-
const
|
|
1097
|
-
const
|
|
1062
|
+
const accumulators = {};
|
|
1063
|
+
const columns = {};
|
|
1064
|
+
const named = [];
|
|
1098
1065
|
for (const entry of groupEntries) {
|
|
1099
1066
|
// Aliases stay as the caller wrote them ($project maps them back); the *refs* address columns.
|
|
1100
|
-
const ref = entry.kind === 'key' ? `$${this.columnOf(meta, entry.alias)}` : `$${this.columnOf(meta, entry.fieldRef)}`;
|
|
1101
1067
|
if (entry.kind === 'key') {
|
|
1102
|
-
groupId[entry.alias] =
|
|
1068
|
+
groupId[entry.alias] = `$${this.groupedPath(meta, joins, entry.path, named)}`;
|
|
1069
|
+
columns[entry.alias] = `$_id.${entry.alias}`;
|
|
1070
|
+
continue;
|
|
1103
1071
|
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1072
|
+
named.push(entry.fieldRef);
|
|
1073
|
+
const ref = `$${this.columnOf(meta, entry.fieldRef)}`;
|
|
1074
|
+
const test = entry.where && this.whereExpression(meta, entry.where, named);
|
|
1075
|
+
// What the accumulator reads from a row its own `$where` passes, and from one it does not.
|
|
1076
|
+
const read = (passed, failed) => (test ? { $cond: [test, passed, failed] } : passed);
|
|
1077
|
+
columns[entry.alias] = 1;
|
|
1078
|
+
if (entry.distinct) {
|
|
1079
|
+
accumulators[entry.alias] = { $addToSet: read(ref, '$$REMOVE') };
|
|
1080
|
+
columns[entry.alias] = { $size: `$${entry.alias}` };
|
|
1108
1081
|
}
|
|
1109
1082
|
else if (entry.op === '$count') {
|
|
1110
1083
|
// COUNT(*) counts every row; COUNT(field) counts non-null values, matching SQL.
|
|
1111
|
-
|
|
1112
|
-
entry.fieldRef === '*' ? { $sum: 1 } : { $sum: { $cond: [{ $ne: [ref, null] }, 1, 0] } };
|
|
1084
|
+
accumulators[entry.alias] = { $sum: entry.fieldRef === '*' ? read(1, 0) : read(MongoDialect.countOf(ref), 0) };
|
|
1113
1085
|
}
|
|
1114
1086
|
else {
|
|
1115
|
-
// `$sum`, `$avg`, `$min` and `$max` are MongoDB accumulators of the same name.
|
|
1116
|
-
|
|
1087
|
+
// `$sum`, `$avg`, `$min` and `$max` are MongoDB accumulators of the same name, and skip a null.
|
|
1088
|
+
accumulators[entry.alias] = { [entry.op]: read(ref, null) };
|
|
1089
|
+
}
|
|
1090
|
+
if (entry.op === '$sum' && !entry.distinct) {
|
|
1091
|
+
const counted = `${SUM_COUNT_ALIAS}_${entry.alias}`;
|
|
1092
|
+
accumulators[counted] = { $sum: read(MongoDialect.countOf(ref), 0) };
|
|
1093
|
+
columns[entry.alias] = { $cond: [{ $eq: [`$${counted}`, 0] }, null, `$${entry.alias}`] };
|
|
1117
1094
|
}
|
|
1118
1095
|
}
|
|
1119
|
-
return { groupId,
|
|
1096
|
+
return { groupId, accumulators, columns, named };
|
|
1097
|
+
}
|
|
1098
|
+
/** `1` where `ref` holds a value, `0` where it is null or missing, which an expression tells apart. */
|
|
1099
|
+
static countOf(ref) {
|
|
1100
|
+
return { $cond: [MongoDialect.isNullExpr(ref), 0, 1] };
|
|
1101
|
+
}
|
|
1102
|
+
/** Whether `ref` is null or missing: an expression compares a missing field as neither. */
|
|
1103
|
+
static isNullExpr(ref) {
|
|
1104
|
+
return { $eq: [{ $ifNull: [ref, null] }, null] };
|
|
1105
|
+
}
|
|
1106
|
+
/** The field a grouped path reads, on the document or on the joined one its lookup unwound. */
|
|
1107
|
+
groupedPath(meta, joins, path, named) {
|
|
1108
|
+
const { key, join } = groupPathField(joins, path);
|
|
1109
|
+
if (!join) {
|
|
1110
|
+
named.push(key);
|
|
1111
|
+
return this.columnOf(meta, key);
|
|
1112
|
+
}
|
|
1113
|
+
if (aggregateOf(join.meta.fields[key])) {
|
|
1114
|
+
throw new TypeError(`cannot $group by '${path.join('.')}' on MongoDB: a joined row's relation aggregate is not read`);
|
|
1115
|
+
}
|
|
1116
|
+
return `${join.path}.${this.columnOf(join.meta, key)}`;
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* An aggregate's own `$where` as the expression a `$cond` tests, which a query filter is not: the
|
|
1120
|
+
* comparisons and the logical operators translate, anything else is refused by name. `named` gathers
|
|
1121
|
+
* the fields it reads, so a relation aggregate among them is on the document first.
|
|
1122
|
+
*/
|
|
1123
|
+
whereExpression(meta, where, named) {
|
|
1124
|
+
const terms = getKeys(where)
|
|
1125
|
+
.filter((key) => where[key] !== undefined)
|
|
1126
|
+
.map((key) => {
|
|
1127
|
+
if (MongoDialect.isGroupOp(key)) {
|
|
1128
|
+
const { join, negate } = MongoDialect.GROUP_OPS[key];
|
|
1129
|
+
const clauses = MongoDialect.groupClauses(key, where[key]).map((clause) => {
|
|
1130
|
+
if (clause instanceof QueryRaw) {
|
|
1131
|
+
throw new TypeError('raw SQL is not supported in an aggregate $where on MongoDB');
|
|
1132
|
+
}
|
|
1133
|
+
return this.whereExpression(meta, clause, named);
|
|
1134
|
+
});
|
|
1135
|
+
return negate ? { $not: [{ [join]: clauses }] } : { [join]: clauses };
|
|
1136
|
+
}
|
|
1137
|
+
if (key.startsWith('$')) {
|
|
1138
|
+
throw new TypeError(`aggregate $where operator '${key}' is not supported on MongoDB`);
|
|
1139
|
+
}
|
|
1140
|
+
const val = where[key];
|
|
1141
|
+
named.push(key);
|
|
1142
|
+
const path = this.pathOf(meta, key);
|
|
1143
|
+
const wire = (value) => path === MongoDialect.ID_KEY || meta.fields[key]?.references ? this.toWireId(value) : value;
|
|
1144
|
+
return this.fieldExpression(`$${path}`, val, wire);
|
|
1145
|
+
});
|
|
1146
|
+
return terms.length === 1 ? terms[0] : { $and: terms };
|
|
1147
|
+
}
|
|
1148
|
+
/** One field's condition as an expression: a value it equals, a list it is in, or a map of comparisons. */
|
|
1149
|
+
fieldExpression(ref, val, wire) {
|
|
1150
|
+
const equals = (value) => (value === null ? MongoDialect.isNullExpr(ref) : { $eq: [ref, wire(value)] });
|
|
1151
|
+
if (!isOperatorMap(val)) {
|
|
1152
|
+
return Array.isArray(val) ? { $in: [ref, wire(val)] } : equals(val);
|
|
1153
|
+
}
|
|
1154
|
+
// A null or missing field compares below every value in an expression, where SQL leaves it unmatched.
|
|
1155
|
+
const present = { $not: [MongoDialect.isNullExpr(ref)] };
|
|
1156
|
+
const terms = Object.entries(val).map(([op, operand]) => {
|
|
1157
|
+
switch (op) {
|
|
1158
|
+
case '$eq':
|
|
1159
|
+
return equals(operand);
|
|
1160
|
+
case '$ne':
|
|
1161
|
+
return { $not: [equals(operand)] };
|
|
1162
|
+
case '$gt':
|
|
1163
|
+
case '$gte':
|
|
1164
|
+
return { [op]: [ref, wire(operand)] };
|
|
1165
|
+
case '$lt':
|
|
1166
|
+
case '$lte':
|
|
1167
|
+
return { $and: [present, { [op]: [ref, wire(operand)] }] };
|
|
1168
|
+
case '$in':
|
|
1169
|
+
return { $in: [ref, wire(operand)] };
|
|
1170
|
+
case '$nin':
|
|
1171
|
+
return { $not: [{ $in: [ref, wire(operand)] }] };
|
|
1172
|
+
case '$between': {
|
|
1173
|
+
const [min, max] = operand;
|
|
1174
|
+
return { $and: [{ $gte: [ref, wire(min)] }, { $lte: [ref, wire(max)] }] };
|
|
1175
|
+
}
|
|
1176
|
+
case '$isNull':
|
|
1177
|
+
return operand ? MongoDialect.isNullExpr(ref) : present;
|
|
1178
|
+
case '$isNotNull':
|
|
1179
|
+
return operand ? present : MongoDialect.isNullExpr(ref);
|
|
1180
|
+
default:
|
|
1181
|
+
throw new TypeError(`aggregate $where operator '${op}' is not supported on MongoDB`);
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
return terms.length === 1 ? terms[0] : { $and: terms };
|
|
1120
1185
|
}
|
|
1121
1186
|
buildHavingFilter(having) {
|
|
1122
1187
|
const filter = {};
|
|
@@ -1156,6 +1221,10 @@ export class MongoDialect extends AbstractDialect {
|
|
|
1156
1221
|
}
|
|
1157
1222
|
return { vectorKey: found.key, vectorSearch: found.search, regularSort: regularSort };
|
|
1158
1223
|
}
|
|
1224
|
+
/** The Atlas index a `$vectorSearch` over `column` reads, and migrations create: its declared name, else `<column>_index`. */
|
|
1225
|
+
vectorSearchIndexName(name, column) {
|
|
1226
|
+
return name ?? `${column}_index`;
|
|
1227
|
+
}
|
|
1159
1228
|
/**
|
|
1160
1229
|
* Build a `$vectorSearch` aggregation pipeline stage.
|
|
1161
1230
|
* Merges `$where` into `$vectorSearch.filter` for optimal pre-filtering.
|
|
@@ -1167,8 +1236,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
1167
1236
|
throw new TypeError(`Field '${key}' not found in entity '${meta.name}'`);
|
|
1168
1237
|
}
|
|
1169
1238
|
const colName = this.resolveColumnName(key, field);
|
|
1170
|
-
|
|
1171
|
-
const indexName = findVectorIndex(meta, key)?.name ?? `${colName}_index`;
|
|
1239
|
+
const indexName = this.vectorSearchIndexName(findVectorIndex(meta, key)?.name, colName);
|
|
1172
1240
|
if (!limit) {
|
|
1173
1241
|
throw new TypeError(`$vectorSearch requires $limit (vector sort on '${key}' of '${meta.name}')`);
|
|
1174
1242
|
}
|
|
@@ -1195,14 +1263,10 @@ export class MongoDialect extends AbstractDialect {
|
|
|
1195
1263
|
function sortDirection(value) {
|
|
1196
1264
|
return value === 'desc' || value === -1 ? -1 : 1;
|
|
1197
1265
|
}
|
|
1198
|
-
/** The relation aggregate a field computes, where it computes one rather than writing SQL. */
|
|
1199
|
-
function aggregateOf(field) {
|
|
1200
|
-
return field?.computed instanceof RelationAggregate ? field.computed.spec : undefined;
|
|
1201
|
-
}
|
|
1202
1266
|
/**
|
|
1203
1267
|
* A `computed` field writing SQL is refused wherever a query names it, since no document engine
|
|
1204
1268
|
* evaluates SQL and answering with the property name would hand back `undefined` for every row. One
|
|
1205
|
-
* computing a relation aggregate is read: `
|
|
1269
|
+
* computing a relation aggregate is read: `appendAggregateField` builds it.
|
|
1206
1270
|
*/
|
|
1207
1271
|
function assertReadable(meta, key) {
|
|
1208
1272
|
const field = meta.fields[key];
|
|
@@ -44,8 +44,6 @@ export declare class MongodbQuerier extends AbstractQuerier {
|
|
|
44
44
|
* filter - the reason {@link UniversalQuerier.estimatedCount} takes none either.
|
|
45
45
|
*/
|
|
46
46
|
estimatedCount<E extends Document>(entity: Type<E>): Promise<number>;
|
|
47
|
-
/** A `find` filter cannot host the `$lookup` a relation condition needs, so such a write names its rows by id. */
|
|
48
|
-
protected settlesWrite<E extends Document>(entity: Type<E>, q: QuerySearch<E>): boolean;
|
|
49
47
|
internalInsertMany<E extends Document>(entity: Type<E>, rows: EntityData<E>[]): Promise<void>;
|
|
50
48
|
internalUpdateMany<E extends Document>(entity: Type<E>, qm: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
|
|
51
49
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AGGREGATE_VALUE_ALIAS } from '../dialect/aliases.js';
|
|
2
2
|
import { hasRequiredJoin } from '../dialect/queryJoins.js';
|
|
3
3
|
import { fieldOf, getMeta, namesKey, soleIdOf } from '../entity/index.js';
|
|
4
4
|
import { AbstractQuerier, enrichError } from '../querier/index.js';
|
|
5
|
-
import { clone, getKeys, getSoftDeleteValue, hasKeys, populatesRelations, throwNoPendingTransaction, throwPendingTransaction, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
5
|
+
import { clone, getKeys, getSoftDeleteValue, hasKeys, populatesRelations, throwNoPendingTransaction, throwPendingTransaction, vectorCandidates, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
6
6
|
/**
|
|
7
7
|
* `$limit: 0` asks for no rows, the way it does on every SQL dialect - but MongoDB reads `limit(0)`
|
|
8
8
|
* as *unlimited*, so a read that passed it straight to the driver came back with the whole
|
|
@@ -113,7 +113,7 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
113
113
|
buildVectorPipeline(entity, q, vectorSort, opts) {
|
|
114
114
|
const scoreAlias = vectorSort.vectorSearch.$project;
|
|
115
115
|
return [
|
|
116
|
-
this.dialect.buildVectorSearchStage(entity, vectorSort.vectorKey, vectorSort.vectorSearch, q.$where, q.$limit ?? 10, opts, q
|
|
116
|
+
this.dialect.buildVectorSearchStage(entity, vectorSort.vectorKey, vectorSort.vectorSearch, q.$where, q.$limit ?? 10, opts, vectorCandidates(q)),
|
|
117
117
|
// The score becomes a real field before anything reads it, so the lookups and the projection
|
|
118
118
|
// that follow treat it like any other - and a query with no projection keeps its own columns.
|
|
119
119
|
...(scoreAlias ? [{ $addFields: { [scoreAlias]: { $meta: 'vectorSearchScore' } } }] : []),
|
|
@@ -143,10 +143,10 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
143
143
|
const [founds, counted] = await Promise.all([
|
|
144
144
|
this.internalFindMany(entity, q, opts),
|
|
145
145
|
this.execute((session) => this.collection(entity)
|
|
146
|
-
.aggregate([...this.dialect.aggregationPipeline(entity, unpaged, opts), { $count:
|
|
146
|
+
.aggregate([...this.dialect.aggregationPipeline(entity, unpaged, opts), { $count: AGGREGATE_VALUE_ALIAS }], { session })
|
|
147
147
|
.toArray()),
|
|
148
148
|
]);
|
|
149
|
-
return [founds, counted[0]?.[
|
|
149
|
+
return [founds, counted[0]?.[AGGREGATE_VALUE_ALIAS] ?? 0];
|
|
150
150
|
}
|
|
151
151
|
/** The pipeline `countDocuments` runs, spelled out so a relation condition gets its lookups and a page its stages. */
|
|
152
152
|
async internalCount(entity, q, opts) {
|
|
@@ -154,10 +154,15 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
154
154
|
return 0;
|
|
155
155
|
}
|
|
156
156
|
return this.timed('internalCount', undefined, async () => {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
157
|
+
const pipeline = [
|
|
158
|
+
...this.dialect.matchStages(entity, q.$where, opts),
|
|
159
|
+
...this.dialect.pagerStages(q),
|
|
160
|
+
{ $count: AGGREGATE_VALUE_ALIAS },
|
|
161
|
+
];
|
|
162
|
+
const [counted] = await this.execute((session) => this.collection(entity)
|
|
163
|
+
.aggregate(pipeline, { session })
|
|
164
|
+
.toArray());
|
|
165
|
+
return counted?.[AGGREGATE_VALUE_ALIAS] ?? 0;
|
|
161
166
|
});
|
|
162
167
|
}
|
|
163
168
|
/**
|
|
@@ -167,10 +172,6 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
167
172
|
async estimatedCount(entity) {
|
|
168
173
|
return this.timed('estimatedCount', undefined, async () => this.execute((session) => this.collection(entity).estimatedDocumentCount({ session })));
|
|
169
174
|
}
|
|
170
|
-
/** A `find` filter cannot host the `$lookup` a relation condition needs, so such a write names its rows by id. */
|
|
171
|
-
settlesWrite(entity, q) {
|
|
172
|
-
return super.settlesWrite(entity, q) || this.dialect.constrainsRelations(entity, q.$where);
|
|
173
|
-
}
|
|
174
175
|
async internalInsertMany(entity, rows) {
|
|
175
176
|
return this.timed('internalInsertMany', undefined, async () => {
|
|
176
177
|
const meta = getMeta(entity);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { relationTermKey } from '../dialect/abstractSqlDialect.js';
|
|
2
|
-
import {
|
|
2
|
+
import { AGGREGATE_VALUE_ALIAS, JSON_PULL_ALIAS } from '../dialect/aliases.js';
|
|
3
3
|
import { BYTES_PREFIX } from '../dialect/hydrateColumn.js';
|
|
4
4
|
import { jsonArraySlotArgs, jsonPath, jsonSlotArgs } from '../dialect/jsonSql.js';
|
|
5
5
|
import { MergeSqlDialect } from '../dialect/mergeSqlDialect.js';
|
|
@@ -25,10 +25,12 @@ const MSSQL_FEATURES = {
|
|
|
25
25
|
commentSyntax: 'none',
|
|
26
26
|
vectorIndexRequiresNotNull: false,
|
|
27
27
|
vectorSupportsLength: true,
|
|
28
|
+
vectorBytes: false,
|
|
28
29
|
supportsTimestamptz: false,
|
|
29
30
|
stringSizing: 'varchar',
|
|
30
31
|
supportsUnsigned: false,
|
|
31
32
|
serverSideCursors: false,
|
|
33
|
+
correlatedWrites: true,
|
|
32
34
|
rowLocks: true,
|
|
33
35
|
rowLockWithWindow: true,
|
|
34
36
|
rowLockOf: true,
|
|
@@ -227,7 +229,7 @@ export class MsSqlDialect extends MergeSqlDialect {
|
|
|
227
229
|
/** The estimate the engine already keeps per partition, live without a stats refresh. */
|
|
228
230
|
estimatedCount(ctx, entity) {
|
|
229
231
|
const meta = getMeta(entity);
|
|
230
|
-
ctx.append(`SELECT SUM(p.rows) ${this.escapeId(
|
|
232
|
+
ctx.append(`SELECT SUM(p.rows) ${this.escapeId(AGGREGATE_VALUE_ALIAS, true)} FROM sys.partitions p` +
|
|
231
233
|
` JOIN sys.objects o ON o.object_id = p.object_id` +
|
|
232
234
|
` JOIN sys.schemas s ON s.schema_id = o.schema_id` +
|
|
233
235
|
` WHERE p.index_id IN (0, 1) AND o.name = `);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AGGREGATE_VALUE_ALIAS } from '../dialect/aliases.js';
|
|
2
2
|
import { PG_FEATURES, PgLikeSqlDialect } from '../dialect/pgLikeSqlDialect.js';
|
|
3
3
|
import { getMeta } from '../entity/index.js';
|
|
4
4
|
/** PostgreSQL, under every driver: `pg`, Neon, PGlite, `bun:sql`. Adds pgvector and the `xmax` upsert `created`. */
|
|
@@ -18,7 +18,7 @@ export class PostgresDialect extends PgLikeSqlDialect {
|
|
|
18
18
|
* back raw it would read as a negative row count.
|
|
19
19
|
*/
|
|
20
20
|
estimatedCount(ctx, entity) {
|
|
21
|
-
ctx.append(`SELECT GREATEST(reltuples, 0)::bigint ${this.escapeId(
|
|
21
|
+
ctx.append(`SELECT GREATEST(reltuples, 0)::bigint ${this.escapeId(AGGREGATE_VALUE_ALIAS, true)} FROM pg_class WHERE oid = to_regclass(`);
|
|
22
22
|
ctx.addValue(this.escapedTableName(getMeta(entity)));
|
|
23
23
|
ctx.append(')');
|
|
24
24
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AbstractDialect } from '../dialect/abstractDialect.js';
|
|
2
|
+
import type { EntityData, EntityId, EntityWrite, ExtraOptions, FieldKey, Querier, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryFilter, QueryFindResult, QueryGroupMap, QueryOneProjected, QueryOptions, QueryPage, QueryProjected, QuerySearch, PrimaryKey, QueryUpdateResult, QueryUpsertOneResult, QueryUpsertManyResult, RelationKey, TransactionOptions, Type, UpdatePayload, UpdateWrite, WrittenId } from '../type/index.js';
|
|
2
3
|
import { LoggerWrapper } from '../util/index.js';
|
|
3
4
|
/** Base class for all database queriers. */
|
|
4
5
|
export declare abstract class AbstractQuerier implements Querier {
|
|
@@ -15,6 +16,7 @@ export declare abstract class AbstractQuerier implements Querier {
|
|
|
15
16
|
*/
|
|
16
17
|
protected released: boolean;
|
|
17
18
|
protected readonly logger: LoggerWrapper;
|
|
19
|
+
abstract readonly dialect: AbstractDialect;
|
|
18
20
|
constructor(extra?: ExtraOptions | undefined);
|
|
19
21
|
protected validateProjectionQuery<E extends object>(entity: Type<E>, q: Query<E>): void;
|
|
20
22
|
private validateProjectionQueryRecursive;
|
|
@@ -73,21 +75,24 @@ export declare abstract class AbstractQuerier implements Querier {
|
|
|
73
75
|
protected abstract internalAggregate<E extends object, G extends QueryGroupMap<E>, A extends QueryAggMap<E>>(entity: Type<E>, q: QueryAggregate<E, G, A>, opts?: QueryOptions): Promise<QueryAggregateResult<E, G, A>[]>;
|
|
74
76
|
/** Abstract outright: nothing is shared to do around it. See {@link UniversalQuerier.estimatedCount}. */
|
|
75
77
|
abstract estimatedCount<E extends object>(entity: Type<E>): Promise<number>;
|
|
76
|
-
insertOne<E extends object>(entity: Type<E>, payload:
|
|
78
|
+
insertOne<E extends object>(entity: Type<E>, payload: EntityWrite<E>): Promise<WrittenId<E> | undefined>;
|
|
77
79
|
/**
|
|
78
80
|
* The `onInsert` values are filled here, before the write, so the after hooks and the ids read the
|
|
79
81
|
* same rows the statement wrote.
|
|
80
82
|
*/
|
|
81
|
-
insertMany<E extends object>(entity: Type<E>, payload:
|
|
83
|
+
insertMany<E extends object>(entity: Type<E>, payload: EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
82
84
|
/** Writes `rows`, and onto each one the key the database generated for it, where it can tell. */
|
|
83
85
|
protected abstract internalInsertMany<E extends object>(entity: Type<E>, rows: EntityData<E>[]): Promise<void>;
|
|
84
|
-
updateOneById<E extends object>(entity: Type<E>, id: EntityId<E>, payload:
|
|
86
|
+
updateOneById<E extends object>(entity: Type<E>, id: EntityId<E>, payload: UpdateWrite<E>, opts?: QueryOptions): Promise<number>;
|
|
85
87
|
/** Settles the rows first where the update cascades, so a payload changing what `$where` reads still names them. */
|
|
86
|
-
updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload:
|
|
88
|
+
updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdateWrite<E>, opts?: QueryOptions): Promise<number>;
|
|
87
89
|
/** The UPDATE, skipped where the payload writes no column, reporting `unwritten` instead. */
|
|
88
90
|
private updateColumns;
|
|
89
|
-
/**
|
|
90
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Whether a write has to name the rows `q` matches by their ids: no engine pages or orders an update or
|
|
93
|
+
* delete, and one without {@link DialectFeatures.correlatedWrites} cannot read a relation in its filter.
|
|
94
|
+
*/
|
|
95
|
+
protected settlesWrite<E extends object>(entity: Type<E>, q: QuerySearch<E>): boolean;
|
|
91
96
|
/** The ids `q` matches, in its own order and page. */
|
|
92
97
|
protected settleIds<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions): Promise<EntityId<E>[]>;
|
|
93
98
|
/** Runs one UPDATE over `q`, which names its rows by id wherever {@link updateMany} settled them. */
|
|
@@ -95,8 +100,8 @@ export declare abstract class AbstractQuerier implements Querier {
|
|
|
95
100
|
restoreOneById<E extends object>(entity: Type<E>, id: EntityId<E>): Promise<number>;
|
|
96
101
|
restoreMany<E extends object>(entity: Type<E>, q: QuerySearch<E>): Promise<number>;
|
|
97
102
|
/** Fires `beforeUpsert`/`afterUpsert`: which branch a row takes is the database's to decide, so neither the insert's nor the update's pair fits. */
|
|
98
|
-
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload:
|
|
99
|
-
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload:
|
|
103
|
+
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityWrite<E>): Promise<QueryUpsertOneResult<E>>;
|
|
104
|
+
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityWrite<E>[]): Promise<QueryUpsertManyResult<E>>;
|
|
100
105
|
protected abstract internalUpsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpdateResult>;
|
|
101
106
|
protected abstract internalUpsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpdateResult>;
|
|
102
107
|
deleteOneById<E extends object>(entity: Type<E>, id: EntityId<E>, opts?: QueryOptions): Promise<number>;
|
|
@@ -107,13 +112,13 @@ export declare abstract class AbstractQuerier implements Querier {
|
|
|
107
112
|
}, opts?: QueryOptions): Promise<number>;
|
|
108
113
|
/** Runs one DELETE (or soft-delete stamp) over `q`, which names its rows by id wherever {@link deleteMany} settled them. */
|
|
109
114
|
protected abstract internalDeleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
|
|
110
|
-
saveOne<E extends object>(entity: Type<E>, payload:
|
|
115
|
+
saveOne<E extends object>(entity: Type<E>, payload: EntityWrite<E>): Promise<WrittenId<E> | undefined>;
|
|
111
116
|
/**
|
|
112
117
|
* Whether a row names its key decides its statement, never whether the row exists: a named row
|
|
113
118
|
* upserts on that key, so a stale id is written rather than silently missed, and an unnamed one
|
|
114
119
|
* inserts. A composite is always named. The hooks follow the statement: a named row fires the upsert pair.
|
|
115
120
|
*/
|
|
116
|
-
saveMany<E extends object>(entity: Type<E>, payload:
|
|
121
|
+
saveMany<E extends object>(entity: Type<E>, payload: EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
117
122
|
/** Writes each inserted row's relations, one set of statements per relation whatever the number of rows. */
|
|
118
123
|
protected insertRelations<E extends object>(entity: Type<E>, rows: E[]): Promise<void>;
|
|
119
124
|
/** `EntityId` because a settled composite row is an object, which {@link childrenOf} reads each foreign key column out of. */
|