@atscript/db-mongo 0.1.135 → 0.1.136
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/agg.cjs +7 -23
- package/dist/agg.mjs +6 -22
- package/dist/index.cjs +194 -143
- package/dist/index.d.cts +11 -2
- package/dist/index.d.mts +11 -2
- package/dist/index.mjs +181 -130
- package/dist/mongo-accumulator-C8J36t07.cjs +256 -0
- package/dist/mongo-accumulator-Dh6eEkyR.mjs +227 -0
- package/package.json +2 -2
- package/dist/mongo-filter-Bf5rWV4P.cjs +0 -73
- package/dist/mongo-filter-CtyITmZM.mjs +0 -68
package/dist/agg.cjs
CHANGED
|
@@ -1,27 +1,11 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
2
|
+
const require_mongo_accumulator = require("./mongo-accumulator-C8J36t07.cjs");
|
|
3
3
|
let _atscript_db_agg = require("@atscript/db/agg");
|
|
4
4
|
let _uniqu_core = require("@uniqu/core");
|
|
5
5
|
//#region src/agg.ts
|
|
6
|
-
/** `$group` accumulator
|
|
7
|
-
const ACCUMULATORS = {
|
|
8
|
-
sum: "$sum",
|
|
9
|
-
avg: "$avg",
|
|
10
|
-
min: "$min",
|
|
11
|
-
max: "$max"
|
|
12
|
-
};
|
|
13
|
-
/**
|
|
14
|
-
* Maps an AggregateExpr to a MongoDB $group accumulator expression.
|
|
15
|
-
*/
|
|
6
|
+
/** Maps an AggregateExpr to its MongoDB `$group` accumulator (see `buildAccumulator`). */
|
|
16
7
|
function toAccumulator(expr) {
|
|
17
|
-
|
|
18
|
-
if (expr.$fn !== "count") return { [ACCUMULATORS[expr.$fn]]: `$${expr.$field}` };
|
|
19
|
-
if (expr.$field === "*") return { $sum: 1 };
|
|
20
|
-
return { $sum: { $cond: [
|
|
21
|
-
{ $ne: [`$${expr.$field}`, null] },
|
|
22
|
-
1,
|
|
23
|
-
0
|
|
24
|
-
] } };
|
|
8
|
+
return require_mongo_accumulator.buildAccumulator(expr.$fn, expr.$field === "*" ? "*" : `$${expr.$field}`);
|
|
25
9
|
}
|
|
26
10
|
const DAY_MS = 864e5;
|
|
27
11
|
const ISO_DATE = "%Y-%m-%d";
|
|
@@ -120,13 +104,13 @@ function bucketExpression(b) {
|
|
|
120
104
|
function buildPrefix(query, searchStage) {
|
|
121
105
|
const controls = query.controls || {};
|
|
122
106
|
const groupBy = controls.$groupBy ?? [];
|
|
123
|
-
const pipeline = searchStage ? [searchStage, { $match:
|
|
107
|
+
const pipeline = searchStage ? [searchStage, { $match: require_mongo_accumulator.buildMongoFilter(query.filter) }] : [{ $match: require_mongo_accumulator.buildMongoFilter(query.filter) }];
|
|
124
108
|
const groupId = {};
|
|
125
109
|
const groupKeys = [];
|
|
126
110
|
for (const [index, key] of groupBy.entries()) {
|
|
127
111
|
const idKey = `k${index}`;
|
|
128
112
|
const bucket = controls.$select?.bucketByAlias(key);
|
|
129
|
-
groupId[idKey] = bucket ? bucketExpression(bucket) :
|
|
113
|
+
groupId[idKey] = bucket ? bucketExpression(bucket) : require_mongo_accumulator.orNull(`$${key}`);
|
|
130
114
|
groupKeys.push([key, idKey]);
|
|
131
115
|
}
|
|
132
116
|
return {
|
|
@@ -163,11 +147,11 @@ function buildGroupedStages(query, { accumulators, searchStage }) {
|
|
|
163
147
|
for (const expr of controls.$select?.aggregates ?? []) {
|
|
164
148
|
const alias = (0, _atscript_db_agg.resolveAlias)(expr);
|
|
165
149
|
groupStage[alias] = toAccumulator(expr);
|
|
166
|
-
project[alias] = 1;
|
|
150
|
+
project[alias] = expr.$fn === "countDistinct" ? require_mongo_accumulator.distinctCountExpr(`$${alias}`) : 1;
|
|
167
151
|
}
|
|
168
152
|
pipeline.push({ $group: groupStage });
|
|
169
153
|
pipeline.push({ $project: project });
|
|
170
|
-
if (controls.$having) pipeline.push({ $match:
|
|
154
|
+
if (controls.$having) pipeline.push({ $match: require_mongo_accumulator.buildMongoFilter(controls.$having) });
|
|
171
155
|
return {
|
|
172
156
|
pipeline,
|
|
173
157
|
controls
|
package/dist/agg.mjs
CHANGED
|
@@ -1,26 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { a as buildMongoFilter, n as distinctCountExpr, r as orNull, t as buildAccumulator } from "./mongo-accumulator-Dh6eEkyR.mjs";
|
|
2
|
+
import { resolveAlias } from "@atscript/db/agg";
|
|
3
3
|
import { BUCKET_MAX_INSTANT, BUCKET_MIN_INSTANT } from "@uniqu/core";
|
|
4
4
|
//#region src/agg.ts
|
|
5
|
-
/** `$group` accumulator
|
|
6
|
-
const ACCUMULATORS = {
|
|
7
|
-
sum: "$sum",
|
|
8
|
-
avg: "$avg",
|
|
9
|
-
min: "$min",
|
|
10
|
-
max: "$max"
|
|
11
|
-
};
|
|
12
|
-
/**
|
|
13
|
-
* Maps an AggregateExpr to a MongoDB $group accumulator expression.
|
|
14
|
-
*/
|
|
5
|
+
/** Maps an AggregateExpr to its MongoDB `$group` accumulator (see `buildAccumulator`). */
|
|
15
6
|
function toAccumulator(expr) {
|
|
16
|
-
|
|
17
|
-
if (expr.$fn !== "count") return { [ACCUMULATORS[expr.$fn]]: `$${expr.$field}` };
|
|
18
|
-
if (expr.$field === "*") return { $sum: 1 };
|
|
19
|
-
return { $sum: { $cond: [
|
|
20
|
-
{ $ne: [`$${expr.$field}`, null] },
|
|
21
|
-
1,
|
|
22
|
-
0
|
|
23
|
-
] } };
|
|
7
|
+
return buildAccumulator(expr.$fn, expr.$field === "*" ? "*" : `$${expr.$field}`);
|
|
24
8
|
}
|
|
25
9
|
const DAY_MS = 864e5;
|
|
26
10
|
const ISO_DATE = "%Y-%m-%d";
|
|
@@ -125,7 +109,7 @@ function buildPrefix(query, searchStage) {
|
|
|
125
109
|
for (const [index, key] of groupBy.entries()) {
|
|
126
110
|
const idKey = `k${index}`;
|
|
127
111
|
const bucket = controls.$select?.bucketByAlias(key);
|
|
128
|
-
groupId[idKey] = bucket ? bucketExpression(bucket) :
|
|
112
|
+
groupId[idKey] = bucket ? bucketExpression(bucket) : orNull(`$${key}`);
|
|
129
113
|
groupKeys.push([key, idKey]);
|
|
130
114
|
}
|
|
131
115
|
return {
|
|
@@ -162,7 +146,7 @@ function buildGroupedStages(query, { accumulators, searchStage }) {
|
|
|
162
146
|
for (const expr of controls.$select?.aggregates ?? []) {
|
|
163
147
|
const alias = resolveAlias(expr);
|
|
164
148
|
groupStage[alias] = toAccumulator(expr);
|
|
165
|
-
project[alias] = 1;
|
|
149
|
+
project[alias] = expr.$fn === "countDistinct" ? distinctCountExpr(`$${alias}`) : 1;
|
|
166
150
|
}
|
|
167
151
|
pipeline.push({ $group: groupStage });
|
|
168
152
|
pipeline.push({ $project: project });
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
2
|
+
const require_mongo_accumulator = require("./mongo-accumulator-C8J36t07.cjs");
|
|
3
3
|
let _atscript_db = require("@atscript/db");
|
|
4
4
|
let mongodb = require("mongodb");
|
|
5
5
|
let _atscript_db_agg = require("@atscript/db/agg");
|
|
@@ -498,7 +498,7 @@ function buildFromLookup(host, withRel, relation, tableResolver) {
|
|
|
498
498
|
if (!targetMeta) return;
|
|
499
499
|
const remoteFK = findRemoteFK(targetMeta, host._table.tableName, relation.alias);
|
|
500
500
|
if (!remoteFK) return;
|
|
501
|
-
const targetTableName =
|
|
501
|
+
const targetTableName = (0, _atscript_db.tableNameOf)(relation.targetType());
|
|
502
502
|
const innerPipeline = buildLookupInnerPipeline(withRel, remoteFK.fields);
|
|
503
503
|
const { letVars, exprMatch } = buildLookupJoin(remoteFK.targetFields, remoteFK.fields, "pk_");
|
|
504
504
|
const stages = [{ $lookup: {
|
|
@@ -523,8 +523,8 @@ function buildViaLookup(host, withRel, relation, tableResolver) {
|
|
|
523
523
|
if (!junctionType) return;
|
|
524
524
|
const junctionMeta = tableResolver(junctionType);
|
|
525
525
|
if (!junctionMeta) return;
|
|
526
|
-
const junctionTableName =
|
|
527
|
-
const targetTableName =
|
|
526
|
+
const junctionTableName = (0, _atscript_db.tableNameOf)(junctionType);
|
|
527
|
+
const targetTableName = (0, _atscript_db.tableNameOf)(relation.targetType());
|
|
528
528
|
const fkToThis = findRemoteFK(junctionMeta, host._table.tableName);
|
|
529
529
|
if (!fkToThis) return;
|
|
530
530
|
const fkToTarget = findRemoteFK(junctionMeta, targetTableName);
|
|
@@ -565,7 +565,7 @@ function buildLookupInnerPipeline(withRel, requiredFields) {
|
|
|
565
565
|
const limit = nested.$limit ?? flatRel.$limit;
|
|
566
566
|
const skip = nested.$skip ?? flatRel.$skip;
|
|
567
567
|
const select = nested.$select || flatRel.$select;
|
|
568
|
-
if (filter && Object.keys(filter).length > 0) pipeline.push({ $match:
|
|
568
|
+
if (filter && Object.keys(filter).length > 0) pipeline.push({ $match: require_mongo_accumulator.buildMongoFilter(filter) });
|
|
569
569
|
if (sort) pipeline.push({ $sort: sort });
|
|
570
570
|
if (skip) pipeline.push({ $skip: skip });
|
|
571
571
|
if (limit !== null && limit !== void 0) pipeline.push({ $limit: limit });
|
|
@@ -620,7 +620,7 @@ function mergeRelationResults(rows, results, primaryKeys, relMeta) {
|
|
|
620
620
|
}
|
|
621
621
|
/** Finds FK entry for a TO relation from this table's foreignKeys map. */
|
|
622
622
|
function findFKForRelation(relation, foreignKeys) {
|
|
623
|
-
const targetTableName =
|
|
623
|
+
const targetTableName = (0, _atscript_db.tableNameOf)(relation.targetType());
|
|
624
624
|
for (const fk of foreignKeys.values()) if (relation.alias) {
|
|
625
625
|
if (fk.alias === relation.alias) return {
|
|
626
626
|
localFields: fk.fields,
|
|
@@ -640,11 +640,6 @@ function findRemoteFK(target, thisTableName, alias) {
|
|
|
640
640
|
if (!alias && fk.targetTable === thisTableName) return fk;
|
|
641
641
|
}
|
|
642
642
|
}
|
|
643
|
-
/** Resolves the target table/collection name from a relation's target type. */
|
|
644
|
-
function resolveRelTargetTableName(relation) {
|
|
645
|
-
const targetType = relation.targetType();
|
|
646
|
-
return targetType?.metadata?.get("db.table") || targetType?.id || "";
|
|
647
|
-
}
|
|
648
643
|
//#endregion
|
|
649
644
|
//#region src/lib/mongo-search.ts
|
|
650
645
|
/** Returns available search indexes as generic metadata for UI. */
|
|
@@ -752,7 +747,7 @@ function buildGeoNearStage(host, point, query, indexName) {
|
|
|
752
747
|
distanceField: DISTANCE_FIELD,
|
|
753
748
|
spherical: true,
|
|
754
749
|
key: resolveGeoKeyPath(host, indexName),
|
|
755
|
-
query:
|
|
750
|
+
query: require_mongo_accumulator.buildMongoFilter(query.filter)
|
|
756
751
|
};
|
|
757
752
|
if (typeof controls.$maxDistance === "number") geoNear.maxDistance = controls.$maxDistance;
|
|
758
753
|
if (typeof controls.$minDistance === "number") geoNear.minDistance = controls.$minDistance;
|
|
@@ -965,7 +960,7 @@ function buildVectorSearchStage(host, vector, indexName, limit) {
|
|
|
965
960
|
}
|
|
966
961
|
/** Runs a search/vector pipeline and returns results. Shared by search + vectorSearch. */
|
|
967
962
|
async function runSearchPipeline(host, stage, query, label, threshold, classicText = false) {
|
|
968
|
-
const filter =
|
|
963
|
+
const filter = require_mongo_accumulator.buildMongoFilter(query.filter);
|
|
969
964
|
const controls = query.controls || {};
|
|
970
965
|
const pipeline = [stage];
|
|
971
966
|
if (threshold !== void 0) {
|
|
@@ -987,7 +982,7 @@ async function runSearchPipeline(host, stage, query, label, threshold, classicTe
|
|
|
987
982
|
}
|
|
988
983
|
/** Runs a search/vector pipeline with $facet for count. Shared by searchWithCount + vectorSearchWithCount. */
|
|
989
984
|
async function runSearchWithCountPipeline(host, stage, query, label, threshold, classicText = false) {
|
|
990
|
-
const filter =
|
|
985
|
+
const filter = require_mongo_accumulator.buildMongoFilter(query.filter);
|
|
991
986
|
const controls = query.controls || {};
|
|
992
987
|
const preStages = [];
|
|
993
988
|
if (threshold !== void 0) {
|
|
@@ -1020,6 +1015,175 @@ async function runSearchWithCountPipeline(host, stage, query, label, threshold,
|
|
|
1020
1015
|
};
|
|
1021
1016
|
}
|
|
1022
1017
|
//#endregion
|
|
1018
|
+
//#region src/lib/mongo-view-pipeline.ts
|
|
1019
|
+
/** Where a joined table's document lands (`$lookup.as`, then unwound). */
|
|
1020
|
+
function joinedAs(table) {
|
|
1021
|
+
return `${JOINED_PREFIX}${table}`;
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Builds the aggregation pipeline of a managed MongoDB view (created with
|
|
1025
|
+
* `viewOn: <entry collection>`):
|
|
1026
|
+
*
|
|
1027
|
+
* 1. per join, in declaration order: `$lookup` + `$unwind` (inner joins drop
|
|
1028
|
+
* unmatched documents, left joins keep them — `preserveNullAndEmptyArrays`);
|
|
1029
|
+
* 2. `@db.view.filter` → `$match`;
|
|
1030
|
+
* 3. aggregates → `$group` (+ `$addFields` turning `countDistinct` sets into
|
|
1031
|
+
* their sizes, + `@db.view.having` → `$match`) + `$project`, otherwise a
|
|
1032
|
+
* flat `$project`. A conditional aggregate's predicate reads the same
|
|
1033
|
+
* pipeline paths (SQL null semantics — `queryNodeToExpr`).
|
|
1034
|
+
*
|
|
1035
|
+
* All field paths are PHYSICAL document paths (`@db.column` renames), joined
|
|
1036
|
+
* tables live under `__joined_<table>`. A column whose source may be missing
|
|
1037
|
+
* (`nullable` mapping) reads as null; every other column is a plain path, so
|
|
1038
|
+
* a `$match` / `$sort` on the view can still push down to an index.
|
|
1039
|
+
* @since 0.1.136
|
|
1040
|
+
*/
|
|
1041
|
+
function buildViewPipeline(view) {
|
|
1042
|
+
const plan = view.viewPlan;
|
|
1043
|
+
const columns = view.getViewColumnMappings();
|
|
1044
|
+
const pipeline = [];
|
|
1045
|
+
const prefixes = new Map([[plan.entryTable, ""]]);
|
|
1046
|
+
const pathOf = (ref) => {
|
|
1047
|
+
const { table, source } = view.resolveRefSource(ref);
|
|
1048
|
+
const prefix = prefixes.get(table);
|
|
1049
|
+
if (prefix === void 0) throw new Error(`View "${view.tableName}": "${table}" is not joined before it is referenced`);
|
|
1050
|
+
return prefix + source.column;
|
|
1051
|
+
};
|
|
1052
|
+
for (const join of plan.joins) {
|
|
1053
|
+
pipeline.push(buildLookup(view, join, prefixes));
|
|
1054
|
+
pipeline.push({ $unwind: {
|
|
1055
|
+
path: `$${joinedAs(join.targetTable)}`,
|
|
1056
|
+
preserveNullAndEmptyArrays: join.kind === "left"
|
|
1057
|
+
} });
|
|
1058
|
+
prefixes.set(join.targetTable, `${joinedAs(join.targetTable)}.`);
|
|
1059
|
+
}
|
|
1060
|
+
if (plan.filter) pipeline.push({ $match: viewMatch(plan.filter, pathOf) });
|
|
1061
|
+
/** A column's source field as an aggregation operand. */
|
|
1062
|
+
const colSourceField = (col) => `$${prefixes.get(col.sourceTable) ?? ""}${col.sourceColumn}`;
|
|
1063
|
+
const colValue = (col) => col.nullable ? require_mongo_accumulator.orNull(colSourceField(col)) : colSourceField(col);
|
|
1064
|
+
if (columns.some((c) => c.aggFn)) {
|
|
1065
|
+
const group = { _id: {} };
|
|
1066
|
+
const project = { _id: 0 };
|
|
1067
|
+
const distinctSizes = {};
|
|
1068
|
+
for (const col of columns) if (col.aggFn) {
|
|
1069
|
+
const predicate = col.aggFilter ? require_mongo_accumulator.queryNodeToExpr(col.aggFilter, (ref) => `$${pathOf(ref)}`) : void 0;
|
|
1070
|
+
group[col.viewColumn] = require_mongo_accumulator.buildAccumulator(col.aggFn, col.aggField === "*" ? "*" : colSourceField(col), predicate, col.viewColumn);
|
|
1071
|
+
project[col.viewColumn] = `$${col.viewColumn}`;
|
|
1072
|
+
if (col.aggFn === "countDistinct") distinctSizes[col.viewColumn] = require_mongo_accumulator.distinctCountExpr(`$${col.viewColumn}`);
|
|
1073
|
+
} else {
|
|
1074
|
+
group._id[col.viewColumn] = colValue(col);
|
|
1075
|
+
project[col.viewColumn] = `$_id.${col.viewColumn}`;
|
|
1076
|
+
}
|
|
1077
|
+
pipeline.push({ $group: group });
|
|
1078
|
+
if (Object.keys(distinctSizes).length > 0) pipeline.push({ $addFields: distinctSizes });
|
|
1079
|
+
if (plan.having) pipeline.push({ $match: havingMatch(plan.having, columns) });
|
|
1080
|
+
pipeline.push({ $project: project });
|
|
1081
|
+
} else {
|
|
1082
|
+
const project = { _id: 0 };
|
|
1083
|
+
for (const col of columns) project[col.viewColumn] = colValue(col);
|
|
1084
|
+
pipeline.push({ $project: project });
|
|
1085
|
+
}
|
|
1086
|
+
return pipeline;
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* One join's `$lookup`.
|
|
1090
|
+
*
|
|
1091
|
+
* The simple `localField` / `foreignField` form is used whenever it is exact:
|
|
1092
|
+
* a single `=` between a field of the joined table and a field of an outer
|
|
1093
|
+
* table (the entry or an earlier join), with the joined field REQUIRED — the
|
|
1094
|
+
* simple form matches a null / missing local value to null / missing foreign
|
|
1095
|
+
* values, which a required foreign field can never hold. It is kept (rather
|
|
1096
|
+
* than always using the pipeline form) because it can use an index on the
|
|
1097
|
+
* foreign field on every MongoDB version; the pipeline form's `$expr` match
|
|
1098
|
+
* can't before MongoDB 5.0. Everything else uses the pipeline form (`let` +
|
|
1099
|
+
* `$match: { $expr }`, SQL null semantics).
|
|
1100
|
+
*/
|
|
1101
|
+
function buildLookup(view, join, outer) {
|
|
1102
|
+
const as = joinedAs(join.targetTable);
|
|
1103
|
+
const simple = simpleJoinFields(view, join, outer);
|
|
1104
|
+
if (simple) return { $lookup: {
|
|
1105
|
+
from: join.targetTable,
|
|
1106
|
+
...simple,
|
|
1107
|
+
as
|
|
1108
|
+
} };
|
|
1109
|
+
const letVars = {};
|
|
1110
|
+
const varByPath = /* @__PURE__ */ new Map();
|
|
1111
|
+
const pathOf = (ref) => {
|
|
1112
|
+
const { table, source } = view.resolveRefSource(ref);
|
|
1113
|
+
if (table === join.targetTable) return `$${source.column}`;
|
|
1114
|
+
const prefix = outer.get(table);
|
|
1115
|
+
if (prefix === void 0) throw new Error(`View "${view.tableName}": the join on "${join.targetTable}" references "${table}", which is not joined before it`);
|
|
1116
|
+
const outerPath = prefix + source.column;
|
|
1117
|
+
let varName = varByPath.get(outerPath);
|
|
1118
|
+
if (!varName) {
|
|
1119
|
+
varName = `v${varByPath.size}`;
|
|
1120
|
+
varByPath.set(outerPath, varName);
|
|
1121
|
+
letVars[varName] = `$${outerPath}`;
|
|
1122
|
+
}
|
|
1123
|
+
return `$$${varName}`;
|
|
1124
|
+
};
|
|
1125
|
+
const expr = require_mongo_accumulator.queryNodeToExpr(join.condition, pathOf);
|
|
1126
|
+
return { $lookup: {
|
|
1127
|
+
from: join.targetTable,
|
|
1128
|
+
...varByPath.size > 0 ? { let: letVars } : {},
|
|
1129
|
+
pipeline: [{ $match: { $expr: expr } }],
|
|
1130
|
+
as
|
|
1131
|
+
} };
|
|
1132
|
+
}
|
|
1133
|
+
/** `{ localField, foreignField }` when the simple `$lookup` form is exact, else `undefined`. */
|
|
1134
|
+
function simpleJoinFields(view, join, outer) {
|
|
1135
|
+
let node = join.condition;
|
|
1136
|
+
if ("$and" in node) {
|
|
1137
|
+
const children = node.$and;
|
|
1138
|
+
if (children.length !== 1) return void 0;
|
|
1139
|
+
node = children[0];
|
|
1140
|
+
}
|
|
1141
|
+
if ("$or" in node || "$not" in node || "$and" in node) return void 0;
|
|
1142
|
+
const comp = node;
|
|
1143
|
+
if (comp.op !== "$eq" || !(0, _atscript_db.isFieldRef)(comp.right)) return void 0;
|
|
1144
|
+
const left = view.resolveRefSource(comp.left);
|
|
1145
|
+
const right = view.resolveRefSource(comp.right);
|
|
1146
|
+
let target;
|
|
1147
|
+
let local;
|
|
1148
|
+
if (left.table === join.targetTable && right.table !== join.targetTable) {
|
|
1149
|
+
target = left;
|
|
1150
|
+
local = right;
|
|
1151
|
+
} else if (right.table === join.targetTable && left.table !== join.targetTable) {
|
|
1152
|
+
target = right;
|
|
1153
|
+
local = left;
|
|
1154
|
+
} else return;
|
|
1155
|
+
const localPrefix = outer.get(local.table);
|
|
1156
|
+
if (localPrefix === void 0) return void 0;
|
|
1157
|
+
const foreign = target.source;
|
|
1158
|
+
if (!(!foreign.optional && foreign.designType !== "union" && foreign.designType !== "unknown")) return void 0;
|
|
1159
|
+
return {
|
|
1160
|
+
localField: localPrefix + local.source.column,
|
|
1161
|
+
foreignField: foreign.column
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* A view predicate (`@db.view.filter`, `@db.view.having`) as a `$match`
|
|
1166
|
+
* over pipeline paths: the shared query translation (`translateQueryTree` →
|
|
1167
|
+
* `buildMongoFilter`), so operators mean what they mean in queries (`exists`
|
|
1168
|
+
* = holds a value, `matches` takes `/re/flags`); a field-to-field comparison
|
|
1169
|
+
* becomes `$expr`.
|
|
1170
|
+
*/
|
|
1171
|
+
function viewMatch(node, pathOf) {
|
|
1172
|
+
return require_mongo_accumulator.buildMongoFilter((0, _atscript_db.translateQueryTree)(node, pathOf), { fieldOperands: true });
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* `@db.view.having` as a `$match` after `$group`: refs name view fields —
|
|
1176
|
+
* aggregates are top-level, dimensions under `_id`.
|
|
1177
|
+
*/
|
|
1178
|
+
function havingMatch(node, columns) {
|
|
1179
|
+
const colMap = new Map(columns.map((c) => [c.viewPath, c]));
|
|
1180
|
+
return viewMatch(node, (ref) => {
|
|
1181
|
+
const col = ref.type ? void 0 : colMap.get(ref.field);
|
|
1182
|
+
if (!col) return ref.field;
|
|
1183
|
+
return col.aggFn ? col.viewColumn : `_id.${col.viewColumn}`;
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
//#endregion
|
|
1023
1187
|
//#region src/lib/mongo-schema-sync.ts
|
|
1024
1188
|
const DESTRUCTIVE_OPTION_KEYS = new Set([
|
|
1025
1189
|
"capped",
|
|
@@ -1080,134 +1244,17 @@ async function getObjectKindImpl(host, name) {
|
|
|
1080
1244
|
if (type === void 0) return;
|
|
1081
1245
|
return type === "view" ? "view" : "table";
|
|
1082
1246
|
}
|
|
1083
|
-
/** Creates a MongoDB view from the AtscriptDbView's view plan. */
|
|
1247
|
+
/** Creates a MongoDB view from the AtscriptDbView's view plan (pipeline: {@link buildViewPipeline}). */
|
|
1084
1248
|
async function ensureView(host, view) {
|
|
1085
1249
|
if (await host.collectionExists()) return;
|
|
1086
1250
|
const plan = view.viewPlan;
|
|
1087
|
-
const
|
|
1088
|
-
const pipeline = [];
|
|
1089
|
-
for (const join of plan.joins) {
|
|
1090
|
-
const { localField, foreignField } = resolveJoinFields(join.condition, plan.entryTable, join.targetTable);
|
|
1091
|
-
pipeline.push({ $lookup: {
|
|
1092
|
-
from: join.targetTable,
|
|
1093
|
-
localField,
|
|
1094
|
-
foreignField,
|
|
1095
|
-
as: `${JOINED_PREFIX}${join.targetTable}`
|
|
1096
|
-
} });
|
|
1097
|
-
pipeline.push({ $unwind: {
|
|
1098
|
-
path: `$__joined_${join.targetTable}`,
|
|
1099
|
-
preserveNullAndEmptyArrays: true
|
|
1100
|
-
} });
|
|
1101
|
-
}
|
|
1102
|
-
if (plan.filter) {
|
|
1103
|
-
const matchExpr = queryNodeToMatch(plan.filter, plan.entryTable);
|
|
1104
|
-
pipeline.push({ $match: matchExpr });
|
|
1105
|
-
}
|
|
1106
|
-
const hasAggregates = columns.some((c) => c.aggFn);
|
|
1107
|
-
/** Resolves a column to its MongoDB source field path. */
|
|
1108
|
-
const colSourceField = (col) => col.sourceTable === plan.entryTable ? `$${col.sourceColumn}` : `$${JOINED_PREFIX}${col.sourceTable}.${col.sourceColumn}`;
|
|
1109
|
-
if (hasAggregates) {
|
|
1110
|
-
const group = { _id: {} };
|
|
1111
|
-
const project = { _id: 0 };
|
|
1112
|
-
for (const col of columns) if (col.aggFn) {
|
|
1113
|
-
if (col.aggFn === "count" && col.aggField === "*") group[col.viewColumn] = { $sum: 1 };
|
|
1114
|
-
else group[col.viewColumn] = { [`$${col.aggFn}`]: colSourceField(col) };
|
|
1115
|
-
project[col.viewColumn] = `$${col.viewColumn}`;
|
|
1116
|
-
} else {
|
|
1117
|
-
group._id[col.viewColumn] = colSourceField(col);
|
|
1118
|
-
project[col.viewColumn] = `$_id.${col.viewColumn}`;
|
|
1119
|
-
}
|
|
1120
|
-
pipeline.push({ $group: group });
|
|
1121
|
-
if (plan.having) {
|
|
1122
|
-
const havingMatch = queryNodeToHaving(plan.having, columns);
|
|
1123
|
-
pipeline.push({ $match: havingMatch });
|
|
1124
|
-
}
|
|
1125
|
-
pipeline.push({ $project: project });
|
|
1126
|
-
} else {
|
|
1127
|
-
const project = { _id: 0 };
|
|
1128
|
-
for (const col of columns) project[col.viewColumn] = colSourceField(col);
|
|
1129
|
-
pipeline.push({ $project: project });
|
|
1130
|
-
}
|
|
1251
|
+
const pipeline = buildViewPipeline(view);
|
|
1131
1252
|
host._log("createView", host._table.tableName, plan.entryTable, pipeline);
|
|
1132
1253
|
await host.db.createCollection(host._table.tableName, {
|
|
1133
1254
|
viewOn: plan.entryTable,
|
|
1134
1255
|
pipeline
|
|
1135
1256
|
});
|
|
1136
1257
|
}
|
|
1137
|
-
/** Extracts localField/foreignField from a join condition. */
|
|
1138
|
-
function resolveJoinFields(condition, entryTable, joinTable) {
|
|
1139
|
-
const c = "$and" in condition ? condition.$and[0] : condition;
|
|
1140
|
-
if ((c.left.type ? c.left.type()?.metadata?.get("db.table") || "" : entryTable) === joinTable) return {
|
|
1141
|
-
localField: c.right.field,
|
|
1142
|
-
foreignField: c.left.field
|
|
1143
|
-
};
|
|
1144
|
-
return {
|
|
1145
|
-
localField: c.left.field,
|
|
1146
|
-
foreignField: c.right.field
|
|
1147
|
-
};
|
|
1148
|
-
}
|
|
1149
|
-
/** Translates an AtscriptQueryNode to a MongoDB $match expression. */
|
|
1150
|
-
function queryNodeToMatch(node, entryTable) {
|
|
1151
|
-
if ("$and" in node) {
|
|
1152
|
-
const items = node.$and;
|
|
1153
|
-
const result = [];
|
|
1154
|
-
for (const item of items) result.push(queryNodeToMatch(item, entryTable));
|
|
1155
|
-
return { $and: result };
|
|
1156
|
-
}
|
|
1157
|
-
if ("$or" in node) {
|
|
1158
|
-
const items = node.$or;
|
|
1159
|
-
const result = [];
|
|
1160
|
-
for (const item of items) result.push(queryNodeToMatch(item, entryTable));
|
|
1161
|
-
return { $or: result };
|
|
1162
|
-
}
|
|
1163
|
-
if ("$not" in node) return { $not: queryNodeToMatch(node.$not, entryTable) };
|
|
1164
|
-
const comp = node;
|
|
1165
|
-
const fieldPath = resolveViewFieldPath(comp.left, entryTable);
|
|
1166
|
-
if (comp.right && typeof comp.right === "object" && "field" in comp.right) {
|
|
1167
|
-
const rightPath = resolveViewFieldPath(comp.right, entryTable);
|
|
1168
|
-
return { $expr: { [comp.op]: [`$${fieldPath}`, `$${rightPath}`] } };
|
|
1169
|
-
}
|
|
1170
|
-
if (comp.op === "$eq") return { [fieldPath]: comp.right };
|
|
1171
|
-
if (comp.op === "$ne") return { [fieldPath]: { $ne: comp.right } };
|
|
1172
|
-
return { [fieldPath]: { [comp.op]: comp.right } };
|
|
1173
|
-
}
|
|
1174
|
-
/** Resolves a field ref to a MongoDB dot path for view pipeline expressions. */
|
|
1175
|
-
function resolveViewFieldPath(ref, entryTable) {
|
|
1176
|
-
if (!ref.type) return ref.field;
|
|
1177
|
-
const table = ref.type()?.metadata?.get("db.table") || "";
|
|
1178
|
-
if (table === entryTable) return ref.field;
|
|
1179
|
-
return `${JOINED_PREFIX}${table}.${ref.field}`;
|
|
1180
|
-
}
|
|
1181
|
-
/**
|
|
1182
|
-
* Translates an AtscriptQueryNode to a MongoDB $match for use after $group (HAVING).
|
|
1183
|
-
* After $group, aggregate fields are top-level and dimension fields are under _id.
|
|
1184
|
-
*/
|
|
1185
|
-
function queryNodeToHaving(node, columns) {
|
|
1186
|
-
const colMap = new Map(columns.map((c) => [c.viewColumn, c]));
|
|
1187
|
-
const resolveHavingField = (ref) => {
|
|
1188
|
-
if (!ref.type) {
|
|
1189
|
-
const col = colMap.get(ref.field);
|
|
1190
|
-
if (col?.aggFn) return ref.field;
|
|
1191
|
-
if (col) return `_id.${ref.field}`;
|
|
1192
|
-
}
|
|
1193
|
-
return ref.field;
|
|
1194
|
-
};
|
|
1195
|
-
return queryNodeToHavingInner(node, resolveHavingField);
|
|
1196
|
-
}
|
|
1197
|
-
function queryNodeToHavingInner(node, resolveField) {
|
|
1198
|
-
if ("$and" in node) return { $and: node.$and.map((n) => queryNodeToHavingInner(n, resolveField)) };
|
|
1199
|
-
if ("$or" in node) return { $or: node.$or.map((n) => queryNodeToHavingInner(n, resolveField)) };
|
|
1200
|
-
if ("$not" in node) return { $not: queryNodeToHavingInner(node.$not, resolveField) };
|
|
1201
|
-
const comp = node;
|
|
1202
|
-
const fieldPath = resolveField(comp.left);
|
|
1203
|
-
if (comp.right && typeof comp.right === "object" && "field" in comp.right) {
|
|
1204
|
-
const rightPath = resolveField(comp.right);
|
|
1205
|
-
return { $expr: { [comp.op]: [`$${fieldPath}`, `$${rightPath}`] } };
|
|
1206
|
-
}
|
|
1207
|
-
if (comp.op === "$eq") return { [fieldPath]: comp.right };
|
|
1208
|
-
if (comp.op === "$ne") return { [fieldPath]: { $ne: comp.right } };
|
|
1209
|
-
return { [fieldPath]: { [comp.op]: comp.right } };
|
|
1210
|
-
}
|
|
1211
1258
|
async function dropTableImpl(host) {
|
|
1212
1259
|
host._log("drop", host._table.tableName);
|
|
1213
1260
|
await host.collection.drop();
|
|
@@ -1767,6 +1814,10 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1767
1814
|
calendarBucketUnits() {
|
|
1768
1815
|
return _atscript_db.ALL_BUCKET_UNITS;
|
|
1769
1816
|
}
|
|
1817
|
+
/** Every aggregate function, `countDistinct` included. */
|
|
1818
|
+
aggregateFns() {
|
|
1819
|
+
return _atscript_db.ALL_AGGREGATE_FNS;
|
|
1820
|
+
}
|
|
1770
1821
|
getValidatorPlugins() {
|
|
1771
1822
|
return [validateMongoIdPlugin];
|
|
1772
1823
|
}
|
|
@@ -2030,7 +2081,7 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2030
2081
|
return geoSearchWithCountImpl(this, point, query, indexName);
|
|
2031
2082
|
}
|
|
2032
2083
|
async findManyWithCount(query) {
|
|
2033
|
-
const filter =
|
|
2084
|
+
const filter = require_mongo_accumulator.buildMongoFilter(query.filter);
|
|
2034
2085
|
const controls = query.controls || {};
|
|
2035
2086
|
const dataStages = [];
|
|
2036
2087
|
if (controls.$sort) dataStages.push({ $sort: controls.$sort });
|
|
@@ -2077,7 +2128,7 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2077
2128
|
_buildCasFilter(filter, expectedVersion, op) {
|
|
2078
2129
|
const versionColumn = this._table.versionColumn;
|
|
2079
2130
|
if (expectedVersion !== void 0 && versionColumn === void 0) throw new Error(`${op}: expectedVersion requires versionColumn`);
|
|
2080
|
-
const mongoFilter =
|
|
2131
|
+
const mongoFilter = require_mongo_accumulator.buildMongoFilter(filter);
|
|
2081
2132
|
if (expectedVersion !== void 0 && versionColumn !== void 0) mongoFilter[versionColumn] = expectedVersion;
|
|
2082
2133
|
return mongoFilter;
|
|
2083
2134
|
}
|
|
@@ -2128,7 +2179,7 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2128
2179
|
};
|
|
2129
2180
|
}
|
|
2130
2181
|
async findOne(query) {
|
|
2131
|
-
const filter =
|
|
2182
|
+
const filter = require_mongo_accumulator.buildMongoFilter(query.filter);
|
|
2132
2183
|
const opts = this._buildFindOptions(query.controls);
|
|
2133
2184
|
this._log("findOne", filter, opts);
|
|
2134
2185
|
return wrapInvalidQuery(() => this.collection.findOne(filter, {
|
|
@@ -2138,7 +2189,7 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2138
2189
|
}));
|
|
2139
2190
|
}
|
|
2140
2191
|
async findMany(query) {
|
|
2141
|
-
const filter =
|
|
2192
|
+
const filter = require_mongo_accumulator.buildMongoFilter(query.filter);
|
|
2142
2193
|
const opts = this._buildFindOptions(query.controls);
|
|
2143
2194
|
this._log("findMany", filter, opts);
|
|
2144
2195
|
return wrapInvalidQuery(() => this.collection.find(filter, {
|
|
@@ -2148,7 +2199,7 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2148
2199
|
}).toArray());
|
|
2149
2200
|
}
|
|
2150
2201
|
async count(query) {
|
|
2151
|
-
const filter =
|
|
2202
|
+
const filter = require_mongo_accumulator.buildMongoFilter(query.filter);
|
|
2152
2203
|
this._log("countDocuments", filter);
|
|
2153
2204
|
return this.collection.countDocuments(filter, {
|
|
2154
2205
|
...this._getCollationOpts(query),
|
|
@@ -2183,13 +2234,13 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2183
2234
|
};
|
|
2184
2235
|
}
|
|
2185
2236
|
async deleteOne(filter) {
|
|
2186
|
-
const mongoFilter =
|
|
2237
|
+
const mongoFilter = require_mongo_accumulator.buildMongoFilter(filter);
|
|
2187
2238
|
this._log("deleteOne", mongoFilter);
|
|
2188
2239
|
return { deletedCount: (await this.collection.deleteOne(mongoFilter, this._getSessionOpts())).deletedCount };
|
|
2189
2240
|
}
|
|
2190
2241
|
async updateMany(filter, data, ops) {
|
|
2191
2242
|
const versionColumn = this._table.versionColumn;
|
|
2192
|
-
const mongoFilter =
|
|
2243
|
+
const mongoFilter = require_mongo_accumulator.buildMongoFilter(filter);
|
|
2193
2244
|
const updateDoc = buildMongoUpdateDoc(data, ops, versionColumn);
|
|
2194
2245
|
this._log("updateMany", mongoFilter, updateDoc);
|
|
2195
2246
|
const result = await this.collection.updateMany(mongoFilter, updateDoc, this._getSessionOpts());
|
|
@@ -2199,7 +2250,7 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2199
2250
|
};
|
|
2200
2251
|
}
|
|
2201
2252
|
async replaceMany(filter, data) {
|
|
2202
|
-
const mongoFilter =
|
|
2253
|
+
const mongoFilter = require_mongo_accumulator.buildMongoFilter(filter);
|
|
2203
2254
|
const updateDoc = buildMongoUpdateDoc(data, void 0, this._table.versionColumn);
|
|
2204
2255
|
this._log("replaceMany", mongoFilter, updateDoc);
|
|
2205
2256
|
const result = await this.collection.updateMany(mongoFilter, updateDoc, this._getSessionOpts());
|
|
@@ -2209,7 +2260,7 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2209
2260
|
};
|
|
2210
2261
|
}
|
|
2211
2262
|
async deleteMany(filter) {
|
|
2212
|
-
const mongoFilter =
|
|
2263
|
+
const mongoFilter = require_mongo_accumulator.buildMongoFilter(filter);
|
|
2213
2264
|
this._log("deleteMany", mongoFilter);
|
|
2214
2265
|
return { deletedCount: (await this.collection.deleteMany(mongoFilter, this._getSessionOpts())).deletedCount };
|
|
2215
2266
|
}
|
|
@@ -2519,7 +2570,7 @@ function createAdapter(connection, _options) {
|
|
|
2519
2570
|
exports.CollectionPatcher = CollectionPatcher;
|
|
2520
2571
|
exports.INDEX_PREFIX = INDEX_PREFIX;
|
|
2521
2572
|
exports.MongoAdapter = MongoAdapter;
|
|
2522
|
-
exports.buildMongoFilter =
|
|
2573
|
+
exports.buildMongoFilter = require_mongo_accumulator.buildMongoFilter;
|
|
2523
2574
|
exports.createAdapter = createAdapter;
|
|
2524
2575
|
exports.mongoIndexKey = mongoIndexKey;
|
|
2525
2576
|
exports.validateMongoIdPlugin = validateMongoIdPlugin;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDeleteResult, TDbFieldMeta, TDbForeignKey, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbRelation, TDbUpdateResult, TExistingTableOption, TFieldOps, TMetadataOverrides, TPrimaryKeyChange, TSearchIndexInfo, TSyncColumnResult, TTableResolver, TableMetadata, WithRelation, getKeyProps } from "@atscript/db";
|
|
2
2
|
import { AggregationCursor, ClientSession, Collection, Db, Document, Filter, MongoClient, ObjectId, UpdateFilter, UpdateOptions } from "mongodb";
|
|
3
3
|
import { TAtscriptAnnotatedType, TMetadataMap, TValidatorOptions, TValidatorPlugin, Validator } from "@atscript/typescript/utils";
|
|
4
|
-
import { BucketUnit } from "@uniqu/core";
|
|
4
|
+
import { AggregateFn, BucketUnit } from "@uniqu/core";
|
|
5
5
|
|
|
6
6
|
//#region src/lib/collection-patcher.d.ts
|
|
7
7
|
/**
|
|
@@ -303,6 +303,8 @@ declare class MongoAdapter extends BaseDbAdapter {
|
|
|
303
303
|
supportsNativePatch(): boolean;
|
|
304
304
|
/** All five units, over MongoDB's bundled time zone database (see `agg.ts` `bucketExpression`). */
|
|
305
305
|
calendarBucketUnits(): ReadonlySet<BucketUnit>;
|
|
306
|
+
/** Every aggregate function, `countDistinct` included. */
|
|
307
|
+
aggregateFns(): ReadonlySet<AggregateFn>;
|
|
306
308
|
getValidatorPlugins(): ReturnType<BaseDbAdapter["getValidatorPlugins"]>;
|
|
307
309
|
/**
|
|
308
310
|
* Mongo can filter on JSON-stored fields natively — arrays via implicit
|
|
@@ -471,8 +473,15 @@ declare class MongoAdapter extends BaseDbAdapter {
|
|
|
471
473
|
*
|
|
472
474
|
* MongoDB's query language is nearly identical to the `FilterExpr` structure,
|
|
473
475
|
* so this is largely a structural pass-through via the `walkFilter` visitor.
|
|
476
|
+
* `fieldOperands` (view predicates only — `translateQueryTree` output) turns
|
|
477
|
+
* `{ $field: path }` operands into field-to-field `$expr` comparisons; a
|
|
478
|
+
* request filter never gets that reading.
|
|
474
479
|
*/
|
|
475
|
-
declare function buildMongoFilter(filter: FilterExpr
|
|
480
|
+
declare function buildMongoFilter(filter: FilterExpr, {
|
|
481
|
+
fieldOperands
|
|
482
|
+
}?: {
|
|
483
|
+
fieldOperands?: boolean;
|
|
484
|
+
}): Filter<any>;
|
|
476
485
|
//#endregion
|
|
477
486
|
//#region src/lib/validate-plugins.d.ts
|
|
478
487
|
declare const validateMongoIdPlugin: TValidatorPlugin;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDeleteResult, TDbFieldMeta, TDbForeignKey, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbRelation, TDbUpdateResult, TExistingTableOption, TFieldOps, TMetadataOverrides, TPrimaryKeyChange, TSearchIndexInfo, TSyncColumnResult, TTableResolver, TableMetadata, WithRelation, getKeyProps } from "@atscript/db";
|
|
2
2
|
import { AggregationCursor, ClientSession, Collection, Db, Document, Filter, MongoClient, ObjectId, UpdateFilter, UpdateOptions } from "mongodb";
|
|
3
|
-
import { BucketUnit } from "@uniqu/core";
|
|
3
|
+
import { AggregateFn, BucketUnit } from "@uniqu/core";
|
|
4
4
|
import { TAtscriptAnnotatedType, TMetadataMap, TValidatorOptions, TValidatorPlugin, Validator } from "@atscript/typescript/utils";
|
|
5
5
|
|
|
6
6
|
//#region src/lib/collection-patcher.d.ts
|
|
@@ -303,6 +303,8 @@ declare class MongoAdapter extends BaseDbAdapter {
|
|
|
303
303
|
supportsNativePatch(): boolean;
|
|
304
304
|
/** All five units, over MongoDB's bundled time zone database (see `agg.ts` `bucketExpression`). */
|
|
305
305
|
calendarBucketUnits(): ReadonlySet<BucketUnit>;
|
|
306
|
+
/** Every aggregate function, `countDistinct` included. */
|
|
307
|
+
aggregateFns(): ReadonlySet<AggregateFn>;
|
|
306
308
|
getValidatorPlugins(): ReturnType<BaseDbAdapter["getValidatorPlugins"]>;
|
|
307
309
|
/**
|
|
308
310
|
* Mongo can filter on JSON-stored fields natively — arrays via implicit
|
|
@@ -471,8 +473,15 @@ declare class MongoAdapter extends BaseDbAdapter {
|
|
|
471
473
|
*
|
|
472
474
|
* MongoDB's query language is nearly identical to the `FilterExpr` structure,
|
|
473
475
|
* so this is largely a structural pass-through via the `walkFilter` visitor.
|
|
476
|
+
* `fieldOperands` (view predicates only — `translateQueryTree` output) turns
|
|
477
|
+
* `{ $field: path }` operands into field-to-field `$expr` comparisons; a
|
|
478
|
+
* request filter never gets that reading.
|
|
474
479
|
*/
|
|
475
|
-
declare function buildMongoFilter(filter: FilterExpr
|
|
480
|
+
declare function buildMongoFilter(filter: FilterExpr, {
|
|
481
|
+
fieldOperands
|
|
482
|
+
}?: {
|
|
483
|
+
fieldOperands?: boolean;
|
|
484
|
+
}): Filter<any>;
|
|
476
485
|
//#endregion
|
|
477
486
|
//#region src/lib/validate-plugins.d.ts
|
|
478
487
|
declare const validateMongoIdPlugin: TValidatorPlugin;
|