turbine-orm 0.31.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/powql.js +38 -1
- package/dist/cjs/query/batched-loader.js +34 -0
- package/dist/cjs/query/builder.js +451 -80
- package/dist/cjs/query/filters.js +28 -0
- package/dist/index.d.ts +1 -1
- package/dist/powql.js +38 -1
- package/dist/query/batched-loader.d.ts +12 -0
- package/dist/query/batched-loader.js +33 -0
- package/dist/query/builder.d.ts +86 -0
- package/dist/query/builder.js +453 -82
- package/dist/query/filters.d.ts +13 -1
- package/dist/query/filters.js +27 -0
- package/dist/query/index.d.ts +1 -1
- package/dist/query/types.d.ts +148 -12
- package/package.json +1 -1
|
@@ -26,6 +26,7 @@ exports.isVectorFilter = isVectorFilter;
|
|
|
26
26
|
exports.isVectorOrderBy = isVectorOrderBy;
|
|
27
27
|
exports.isOrderBySpec = isOrderBySpec;
|
|
28
28
|
exports.isJsonPathOrderBy = isJsonPathOrderBy;
|
|
29
|
+
exports.isRelationPickOrderBy = isRelationPickOrderBy;
|
|
29
30
|
exports.normalizeOrderBy = normalizeOrderBy;
|
|
30
31
|
const errors_js_1 = require("../errors.js");
|
|
31
32
|
const utils_js_1 = require("./utils.js");
|
|
@@ -316,6 +317,33 @@ function isJsonPathOrderBy(value) {
|
|
|
316
317
|
return false;
|
|
317
318
|
return Array.isArray(value.path);
|
|
318
319
|
}
|
|
320
|
+
/**
|
|
321
|
+
* Check if an orderBy value is a pick-row relation ordering:
|
|
322
|
+
* `{ pick: { orderBy, ... }, by, direction?, nulls? }`. The full shape is
|
|
323
|
+
* required — `pick` must be an object carrying `orderBy`, `by` must be
|
|
324
|
+
* present, and no keys outside `{ pick, by, direction, nulls }` — so a to-one
|
|
325
|
+
* relation whose target has real columns literally named `pick` and `by`
|
|
326
|
+
* (whose values are direction strings or `{ sort, nulls }` specs, never an
|
|
327
|
+
* object with `orderBy`) still falls through to column ordering. `distance`
|
|
328
|
+
* (vector), `sort` (OrderBySpec), and a top-level array `path` (JSON-path
|
|
329
|
+
* ordering) are excluded up front.
|
|
330
|
+
*/
|
|
331
|
+
function isRelationPickOrderBy(value) {
|
|
332
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
333
|
+
return false;
|
|
334
|
+
if ('distance' in value || 'sort' in value || Array.isArray(value.path))
|
|
335
|
+
return false;
|
|
336
|
+
const v = value;
|
|
337
|
+
if (!('pick' in v) || !('by' in v))
|
|
338
|
+
return false;
|
|
339
|
+
if (typeof v.pick !== 'object' || v.pick === null || Array.isArray(v.pick) || !('orderBy' in v.pick))
|
|
340
|
+
return false;
|
|
341
|
+
for (const key of Object.keys(v)) {
|
|
342
|
+
if (key !== 'pick' && key !== 'by' && key !== 'direction' && key !== 'nulls')
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
319
347
|
/**
|
|
320
348
|
* Normalize an orderBy value into `{ direction, nulls }`. Accepts a plain
|
|
321
349
|
* direction string or an {@link OrderBySpec}. Used by every ORDER BY compile
|
package/dist/index.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ export { type IntrospectOptions, introspect } from './introspect.js';
|
|
|
43
43
|
export { executeNestedCreate, executeNestedUpdate, hasRelationFields, type NestedWriteContext, } from './nested-write.js';
|
|
44
44
|
export type { ObserveConfig, ObserveHandle } from './observe.js';
|
|
45
45
|
export { executePipeline, type PipelineOptions, type PipelineResults, pipelineSupported } from './pipeline.js';
|
|
46
|
-
export { type AggregateArgs, type AggregateResult, type ArrayFilter, type ColumnRef, type ConnectOrCreateOp, type CountArgs, type CreateArgs, type CreateDataInput, type CreateManyArgs, type DeferredQuery, type DeleteArgs, type DeleteManyArgs, type FieldResult, type FindManyArgs, type FindManyStreamArgs, type FindUniqueArgs, type GlobalFilters, type GroupByArgs, type HavingClause, type JsonFilter, type JsonPathOrderBy, type MiddlewareFn, type NestedCreateOp, type NestedUpdateOp, type NestedUpdateOpItem, type NestedUpsertOpItem, type OmitResult, type OrderByClause, type OrderDirection, type QueryEvent, type QueryEventListener, QueryInterface, type QueryResult, type RelationDescriptor, type RelationFilter, type SelectResult, type SkipGlobalFilters, type TextSearchFilter, type TypedWithClause, type UpdateArgs, type UpdateDataInput, type UpdateInput, type UpdateManyArgs, type UpdateOperatorInput, type UpsertArgs, type VectorDistanceFilter, type VectorFilter, type VectorMetric, type VectorOrderBy, type VectorOrderByDistance, type WhereClause, type WhereOperator, type WhereValue, type WithClause, type WithOptions, type WithResult, } from './query/index.js';
|
|
46
|
+
export { type AggregateArgs, type AggregateResult, type ArrayFilter, type ColumnRef, type ConnectOrCreateOp, type CountArgs, type CreateArgs, type CreateDataInput, type CreateManyArgs, type DeferredQuery, type DeleteArgs, type DeleteManyArgs, type FieldResult, type FindManyArgs, type FindManyStreamArgs, type FindUniqueArgs, type GlobalFilters, type GroupByAggregateSpec, type GroupByArgs, type GroupByDistinctOn, type HavingClause, type JsonFilter, type JsonPathAggregateTarget, type JsonPathGroupKey, type JsonPathOrderBy, type MiddlewareFn, type NestedCreateOp, type NestedUpdateOp, type NestedUpdateOpItem, type NestedUpsertOpItem, type OmitResult, type OrderByClause, type OrderDirection, type QueryEvent, type QueryEventListener, QueryInterface, type QueryResult, type RelationDescriptor, type RelationFilter, type RelationPickBy, type RelationPickOrderBy, type SelectResult, type SkipGlobalFilters, type TextSearchFilter, type TypedWithClause, type UpdateArgs, type UpdateDataInput, type UpdateInput, type UpdateManyArgs, type UpdateOperatorInput, type UpsertArgs, type VectorDistanceFilter, type VectorFilter, type VectorMetric, type VectorOrderBy, type VectorOrderByDistance, type WhereClause, type WhereOperator, type WhereValue, type WithClause, type WithOptions, type WithResult, } from './query/index.js';
|
|
47
47
|
export { type ActiveSubscription, type NotificationHandler, type Subscription, validateChannel } from './realtime.js';
|
|
48
48
|
export type { CheckMetadata, ColumnMetadata, IndexMetadata, ReferentialAction, RelationDef, SchemaMetadata, TableMetadata, } from './schema.js';
|
|
49
49
|
export { camelToSnake, isDateType, normalizeKeyColumns, pgArrayType, pgTypeToTs, singularize, snakeToCamel, snakeToPascal, } from './schema.js';
|
package/dist/powql.js
CHANGED
|
@@ -38,6 +38,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
38
38
|
import { NotFoundError, TimeoutError, UnsupportedFeatureError, ValidationError } from './errors.js';
|
|
39
39
|
import { executeNestedCreate, executeNestedUpdate, hasRelationFields, } from './nested-write.js';
|
|
40
40
|
import { PowdbFloatParam, powqlColumnType, quotePowqlIdent, rowToEntity } from './powdb.js';
|
|
41
|
+
import { isRelationPickOrderBy } from './query/filters.js';
|
|
41
42
|
import { escapeLike } from './query/utils.js';
|
|
42
43
|
import { normalizeKeyColumns, snakeToCamel, } from './schema.js';
|
|
43
44
|
/**
|
|
@@ -475,7 +476,22 @@ export class PowqlInterface {
|
|
|
475
476
|
return '';
|
|
476
477
|
const parts = keys.map(([field, dir]) => {
|
|
477
478
|
if (dir && typeof dir === 'object') {
|
|
478
|
-
|
|
479
|
+
// Name the actual feature in the refusal — a pick-row ordering
|
|
480
|
+
// reported as "vector / distance ordering" sends users hunting for
|
|
481
|
+
// pgvector docs. All object-valued orderings stay E017 on PowDB.
|
|
482
|
+
const o = dir;
|
|
483
|
+
const feature = isRelationPickOrderBy(dir)
|
|
484
|
+
? 'relation pick-row ordering'
|
|
485
|
+
: 'distance' in o
|
|
486
|
+
? 'vector / distance ordering'
|
|
487
|
+
: Array.isArray(o.path)
|
|
488
|
+
? 'JSON-path ordering'
|
|
489
|
+
: '_count' in o
|
|
490
|
+
? 'relation _count ordering'
|
|
491
|
+
: 'sort' in o || 'nulls' in o
|
|
492
|
+
? 'NULLS placement / sort-spec ordering'
|
|
493
|
+
: 'object-valued ordering';
|
|
494
|
+
throw new UnsupportedFeatureError(feature, 'PowDB', `field "${field}"`);
|
|
479
495
|
}
|
|
480
496
|
return `${this.ref(field)} ${dir === 'desc' ? 'desc' : 'asc'}`;
|
|
481
497
|
});
|
|
@@ -1119,6 +1135,27 @@ export class PowqlInterface {
|
|
|
1119
1135
|
}
|
|
1120
1136
|
async groupBy(args) {
|
|
1121
1137
|
return this.withMiddleware('groupBy', args, async () => {
|
|
1138
|
+
// The SQL-only groupBy extensions (DISTINCT ON row source, JSON-path
|
|
1139
|
+
// group keys / aggregate targets) have no PowQL equivalent: refuse
|
|
1140
|
+
// clearly instead of emitting broken PowQL.
|
|
1141
|
+
if (args.distinctOn) {
|
|
1142
|
+
throw new UnsupportedFeatureError('groupBy distinctOn row source', 'PowDB');
|
|
1143
|
+
}
|
|
1144
|
+
for (const entry of args.by) {
|
|
1145
|
+
if (typeof entry !== 'string') {
|
|
1146
|
+
throw new UnsupportedFeatureError('JSON-path groupBy keys', 'PowDB');
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
for (const fn of ['_sum', '_avg', '_min', '_max']) {
|
|
1150
|
+
const spec = args[fn];
|
|
1151
|
+
if (!spec)
|
|
1152
|
+
continue;
|
|
1153
|
+
for (const value of Object.values(spec)) {
|
|
1154
|
+
if (value !== undefined && typeof value !== 'boolean') {
|
|
1155
|
+
throw new UnsupportedFeatureError(`JSON-path ${fn} aggregate targets`, 'PowDB');
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1122
1159
|
const params = [];
|
|
1123
1160
|
const resolvedWhere = await this.resolveRelationFilters(args.where, args.timeout);
|
|
1124
1161
|
const where = this.buildWhere(resolvedWhere, params);
|
|
@@ -138,6 +138,18 @@ export declare function neededParentKeyFields(parentMeta: TableMetadata, withCla
|
|
|
138
138
|
* ({@link ValidationError}) when a named relation is to-one.
|
|
139
139
|
*/
|
|
140
140
|
export declare function resolveCountRelations(parentMeta: TableMetadata, countSpec: WithCount): RelationDef[];
|
|
141
|
+
/**
|
|
142
|
+
* Reject pick-row relation ordering anywhere inside a `with` tree's orderBy —
|
|
143
|
+
* strategy parity with the join path, which throws this exact E003 at SQL
|
|
144
|
+
* build time (`pickOrderNestedError` in builder.ts). Without this guard the
|
|
145
|
+
* loaders would forward `options.orderBy` as the child reader's TOP-LEVEL
|
|
146
|
+
* findMany orderBy, where the pick shape compiles fine — so the same query
|
|
147
|
+
* would execute on 'batched' but throw on 'join'. Walks the whole tree up
|
|
148
|
+
* front so acceptance never depends on which levels have rows — the batched
|
|
149
|
+
* runners in builder.ts call this BEFORE the base query (a zero-row base
|
|
150
|
+
* result must still reject, exactly like the join strategy's build-time throw).
|
|
151
|
+
*/
|
|
152
|
+
export declare function rejectNestedPickOrder(withClause: WithClause): void;
|
|
141
153
|
/**
|
|
142
154
|
* Load every relation in `withClause` for `parents` and attach it onto each row
|
|
143
155
|
* in place. Mirrors the join strategy's output shape exactly. Recurses for nested
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
*/
|
|
49
49
|
import { CircularRelationError, RelationError, UnsupportedFeatureError, ValidationError } from '../errors.js';
|
|
50
50
|
import { normalizeKeyColumns } from '../schema.js';
|
|
51
|
+
import { isRelationPickOrderBy } from './filters.js';
|
|
51
52
|
/**
|
|
52
53
|
* Max parent keys per follow-up query. On Postgres the whole key set travels as
|
|
53
54
|
* ONE array parameter (`= ANY($1)`), so this is not a bind-parameter limit — it
|
|
@@ -177,6 +178,34 @@ export function resolveCountRelations(parentMeta, countSpec) {
|
|
|
177
178
|
function keyOf(value) {
|
|
178
179
|
return String(value);
|
|
179
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Reject pick-row relation ordering anywhere inside a `with` tree's orderBy —
|
|
183
|
+
* strategy parity with the join path, which throws this exact E003 at SQL
|
|
184
|
+
* build time (`pickOrderNestedError` in builder.ts). Without this guard the
|
|
185
|
+
* loaders would forward `options.orderBy` as the child reader's TOP-LEVEL
|
|
186
|
+
* findMany orderBy, where the pick shape compiles fine — so the same query
|
|
187
|
+
* would execute on 'batched' but throw on 'join'. Walks the whole tree up
|
|
188
|
+
* front so acceptance never depends on which levels have rows — the batched
|
|
189
|
+
* runners in builder.ts call this BEFORE the base query (a zero-row base
|
|
190
|
+
* result must still reject, exactly like the join strategy's build-time throw).
|
|
191
|
+
*/
|
|
192
|
+
export function rejectNestedPickOrder(withClause) {
|
|
193
|
+
for (const spec of Object.values(withClause)) {
|
|
194
|
+
if (!spec || spec === true)
|
|
195
|
+
continue;
|
|
196
|
+
const options = spec;
|
|
197
|
+
if (options.orderBy) {
|
|
198
|
+
for (const [key, value] of Object.entries(options.orderBy)) {
|
|
199
|
+
if (isRelationPickOrderBy(value)) {
|
|
200
|
+
throw new ValidationError(`[turbine] Pick-row ordering on relation "${key}" is only supported in a top-level ` +
|
|
201
|
+
'findMany orderBy: nested `with` orderBy does not support it.');
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (options.with)
|
|
206
|
+
rejectNestedPickOrder(options.with);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
180
209
|
/**
|
|
181
210
|
* Load every relation in `withClause` for `parents` and attach it onto each row
|
|
182
211
|
* in place. Mirrors the join strategy's output shape exactly. Recurses for nested
|
|
@@ -185,6 +214,10 @@ function keyOf(value) {
|
|
|
185
214
|
export async function loadRelationsBatched(ctx, parents, withClause, timeout, depth = 0, path = [ctx.parentMeta.name]) {
|
|
186
215
|
if (depth >= MAX_DEPTH)
|
|
187
216
|
throw new CircularRelationError([...path, '…']);
|
|
217
|
+
// Scope-rule parity with the join strategy: validate the whole tree BEFORE
|
|
218
|
+
// the empty-parents early return, so accept/reject never depends on data.
|
|
219
|
+
if (depth === 0)
|
|
220
|
+
rejectNestedPickOrder(withClause);
|
|
188
221
|
if (parents.length === 0)
|
|
189
222
|
return;
|
|
190
223
|
// Sibling relations are independent (each writes only its own parent[relName]
|
package/dist/query/builder.d.ts
CHANGED
|
@@ -339,6 +339,27 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
339
339
|
buildCount(args?: CountArgs<T>): DeferredQuery<number>;
|
|
340
340
|
groupBy(args: GroupByArgs<T>): Promise<Record<string, unknown>[]>;
|
|
341
341
|
buildGroupBy(args: GroupByArgs<T>): DeferredQuery<Record<string, unknown>[]>;
|
|
342
|
+
/**
|
|
343
|
+
* Validate a JSON-path target (group key or aggregate target) in groupBy:
|
|
344
|
+
* the field must resolve to a real json/jsonb column and the path must be a
|
|
345
|
+
* non-empty array of keys/indexes. Returns the resolved snake_case column.
|
|
346
|
+
*/
|
|
347
|
+
private resolveJsonPathTarget;
|
|
348
|
+
/**
|
|
349
|
+
* Build the `distinctOn` row source for groupBy (PostgreSQL only: other
|
|
350
|
+
* engines throw {@link UnsupportedFeatureError} E017):
|
|
351
|
+
*
|
|
352
|
+
* ```sql
|
|
353
|
+
* (SELECT DISTINCT ON ("c1") * FROM "table"<WHERE> ORDER BY "c1", <orderBy>) AS "table"
|
|
354
|
+
* ```
|
|
355
|
+
*
|
|
356
|
+
* The wrapper is aliased as the table name so every outer expression (group
|
|
357
|
+
* keys, aggregates, HAVING, ORDER BY) is byte-identical to the plain path.
|
|
358
|
+
* `distinctOn.orderBy` is required (it decides which row survives) and
|
|
359
|
+
* supports plain columns, {@link OrderBySpec} nulls, and JSON-path specs;
|
|
360
|
+
* JSON paths push their text[] param here, after the WHERE params.
|
|
361
|
+
*/
|
|
362
|
+
private buildDistinctOnSource;
|
|
342
363
|
/**
|
|
343
364
|
* Build the SQL fragments for a {@link HavingClause}.
|
|
344
365
|
*
|
|
@@ -349,6 +370,12 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
349
370
|
* comparison value is pushed onto the shared `params` array and referenced by
|
|
350
371
|
* a `$N` placeholder via {@link buildHavingNumericClauses} — there is no string
|
|
351
372
|
* interpolation of user values.
|
|
373
|
+
*
|
|
374
|
+
* `jsonAggExprs` (from {@link buildGroupBy}) maps `alias:aggKey` to the
|
|
375
|
+
* exact aggregate expression a JSON-path aggregate emitted in SELECT
|
|
376
|
+
* (including its already-bound path placeholder), so HAVING on a JSON-path
|
|
377
|
+
* aggregate alias reuses the same expression instead of resolving the alias
|
|
378
|
+
* as a column.
|
|
352
379
|
*/
|
|
353
380
|
private buildHavingClauses;
|
|
354
381
|
/**
|
|
@@ -712,6 +739,46 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
712
739
|
* the correlation parent is the relation's alias, not `this.table`.
|
|
713
740
|
*/
|
|
714
741
|
private buildRelationOrderBy;
|
|
742
|
+
/**
|
|
743
|
+
* Validate a {@link RelationPickOrderBy} entry's scope and shape. Shared by
|
|
744
|
+
* the SQL-build path ({@link buildRelationPickOrderBy}) and the cache-hit
|
|
745
|
+
* param-collect mirror ({@link collectRelationPickOrderParams}) so both
|
|
746
|
+
* always throw identically:
|
|
747
|
+
*
|
|
748
|
+
* - `nested` (inside a relation subquery's orderBy or a pick.orderBy):
|
|
749
|
+
* top-level findMany only in this release (E003),
|
|
750
|
+
* - manyToMany: not supported (E003 naming the limitation),
|
|
751
|
+
* - to-one: order by the target column directly instead (E003),
|
|
752
|
+
* - `pick.orderBy` is REQUIRED (deterministic row choice),
|
|
753
|
+
* - `by` must be a target column name or a `{ field, path }` JSON-path spec.
|
|
754
|
+
*/
|
|
755
|
+
private pickOrderNestedError;
|
|
756
|
+
private validatePickOrderBy;
|
|
757
|
+
/**
|
|
758
|
+
* Compile a {@link RelationPickOrderBy} term: a correlated scalar subquery
|
|
759
|
+
* that picks ONE related row (`ORDER BY <pick.orderBy> LIMIT 1`, optionally
|
|
760
|
+
* filtered by `pick.where` and the target's global filter) and surfaces one
|
|
761
|
+
* value from it (a plain target column or a JSON-path extraction) as the
|
|
762
|
+
* parent ORDER BY key:
|
|
763
|
+
*
|
|
764
|
+
* ```sql
|
|
765
|
+
* (SELECT ord0."data" #>> $1::text[] FROM "versions" ord0
|
|
766
|
+
* WHERE ord0."instance_id" = "instances"."id" AND ord0."is_current" = $2
|
|
767
|
+
* ORDER BY ord0."created_at" DESC LIMIT 1) ASC NULLS LAST
|
|
768
|
+
* ```
|
|
769
|
+
*
|
|
770
|
+
* Param-push order (mirrored EXACTLY by
|
|
771
|
+
* {@link collectRelationPickOrderParams}): `by` JSON path (if any) →
|
|
772
|
+
* target global filter → `pick.where` → `pick.orderBy` JSON paths.
|
|
773
|
+
*/
|
|
774
|
+
private buildRelationPickOrderBy;
|
|
775
|
+
/**
|
|
776
|
+
* Param-collect mirror of {@link buildRelationPickOrderBy}: re-runs the same
|
|
777
|
+
* validation (a warmed cache can never skip it), then pushes in the same
|
|
778
|
+
* order: `by` JSON path → target global filter → `pick.where` →
|
|
779
|
+
* `pick.orderBy` JSON paths.
|
|
780
|
+
*/
|
|
781
|
+
private collectRelationPickOrderParams;
|
|
715
782
|
/**
|
|
716
783
|
* Compile the ORDER BY terms of a relation `with` clause against the
|
|
717
784
|
* relation's table alias. One unified path for every relation shape
|
|
@@ -1009,6 +1076,13 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
1009
1076
|
* Used to detect JSONB/array columns for specialized operators.
|
|
1010
1077
|
* Uses pre-computed Map for O(1) lookup instead of linear scan.
|
|
1011
1078
|
*/
|
|
1079
|
+
/**
|
|
1080
|
+
* Case-insensitive json/jsonb column-type check. Postgres reports lowercase
|
|
1081
|
+
* udt_names, but SQLite/MySQL introspection surfaces the DECLARED type
|
|
1082
|
+
* (e.g. `JSON`), so every JSON-feature gate compares through this predicate
|
|
1083
|
+
* — build and collect sides alike, keeping the SQL-cache lockstep.
|
|
1084
|
+
*/
|
|
1085
|
+
private isJsonColumnType;
|
|
1012
1086
|
private getColumnPgType;
|
|
1013
1087
|
/**
|
|
1014
1088
|
* Get the Postgres base element type for an array column.
|
|
@@ -1033,6 +1107,18 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
1033
1107
|
* stays byte-identical to {@link collectJsonFilterParams}.
|
|
1034
1108
|
*/
|
|
1035
1109
|
private buildJsonFilterClauses;
|
|
1110
|
+
/**
|
|
1111
|
+
* Bind value for a JSON path parameter, encoded per dialect. PostgreSQL's
|
|
1112
|
+
* `#>>` takes a `text[]` (the segments as strings — or `nativeForm` when the
|
|
1113
|
+
* caller has a specific native binding, e.g. JsonFilter's raw path array).
|
|
1114
|
+
* Every other engine's JSON function (`json_extract` / `JSON_EXTRACT` /
|
|
1115
|
+
* `JSON_VALUE`) takes a `'$'`-rooted JSONPath STRING: binding the raw array
|
|
1116
|
+
* would arrive as `'["a"]'` (the driver shims JSON.stringify non-primitive
|
|
1117
|
+
* params) and fail at runtime with the engine's bad-JSON-path error. The
|
|
1118
|
+
* encoded path stays a bound parameter — never spliced into SQL text — so
|
|
1119
|
+
* the build/collect param mirrors stay in lockstep and injection-safe.
|
|
1120
|
+
*/
|
|
1121
|
+
private jsonPathParam;
|
|
1036
1122
|
/**
|
|
1037
1123
|
* Cast an extracted JSON path text value to a numeric type for range
|
|
1038
1124
|
* comparison. PostgreSQL uses `(expr)::numeric` (exact — the right way to
|