@atscript/db 0.1.131 → 0.1.133
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 +19 -5
- package/dist/agg.d.cts +3 -4
- package/dist/agg.d.mts +3 -4
- package/dist/agg.mjs +2 -5
- package/dist/{db-readable-D8aa-wfP.d.mts → buckets-BENuu_tK.d.mts} +1346 -1141
- package/dist/{db-readable-Cmrg9aLU.d.cts → buckets-CTWy7Y1k.d.cts} +1346 -1141
- package/dist/{db-error-COrO58t5.mjs → db-error-D5uilS_A.mjs} +13 -1
- package/dist/{db-error-C4JuLcvb.cjs → db-error-DTkkeu5b.cjs} +18 -0
- package/dist/{db-space-B4MjNjY4.d.mts → db-space-Bv3Ar_Xd.d.mts} +1 -1
- package/dist/{db-space-rQFgz3-k.d.cts → db-space-DROLifb-.d.cts} +1 -1
- package/dist/{db-view-DUDnyQ3b.cjs → db-view-B5-bQ5AV.cjs} +552 -120
- package/dist/{db-view-BYH4HJ-U.mjs → db-view-caPATLmX.mjs} +486 -120
- package/dist/index.cjs +16 -4
- package/dist/index.d.cts +163 -38
- package/dist/index.d.mts +163 -38
- package/dist/index.mjs +5 -5
- package/dist/{nested-writer-CkDo-ZfH.mjs → nested-writer-CqL24ojl.mjs} +1 -1
- package/dist/{nested-writer-DxPhmWFz.cjs → nested-writer-wk1EFUNY.cjs} +1 -1
- package/dist/ops.cjs +1 -1
- package/dist/ops.mjs +1 -1
- package/dist/rel.cjs +2 -2
- package/dist/rel.d.cts +1 -1
- package/dist/rel.d.mts +1 -1
- package/dist/rel.mjs +2 -2
- package/dist/{relation-loader-CUGcxJ18.mjs → relation-loader-B68R1LET.mjs} +1 -1
- package/dist/{relation-loader-C8GOpNYJ.cjs → relation-loader-BD4xANQJ.cjs} +1 -1
- package/dist/sync.cjs +1 -1
- package/dist/sync.d.cts +2 -2
- package/dist/sync.d.mts +2 -2
- package/dist/sync.mjs +1 -1
- package/dist/{validator-lkCJKuoo.cjs → validator-BtZbcLN2.cjs} +1 -1
- package/dist/{validator-wBARmD68.d.cts → validator-CewfnGZj.d.cts} +9 -2
- package/dist/{validator-wBARmD68.d.mts → validator-CewfnGZj.d.mts} +9 -2
- package/dist/{validator-CeD_fqyW.mjs → validator-Ch7UIQl9.mjs} +1 -1
- package/dist/validator.cjs +2 -2
- package/dist/validator.d.cts +1 -1
- package/dist/validator.d.mts +1 -1
- package/dist/validator.mjs +2 -2
- package/package.json +3 -3
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
const require_db_error = require("./db-error-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const require_nested_writer = require("./nested-writer-
|
|
1
|
+
const require_db_error = require("./db-error-DTkkeu5b.cjs");
|
|
2
|
+
require("./agg.cjs");
|
|
3
|
+
const require_validator = require("./validator-BtZbcLN2.cjs");
|
|
4
|
+
const require_nested_writer = require("./nested-writer-wk1EFUNY.cjs");
|
|
5
5
|
const require_ops = require("./ops.cjs");
|
|
6
6
|
let _atscript_typescript_utils = require("@atscript/typescript/utils");
|
|
7
|
+
let _uniqu_core = require("@uniqu/core");
|
|
7
8
|
let node_async_hooks = require("node:async_hooks");
|
|
8
9
|
//#region src/logger.ts
|
|
9
10
|
const NoopLogger = {
|
|
@@ -14,6 +15,83 @@ const NoopLogger = {
|
|
|
14
15
|
debug: () => {}
|
|
15
16
|
};
|
|
16
17
|
//#endregion
|
|
18
|
+
//#region src/query/buckets.ts
|
|
19
|
+
/**
|
|
20
|
+
* The one normalizer of `$select` computed entries (since 0.1.132) — uniqu's
|
|
21
|
+
* `resolveBuckets` (entry shapes, unit, time zone canonicalization, week
|
|
22
|
+
* start, alias syntax and uniqueness, "grouped queries only", "must also
|
|
23
|
+
* appear in $groupBy", string `$groupBy` entries) with the table's names as
|
|
24
|
+
* the collision set: a bucket alias may not equal a logical path, a physical
|
|
25
|
+
* column or a navigation field, so a label is never reverse-mapped as a
|
|
26
|
+
* column.
|
|
27
|
+
*
|
|
28
|
+
* Which layer validates what:
|
|
29
|
+
* - **Shapes** (this normalizer) run FIRST at every entry point — the core's
|
|
30
|
+
* read path (`guardQuery`), its aggregate path (`AtscriptDbReadable.aggregate`,
|
|
31
|
+
* which hands the result on to `guardAggregate` and the field mapper), and
|
|
32
|
+
* moost-db's HTTP gate — so both layers answer with the same wording.
|
|
33
|
+
* Everything downstream (`collectQueryPaths`, the field mappers,
|
|
34
|
+
* `UniquSelect`, adapters) assumes normalized input and does not re-check.
|
|
35
|
+
* - **The source field** (encryption, JSON ancestor, timestamp type,
|
|
36
|
+
* physical filterability, strict-mode dimension, an adapter with calendar
|
|
37
|
+
* buckets) is `bucketSourceVerdict` — ONE function, called by the path
|
|
38
|
+
* guard (`guardPath` op `bucket`) and by moost-db's capability index, so
|
|
39
|
+
* `/meta.fields[P].bucketable` and the core cannot disagree.
|
|
40
|
+
* - **The unit** (`calendarBucketUnits()` lacks it) is `guardAggregate`'s
|
|
41
|
+
* (`BUCKET_NOT_SUPPORTED`); SQL builders only re-assert the inlined
|
|
42
|
+
* literals (defense in depth).
|
|
43
|
+
*
|
|
44
|
+
* `aggregate` defaults to "`$groupBy` is non-empty".
|
|
45
|
+
*
|
|
46
|
+
* @throws DbError `INVALID_QUERY` carrying every issue (`path` `$select` / `$groupBy`).
|
|
47
|
+
*/
|
|
48
|
+
function resolveCalendarBuckets(controls, fields, aggregate) {
|
|
49
|
+
const res = (0, _uniqu_core.resolveBuckets)(controls, {
|
|
50
|
+
aggregate,
|
|
51
|
+
isField: (name) => fields.flatMap.has(name) || fields.navFields.has(name) || fields.physicalNames.has(name)
|
|
52
|
+
});
|
|
53
|
+
if (!res.ok) throw new require_db_error.DbError("INVALID_QUERY", res.issues);
|
|
54
|
+
return res.buckets;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Whether a field's TYPE allows it to be the source of a calendar bucket: a
|
|
58
|
+
* `number` / `integer` leaf carrying the `timestamp` tag
|
|
59
|
+
* (`number.timestamp`, `.created`, `.updated`) that is not
|
|
60
|
+
* `@db.encrypted`. The type is the declaration — no annotation opts a field
|
|
61
|
+
* in.
|
|
62
|
+
*
|
|
63
|
+
* @deprecated since 0.1.133 — the type rule alone; use `bucketSourceVerdict`,
|
|
64
|
+
* the full bucket-source rule the core and moost-db apply.
|
|
65
|
+
*/
|
|
66
|
+
function isBucketableField(fd) {
|
|
67
|
+
if (fd.encrypted) return false;
|
|
68
|
+
if (fd.designType !== "number" && fd.designType !== "integer") return false;
|
|
69
|
+
return ((fd.type?.type)?.tags)?.has("timestamp") === true;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Whether a field holds a JSON value — a `@db.json` object / JSON-stored
|
|
73
|
+
* column or an array. The members of `TableMetadata.jsonValueParents` (and
|
|
74
|
+
* moost-db's equivalent set) — see {@link jsonValueAncestor}.
|
|
75
|
+
*/
|
|
76
|
+
function isJsonValueField(fd) {
|
|
77
|
+
return fd.storage === "json" || fd.designType === "json" || fd.designType === "array";
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The outermost ancestor of `path` in `jsonValueParents` (the paths of the
|
|
81
|
+
* {@link isJsonValueField} descriptors), or `undefined`. A timestamp inside a
|
|
82
|
+
* JSON value is never a bucket source — relational adapters cannot address
|
|
83
|
+
* it and nested-object adapters (which can) must not diverge from them.
|
|
84
|
+
*/
|
|
85
|
+
function jsonValueAncestor(path, jsonValueParents) {
|
|
86
|
+
if (jsonValueParents.size === 0) return void 0;
|
|
87
|
+
let pos = path.indexOf(".");
|
|
88
|
+
while (pos !== -1) {
|
|
89
|
+
const ancestor = path.slice(0, pos);
|
|
90
|
+
if (jsonValueParents.has(ancestor)) return ancestor;
|
|
91
|
+
pos = path.indexOf(".", pos + 1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
17
95
|
//#region src/table/table-metadata.ts
|
|
18
96
|
const INDEX_PREFIX = "atscript__";
|
|
19
97
|
function indexKey(type, name) {
|
|
@@ -124,6 +202,14 @@ var TableMetadata = class {
|
|
|
124
202
|
* dotted paths as descriptors).
|
|
125
203
|
*/
|
|
126
204
|
jsonParents = /* @__PURE__ */ new Set();
|
|
205
|
+
/**
|
|
206
|
+
* Logical paths holding a JSON value (`isJsonValueField`: JSON-stored, `json`
|
|
207
|
+
* or `array` design type; non-ignored, nav-free descriptors) — a timestamp
|
|
208
|
+
* beneath one is never a calendar-bucket source (`jsonValueAncestor`).
|
|
209
|
+
*/
|
|
210
|
+
jsonValueParents = /* @__PURE__ */ new Set();
|
|
211
|
+
/** Every field descriptor's `physicalName` — reserved names a bucket alias may not take. */
|
|
212
|
+
physicalNames = /* @__PURE__ */ new Set();
|
|
127
213
|
_built = false;
|
|
128
214
|
_identifications;
|
|
129
215
|
_collateMap = /* @__PURE__ */ new Map();
|
|
@@ -135,6 +221,21 @@ var TableMetadata = class {
|
|
|
135
221
|
return this._built;
|
|
136
222
|
}
|
|
137
223
|
/**
|
|
224
|
+
* Logical field path → its physical path in document storage (nested
|
|
225
|
+
* objects kept inline). `@db.column` renames apply to the annotated key,
|
|
226
|
+
* and a document renames the TOP-LEVEL key only — nested keys are stored
|
|
227
|
+
* as-is — so a dotted path under a renamed top-level object renames its
|
|
228
|
+
* first segment: `profile.bio` under `@db.column 'prof'` → `prof.bio`.
|
|
229
|
+
*/
|
|
230
|
+
documentPath(path) {
|
|
231
|
+
const direct = this.columnMap.get(path);
|
|
232
|
+
if (direct !== void 0) return direct;
|
|
233
|
+
const dot = path.indexOf(".");
|
|
234
|
+
if (dot === -1) return path;
|
|
235
|
+
const top = this.columnMap.get(path.slice(0, dot));
|
|
236
|
+
return top === void 0 ? path : top + path.slice(dot);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
138
239
|
* Runs the full metadata compilation pipeline. Called once by
|
|
139
240
|
* `AtscriptDbReadable._ensureBuilt()` on first metadata access.
|
|
140
241
|
*
|
|
@@ -187,7 +288,7 @@ var TableMetadata = class {
|
|
|
187
288
|
this._built = true;
|
|
188
289
|
adapter.onAfterFlatten?.();
|
|
189
290
|
if (this.nestedObjects && this.flatMap) {
|
|
190
|
-
for (const path of this.flatMap.keys()) if (path && !this.ignoredFields.has(path) && !this.navFields.has(path) && findAncestorInSet(path, this.navFields) === void 0) this.allPhysicalFields.push(path);
|
|
291
|
+
for (const path of this.flatMap.keys()) if (path && !this.ignoredFields.has(path) && !this.navFields.has(path) && findAncestorInSet(path, this.navFields) === void 0) this.allPhysicalFields.push(this.documentPath(path));
|
|
191
292
|
} else for (const [path, physical] of this.pathToPhysical) {
|
|
192
293
|
if (this.navFields.has(path)) continue;
|
|
193
294
|
if (findAncestorInSet(path, this.navFields) !== void 0) continue;
|
|
@@ -419,19 +520,25 @@ var TableMetadata = class {
|
|
|
419
520
|
/** Nearest `@db.encrypted` ancestor of `path` (exclusive), or `undefined`. */
|
|
420
521
|
/**
|
|
421
522
|
* Indexes non-ignored descriptors by logical path and retains the JSON-parent
|
|
422
|
-
*
|
|
523
|
+
* sets (plus every descriptor's physical name). Navigation relations and their descendants are skipped even when the
|
|
423
524
|
* adapter keeps them as descriptors (nested-object adapters do) — they are
|
|
424
525
|
* loaded with `$with`, never addressed as columns of this table.
|
|
425
526
|
*/
|
|
426
527
|
_buildGuardIndexes() {
|
|
427
528
|
const jsonParents = /* @__PURE__ */ new Set();
|
|
529
|
+
const jsonValueParents = /* @__PURE__ */ new Set();
|
|
530
|
+
const physicalNames = /* @__PURE__ */ new Set();
|
|
428
531
|
for (const fd of this.fieldDescriptors) {
|
|
532
|
+
physicalNames.add(fd.physicalName);
|
|
429
533
|
if (fd.ignored) continue;
|
|
430
534
|
if (this.navFields.has(fd.path) || findAncestorInSet(fd.path, this.navFields) !== void 0) continue;
|
|
431
535
|
this.descriptorByPath.set(fd.path, fd);
|
|
432
536
|
if (fd.storage === "json") jsonParents.add(fd.path);
|
|
537
|
+
if (isJsonValueField(fd)) jsonValueParents.add(fd.path);
|
|
433
538
|
}
|
|
434
539
|
this.jsonParents = jsonParents;
|
|
540
|
+
this.jsonValueParents = jsonValueParents;
|
|
541
|
+
this.physicalNames = physicalNames;
|
|
435
542
|
}
|
|
436
543
|
/**
|
|
437
544
|
* Indexes `fieldDescriptors` into two lookup maps for unified
|
|
@@ -653,6 +760,11 @@ var TableMetadata = class {
|
|
|
653
760
|
* `controls.$select` is `UniquSelect | undefined`.
|
|
654
761
|
*
|
|
655
762
|
* For exclusion → inclusion inversion, pass `allFields` (physical field names).
|
|
763
|
+
*
|
|
764
|
+
* An array `$select` holds plain field names and computed entries —
|
|
765
|
+
* aggregates (`{ $fn, $field }`, {@link aggregates}) and calendar buckets
|
|
766
|
+
* (`{ $bucket, $field }`, {@link buckets}). Entries arrive normalized
|
|
767
|
+
* (`resolveCalendarBuckets` rejects any other shape before translation).
|
|
656
768
|
*/
|
|
657
769
|
var UniquSelect = class UniquSelect {
|
|
658
770
|
static UNRESOLVED = Symbol("unresolved");
|
|
@@ -661,17 +773,29 @@ var UniquSelect = class UniquSelect {
|
|
|
661
773
|
_array = UniquSelect.UNRESOLVED;
|
|
662
774
|
_projection = UniquSelect.UNRESOLVED;
|
|
663
775
|
_aggregates = UniquSelect.UNRESOLVED;
|
|
664
|
-
|
|
776
|
+
/**
|
|
777
|
+
* The calendar buckets of an aggregate `$select`, normalized (canonical
|
|
778
|
+
* zone, week start, alias) with the PHYSICAL source `field` and its
|
|
779
|
+
* descriptor `fd`. `undefined` when there are none. A `$groupBy` key equal
|
|
780
|
+
* to a bucket's `alias` groups by that bucket (aliases never collide with
|
|
781
|
+
* columns). Since 0.1.132.
|
|
782
|
+
*/
|
|
783
|
+
buckets;
|
|
784
|
+
/**
|
|
785
|
+
* @param raw - the `$select` value (field paths already physical).
|
|
786
|
+
* @param allFields - physical field names, for exclusion-form inversion.
|
|
787
|
+
* @param buckets - the resolved calendar buckets of the raw `$select`'s
|
|
788
|
+
* `{ $bucket }` entries (the field mappers supply them — physical `field`,
|
|
789
|
+
* source `fd`).
|
|
790
|
+
*/
|
|
791
|
+
constructor(raw, allFields, buckets) {
|
|
665
792
|
this._raw = raw;
|
|
666
793
|
this._allFields = allFields;
|
|
667
|
-
|
|
668
|
-
/** Type guard: checks if a value is an AggregateExpr ({$fn, $field}). */
|
|
669
|
-
static _isAggregateExpr(v) {
|
|
670
|
-
return typeof v === "object" && v !== null && "$fn" in v && "$field" in v;
|
|
794
|
+
this.buckets = buckets?.length ? buckets : void 0;
|
|
671
795
|
}
|
|
672
796
|
/**
|
|
673
797
|
* Resolved inclusion array of plain field names (strings only).
|
|
674
|
-
*
|
|
798
|
+
* Computed entries (aggregates, calendar buckets) are filtered out.
|
|
675
799
|
* For exclusion form, inverts using `allFields` from constructor.
|
|
676
800
|
*/
|
|
677
801
|
get asArray() {
|
|
@@ -726,7 +850,7 @@ var UniquSelect = class UniquSelect {
|
|
|
726
850
|
this._aggregates = void 0;
|
|
727
851
|
return;
|
|
728
852
|
}
|
|
729
|
-
const aggs = this._raw.filter(
|
|
853
|
+
const aggs = this._raw.filter(_uniqu_core.isAggregateExpr);
|
|
730
854
|
this._aggregates = aggs.length > 0 ? aggs : void 0;
|
|
731
855
|
return this._aggregates;
|
|
732
856
|
}
|
|
@@ -734,6 +858,10 @@ var UniquSelect = class UniquSelect {
|
|
|
734
858
|
get hasAggregates() {
|
|
735
859
|
return !!this.aggregates?.length;
|
|
736
860
|
}
|
|
861
|
+
/** The calendar bucket whose alias is `key`, if any — how adapters resolve a `$groupBy` / `$having` key. */
|
|
862
|
+
bucketByAlias(key) {
|
|
863
|
+
return this.buckets?.find((b) => b.alias === key);
|
|
864
|
+
}
|
|
737
865
|
};
|
|
738
866
|
//#endregion
|
|
739
867
|
//#region src/strategies/field-mapping.ts
|
|
@@ -754,9 +882,83 @@ function toDecimalString(value) {
|
|
|
754
882
|
* and `RelationalFieldMapper` (flattened columns, SQL).
|
|
755
883
|
*/
|
|
756
884
|
var FieldMappingStrategy = class {
|
|
885
|
+
/**
|
|
886
|
+
* Whether {@link physicalPath} can differ from the logical path for this
|
|
887
|
+
* table; `false` lets the path translations hand their input back as-is.
|
|
888
|
+
*/
|
|
889
|
+
renamesPaths(_meta) {
|
|
890
|
+
return true;
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Translates a grouped query to physical names: the filter and `$having`
|
|
894
|
+
* through {@link translateFilter}, and every field path in `$groupBy`,
|
|
895
|
+
* `$select` (plain and computed `$field`s) and `$sort` through
|
|
896
|
+
* {@link physicalPath}. Computed aliases pass through (a bucket alias never
|
|
897
|
+
* equals a field name).
|
|
898
|
+
*
|
|
899
|
+
* `buckets` are the query's calendar buckets as the core's normalizer
|
|
900
|
+
* resolved them (`resolveCalendarBuckets` — `AtscriptDbReadable.aggregate`
|
|
901
|
+
* runs it before the guards); they reach adapters with `field` made
|
|
902
|
+
* physical and the source descriptor as `fd`.
|
|
903
|
+
*/
|
|
904
|
+
translateAggregateQuery(query, meta, buckets) {
|
|
905
|
+
const controls = query.controls;
|
|
906
|
+
const aliases = this.computedAliasSet(controls.$select);
|
|
907
|
+
const physicalBuckets = buckets.map((b) => ({
|
|
908
|
+
...b,
|
|
909
|
+
field: this.physicalPath(b.field, meta),
|
|
910
|
+
fd: meta.descriptorByPath.get(b.field)
|
|
911
|
+
}));
|
|
912
|
+
const select = controls.$select && this.physicalSelect(controls.$select, meta);
|
|
913
|
+
return {
|
|
914
|
+
filter: this.translateFilter(query.filter ?? {}, meta),
|
|
915
|
+
controls: {
|
|
916
|
+
...controls,
|
|
917
|
+
$with: void 0,
|
|
918
|
+
$groupBy: this.renamesPaths(meta) ? controls.$groupBy.map((key) => aliases.has(key) ? key : this.physicalPath(key, meta)) : controls.$groupBy,
|
|
919
|
+
$select: select ? new UniquSelect(select, meta.allPhysicalFields, physicalBuckets) : void 0,
|
|
920
|
+
$sort: controls.$sort && this.physicalSort(controls.$sort, meta, aliases),
|
|
921
|
+
$having: controls.$having ? this.translateFilter(controls.$having, meta) : void 0
|
|
922
|
+
},
|
|
923
|
+
insights: query.insights
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
/** Output aliases of the computed `$select` entries (aggregates and calendar buckets). */
|
|
927
|
+
computedAliasSet(select) {
|
|
928
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
929
|
+
for (const item of select ?? []) if ((0, _uniqu_core.isAggregateExpr)(item) || (0, _uniqu_core.isBucketExpr)(item)) aliases.add((0, _uniqu_core.resolveAlias)(item));
|
|
930
|
+
return aliases;
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* `$select` with its field paths made physical: array-form names and
|
|
934
|
+
* computed `$field`s (`'*'` kept), or the keys of the object
|
|
935
|
+
* (inclusion / exclusion) form.
|
|
936
|
+
*/
|
|
937
|
+
physicalSelect(select, meta) {
|
|
938
|
+
if (!this.renamesPaths(meta)) return select;
|
|
939
|
+
if (Array.isArray(select)) return select.map((item) => {
|
|
940
|
+
if (typeof item === "string") return this.physicalPath(item, meta);
|
|
941
|
+
if ((0, _uniqu_core.isAggregateExpr)(item) || (0, _uniqu_core.isBucketExpr)(item)) return item.$field === "*" ? item : {
|
|
942
|
+
...item,
|
|
943
|
+
$field: this.physicalPath(item.$field, meta)
|
|
944
|
+
};
|
|
945
|
+
return item;
|
|
946
|
+
});
|
|
947
|
+
const translated = {};
|
|
948
|
+
for (const [key, flag] of Object.entries(select)) translated[this.physicalPath(key, meta)] = flag;
|
|
949
|
+
return translated;
|
|
950
|
+
}
|
|
951
|
+
/** `$sort` with physical keys; computed `aliases` (grouped queries) pass through. */
|
|
952
|
+
physicalSort(sort, meta, aliases) {
|
|
953
|
+
if (!this.renamesPaths(meta)) return sort;
|
|
954
|
+
const translated = {};
|
|
955
|
+
for (const [key, dir] of Object.entries(sort)) translated[aliases?.has(key) ? key : this.physicalPath(key, meta)] = dir;
|
|
956
|
+
return translated;
|
|
957
|
+
}
|
|
757
958
|
/**
|
|
758
959
|
* Recursively walks a filter expression, applying `@db.column` key renames
|
|
759
|
-
*
|
|
960
|
+
* (document paths — {@link TableMetadata.documentPath}) and adapter-specific
|
|
961
|
+
* value formatting via `formatFilterValue`.
|
|
760
962
|
*
|
|
761
963
|
* The relational mapper overrides this to use `leafByLogical` for deeper
|
|
762
964
|
* key resolution (flattened nested paths).
|
|
@@ -769,8 +971,8 @@ var FieldMappingStrategy = class {
|
|
|
769
971
|
else if (key === "$not") result[key] = this.translateFilter(value, meta);
|
|
770
972
|
else if (key.startsWith("$")) result[key] = value;
|
|
771
973
|
else {
|
|
772
|
-
const
|
|
773
|
-
result[
|
|
974
|
+
const formatKey = meta.columnMap.get(key) ?? key;
|
|
975
|
+
result[meta.documentPath(key)] = this.formatFilterValue(formatKey, value, meta);
|
|
774
976
|
}
|
|
775
977
|
return result;
|
|
776
978
|
}
|
|
@@ -898,29 +1100,31 @@ var DocumentFieldMapper = class extends FieldMappingStrategy {
|
|
|
898
1100
|
if (meta.columnMap.size > 0) this.reverseColumnRenames(row, meta);
|
|
899
1101
|
return row;
|
|
900
1102
|
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Every field-path position goes through `@db.column` renames
|
|
1105
|
+
* ({@link TableMetadata.documentPath}): filter keys, `$select` fields
|
|
1106
|
+
* (array, inclusion and exclusion forms) and `$sort` keys.
|
|
1107
|
+
*/
|
|
901
1108
|
translateQuery(query, meta) {
|
|
902
1109
|
const controls = query.controls;
|
|
1110
|
+
const select = controls?.$select && this.physicalSelect(controls.$select, meta);
|
|
903
1111
|
return {
|
|
904
1112
|
filter: this.translateFilter(query.filter, meta),
|
|
905
1113
|
controls: {
|
|
906
1114
|
...controls,
|
|
907
1115
|
$with: void 0,
|
|
908
|
-
$select:
|
|
1116
|
+
$select: select ? new UniquSelect(select, meta.allPhysicalFields) : void 0,
|
|
1117
|
+
$sort: controls?.$sort && this.physicalSort(controls.$sort, meta)
|
|
909
1118
|
},
|
|
910
1119
|
insights: query.insights
|
|
911
1120
|
};
|
|
912
1121
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
return
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
$with: void 0,
|
|
920
|
-
$select: controls.$select ? new UniquSelect(controls.$select, meta.allPhysicalFields) : void 0
|
|
921
|
-
},
|
|
922
|
-
insights: query.insights
|
|
923
|
-
};
|
|
1122
|
+
/** A document path with `@db.column` renames ({@link TableMetadata.documentPath}). */
|
|
1123
|
+
physicalPath(logical, meta) {
|
|
1124
|
+
return meta.documentPath(logical);
|
|
1125
|
+
}
|
|
1126
|
+
renamesPaths(meta) {
|
|
1127
|
+
return meta.columnMap.size > 0;
|
|
924
1128
|
}
|
|
925
1129
|
prepareForWrite(payload, meta, adapter) {
|
|
926
1130
|
const data = { ...payload };
|
|
@@ -995,46 +1199,9 @@ var RelationalFieldMapper = class extends FieldMappingStrategy {
|
|
|
995
1199
|
insights: query.insights
|
|
996
1200
|
};
|
|
997
1201
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
const groupBy = controls.$groupBy.map((field) => meta.leafByLogical.get(field)?.physicalName ?? field);
|
|
1002
|
-
let select;
|
|
1003
|
-
if (controls.$select) select = controls.$select.map((item) => {
|
|
1004
|
-
if (typeof item === "string") return meta.leafByLogical.get(item)?.physicalName ?? item;
|
|
1005
|
-
if (item.$field === "*") return item;
|
|
1006
|
-
return {
|
|
1007
|
-
...item,
|
|
1008
|
-
$field: meta.leafByLogical.get(item.$field)?.physicalName ?? item.$field
|
|
1009
|
-
};
|
|
1010
|
-
});
|
|
1011
|
-
const aliases = /* @__PURE__ */ new Set();
|
|
1012
|
-
if (controls.$select) {
|
|
1013
|
-
for (const item of controls.$select) if (typeof item !== "string") aliases.add(require_agg.resolveAlias(item));
|
|
1014
|
-
}
|
|
1015
|
-
let sort;
|
|
1016
|
-
if (controls.$sort) {
|
|
1017
|
-
const translated = {};
|
|
1018
|
-
for (const [key, dir] of Object.entries(controls.$sort)) if (aliases.has(key)) translated[key] = dir;
|
|
1019
|
-
else {
|
|
1020
|
-
const physical = meta.leafByLogical.get(key)?.physicalName ?? key;
|
|
1021
|
-
translated[physical] = dir;
|
|
1022
|
-
}
|
|
1023
|
-
sort = translated;
|
|
1024
|
-
}
|
|
1025
|
-
let having;
|
|
1026
|
-
if (controls.$having) having = meta.requiresMappings ? this.translateFilterWithRename(controls.$having, meta) : meta.toStorageFormatters ? this.translateFilter(controls.$having, meta) : controls.$having;
|
|
1027
|
-
return {
|
|
1028
|
-
filter,
|
|
1029
|
-
controls: {
|
|
1030
|
-
...controls,
|
|
1031
|
-
$groupBy: groupBy,
|
|
1032
|
-
$select: select ? new UniquSelect(select, meta.allPhysicalFields) : void 0,
|
|
1033
|
-
$sort: sort,
|
|
1034
|
-
$having: having
|
|
1035
|
-
},
|
|
1036
|
-
insights: query.insights
|
|
1037
|
-
};
|
|
1202
|
+
/** The flattened column of a logical path (`contact.email` → `contact__email`). */
|
|
1203
|
+
physicalPath(logical, meta) {
|
|
1204
|
+
return meta.leafByLogical.get(logical)?.physicalName ?? logical;
|
|
1038
1205
|
}
|
|
1039
1206
|
/**
|
|
1040
1207
|
* Overrides the base `translateFilter` to use `leafByLogical` for key resolution
|
|
@@ -1196,11 +1363,20 @@ var RelationalFieldMapper = class extends FieldMappingStrategy {
|
|
|
1196
1363
|
* - `$geoWithin` on a non-geoPoint field → `FILTER_TYPE_MISMATCH`
|
|
1197
1364
|
* - `$geoWithin` with a malformed circle → `INVALID_QUERY`
|
|
1198
1365
|
* - `$geoWithin` on an adapter without geo support → `GEO_NOT_SUPPORTED`
|
|
1366
|
+
* - `$exists` with a non-boolean operand → `INVALID_QUERY`
|
|
1367
|
+
* - malformed `$select` computed entries / `$groupBy` entries, calendar
|
|
1368
|
+
* buckets outside grouped queries or with a bad unit / zone / alias →
|
|
1369
|
+
* `INVALID_QUERY` (the shared normalizer, `resolveCalendarBuckets`)
|
|
1370
|
+
* - a calendar bucket over a field that is not a bucket source
|
|
1371
|
+
* (`bucketSourceVerdict`) → `INVALID_QUERY`, or `BUCKET_NOT_SUPPORTED`
|
|
1372
|
+
* when the adapter has no calendar buckets at all; a unit the adapter's
|
|
1373
|
+
* `calendarBucketUnits()` lacks → `BUCKET_NOT_SUPPORTED`
|
|
1199
1374
|
* - every filter / `$sort` / `$select` / `$groupBy` / `$having` / aggregate
|
|
1200
1375
|
* path must resolve to physical storage on THIS adapter and pass the
|
|
1201
|
-
*
|
|
1202
|
-
*
|
|
1203
|
-
* keep firing first for
|
|
1376
|
+
* capability its position (for a filter entry: its predicate class, see
|
|
1377
|
+
* {@link canFilterLeaf}) needs → `INVALID_QUERY` (see {@link guardPaths}).
|
|
1378
|
+
* Runs after the checks above so `ENC_*` codes keep firing first for
|
|
1379
|
+
* encrypted subtrees.
|
|
1204
1380
|
*/
|
|
1205
1381
|
/** Validates a `[lng, lat]` tuple (GeoJSON coordinate order). */
|
|
1206
1382
|
function assertGeoPoint(point, path) {
|
|
@@ -1241,7 +1417,8 @@ function guardGeoWithin(meta, adapter, field, value) {
|
|
|
1241
1417
|
}
|
|
1242
1418
|
/**
|
|
1243
1419
|
* Walks a filter expression, rejecting encrypted-field references and
|
|
1244
|
-
* validating `$geoWithin`
|
|
1420
|
+
* validating operator operands: `$geoWithin` shapes and the boolean
|
|
1421
|
+
* `$exists` operand (this is the one owner of that rule).
|
|
1245
1422
|
*/
|
|
1246
1423
|
function guardFilter(meta, adapter, filter, encCode = "ENC_FIELD_FILTER") {
|
|
1247
1424
|
if (!filter || typeof filter !== "object") return;
|
|
@@ -1257,8 +1434,12 @@ function guardFilter(meta, adapter, filter, encCode = "ENC_FIELD_FILTER") {
|
|
|
1257
1434
|
}
|
|
1258
1435
|
if (key.startsWith("$")) continue;
|
|
1259
1436
|
if (hasEncrypted && isEncryptedRef(meta, key)) throw encryptedRefError(encCode, key, "filter on");
|
|
1260
|
-
if (
|
|
1437
|
+
if (!(0, _uniqu_core.isPrimitive)(value)) {
|
|
1261
1438
|
for (const [op, opValue] of Object.entries(value)) if (op === "$geoWithin") guardGeoWithin(meta, adapter, key, opValue);
|
|
1439
|
+
else if (op === "$exists" && typeof opValue !== "boolean") throw new require_db_error.DbError("INVALID_QUERY", [{
|
|
1440
|
+
path: key,
|
|
1441
|
+
message: `$exists on "${key}" expects true or false`
|
|
1442
|
+
}]);
|
|
1262
1443
|
}
|
|
1263
1444
|
}
|
|
1264
1445
|
}
|
|
@@ -1273,10 +1454,11 @@ const OP_VERB = {
|
|
|
1273
1454
|
select: "select",
|
|
1274
1455
|
groupBy: "group by",
|
|
1275
1456
|
having: "filter ($having) on",
|
|
1276
|
-
aggregate: "aggregate over"
|
|
1457
|
+
aggregate: "aggregate over",
|
|
1458
|
+
bucket: "bucket"
|
|
1277
1459
|
};
|
|
1278
|
-
function pathError(path, message) {
|
|
1279
|
-
return new require_db_error.DbError(
|
|
1460
|
+
function pathError(path, message, code = "INVALID_QUERY") {
|
|
1461
|
+
return new require_db_error.DbError(code, [{
|
|
1280
1462
|
path,
|
|
1281
1463
|
message
|
|
1282
1464
|
}]);
|
|
@@ -1313,16 +1495,120 @@ function sortFieldNames(sort) {
|
|
|
1313
1495
|
if (typeof sort === "object") return Object.keys(sort);
|
|
1314
1496
|
return [];
|
|
1315
1497
|
}
|
|
1316
|
-
|
|
1498
|
+
/**
|
|
1499
|
+
* The reason clause for a leaf the adapter cannot filter (value comparison).
|
|
1500
|
+
* @internal Shared wording for moost-db's capability index — not consumer API.
|
|
1501
|
+
*/
|
|
1502
|
+
const ADAPTER_FILTER_REASON = "adapter cannot filter on this storage type";
|
|
1503
|
+
/**
|
|
1504
|
+
* The reason clause for an `@db.encrypted` leaf in a comparing / ordering position.
|
|
1505
|
+
* @internal Shared wording for moost-db's capability index — not consumer API.
|
|
1506
|
+
*/
|
|
1507
|
+
const ENCRYPTED_REASON = "field is @db.encrypted (ciphertext cannot be compared or ordered)";
|
|
1508
|
+
/** The operator each non-`compare` predicate class stands for, in `filterOps` order. */
|
|
1509
|
+
const FILTER_PREDICATE_OPS = {
|
|
1510
|
+
exists: "$exists",
|
|
1511
|
+
geo: "$geoWithin"
|
|
1512
|
+
};
|
|
1513
|
+
/** Classifies one filter entry's value (`{ key: value }`); operand validity is `guardFilter`'s. */
|
|
1514
|
+
function filterPredicateOf(value) {
|
|
1515
|
+
if ((0, _uniqu_core.isPrimitive)(value)) return "compare";
|
|
1516
|
+
const ops = value;
|
|
1517
|
+
if ("$geoWithin" in ops) return "geo";
|
|
1518
|
+
const keys = Object.keys(ops);
|
|
1519
|
+
return keys.length === 1 && keys[0] === "$exists" ? "exists" : "compare";
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* Whether a stored leaf physically supports a filter predicate of this class
|
|
1523
|
+
* — the one rule the core path guard and moost-db's HTTP capability index
|
|
1524
|
+
* both apply:
|
|
1525
|
+
*
|
|
1526
|
+
* - `compare` → `adapter.canFilterField(fd)`;
|
|
1527
|
+
* - `exists` → any stored column: it tests whether the column holds a value
|
|
1528
|
+
* (SQL `IS [NOT] NULL`), never its content, so the scalar veto (JSON /
|
|
1529
|
+
* array storage on relational adapters) does not apply;
|
|
1530
|
+
* - `geo` → a `db.geoPoint` leaf on a geo-searchable adapter.
|
|
1531
|
+
*
|
|
1532
|
+
* `@db.encrypted` vetoes every class.
|
|
1533
|
+
*/
|
|
1534
|
+
function canFilterLeaf(fd, predicate, adapter) {
|
|
1535
|
+
if (fd.encrypted) return false;
|
|
1536
|
+
switch (predicate) {
|
|
1537
|
+
case "exists": return true;
|
|
1538
|
+
case "geo": return fd.isGeoPoint === true && adapter.isGeoSearchable();
|
|
1539
|
+
default: return adapter.canFilterField(fd);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* The operators of the non-`compare` predicate classes a leaf physically
|
|
1544
|
+
* accepts — named in a value-comparison rejection by the core guard, and
|
|
1545
|
+
* listed (under the HTTP policy) as `/meta.fields[P].filterOps`.
|
|
1546
|
+
*/
|
|
1547
|
+
function narrowerFilterOps(fd, adapter) {
|
|
1548
|
+
const ops = [];
|
|
1549
|
+
for (const [predicate, op] of Object.entries(FILTER_PREDICATE_OPS)) if (canFilterLeaf(fd, predicate, adapter)) ops.push(op);
|
|
1550
|
+
return ops;
|
|
1551
|
+
}
|
|
1552
|
+
/** The rejection suffix naming {@link narrowerFilterOps} — `""` when there are none. */
|
|
1553
|
+
function acceptedOperatorsHint(ops) {
|
|
1554
|
+
return ops.length > 0 ? ` (accepted operators: ${ops.join(", ")})` : "";
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Strict mode: a table declaring dimensions or measures groups only by
|
|
1558
|
+
* dimensions (a calendar bucket's source included) and aggregates only
|
|
1559
|
+
* measures.
|
|
1560
|
+
*/
|
|
1561
|
+
function isStrictTable(table) {
|
|
1562
|
+
return table.dimensions.length > 0 || table.measures.length > 0;
|
|
1563
|
+
}
|
|
1564
|
+
function rejectSource(code, reason) {
|
|
1565
|
+
return {
|
|
1566
|
+
ok: false,
|
|
1567
|
+
code,
|
|
1568
|
+
reason
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
/**
|
|
1572
|
+
* Whether the stored leaf `fd` may be the source of a calendar bucket
|
|
1573
|
+
* (since 0.1.133) — every schema and adapter rule, once, for the core path
|
|
1574
|
+
* guard ({@link guardPath} op `bucket`) and moost-db's capability index
|
|
1575
|
+
* (`/meta.fields[P].bucketable` and the HTTP gate) alike. First failing rule
|
|
1576
|
+
* wins, in this order:
|
|
1577
|
+
*
|
|
1578
|
+
* 1. `encrypted` — `@db.encrypted` (ciphertext has no calendar);
|
|
1579
|
+
* 2. `jsonDescendant` — inside a JSON value (`jsonValueAncestor`):
|
|
1580
|
+
* relational adapters cannot address it and nested-object adapters must
|
|
1581
|
+
* not diverge from them;
|
|
1582
|
+
* 3. `notTimestamp` — not a `number.timestamp` leaf;
|
|
1583
|
+
* 4. `notFilterable` — the adapter cannot filter (so cannot group) the storage;
|
|
1584
|
+
* 5. `notDimension` — a strict table ({@link isStrictTable}) and the field
|
|
1585
|
+
* is not a dimension;
|
|
1586
|
+
* 6. `noBuckets` — the adapter reports no `calendarBucketUnits()`.
|
|
1587
|
+
*
|
|
1588
|
+
* Whether the adapter supports the requested UNIT is a per-query rule, not
|
|
1589
|
+
* a field's (the core's `BUCKET_NOT_SUPPORTED`). Caller-specific policy
|
|
1590
|
+
* layers on top: moost-db rejects `@db.writeOnly` sources before asking.
|
|
1591
|
+
*/
|
|
1592
|
+
function bucketSourceVerdict(fd, table, adapter) {
|
|
1593
|
+
if (fd.encrypted) return rejectSource("encrypted", ENCRYPTED_REASON);
|
|
1594
|
+
const jsonAncestor = jsonValueAncestor(fd.path, table.jsonValueParents);
|
|
1595
|
+
if (jsonAncestor !== void 0) return rejectSource("jsonDescendant", `inside JSON-stored column "${jsonAncestor}"`);
|
|
1596
|
+
if (!isBucketableField(fd)) return rejectSource("notTimestamp", "not a timestamp field (declare it number.timestamp)");
|
|
1597
|
+
if (!adapter.canFilterField(fd)) return rejectSource("notFilterable", ADAPTER_FILTER_REASON);
|
|
1598
|
+
if (isStrictTable(table) && !table.dimensions.includes(fd.path)) return rejectSource("notDimension", "not a dimension");
|
|
1599
|
+
if (adapter.calendarBucketUnits().size === 0) return rejectSource("noBuckets", "adapter has no calendar buckets");
|
|
1600
|
+
return { ok: true };
|
|
1601
|
+
}
|
|
1602
|
+
function collectFilterKeys(filter, push, skip, refs) {
|
|
1317
1603
|
if (!filter || typeof filter !== "object" || Array.isArray(filter)) return;
|
|
1318
1604
|
for (const [key, value] of Object.entries(filter)) {
|
|
1319
1605
|
if (refs.unsupportedOperator) return;
|
|
1320
1606
|
if (key === "$and" || key === "$or") {
|
|
1321
|
-
if (Array.isArray(value)) for (const child of value) collectFilterKeys(child,
|
|
1607
|
+
if (Array.isArray(value)) for (const child of value) collectFilterKeys(child, push, skip, refs);
|
|
1322
1608
|
continue;
|
|
1323
1609
|
}
|
|
1324
1610
|
if (key === "$not") {
|
|
1325
|
-
collectFilterKeys(value,
|
|
1611
|
+
collectFilterKeys(value, push, skip, refs);
|
|
1326
1612
|
continue;
|
|
1327
1613
|
}
|
|
1328
1614
|
if (key.startsWith("$")) {
|
|
@@ -1330,7 +1616,7 @@ function collectFilterKeys(filter, out, geo, skip, refs) {
|
|
|
1330
1616
|
return;
|
|
1331
1617
|
}
|
|
1332
1618
|
if (skip?.has(key)) continue;
|
|
1333
|
-
(
|
|
1619
|
+
push(key, value);
|
|
1334
1620
|
}
|
|
1335
1621
|
}
|
|
1336
1622
|
/**
|
|
@@ -1340,40 +1626,52 @@ function collectFilterKeys(filter, out, geo, skip, refs) {
|
|
|
1340
1626
|
* visited — they are validated against their target relation separately.
|
|
1341
1627
|
*
|
|
1342
1628
|
* Aggregate mode is `aggregate` when given, else the presence of `$groupBy`.
|
|
1343
|
-
* In aggregate mode `$select` entries are
|
|
1344
|
-
* `$field`
|
|
1345
|
-
* `resolveAlias`)
|
|
1346
|
-
* `$
|
|
1629
|
+
* In aggregate mode `$select` computed entries are collected by kind — an
|
|
1630
|
+
* aggregate's `$field` into `aggregate`, a calendar bucket's into `bucket` —
|
|
1631
|
+
* and their aliases (`$as`, else uniqu's `resolveAlias`) are exempted from
|
|
1632
|
+
* `$sort` / `$having`; a bucket alias is also dropped from `groupBy`, which
|
|
1633
|
+
* lists grouped fields only.
|
|
1634
|
+
*
|
|
1635
|
+
* Entry shapes are not checked here — see `resolveCalendarBuckets`.
|
|
1347
1636
|
*/
|
|
1348
1637
|
function collectQueryPaths(query, aggregate) {
|
|
1349
1638
|
const refs = {
|
|
1350
1639
|
filter: [],
|
|
1351
|
-
geoFilter: [],
|
|
1352
1640
|
sort: [],
|
|
1353
1641
|
select: [],
|
|
1354
1642
|
groupBy: [],
|
|
1355
1643
|
having: [],
|
|
1356
1644
|
aggregate: [],
|
|
1645
|
+
bucket: [],
|
|
1357
1646
|
aggregateMode: false
|
|
1358
1647
|
};
|
|
1359
|
-
collectFilterKeys(query.filter,
|
|
1648
|
+
collectFilterKeys(query.filter, (path, value) => refs.filter.push({
|
|
1649
|
+
path,
|
|
1650
|
+
predicate: filterPredicateOf(value)
|
|
1651
|
+
}), void 0, refs);
|
|
1360
1652
|
const controls = query.controls ?? {};
|
|
1361
1653
|
const rawGroupBy = controls.$groupBy;
|
|
1362
1654
|
const groupBy = Array.isArray(rawGroupBy) ? rawGroupBy.filter((f) => typeof f === "string") : typeof rawGroupBy === "string" ? [rawGroupBy] : [];
|
|
1363
1655
|
refs.aggregateMode = aggregate ?? groupBy.length > 0;
|
|
1364
|
-
refs.groupBy = groupBy;
|
|
1365
1656
|
const aliases = /* @__PURE__ */ new Set();
|
|
1657
|
+
const bucketAliases = /* @__PURE__ */ new Set();
|
|
1366
1658
|
const select = controls.$select;
|
|
1367
1659
|
if (Array.isArray(select)) {
|
|
1368
1660
|
for (const item of select) if (typeof item === "string") refs.select.push(item);
|
|
1369
|
-
else if (refs.aggregateMode
|
|
1370
|
-
|
|
1371
|
-
aliases.add(
|
|
1372
|
-
if (
|
|
1661
|
+
else if (!refs.aggregateMode) continue;
|
|
1662
|
+
else if ((0, _uniqu_core.isAggregateExpr)(item)) {
|
|
1663
|
+
aliases.add((0, _uniqu_core.resolveAlias)(item));
|
|
1664
|
+
if (item.$field !== "*") refs.aggregate.push(item.$field);
|
|
1665
|
+
} else if ((0, _uniqu_core.isBucketExpr)(item)) {
|
|
1666
|
+
const alias = (0, _uniqu_core.resolveAlias)(item);
|
|
1667
|
+
aliases.add(alias);
|
|
1668
|
+
bucketAliases.add(alias);
|
|
1669
|
+
refs.bucket.push(item.$field);
|
|
1373
1670
|
}
|
|
1374
1671
|
} else if (select && typeof select === "object") refs.select.push(...Object.keys(select));
|
|
1672
|
+
refs.groupBy = bucketAliases.size > 0 ? groupBy.filter((name) => !bucketAliases.has(name)) : groupBy;
|
|
1375
1673
|
for (const name of sortFieldNames(controls.$sort)) if (!aliases.has(name)) refs.sort.push(name);
|
|
1376
|
-
if (refs.aggregateMode) collectFilterKeys(controls.$having, refs.having,
|
|
1674
|
+
if (refs.aggregateMode) collectFilterKeys(controls.$having, (path) => refs.having.push(path), aliases, refs);
|
|
1377
1675
|
return refs;
|
|
1378
1676
|
}
|
|
1379
1677
|
/**
|
|
@@ -1424,21 +1722,22 @@ function pathSourceOf(meta) {
|
|
|
1424
1722
|
* metadata and adapter capability — the classification of
|
|
1425
1723
|
* {@link classifyQueryPath} plus the position's physical requirement:
|
|
1426
1724
|
*
|
|
1427
|
-
* - a leaf → physical capability (`
|
|
1428
|
-
*
|
|
1725
|
+
* - a leaf → physical capability (`canSortField` for `$sort`; for a filter
|
|
1726
|
+
* entry {@link canFilterLeaf} of its `predicate` class; `canFilterField`
|
|
1727
|
+
* for `$groupBy` / `$having` / aggregate `$field`s; `$select` always passes);
|
|
1728
|
+
* a calendar-bucket source must pass `bucketSourceVerdict` (the rules
|
|
1729
|
+
* moost-db's capability index applies too);
|
|
1429
1730
|
* - a nested-object parent → only `$select`, and only when it expands to
|
|
1430
1731
|
* leaf columns (`selectExpansion`);
|
|
1431
1732
|
* - everything else is rejected.
|
|
1432
1733
|
*
|
|
1433
|
-
* `
|
|
1434
|
-
* shape and index support were already validated by {@link guardFilter}, so
|
|
1435
|
-
* the adapter's scalar `canFilterField` veto does not apply.
|
|
1734
|
+
* `predicate` is a filter entry's class; other positions leave the default.
|
|
1436
1735
|
*
|
|
1437
1736
|
* Messages are the short programmatic forms; the HTTP wording (moost-db's
|
|
1438
1737
|
* `FieldCapabilityIndex`, with `$with` hints and leaf lists) is what clients
|
|
1439
1738
|
* see and is authoritative — the HTTP gate always answers first.
|
|
1440
1739
|
*/
|
|
1441
|
-
function guardPath(meta, adapter, path, op,
|
|
1740
|
+
function guardPath(meta, adapter, path, op, predicate = "compare") {
|
|
1442
1741
|
const verb = OP_VERB[op];
|
|
1443
1742
|
const { kind, parent } = classifyQueryPath(pathSourceOf(meta), path);
|
|
1444
1743
|
switch (kind) {
|
|
@@ -1446,12 +1745,16 @@ function guardPath(meta, adapter, path, op, geoPredicate = false) {
|
|
|
1446
1745
|
case "leaf": {
|
|
1447
1746
|
if (op === "select") return;
|
|
1448
1747
|
const fd = meta.descriptorByPath.get(path);
|
|
1748
|
+
if (op === "bucket") {
|
|
1749
|
+
const verdict = bucketSourceVerdict(fd, meta, adapter);
|
|
1750
|
+
if (!verdict.ok) throw pathError(path, `Cannot bucket "${path}" — ${verdict.reason}`, verdict.code === "noBuckets" ? "BUCKET_NOT_SUPPORTED" : "INVALID_QUERY");
|
|
1751
|
+
return;
|
|
1752
|
+
}
|
|
1449
1753
|
if (op === "sort") {
|
|
1450
1754
|
if (!adapter.canSortField(fd)) throw pathError(path, `Cannot sort by "${path}" — adapter cannot sort on this storage type`);
|
|
1451
1755
|
return;
|
|
1452
1756
|
}
|
|
1453
|
-
if (
|
|
1454
|
-
if (!adapter.canFilterField(fd)) throw pathError(path, `Cannot ${verb} "${path}" — adapter cannot filter on this storage type`);
|
|
1757
|
+
if (!canFilterLeaf(fd, predicate, adapter)) throw pathError(path, `Cannot ${verb} "${path}" — ${ADAPTER_FILTER_REASON}${op === "filter" ? acceptedOperatorsHint(narrowerFilterOps(fd, adapter)) : ""}`);
|
|
1455
1758
|
return;
|
|
1456
1759
|
}
|
|
1457
1760
|
case "objectParent":
|
|
@@ -1469,9 +1772,13 @@ function guardPath(meta, adapter, path, op, geoPredicate = false) {
|
|
|
1469
1772
|
* {@link guardPath}). Adapters may therefore assume every path they receive
|
|
1470
1773
|
* is physical.
|
|
1471
1774
|
*
|
|
1472
|
-
*
|
|
1473
|
-
*
|
|
1474
|
-
*
|
|
1775
|
+
* Entry shapes are the caller's to normalize first (`resolveCalendarBuckets`
|
|
1776
|
+
* — {@link guardQuery} / {@link guardAggregate} do).
|
|
1777
|
+
*
|
|
1778
|
+
* In aggregate mode (`aggregate = true`) `$select` computed entries have
|
|
1779
|
+
* their `$field` checked (aggregates as `aggregate`, calendar buckets as
|
|
1780
|
+
* `bucket`), `$groupBy` fields are checked, and computed aliases (`$as` or
|
|
1781
|
+
* the default) are exempt in `$sort` / `$having`.
|
|
1475
1782
|
*
|
|
1476
1783
|
* Returns the collected refs so callers can run further structural rules
|
|
1477
1784
|
* (see {@link checkHavingKeys}) without walking the query again.
|
|
@@ -1480,27 +1787,32 @@ function guardPaths(meta, adapter, query, aggregate = false) {
|
|
|
1480
1787
|
if (!query) return;
|
|
1481
1788
|
const refs = collectQueryPaths(query, aggregate);
|
|
1482
1789
|
if (refs.unsupportedOperator !== void 0) throw pathError(refs.unsupportedOperator, unsupportedOperatorMessage(refs.unsupportedOperator));
|
|
1483
|
-
for (const
|
|
1484
|
-
for (const path of refs.geoFilter) guardPath(meta, adapter, path, "filter", true);
|
|
1790
|
+
for (const ref of refs.filter) guardPath(meta, adapter, ref.path, "filter", ref.predicate);
|
|
1485
1791
|
for (const path of refs.sort) guardPath(meta, adapter, path, "sort");
|
|
1486
1792
|
for (const path of refs.select) guardPath(meta, adapter, path, "select");
|
|
1487
1793
|
for (const path of refs.aggregate) guardPath(meta, adapter, path, "aggregate");
|
|
1794
|
+
for (const path of refs.bucket) guardPath(meta, adapter, path, "bucket");
|
|
1488
1795
|
for (const path of refs.groupBy) guardPath(meta, adapter, path, "groupBy");
|
|
1489
1796
|
for (const path of refs.having) guardPath(meta, adapter, path, "having");
|
|
1490
1797
|
return refs;
|
|
1491
1798
|
}
|
|
1492
|
-
/**
|
|
1799
|
+
/**
|
|
1800
|
+
* Shared read-path guard: filter + $sort encryption checks, the `$select`
|
|
1801
|
+
* normalizer (a calendar bucket is invalid outside a grouped query), then
|
|
1802
|
+
* the path guard.
|
|
1803
|
+
*/
|
|
1493
1804
|
function guardQuery(meta, adapter, query) {
|
|
1494
1805
|
if (!query) return;
|
|
1495
1806
|
guardFilter(meta, adapter, query.filter);
|
|
1496
1807
|
guardSort(meta, query.controls?.$sort);
|
|
1808
|
+
resolveCalendarBuckets(query.controls, meta, false);
|
|
1497
1809
|
guardPaths(meta, adapter, query);
|
|
1498
1810
|
}
|
|
1499
1811
|
/**
|
|
1500
|
-
* `$having` is a post-aggregation filter, so a key is either
|
|
1501
|
-
* alias (
|
|
1502
|
-
* or a `$groupBy` field (exact logical-path
|
|
1503
|
-
* stays valid). Any other key — a real but non-grouped column included — is
|
|
1812
|
+
* `$having` is a post-aggregation filter, so a key is either a computed
|
|
1813
|
+
* alias (aggregate or calendar bucket — already exempt in
|
|
1814
|
+
* {@link collectQueryPaths}) or a `$groupBy` field (exact logical-path
|
|
1815
|
+
* match: `metadata.clicks` grouped stays valid). Any other key — a real but non-grouped column included — is
|
|
1504
1816
|
* rejected here, once, for SDK and HTTP callers alike, instead of by the
|
|
1505
1817
|
* engine (PostgreSQL / MySQL error, SQLite tolerance, Mongo `[]`). Returns
|
|
1506
1818
|
* the first offending key as an error entry (`path` = the bare key, as the
|
|
@@ -1516,11 +1828,16 @@ function checkHavingKeys(refs) {
|
|
|
1516
1828
|
}
|
|
1517
1829
|
/**
|
|
1518
1830
|
* Aggregate-path guard: $groupBy / $select / $having encryption refs + filter
|
|
1519
|
-
* + $sort, then the path guard, then the
|
|
1831
|
+
* + $sort, then the path guard, then the adapter's calendar-bucket units
|
|
1832
|
+
* (`BUCKET_NOT_SUPPORTED`), then the `$having` key rule
|
|
1520
1833
|
* ({@link checkHavingKeys} — after the path guard so an unknown key still
|
|
1521
1834
|
* reads `Unknown field`).
|
|
1835
|
+
*
|
|
1836
|
+
* `buckets` are the query's resolved calendar buckets when the caller already
|
|
1837
|
+
* ran `resolveCalendarBuckets` (resolved here otherwise).
|
|
1522
1838
|
*/
|
|
1523
|
-
function guardAggregate(meta, adapter, query) {
|
|
1839
|
+
function guardAggregate(meta, adapter, query, resolved) {
|
|
1840
|
+
const buckets = resolved ?? resolveCalendarBuckets(query.controls, meta, true);
|
|
1524
1841
|
guardFilter(meta, adapter, query.filter);
|
|
1525
1842
|
const controls = query.controls;
|
|
1526
1843
|
if (meta.encryptedFields.size > 0) {
|
|
@@ -1533,9 +1850,24 @@ function guardAggregate(meta, adapter, query) {
|
|
|
1533
1850
|
guardSort(meta, controls.$sort);
|
|
1534
1851
|
}
|
|
1535
1852
|
const refs = guardPaths(meta, adapter, query, true);
|
|
1853
|
+
guardBucketUnits(adapter, buckets);
|
|
1536
1854
|
const having = refs ? checkHavingKeys(refs) : void 0;
|
|
1537
1855
|
if (having) throw new require_db_error.DbError("INVALID_QUERY", [having]);
|
|
1538
1856
|
}
|
|
1857
|
+
/**
|
|
1858
|
+
* Rejects a calendar bucket whose unit this adapter cannot group by
|
|
1859
|
+
* (`calendarBucketUnits()`; an adapter with none was already answered per
|
|
1860
|
+
* source by `bucketSourceVerdict`) with `BUCKET_NOT_SUPPORTED`,
|
|
1861
|
+
* before anything is translated — third-party adapters get a clean 400,
|
|
1862
|
+
* never an engine error or a silent fallback.
|
|
1863
|
+
*/
|
|
1864
|
+
function guardBucketUnits(adapter, buckets) {
|
|
1865
|
+
const units = adapter.calendarBucketUnits();
|
|
1866
|
+
for (const b of buckets) if (!units.has(b.unit)) throw new require_db_error.DbError("BUCKET_NOT_SUPPORTED", [{
|
|
1867
|
+
path: "$select",
|
|
1868
|
+
message: `Calendar bucket "${b.unit}" is not supported by this adapter`
|
|
1869
|
+
}]);
|
|
1870
|
+
}
|
|
1539
1871
|
//#endregion
|
|
1540
1872
|
//#region src/table/db-readable.ts
|
|
1541
1873
|
/**
|
|
@@ -2002,9 +2334,15 @@ var AtscriptDbReadable = class {
|
|
|
2002
2334
|
* Executes an aggregate query with GROUP BY and aggregate functions.
|
|
2003
2335
|
*
|
|
2004
2336
|
* Validates:
|
|
2337
|
+
* - `$select` computed entries and calendar buckets (the shared normalizer,
|
|
2338
|
+
* `resolveCalendarBuckets`: shapes, unit, zone, alias, grouping)
|
|
2005
2339
|
* - Plain fields in $select are a subset of $groupBy
|
|
2006
2340
|
* - When dimensions/measures are defined (strict mode): $groupBy fields
|
|
2007
2341
|
* must be dimensions, aggregate $field values must be measures (or '*')
|
|
2342
|
+
* - the path guard (a bucket source must pass `bucketSourceVerdict` —
|
|
2343
|
+
* timestamp type, no JSON ancestor, a dimension in strict mode, an
|
|
2344
|
+
* adapter with calendar buckets) and the adapter's calendar-bucket units
|
|
2345
|
+
* (`BUCKET_NOT_SUPPORTED`)
|
|
2008
2346
|
*
|
|
2009
2347
|
* Translates field names, delegates to adapter.aggregate(),
|
|
2010
2348
|
* then reverse-maps and applies fromStorage formatters on results.
|
|
@@ -2012,6 +2350,7 @@ var AtscriptDbReadable = class {
|
|
|
2012
2350
|
async aggregate(query) {
|
|
2013
2351
|
this._ensureBuilt();
|
|
2014
2352
|
const { $groupBy, $select } = query.controls;
|
|
2353
|
+
const buckets = resolveCalendarBuckets(query.controls, this._meta, true);
|
|
2015
2354
|
if ($select) {
|
|
2016
2355
|
const groupBySet = new Set($groupBy);
|
|
2017
2356
|
for (const item of $select) if (typeof item === "string" && !groupBySet.has(item)) throw new require_db_error.DbError("INVALID_QUERY", [{
|
|
@@ -2020,15 +2359,16 @@ var AtscriptDbReadable = class {
|
|
|
2020
2359
|
}]);
|
|
2021
2360
|
}
|
|
2022
2361
|
const { dimensions, measures } = this._meta;
|
|
2023
|
-
if (
|
|
2362
|
+
if (isStrictTable(this._meta)) {
|
|
2024
2363
|
const dimSet = new Set(dimensions);
|
|
2025
2364
|
const measSet = new Set(measures);
|
|
2026
|
-
|
|
2365
|
+
const bucketAliases = new Set(buckets.map((b) => b.alias));
|
|
2366
|
+
for (const field of $groupBy) if (!dimSet.has(field) && !bucketAliases.has(field)) throw new require_db_error.DbError("INVALID_QUERY", [{
|
|
2027
2367
|
path: "$groupBy",
|
|
2028
2368
|
message: `Field "${field}" is not a dimension`
|
|
2029
2369
|
}]);
|
|
2030
2370
|
if ($select) {
|
|
2031
|
-
for (const item of $select) if (
|
|
2371
|
+
for (const item of $select) if ((0, _uniqu_core.isAggregateExpr)(item) && item.$field !== "*" && !measSet.has(item.$field)) throw new require_db_error.DbError("INVALID_QUERY", [{
|
|
2032
2372
|
path: "$select",
|
|
2033
2373
|
message: `Aggregate field "${item.$field}" is not a measure`
|
|
2034
2374
|
}]);
|
|
@@ -2038,8 +2378,7 @@ var AtscriptDbReadable = class {
|
|
|
2038
2378
|
if ($select && quantityRefByField.size > 0) {
|
|
2039
2379
|
const groupBySet = new Set($groupBy);
|
|
2040
2380
|
for (const item of $select) {
|
|
2041
|
-
if (
|
|
2042
|
-
if (item.$field === "*") continue;
|
|
2381
|
+
if (!(0, _uniqu_core.isAggregateExpr)(item) || item.$field === "*") continue;
|
|
2043
2382
|
const refField = quantityRefByField.get(item.$field);
|
|
2044
2383
|
if (refField && !groupBySet.has(refField)) throw new require_db_error.DbError("INVALID_QUERY", [{
|
|
2045
2384
|
path: "$select",
|
|
@@ -2052,8 +2391,8 @@ var AtscriptDbReadable = class {
|
|
|
2052
2391
|
this._ensureSearchable();
|
|
2053
2392
|
if (!searchTerm.trim()) return query.controls.$count ? [{ count: 0 }] : [];
|
|
2054
2393
|
}
|
|
2055
|
-
guardAggregate(this._meta, this.adapter, query);
|
|
2056
|
-
const dbQuery = this._fieldMapper.translateAggregateQuery(query, this._meta);
|
|
2394
|
+
guardAggregate(this._meta, this.adapter, query, buckets);
|
|
2395
|
+
const dbQuery = this._fieldMapper.translateAggregateQuery(query, this._meta, buckets);
|
|
2057
2396
|
return (await this.adapter.aggregate(dbQuery)).map((row) => this._fieldMapper.reconstructFromRead(row, this._meta));
|
|
2058
2397
|
}
|
|
2059
2398
|
/** Whether the underlying adapter supports text search. */
|
|
@@ -2064,6 +2403,10 @@ var AtscriptDbReadable = class {
|
|
|
2064
2403
|
canFilterField(fd) {
|
|
2065
2404
|
return this.adapter.canFilterField(fd);
|
|
2066
2405
|
}
|
|
2406
|
+
/** Calendar-bucket units the adapter can group by (proxies adapter capability; empty = none). */
|
|
2407
|
+
calendarBucketUnits() {
|
|
2408
|
+
return this.adapter.calendarBucketUnits();
|
|
2409
|
+
}
|
|
2067
2410
|
/** Whether the adapter can sort by a given field (proxies adapter capability). */
|
|
2068
2411
|
canSortField(fd) {
|
|
2069
2412
|
return this.adapter.canSortField(fd);
|
|
@@ -2350,7 +2693,7 @@ var AtscriptDbReadable = class {
|
|
|
2350
2693
|
* Public entry point for relation loading. Used by adapters for nested $with delegation.
|
|
2351
2694
|
*/
|
|
2352
2695
|
async loadRelations(rows, withRelations) {
|
|
2353
|
-
const { loadRelationsImpl } = await Promise.resolve().then(() => require("./relation-loader-
|
|
2696
|
+
const { loadRelationsImpl } = await Promise.resolve().then(() => require("./relation-loader-BD4xANQJ.cjs")).then((n) => n.relation_loader_exports);
|
|
2354
2697
|
return loadRelationsImpl(rows, withRelations, this);
|
|
2355
2698
|
}
|
|
2356
2699
|
/**
|
|
@@ -2415,6 +2758,9 @@ function createFailureCollector(what) {
|
|
|
2415
2758
|
//#endregion
|
|
2416
2759
|
//#region src/base-adapter.ts
|
|
2417
2760
|
const EMPTY_DEFAULT_FNS = /* @__PURE__ */ new Set();
|
|
2761
|
+
const EMPTY_BUCKET_UNITS = /* @__PURE__ */ new Set();
|
|
2762
|
+
/** Every calendar-bucket unit — what an adapter that renders them all returns from `calendarBucketUnits()`. */
|
|
2763
|
+
const ALL_BUCKET_UNITS = new Set(_uniqu_core.BUCKET_UNITS);
|
|
2418
2764
|
const txStorage = new node_async_hooks.AsyncLocalStorage();
|
|
2419
2765
|
/** The innermost open transaction of `owner` in the current async chain. */
|
|
2420
2766
|
function findTxContext(owner) {
|
|
@@ -2629,6 +2975,23 @@ var BaseDbAdapter = class {
|
|
|
2629
2975
|
return EMPTY_DEFAULT_FNS;
|
|
2630
2976
|
}
|
|
2631
2977
|
/**
|
|
2978
|
+
* Calendar-bucket units (`{ $bucket, $field }` in an aggregate `$select`)
|
|
2979
|
+
* this adapter can group by, over IANA time zones. Empty (the default) =
|
|
2980
|
+
* calendar buckets unsupported: the core rejects them with
|
|
2981
|
+
* `BUCKET_NOT_SUPPORTED` before dispatch, and moost-db's `/meta` advertises
|
|
2982
|
+
* no bucketable field.
|
|
2983
|
+
*
|
|
2984
|
+
* A set rather than a boolean (like {@link nativeDefaultFns}) so a unit can
|
|
2985
|
+
* be adopted adapter by adapter. An adapter that returns a unit must group
|
|
2986
|
+
* by the bucket alias in `$groupBy` — see `controls.$select.buckets`
|
|
2987
|
+
* (`TResolvedBucket`: physical `field`, source `fd`) — and return the
|
|
2988
|
+
* `YYYY-MM-DD` label of the bucket's first local day (null for a null or
|
|
2989
|
+
* out-of-range source, uniqu's `bucketLabel` semantics). Since 0.1.132.
|
|
2990
|
+
*/
|
|
2991
|
+
calendarBucketUnits() {
|
|
2992
|
+
return EMPTY_BUCKET_UNITS;
|
|
2993
|
+
}
|
|
2994
|
+
/**
|
|
2632
2995
|
* Whether this adapter enforces foreign key constraints natively.
|
|
2633
2996
|
* When `true`, the generic layer skips application-level cascade/setNull
|
|
2634
2997
|
* on delete — the DB engine handles it (e.g. SQLite `ON DELETE CASCADE`).
|
|
@@ -2650,6 +3013,9 @@ var BaseDbAdapter = class {
|
|
|
2650
3013
|
* Used by `AsDbReadableController.buildMetaResponse()` to gate the
|
|
2651
3014
|
* `filterable` flag exposed to UIs — the adapter's answer is a hard gate
|
|
2652
3015
|
* even when the field carries `@db.column.filterable`.
|
|
3016
|
+
*
|
|
3017
|
+
* Vetoes value comparison only — a sole-`$exists` entry needs just a stored
|
|
3018
|
+
* column (`canFilterLeaf`).
|
|
2653
3019
|
*/
|
|
2654
3020
|
canFilterField(fd) {
|
|
2655
3021
|
if (fd.encrypted) return false;
|
|
@@ -4345,6 +4711,18 @@ function isAtscriptDbView(readable) {
|
|
|
4345
4711
|
return readable.isView;
|
|
4346
4712
|
}
|
|
4347
4713
|
//#endregion
|
|
4714
|
+
Object.defineProperty(exports, "ADAPTER_FILTER_REASON", {
|
|
4715
|
+
enumerable: true,
|
|
4716
|
+
get: function() {
|
|
4717
|
+
return ADAPTER_FILTER_REASON;
|
|
4718
|
+
}
|
|
4719
|
+
});
|
|
4720
|
+
Object.defineProperty(exports, "ALL_BUCKET_UNITS", {
|
|
4721
|
+
enumerable: true,
|
|
4722
|
+
get: function() {
|
|
4723
|
+
return ALL_BUCKET_UNITS;
|
|
4724
|
+
}
|
|
4725
|
+
});
|
|
4348
4726
|
Object.defineProperty(exports, "ApplicationIntegrity", {
|
|
4349
4727
|
enumerable: true,
|
|
4350
4728
|
get: function() {
|
|
@@ -4381,6 +4759,12 @@ Object.defineProperty(exports, "DocumentFieldMapper", {
|
|
|
4381
4759
|
return DocumentFieldMapper;
|
|
4382
4760
|
}
|
|
4383
4761
|
});
|
|
4762
|
+
Object.defineProperty(exports, "ENCRYPTED_REASON", {
|
|
4763
|
+
enumerable: true,
|
|
4764
|
+
get: function() {
|
|
4765
|
+
return ENCRYPTED_REASON;
|
|
4766
|
+
}
|
|
4767
|
+
});
|
|
4384
4768
|
Object.defineProperty(exports, "FieldMappingStrategy", {
|
|
4385
4769
|
enumerable: true,
|
|
4386
4770
|
get: function() {
|
|
@@ -4423,6 +4807,12 @@ Object.defineProperty(exports, "UniquSelect", {
|
|
|
4423
4807
|
return UniquSelect;
|
|
4424
4808
|
}
|
|
4425
4809
|
});
|
|
4810
|
+
Object.defineProperty(exports, "acceptedOperatorsHint", {
|
|
4811
|
+
enumerable: true,
|
|
4812
|
+
get: function() {
|
|
4813
|
+
return acceptedOperatorsHint;
|
|
4814
|
+
}
|
|
4815
|
+
});
|
|
4426
4816
|
Object.defineProperty(exports, "assertGeoPoint", {
|
|
4427
4817
|
enumerable: true,
|
|
4428
4818
|
get: function() {
|
|
@@ -4435,6 +4825,18 @@ Object.defineProperty(exports, "assertNoVersionWrites", {
|
|
|
4435
4825
|
return assertNoVersionWrites;
|
|
4436
4826
|
}
|
|
4437
4827
|
});
|
|
4828
|
+
Object.defineProperty(exports, "bucketSourceVerdict", {
|
|
4829
|
+
enumerable: true,
|
|
4830
|
+
get: function() {
|
|
4831
|
+
return bucketSourceVerdict;
|
|
4832
|
+
}
|
|
4833
|
+
});
|
|
4834
|
+
Object.defineProperty(exports, "canFilterLeaf", {
|
|
4835
|
+
enumerable: true,
|
|
4836
|
+
get: function() {
|
|
4837
|
+
return canFilterLeaf;
|
|
4838
|
+
}
|
|
4839
|
+
});
|
|
4438
4840
|
Object.defineProperty(exports, "checkHavingKeys", {
|
|
4439
4841
|
enumerable: true,
|
|
4440
4842
|
get: function() {
|
|
@@ -4507,6 +4909,12 @@ Object.defineProperty(exports, "isAtscriptDbView", {
|
|
|
4507
4909
|
return isAtscriptDbView;
|
|
4508
4910
|
}
|
|
4509
4911
|
});
|
|
4912
|
+
Object.defineProperty(exports, "isBucketableField", {
|
|
4913
|
+
enumerable: true,
|
|
4914
|
+
get: function() {
|
|
4915
|
+
return isBucketableField;
|
|
4916
|
+
}
|
|
4917
|
+
});
|
|
4510
4918
|
Object.defineProperty(exports, "isGeoIndexableType", {
|
|
4511
4919
|
enumerable: true,
|
|
4512
4920
|
get: function() {
|
|
@@ -4519,6 +4927,30 @@ Object.defineProperty(exports, "isGeoPointType", {
|
|
|
4519
4927
|
return isGeoPointType;
|
|
4520
4928
|
}
|
|
4521
4929
|
});
|
|
4930
|
+
Object.defineProperty(exports, "isJsonValueField", {
|
|
4931
|
+
enumerable: true,
|
|
4932
|
+
get: function() {
|
|
4933
|
+
return isJsonValueField;
|
|
4934
|
+
}
|
|
4935
|
+
});
|
|
4936
|
+
Object.defineProperty(exports, "jsonValueAncestor", {
|
|
4937
|
+
enumerable: true,
|
|
4938
|
+
get: function() {
|
|
4939
|
+
return jsonValueAncestor;
|
|
4940
|
+
}
|
|
4941
|
+
});
|
|
4942
|
+
Object.defineProperty(exports, "narrowerFilterOps", {
|
|
4943
|
+
enumerable: true,
|
|
4944
|
+
get: function() {
|
|
4945
|
+
return narrowerFilterOps;
|
|
4946
|
+
}
|
|
4947
|
+
});
|
|
4948
|
+
Object.defineProperty(exports, "resolveCalendarBuckets", {
|
|
4949
|
+
enumerable: true,
|
|
4950
|
+
get: function() {
|
|
4951
|
+
return resolveCalendarBuckets;
|
|
4952
|
+
}
|
|
4953
|
+
});
|
|
4522
4954
|
Object.defineProperty(exports, "resolveDesignType", {
|
|
4523
4955
|
enumerable: true,
|
|
4524
4956
|
get: function() {
|