turbine-orm 0.35.0 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -16
- package/dist/cjs/cli/index.js +109 -16
- package/dist/cjs/cli/migrate.js +78 -3
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/studio.js +333 -22
- package/dist/cjs/cli/ui.js +7 -1
- package/dist/cjs/dialect.js +1 -1
- package/dist/cjs/generate.js +23 -2
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/mssql.js +22 -5
- package/dist/cjs/powdb.js +41 -1
- package/dist/cjs/powql.js +80 -25
- package/dist/cjs/query/aggregates.js +683 -0
- package/dist/cjs/query/batched-loader.js +2 -0
- package/dist/cjs/query/builder.js +297 -4504
- package/dist/cjs/query/filters.js +12 -0
- package/dist/cjs/query/relations.js +1698 -0
- package/dist/cjs/query/where-compile.js +180 -0
- package/dist/cjs/query/where.js +1491 -0
- package/dist/cjs/query/writes.js +680 -0
- package/dist/cjs/schema-builder.js +6 -0
- package/dist/cjs/schema-metadata.js +4 -0
- package/dist/cjs/schema-sql.js +265 -3
- package/dist/cjs/sqlite.js +1 -1
- package/dist/cli/index.d.ts +8 -2
- package/dist/cli/index.js +111 -18
- package/dist/cli/migrate.d.ts +24 -1
- package/dist/cli/migrate.js +77 -3
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/studio.d.ts +46 -13
- package/dist/cli/studio.js +331 -23
- package/dist/cli/ui.js +7 -1
- package/dist/dialect.d.ts +15 -6
- package/dist/dialect.js +1 -1
- package/dist/generate.js +23 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mssql.js +22 -5
- package/dist/powdb.d.ts +20 -0
- package/dist/powdb.js +40 -0
- package/dist/powql.d.ts +33 -1
- package/dist/powql.js +80 -25
- package/dist/query/aggregates.d.ts +74 -0
- package/dist/query/aggregates.js +641 -0
- package/dist/query/batched-loader.d.ts +6 -0
- package/dist/query/batched-loader.js +2 -0
- package/dist/query/builder.d.ts +62 -829
- package/dist/query/builder.js +302 -4509
- package/dist/query/deferred.d.ts +7 -0
- package/dist/query/filters.d.ts +7 -0
- package/dist/query/filters.js +11 -0
- package/dist/query/relations.d.ts +441 -0
- package/dist/query/relations.js +1627 -0
- package/dist/query/types.d.ts +15 -0
- package/dist/query/where-compile.d.ts +139 -0
- package/dist/query/where-compile.js +175 -0
- package/dist/query/where.d.ts +494 -0
- package/dist/query/where.js +1431 -0
- package/dist/query/writes.d.ts +131 -0
- package/dist/query/writes.js +626 -0
- package/dist/schema-builder.d.ts +18 -3
- package/dist/schema-builder.js +6 -0
- package/dist/schema-metadata.js +4 -0
- package/dist/schema-sql.d.ts +60 -3
- package/dist/schema-sql.js +261 -4
- package/dist/schema.d.ts +10 -0
- package/dist/sqlite.js +1 -1
- package/package.json +2 -2
package/dist/query/builder.d.ts
CHANGED
|
@@ -100,6 +100,28 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
100
100
|
* synchronous per call, so this transient is never observed across an await.
|
|
101
101
|
*/
|
|
102
102
|
private currentSkip;
|
|
103
|
+
/**
|
|
104
|
+
* The bound view of this instance passed to the shared WHERE walk
|
|
105
|
+
* (`where-compile.ts`). Built once in the constructor so `fingerprintWhere` /
|
|
106
|
+
* `buildWhereClause` / `collectWhereParams` all drive ONE enumeration + ONE
|
|
107
|
+
* scalar classifier without widening the class's public surface or allocating
|
|
108
|
+
* per call. See {@link WhereHost}.
|
|
109
|
+
*/
|
|
110
|
+
private readonly whereHost;
|
|
111
|
+
/**
|
|
112
|
+
* Per-target-table {@link WhereHost} memo for scoped sub-wheres (relation
|
|
113
|
+
* `EXISTS` filters + relation `with`-clause `where`s). Keyed by table name;
|
|
114
|
+
* the host depends only on the target table's metadata, so it is shared across
|
|
115
|
+
* every alias/qualifier for that table. Lazily filled by {@link scopedWhereHost}.
|
|
116
|
+
*/
|
|
117
|
+
private readonly scopedHostCache;
|
|
118
|
+
/**
|
|
119
|
+
* The privacy-preserving view of this instance handed to the extracted WHERE
|
|
120
|
+
* module (`where.ts`). Built once in the constructor (mirroring the
|
|
121
|
+
* `whereHost` precedent) so the free functions there reach exactly the
|
|
122
|
+
* class-resident primitives they need without widening the public surface.
|
|
123
|
+
*/
|
|
124
|
+
private readonly ctx;
|
|
103
125
|
constructor(pool: pg.Pool, table: string, schema: SchemaMetadata, middlewares?: MiddlewareFn[], options?: QueryInterfaceOptions);
|
|
104
126
|
/** Quote an identifier through the active SQL dialect. */
|
|
105
127
|
private q;
|
|
@@ -260,13 +282,17 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
260
282
|
* write and a follow-up SELECT; the SELECT's rows feed the transform.
|
|
261
283
|
*/
|
|
262
284
|
private executeMutation;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
285
|
+
buildCreate(args: CreateArgs<T>): DeferredQuery<T>;
|
|
286
|
+
buildCreateMany(args: CreateManyArgs<T>): DeferredQuery<T[]>;
|
|
287
|
+
buildUpdate(args: UpdateArgs<T>): DeferredQuery<T>;
|
|
288
|
+
buildDelete(args: DeleteArgs<T>): DeferredQuery<T>;
|
|
289
|
+
buildUpsert(args: UpsertArgs<T>): DeferredQuery<T>;
|
|
290
|
+
buildUpdateMany(args: UpdateManyArgs<T>): DeferredQuery<{
|
|
291
|
+
count: number;
|
|
292
|
+
}>;
|
|
293
|
+
buildDeleteMany(args: DeleteManyArgs<T>): DeferredQuery<{
|
|
294
|
+
count: number;
|
|
295
|
+
}>;
|
|
270
296
|
/**
|
|
271
297
|
* Best-effort extraction of an auto-generated primary key from a write
|
|
272
298
|
* result for `'reselect'` engines (e.g. mysql2's `insertId`). Returns
|
|
@@ -380,385 +406,72 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
380
406
|
findUniqueOrThrow<W extends TypedWithClause<R> = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: FindUniqueArgs<T, R, W, S, O>): Promise<QueryResult<T, R, W, S, O>>;
|
|
381
407
|
buildFindUniqueOrThrow<W extends TypedWithClause<R> = {}>(args: FindUniqueArgs<T, R, W, Record<string, boolean> | undefined, Record<string, boolean> | undefined>): DeferredQuery<T>;
|
|
382
408
|
create(args: CreateArgs<T, R>): Promise<T>;
|
|
383
|
-
buildCreate(args: CreateArgs<T>): DeferredQuery<T>;
|
|
384
|
-
/**
|
|
385
|
-
* Build the `'reselect'` plan for {@link buildCreate}: run the INSERT, then
|
|
386
|
-
* `SELECT * WHERE pk = ?`. Returns `undefined` (skipped) unless the active
|
|
387
|
-
* dialect's result strategy is `'reselect'`, so the PostgreSQL/RETURNING path
|
|
388
|
-
* pays nothing. Not yet wired to a real non-RETURNING engine.
|
|
389
|
-
*/
|
|
390
|
-
private makeCreateReselect;
|
|
391
409
|
createMany(args: CreateManyArgs<T>): Promise<T[]>;
|
|
392
|
-
buildCreateMany(args: CreateManyArgs<T>): DeferredQuery<T[]>;
|
|
393
410
|
update(args: UpdateArgs<T, R>): Promise<T>;
|
|
394
|
-
buildUpdate(args: UpdateArgs<T>): DeferredQuery<T>;
|
|
395
411
|
private nestedCreate;
|
|
396
412
|
private nestedUpdate;
|
|
397
413
|
private runInImplicitTx;
|
|
398
414
|
private buildNestedCtx;
|
|
399
415
|
private makeTxProxy;
|
|
400
416
|
delete(args: DeleteArgs<T>): Promise<T>;
|
|
401
|
-
buildDelete(args: DeleteArgs<T>): DeferredQuery<T>;
|
|
402
417
|
upsert(args: UpsertArgs<T>): Promise<T>;
|
|
403
|
-
buildUpsert(args: UpsertArgs<T>): DeferredQuery<T>;
|
|
404
418
|
updateMany(args: UpdateManyArgs<T>): Promise<{
|
|
405
419
|
count: number;
|
|
406
420
|
}>;
|
|
407
|
-
buildUpdateMany(args: UpdateManyArgs<T>): DeferredQuery<{
|
|
408
|
-
count: number;
|
|
409
|
-
}>;
|
|
410
421
|
deleteMany(args: DeleteManyArgs<T>): Promise<{
|
|
411
422
|
count: number;
|
|
412
423
|
}>;
|
|
413
|
-
buildDeleteMany(args: DeleteManyArgs<T>): DeferredQuery<{
|
|
414
|
-
count: number;
|
|
415
|
-
}>;
|
|
416
424
|
count(args?: CountArgs<T>): Promise<number>;
|
|
417
425
|
buildCount(args?: CountArgs<T>): DeferredQuery<number>;
|
|
418
426
|
groupBy(args: GroupByArgs<T>): Promise<Record<string, unknown>[]>;
|
|
419
427
|
buildGroupBy(args: GroupByArgs<T>): DeferredQuery<Record<string, unknown>[]>;
|
|
420
|
-
/**
|
|
421
|
-
* Compile a groupBy `orderBy` into an ORDER BY body. Unlike findMany ORDER BY
|
|
422
|
-
* ({@link buildOrderBy}, which validates keys against the table's physical
|
|
423
|
-
* columns), groupBy ordering targets the columns the RESULT actually
|
|
424
|
-
* contains: plain by-fields, JSON group-key aliases, and requested aggregates
|
|
425
|
-
* (`_count` / `_sum` / `_avg` / `_min` / `_max`). Each key re-emits the exact
|
|
426
|
-
* SELECT expression that produced it (`byOrderExprs` / `aggOrderExprs`),
|
|
427
|
-
* mirroring how HAVING re-emits aggregate expressions, so no dialect ever has
|
|
428
|
-
* to accept a SELECT-alias reference in ORDER BY, and any already-bound
|
|
429
|
-
* JSON-path placeholder is reused verbatim (ORDER BY is the last clause, so
|
|
430
|
-
* no `$n` renumbering). An aggregate key that was not requested, or an unknown
|
|
431
|
-
* by-key, throws {@link ValidationError} E003 listing the valid keys.
|
|
432
|
-
*/
|
|
433
|
-
private buildGroupByOrderBy;
|
|
434
|
-
/**
|
|
435
|
-
* Validate a JSON-path target (group key or aggregate target) in groupBy:
|
|
436
|
-
* the field must resolve to a real json/jsonb column and the path must be a
|
|
437
|
-
* non-empty array of keys/indexes. Returns the resolved snake_case column.
|
|
438
|
-
*/
|
|
439
|
-
private resolveJsonPathTarget;
|
|
440
|
-
/**
|
|
441
|
-
* Build the `distinctOn` row source for groupBy (PostgreSQL only: other
|
|
442
|
-
* engines throw {@link UnsupportedFeatureError} E017):
|
|
443
|
-
*
|
|
444
|
-
* ```sql
|
|
445
|
-
* (SELECT DISTINCT ON ("c1") * FROM "table"<WHERE> ORDER BY "c1", <orderBy>) AS "table"
|
|
446
|
-
* ```
|
|
447
|
-
*
|
|
448
|
-
* The wrapper is aliased as the table name so every outer expression (group
|
|
449
|
-
* keys, aggregates, HAVING, ORDER BY) is byte-identical to the plain path.
|
|
450
|
-
* `distinctOn.orderBy` is required (it decides which row survives) and
|
|
451
|
-
* supports plain columns, {@link OrderBySpec} nulls, and JSON-path specs;
|
|
452
|
-
* JSON paths push their text[] param here, after the WHERE params.
|
|
453
|
-
*/
|
|
454
|
-
private buildDistinctOnSource;
|
|
455
|
-
/**
|
|
456
|
-
* Build the SQL fragments for a {@link HavingClause}.
|
|
457
|
-
*
|
|
458
|
-
* Each aggregate expression (`COUNT(*)`, `SUM("col")`, etc.) is constructed
|
|
459
|
-
* from a **schema-validated, quoted** column identifier — `this.toColumn()`
|
|
460
|
-
* throws {@link ValidationError} for unknown fields and `this.q()` quotes via
|
|
461
|
-
* the dialect, so no unvalidated identifier ever reaches the SQL string. Every
|
|
462
|
-
* comparison value is pushed onto the shared `params` array and referenced by
|
|
463
|
-
* a `$N` placeholder via {@link buildHavingNumericClauses} — there is no string
|
|
464
|
-
* interpolation of user values.
|
|
465
|
-
*
|
|
466
|
-
* `jsonAggExprs` (from {@link buildGroupBy}) maps `alias:aggKey` to the
|
|
467
|
-
* exact aggregate expression a JSON-path aggregate emitted in SELECT
|
|
468
|
-
* (including its already-bound path placeholder), so HAVING on a JSON-path
|
|
469
|
-
* aggregate alias reuses the same expression instead of resolving the alias
|
|
470
|
-
* as a column.
|
|
471
|
-
*/
|
|
472
|
-
private buildHavingClauses;
|
|
473
|
-
/**
|
|
474
|
-
* Convert a single having filter into one or more parameterized SQL
|
|
475
|
-
* comparisons against the given aggregate expression. A bare number is
|
|
476
|
-
* shorthand for equality. Unknown operator keys throw {@link ValidationError}.
|
|
477
|
-
*/
|
|
478
|
-
private buildHavingNumericClauses;
|
|
479
|
-
aggregate(args: AggregateArgs<T>): Promise<AggregateResult<T>>;
|
|
480
428
|
buildAggregate(args: AggregateArgs<T>): DeferredQuery<AggregateResult<T>>;
|
|
481
|
-
|
|
482
|
-
* Resolve select/omit options into a list of snake_case column names.
|
|
483
|
-
* Returns null if neither is provided (meaning all columns).
|
|
484
|
-
*/
|
|
429
|
+
aggregate(args: AggregateArgs<T>): Promise<AggregateResult<T>>;
|
|
485
430
|
private resolveColumns;
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
private
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
private assertNoGeneratedColumns;
|
|
431
|
+
withFingerprint(withClause: WithClause | undefined, table?: string, depth?: number): string;
|
|
432
|
+
private collectWithParams;
|
|
433
|
+
private orderByEntryFingerprint;
|
|
434
|
+
private buildOrderBy;
|
|
435
|
+
private isRelationOrderByValue;
|
|
436
|
+
private nullsSuffix;
|
|
437
|
+
private resolveOrderByColumn;
|
|
438
|
+
private validateJsonPathOrderBy;
|
|
439
|
+
private buildJsonPathOrderEntry;
|
|
440
|
+
private collectRelationPickOrderParams;
|
|
441
|
+
private collectRelationCountParams;
|
|
442
|
+
private getCamelDateFields;
|
|
443
|
+
private makeNestedParser;
|
|
444
|
+
private buildSelectWithRelations;
|
|
501
445
|
/** Convert camelCase field name to snake_case column name (unquoted, for non-SQL uses) */
|
|
502
446
|
private toColumn;
|
|
503
447
|
/** Convert camelCase field name to a double-quoted SQL identifier */
|
|
504
448
|
private toSqlColumn;
|
|
505
|
-
/**
|
|
506
|
-
* Build a single SET clause entry for update/updateMany.
|
|
507
|
-
*
|
|
508
|
-
* Supports plain values and atomic operator objects ({ set, increment,
|
|
509
|
-
* decrement, multiply, divide }). An operator object is detected ONLY when
|
|
510
|
-
* it has EXACTLY one key that is one of the 5 operator keys — this avoids
|
|
511
|
-
* misinterpreting JSON column values like `{ set: 'x' }` as operators
|
|
512
|
-
* (real operator objects always have exactly one key, and a plain JSON
|
|
513
|
-
* payload that happens to have a single `set` key is extremely unusual).
|
|
514
|
-
* Multi-key objects are always treated as plain (JSON) values.
|
|
515
|
-
*
|
|
516
|
-
* Returns the SQL fragment (e.g., `"view_count" = "view_count" + $3`) and
|
|
517
|
-
* pushes any required params onto the shared params array so that WHERE
|
|
518
|
-
* clause numbering continues correctly afterward.
|
|
519
|
-
*/
|
|
520
|
-
private buildSetClause;
|
|
521
|
-
/**
|
|
522
|
-
* Produce a value-invariant fingerprint of a where clause.
|
|
523
|
-
* Same keys + same operator shapes + same combinator structure => same string.
|
|
524
|
-
* Different values (e.g. id=1 vs id=999) => identical fingerprint.
|
|
525
|
-
*
|
|
526
|
-
* @internal Exposed as package-private for testing via class access.
|
|
527
|
-
*/
|
|
528
449
|
fingerprintWhere(where: Record<string, unknown>): string;
|
|
529
|
-
/**
|
|
530
|
-
* Produce a value-invariant fingerprint for array filters while preserving
|
|
531
|
-
* parameterless boolean operators that change SQL shape.
|
|
532
|
-
*/
|
|
533
|
-
private fingerprintArrayFilter;
|
|
534
|
-
/**
|
|
535
|
-
* Fingerprint a relation filter sub-where for some/every/none.
|
|
536
|
-
*/
|
|
537
|
-
private fingerprintRelFilter;
|
|
538
|
-
/**
|
|
539
|
-
* Walk a where clause and push ONLY values into `params`, in the EXACT same
|
|
540
|
-
* order that `buildWhereClause` pushes them. Used on cache hit to fill params
|
|
541
|
-
* without rebuilding SQL.
|
|
542
|
-
*
|
|
543
|
-
* @internal Exposed as package-private for testing.
|
|
544
|
-
*/
|
|
545
450
|
collectWhereParams(where: Record<string, unknown>, params: unknown[]): void;
|
|
546
|
-
/**
|
|
547
|
-
* Param-collect mirror of {@link buildRelationFilter} for one relation-filter
|
|
548
|
-
* object (`{ some/every/none/is/isNot }`, already normalized). Pushes, per
|
|
549
|
-
* present branch and in the canonical order some→none→every→is→isNot, the
|
|
550
|
-
* branch's sub-where params THEN the target table's global-filter params —
|
|
551
|
-
* exactly the order buildRelationFilter emits. When no global filter applies
|
|
552
|
-
* the gf calls are no-ops, so this stays byte-identical to the pre-0.28 path.
|
|
553
|
-
* Shared by every collect site that mirrors buildRelationFilter
|
|
554
|
-
* (collectWhereParams, collectRelFilterParams, collectAliasWhereParams).
|
|
555
|
-
*/
|
|
556
|
-
private collectRelationFilterParams;
|
|
557
|
-
private collectRelFilterParams;
|
|
558
|
-
/**
|
|
559
|
-
* Collect params from operator clauses. Mirrors buildOperatorClauses:
|
|
560
|
-
* {@link ColumnRef} values compile into the SQL text, so they push NOTHING -
|
|
561
|
-
* but they re-run the same validation (unknown ref / insensitive mode) so a
|
|
562
|
-
* warmed cache can never skip a check the build path enforces.
|
|
563
|
-
*/
|
|
564
|
-
private collectOperatorParams;
|
|
565
|
-
/**
|
|
566
|
-
* Collect params from JSON filter. Mirrors buildJsonFilterClauses exactly:
|
|
567
|
-
* the `path` is bound at most once (its placeholder is shared by every
|
|
568
|
-
* extraction clause), then equals/contains/hasKey values, then the range
|
|
569
|
-
* comparison values in {@link JSON_RANGE_OPERATORS} order.
|
|
570
|
-
*/
|
|
571
|
-
private collectJsonFilterParams;
|
|
572
|
-
/** Collect params from array filter. Mirrors buildArrayFilterClauses. */
|
|
573
|
-
private collectArrayFilterParams;
|
|
574
|
-
/**
|
|
575
|
-
* Collect params for an orderBy clause. Vector KNN ordering pushes the
|
|
576
|
-
* `$n::vector` query vector and JSON-path ordering pushes its text[] path;
|
|
577
|
-
* plain direction ordering is parameterless. Mirrors buildOrderBy's push
|
|
578
|
-
* order exactly so the cached-SQL param re-collection stays in lockstep.
|
|
579
|
-
*/
|
|
580
|
-
private collectOrderByParams;
|
|
581
|
-
/**
|
|
582
|
-
* Collect params for a vector distance WHERE filter. Mirrors
|
|
583
|
-
* {@link buildVectorFilterClauses}: the `$n::vector` query vector first, then
|
|
584
|
-
* the comparison threshold(s).
|
|
585
|
-
*/
|
|
586
|
-
private collectVectorFilterParams;
|
|
587
|
-
/**
|
|
588
|
-
* Produce a fingerprint for a `with` clause tree. Recursion mirrors
|
|
589
|
-
* buildSelectWithRelations / buildRelationSubquery.
|
|
590
|
-
*
|
|
591
|
-
* @internal Exposed as package-private for testing.
|
|
592
|
-
*/
|
|
593
|
-
withFingerprint(withClause: WithClause | undefined, table?: string, depth?: number): string;
|
|
594
|
-
/**
|
|
595
|
-
* Collect params from a `with` clause tree. Mirrors buildSelectWithRelations +
|
|
596
|
-
* buildRelationSubquery param-push order.
|
|
597
|
-
*/
|
|
598
|
-
private collectWithParams;
|
|
599
|
-
/**
|
|
600
|
-
* Collect params from a single relation subquery. Mirrors buildRelationSubquery.
|
|
601
|
-
*/
|
|
602
|
-
private collectRelationSubqueryParams;
|
|
603
|
-
/**
|
|
604
|
-
* Fingerprint SET clauses for update/updateMany.
|
|
605
|
-
* Captures key names + operator types (set/increment/etc) but not values.
|
|
606
|
-
*/
|
|
607
|
-
private fingerprintSet;
|
|
608
|
-
/**
|
|
609
|
-
* Collect SET params for update/updateMany. Mirrors buildSetClause param order.
|
|
610
|
-
*/
|
|
611
|
-
private collectSetParams;
|
|
612
|
-
/** Build WHERE clause from a where object (supports operators, NULL, OR) */
|
|
613
|
-
private buildWhere;
|
|
614
|
-
/**
|
|
615
|
-
* Resolve the configured global filter for `table`, evaluating a function
|
|
616
|
-
* filter, honoring the active query's `skipGlobalFilters`. Returns `null` when
|
|
617
|
-
* no filter applies, the query opted out, or the filter is empty.
|
|
618
|
-
*/
|
|
619
451
|
private resolveGlobalFilter;
|
|
620
|
-
/**
|
|
621
|
-
* AND-merge this table's resolved global filter into a user `where`. Either
|
|
622
|
-
* side may be absent. When no filter applies the user where is returned by
|
|
623
|
-
* reference, so fingerprints/SQL stay byte-identical to the pre-0.28 path.
|
|
624
|
-
*/
|
|
625
452
|
private mergeGlobalFilter;
|
|
626
|
-
/**
|
|
627
|
-
* SQL clause for `targetTable`'s global filter rendered against `alias`
|
|
628
|
-
* (relation subqueries, `_count`, relation `orderBy`). Pushes its params to
|
|
629
|
-
* `params`; returns `''` when no filter applies. Mirror:
|
|
630
|
-
* {@link collectTargetGlobalFilterAlias}.
|
|
631
|
-
*/
|
|
632
|
-
private targetGlobalFilterAlias;
|
|
633
|
-
/** Param-collect mirror of {@link targetGlobalFilterAlias}. */
|
|
634
453
|
private collectTargetGlobalFilterAlias;
|
|
635
|
-
/**
|
|
636
|
-
* SQL clause for `targetTable`'s global filter rendered against the bare
|
|
637
|
-
* (unaliased) table name — the form used inside relation-filter `EXISTS`
|
|
638
|
-
* subqueries. Pushes its params; `''` when none. Mirror:
|
|
639
|
-
* {@link collectTargetGlobalFilterExists}.
|
|
640
|
-
*/
|
|
641
|
-
private targetGlobalFilterExists;
|
|
642
|
-
/** Param-collect mirror of {@link targetGlobalFilterExists}. */
|
|
643
|
-
private collectTargetGlobalFilterExists;
|
|
644
|
-
/**
|
|
645
|
-
* Value-invariant SQL-cache-key segment for the active global-filter
|
|
646
|
-
* environment. Relation-subquery / relation-filter / `_count` / relation-
|
|
647
|
-
* `orderBy` global filters are rendered at build time but their SHAPE is not
|
|
648
|
-
* otherwise in the where/with fingerprint, so this segment guards the cache:
|
|
649
|
-
* two different filter shapes never collide on one cached SQL text, while two
|
|
650
|
-
* function-filter results of the SAME shape (differing only in values) share
|
|
651
|
-
* the entry and bind their own params. Empty (`''`) when no filter applies, so
|
|
652
|
-
* cache keys stay byte-identical when the feature is unused.
|
|
653
|
-
*/
|
|
654
454
|
private globalFilterCacheSegment;
|
|
655
|
-
/**
|
|
656
|
-
* True when the USER-supplied `where` compiles to no predicate (`{}`,
|
|
657
|
-
* `{ id: undefined }`, `{ OR: [{ a: undefined }] }`, …). This is the exact
|
|
658
|
-
* signal the empty-`where` guard needs — the compiled emptiness, NOT the
|
|
659
|
-
* fingerprint (which is non-empty for an all-undefined `OR`/`AND`). It ignores
|
|
660
|
-
* any configured global filter, so a global filter never lets an unguarded
|
|
661
|
-
* mass mutation through.
|
|
662
|
-
*/
|
|
663
|
-
private userPredicateIsEmpty;
|
|
664
|
-
private assertMutationHasPredicate;
|
|
665
|
-
/**
|
|
666
|
-
* Build the inner WHERE expression (without the WHERE keyword).
|
|
667
|
-
* Returns null if no conditions exist.
|
|
668
|
-
* Supports: equality, operators, NULL, OR, AND, NOT, relation filters (some/every/none).
|
|
669
|
-
*/
|
|
670
455
|
private buildWhereClause;
|
|
671
|
-
/**
|
|
672
|
-
* Build relation filter SQL: WHERE EXISTS / NOT EXISTS subquery
|
|
673
|
-
* Supports: some (EXISTS), every (NOT EXISTS ... NOT), none (NOT EXISTS)
|
|
674
|
-
*/
|
|
675
|
-
private buildRelationFilter;
|
|
676
|
-
/**
|
|
677
|
-
* Build WHERE clause conditions for a relation filter subquery.
|
|
678
|
-
* Uses the target table's column mapping to resolve field names.
|
|
679
|
-
*/
|
|
680
|
-
private buildSubWhereForRelation;
|
|
681
|
-
/**
|
|
682
|
-
* Resolve a column's Postgres type from an arbitrary table's metadata
|
|
683
|
-
* (relation targets, not just `this.table`).
|
|
684
|
-
*/
|
|
685
|
-
private pgTypeForColumn;
|
|
686
|
-
/**
|
|
687
|
-
* The Postgres enum type name for a column, when the schema knows one.
|
|
688
|
-
*
|
|
689
|
-
* Introspection stores each column's `udt_name` in `pgTypes` and every
|
|
690
|
-
* database enum in `schema.enums` (typname → labels); a column whose type
|
|
691
|
-
* matches an enum key needs an explicit `::"EnumName"` cast on its write
|
|
692
|
-
* binds — bulk-insert forms like `UNNEST($1::text[])` otherwise type the
|
|
693
|
-
* value as text and Postgres refuses the implicit text→enum coercion
|
|
694
|
-
* ("column X is of type Y but expression is of type text").
|
|
695
|
-
*
|
|
696
|
-
* Postgres-only by construction: gated on the active dialect being
|
|
697
|
-
* `postgresql` AND on `schema.enums` having entries (only PG introspection
|
|
698
|
-
* produces them — `defineSchema` and the other engines leave it empty), so
|
|
699
|
-
* SQLite/MySQL/MSSQL/PowDB output is byte-identical.
|
|
700
|
-
*/
|
|
701
|
-
private enumTypeForColumn;
|
|
702
|
-
/**
|
|
703
|
-
* `::"EnumName"` cast suffix for a write-bind placeholder on an enum
|
|
704
|
-
* column; `''` for every other column, so non-enum SQL stays byte-identical.
|
|
705
|
-
* The type name is an introspected identifier and is quoted via the dialect.
|
|
706
|
-
*/
|
|
707
|
-
private enumCastSuffix;
|
|
708
|
-
/**
|
|
709
|
-
* Equality-fallthrough guard shared by every SQL-build path AND every
|
|
710
|
-
* cache-hit param-collect path. A plain object literal that matched no known
|
|
711
|
-
* filter shape on a non-JSON column is almost always a misspelled operator
|
|
712
|
-
* (`startWith` for `startsWith`); binding it as `col = $1` silently returns
|
|
713
|
-
* wrong rows. Class instances (Buffer for bytea, Decimal wrappers, ...) are
|
|
714
|
-
* legitimate bind values and pass through, as do objects on json/jsonb
|
|
715
|
-
* columns (object equality).
|
|
716
|
-
*/
|
|
717
|
-
private assertBindableEqualityValue;
|
|
718
|
-
/**
|
|
719
|
-
* Build the user-supplied `where` filter of a relation `with` clause against
|
|
720
|
-
* the relation's table alias. Supports the same scalar surface as the
|
|
721
|
-
* top-level WHERE builder — equality, IS NULL, operator objects (incl.
|
|
722
|
-
* `mode: 'insensitive'`), and OR/AND/NOT combinators. Unknown operator
|
|
723
|
-
* objects throw via {@link assertBindableEqualityValue}.
|
|
724
|
-
*
|
|
725
|
-
* Param push order MUST mirror {@link collectAliasWhereParams} exactly, or
|
|
726
|
-
* cache hits and pipeline batching will desync.
|
|
727
|
-
*/
|
|
728
456
|
private buildAliasWhere;
|
|
729
|
-
|
|
730
|
-
private
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
* differently-shaped wheres would share one cached SQL string.
|
|
736
|
-
*/
|
|
737
|
-
private fingerprintAliasWhere;
|
|
738
|
-
/**
|
|
739
|
-
* Validate a `{ col }` column reference against its table and return the
|
|
740
|
-
* resolved snake_case column name. Shared by the SQL-build path
|
|
741
|
-
* ({@link buildOperatorClauses}) and the cache-hit param-collect path
|
|
742
|
-
* (`collectOperatorParams`) so both always throw identically: a warmed
|
|
743
|
-
* cache can never skip the check.
|
|
744
|
-
*/
|
|
745
|
-
private resolveColumnRef;
|
|
457
|
+
private vectorOperator;
|
|
458
|
+
private pushVectorParam;
|
|
459
|
+
private normalizeRelationFilter;
|
|
460
|
+
private isJsonColumnType;
|
|
461
|
+
private getColumnPgType;
|
|
462
|
+
private jsonPathParam;
|
|
746
463
|
/**
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
*
|
|
464
|
+
* Collect params for an orderBy clause. Vector KNN ordering pushes the
|
|
465
|
+
* `$n::vector` query vector and JSON-path ordering pushes its text[] path;
|
|
466
|
+
* plain direction ordering is parameterless. Mirrors buildOrderBy's push
|
|
467
|
+
* order exactly so the cached-SQL param re-collection stays in lockstep.
|
|
750
468
|
*/
|
|
751
|
-
private
|
|
469
|
+
private collectOrderByParams;
|
|
752
470
|
/**
|
|
753
|
-
*
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
* `equals`/`not`/`gt`/`gte`/`lt`/`lte` also accept a {@link ColumnRef}
|
|
757
|
-
* (`{ col: 'otherField' }`) which compiles to a column-to-column comparison
|
|
758
|
-
* against `refCtx`: no param bound, so `collectOperatorParams` mirrors by
|
|
759
|
-
* pushing nothing and the referenced name lives in the fingerprint.
|
|
471
|
+
* The {@link WhereHost} for a scoped sub-where over `meta`'s table. Memoized
|
|
472
|
+
* per table (see {@link scopedHostCache}); the host depends only on the target
|
|
473
|
+
* metadata, not on the caller's alias/qualifier.
|
|
760
474
|
*/
|
|
761
|
-
private buildOperatorClauses;
|
|
762
475
|
/**
|
|
763
476
|
* Build ORDER BY clause from an object.
|
|
764
477
|
*
|
|
@@ -769,187 +482,6 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
769
482
|
* findMany path). When `params` is omitted (groupBy / relation path) a vector
|
|
770
483
|
* ordering throws — KNN ordering is only supported at the top level.
|
|
771
484
|
*/
|
|
772
|
-
/**
|
|
773
|
-
* Value-shape fingerprint for a single orderBy entry, so two queries whose
|
|
774
|
-
* ORDER BY differs only in nulls placement, vector metric, or relation-count
|
|
775
|
-
* vs relation-column never collide on one cached SQL string. Captures the
|
|
776
|
-
* SQL-shaping bits (direction, nulls, metric, relation keys) — never values.
|
|
777
|
-
*/
|
|
778
|
-
private orderByEntryFingerprint;
|
|
779
|
-
private buildOrderBy;
|
|
780
|
-
/**
|
|
781
|
-
* True when an orderBy value is a relation-ordering object: a plain object
|
|
782
|
-
* that is neither a vector KNN ordering nor an {@link OrderBySpec}. Its key
|
|
783
|
-
* in the orderBy clause is a relation name.
|
|
784
|
-
*/
|
|
785
|
-
private isRelationOrderByValue;
|
|
786
|
-
/**
|
|
787
|
-
* Render the ` NULLS FIRST` / ` NULLS LAST` suffix for a column ordering.
|
|
788
|
-
* Only PostgreSQL and SQLite support the `NULLS FIRST/LAST` grammar — on any
|
|
789
|
-
* other engine a caller asking for explicit nulls placement gets a clear
|
|
790
|
-
* {@link UnsupportedFeatureError} (E017) instead of broken SQL.
|
|
791
|
-
*/
|
|
792
|
-
private nullsSuffix;
|
|
793
|
-
/**
|
|
794
|
-
* Resolve an orderBy key to its snake_case column via the table's columnMap
|
|
795
|
-
* (camelToSnake fallback), throwing the SAME unknown-field E003 the top-level
|
|
796
|
-
* where path uses. Shared by top-level JSON-path ordering and every nested
|
|
797
|
-
* relation orderBy path so nested orderBy accepts exactly what top-level
|
|
798
|
-
* accepts (the 0.30.x bug: nested orderBy skipped the columnMap and rejected
|
|
799
|
-
* camelCase-named DB columns like "sortOrder").
|
|
800
|
-
*/
|
|
801
|
-
private resolveOrderByColumn;
|
|
802
|
-
/**
|
|
803
|
-
* Validate a {@link JsonPathOrderBy} entry: column must exist AND be
|
|
804
|
-
* json/jsonb, path must be a non-empty array of keys/indexes: and return
|
|
805
|
-
* the resolved column. Shared by the SQL-build path
|
|
806
|
-
* ({@link buildJsonPathOrderEntry}) and the cache-hit param-collect mirrors
|
|
807
|
-
* so both always throw identically.
|
|
808
|
-
*/
|
|
809
|
-
private validateJsonPathOrderBy;
|
|
810
|
-
/**
|
|
811
|
-
* Compile one {@link JsonPathOrderBy} entry:
|
|
812
|
-
* `("col" #>> $n::text[])::numeric ASC`: the numeric cast only with
|
|
813
|
-
* `type: 'numeric'` (default is text comparison), the extraction routed
|
|
814
|
-
* through the dialect's JSON hook exactly like the JSON where-filters, the
|
|
815
|
-
* path bound as ONE text[] param (mirrored by the order-param collectors).
|
|
816
|
-
* `prefix` scopes the column (`''` top-level, `t0.` inside a relation
|
|
817
|
-
* subquery).
|
|
818
|
-
*/
|
|
819
|
-
private buildJsonPathOrderEntry;
|
|
820
|
-
/**
|
|
821
|
-
* Compile a relation ordering term. For a to-many relation the only allowed
|
|
822
|
-
* key is `_count`, which becomes a correlated `COUNT(*)` subquery. For a
|
|
823
|
-
* to-one relation each entry names a target column and becomes a correlated
|
|
824
|
-
* scalar subquery (supporting {@link OrderBySpec} nulls placement).
|
|
825
|
-
*
|
|
826
|
-
* Validation: relation must exist (E005); to-many only allows `_count`, and
|
|
827
|
-
* to-one only allows real target columns (E003).
|
|
828
|
-
*
|
|
829
|
-
* `ctx` generalizes the term beyond the root table: inside a relation
|
|
830
|
-
* subquery's orderBy the relations live on the TARGET table's metadata and
|
|
831
|
-
* the correlation parent is the relation's alias, not `this.table`.
|
|
832
|
-
*/
|
|
833
|
-
private buildRelationOrderBy;
|
|
834
|
-
/**
|
|
835
|
-
* Validate a {@link RelationPickOrderBy} entry's scope and shape. Shared by
|
|
836
|
-
* the SQL-build path ({@link buildRelationPickOrderBy}) and the cache-hit
|
|
837
|
-
* param-collect mirror ({@link collectRelationPickOrderParams}) so both
|
|
838
|
-
* always throw identically:
|
|
839
|
-
*
|
|
840
|
-
* - `nested` (inside a relation subquery's orderBy or a pick.orderBy):
|
|
841
|
-
* top-level findMany only in this release (E003),
|
|
842
|
-
* - manyToMany: not supported (E003 naming the limitation),
|
|
843
|
-
* - to-one: order by the target column directly instead (E003),
|
|
844
|
-
* - `pick.orderBy` is REQUIRED (deterministic row choice),
|
|
845
|
-
* - `by` must be a target column name or a `{ field, path }` JSON-path spec.
|
|
846
|
-
*/
|
|
847
|
-
private pickOrderNestedError;
|
|
848
|
-
private validatePickOrderBy;
|
|
849
|
-
/**
|
|
850
|
-
* Compile a {@link RelationPickOrderBy} term: a correlated scalar subquery
|
|
851
|
-
* that picks ONE related row (`ORDER BY <pick.orderBy> LIMIT 1`, optionally
|
|
852
|
-
* filtered by `pick.where` and the target's global filter) and surfaces one
|
|
853
|
-
* value from it (a plain target column or a JSON-path extraction) as the
|
|
854
|
-
* parent ORDER BY key:
|
|
855
|
-
*
|
|
856
|
-
* ```sql
|
|
857
|
-
* (SELECT ord0."data" #>> $1::text[] FROM "versions" ord0
|
|
858
|
-
* WHERE ord0."instance_id" = "instances"."id" AND ord0."is_current" = $2
|
|
859
|
-
* ORDER BY ord0."created_at" DESC LIMIT 1) ASC NULLS LAST
|
|
860
|
-
* ```
|
|
861
|
-
*
|
|
862
|
-
* Param-push order (mirrored EXACTLY by
|
|
863
|
-
* {@link collectRelationPickOrderParams}): `by` JSON path (if any) →
|
|
864
|
-
* target global filter → `pick.where` → `pick.orderBy` JSON paths.
|
|
865
|
-
*/
|
|
866
|
-
private buildRelationPickOrderBy;
|
|
867
|
-
/**
|
|
868
|
-
* Compile the shared inner pieces of a pick-row ordering against `childAlias`
|
|
869
|
-
* (the table alias the related row is read from): the `by` value expression,
|
|
870
|
-
* the correlation + target global filter + `pick.where` predicate, and the
|
|
871
|
-
* `pick.orderBy` clause. Factored out of {@link buildRelationPickOrderBy} so
|
|
872
|
-
* the subquery and lateral plans build IDENTICAL pieces in the SAME param
|
|
873
|
-
* push order (`by` JSON path → target global filter → `pick.where` →
|
|
874
|
-
* `pick.orderBy` JSON paths), which is why the collect mirror
|
|
875
|
-
* ({@link collectRelationPickOrderParams}) is plan-agnostic.
|
|
876
|
-
*/
|
|
877
|
-
private compilePickPieces;
|
|
878
|
-
/**
|
|
879
|
-
* Param-collect mirror of {@link buildRelationPickOrderBy}: re-runs the same
|
|
880
|
-
* validation (a warmed cache can never skip it), then pushes in the same
|
|
881
|
-
* order: `by` JSON path → target global filter → `pick.where` →
|
|
882
|
-
* `pick.orderBy` JSON paths.
|
|
883
|
-
*/
|
|
884
|
-
private collectRelationPickOrderParams;
|
|
885
|
-
/**
|
|
886
|
-
* Compile the ORDER BY terms of a relation `with` clause against the
|
|
887
|
-
* relation's table alias. One unified path for every relation shape
|
|
888
|
-
* (hasMany / manyToMany / belongsTo / hasOne) supporting exactly what the
|
|
889
|
-
* top-level orderBy accepts at this level:
|
|
890
|
-
*
|
|
891
|
-
* - scalar columns via columnMap resolution (camelToSnake fallback) with
|
|
892
|
-
* {@link OrderBySpec} nulls placement,
|
|
893
|
-
* - {@link JsonPathOrderBy} entries (path bound as one text[] param),
|
|
894
|
-
* - relation ordering on the TARGET's relations (`_count` for to-many, a
|
|
895
|
-
* target column for to-one), correlated to the relation alias,
|
|
896
|
-
* - vector KNN ordering stays top-level-only (E003, same as before).
|
|
897
|
-
*
|
|
898
|
-
* Param pushes (JSON paths, relation-order global filters) MUST be mirrored,
|
|
899
|
-
* in the same order, by {@link collectRelationOrderParams}.
|
|
900
|
-
*/
|
|
901
|
-
private buildRelationOrderClause;
|
|
902
|
-
/**
|
|
903
|
-
* Param-collect mirror of {@link buildRelationOrderClause}: JSON-path
|
|
904
|
-
* entries push their path (one text[] param each); relation-order entries
|
|
905
|
-
* mirror {@link collectOrderByParams}' relation branch (count / to-one
|
|
906
|
-
* global-filter params); scalar entries push nothing but re-run the same
|
|
907
|
-
* column validation so a warmed cache can never skip it.
|
|
908
|
-
*/
|
|
909
|
-
private collectRelationOrderParams;
|
|
910
|
-
/**
|
|
911
|
-
* Build a correlated `(SELECT COUNT(*) …)` scalar subquery for a to-many
|
|
912
|
-
* relation, correlated to `parentRef`. hasMany counts child rows via the FK;
|
|
913
|
-
* manyToMany counts junction rows via the source key. Shared by the `_count`
|
|
914
|
-
* `with` key and to-many relation orderBy.
|
|
915
|
-
*
|
|
916
|
-
* When `params` is supplied and the target has a global filter, it is
|
|
917
|
-
* AND-merged so the count only sees surviving rows (a soft-deleted child is
|
|
918
|
-
* not counted): hasMany filters the counted rows directly; manyToMany adds an
|
|
919
|
-
* `EXISTS` on the target through the junction (the junction rows themselves
|
|
920
|
-
* carry no filter). Params are mirrored by {@link collectRelationCountParams}.
|
|
921
|
-
*/
|
|
922
|
-
private buildRelationCountExpr;
|
|
923
|
-
/**
|
|
924
|
-
* `EXISTS (SELECT 1 FROM <target> <talias> WHERE <join> AND <gf>)` restricting
|
|
925
|
-
* a manyToMany `_count` to targets that survive their global filter. `''` when
|
|
926
|
-
* the target has no filter. Pushes gf params; mirror:
|
|
927
|
-
* {@link collectManyToManyTargetGlobalFilter}.
|
|
928
|
-
*/
|
|
929
|
-
private manyToManyTargetGlobalFilterExists;
|
|
930
|
-
/** Param-collect mirror of {@link manyToManyTargetGlobalFilterExists}. */
|
|
931
|
-
private collectManyToManyTargetGlobalFilter;
|
|
932
|
-
/**
|
|
933
|
-
* Param-collect mirror of {@link buildRelationCountExpr}'s global-filter
|
|
934
|
-
* params (hasMany direct filter, or manyToMany EXISTS-on-target). Only pushes
|
|
935
|
-
* when a filter applies — no-op otherwise.
|
|
936
|
-
*/
|
|
937
|
-
private collectRelationCountParams;
|
|
938
|
-
/**
|
|
939
|
-
* Resolve a {@link VectorMetric} to its pgvector distance operator from a
|
|
940
|
-
* fixed allow-list, validating the target column is actually a `vector`
|
|
941
|
-
* column. Throws {@link ValidationError} for an unknown metric or a
|
|
942
|
-
* non-vector column — a user-supplied string can never become a SQL operator.
|
|
943
|
-
*/
|
|
944
|
-
private vectorOperator;
|
|
945
|
-
/**
|
|
946
|
-
* Validate and bind a query vector as a single `$n::vector` parameter.
|
|
947
|
-
* Every element must be a finite number (no NaN / Infinity / strings) so a
|
|
948
|
-
* malformed array can never produce a broken `::vector` literal, and the array
|
|
949
|
-
* is NEVER string-interpolated into the SQL text. Returns the `$n::vector`
|
|
950
|
-
* placeholder string.
|
|
951
|
-
*/
|
|
952
|
-
private pushVectorParam;
|
|
953
485
|
/** Parse a flat row: convert snake_case to camelCase + Date coercion */
|
|
954
486
|
/**
|
|
955
487
|
* Returns the set of camelCase field names for a table's date columns,
|
|
@@ -957,304 +489,5 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
957
489
|
* memoized per table. Used so nested relation rows (camelCase keys) coerce
|
|
958
490
|
* dates the same way top-level rows do.
|
|
959
491
|
*/
|
|
960
|
-
/**
|
|
961
|
-
* Prisma-compat: a plain object on a to-one relation key —
|
|
962
|
-
* `where: { vendor: { name: { contains: 'x' } } }` — is an implicit `is`
|
|
963
|
-
* filter. Normalize it to `{ is: obj }` so all downstream handling (SQL,
|
|
964
|
-
* params, fingerprint) sees one canonical shape. To-many relations still
|
|
965
|
-
* require an explicit `some`/`every`/`none` (a bare object there is
|
|
966
|
-
* ambiguous and was never valid in Prisma either).
|
|
967
|
-
*/
|
|
968
|
-
private normalizeRelationFilter;
|
|
969
|
-
private getCamelDateFields;
|
|
970
492
|
private parseRow;
|
|
971
|
-
/** Parse a row that may contain JSON nested relation columns */
|
|
972
|
-
private parseNestedRow;
|
|
973
|
-
/**
|
|
974
|
-
* Resolve the emitted column list for a relation, honoring `select` / `omit`.
|
|
975
|
-
* Shared by {@link buildRelationSubquery} (json order) and
|
|
976
|
-
* {@link buildRelationShape} (decode key order) so they can never diverge.
|
|
977
|
-
*/
|
|
978
|
-
private resolveTargetColumns;
|
|
979
|
-
/**
|
|
980
|
-
* Render a single relation row's JSON: a keyed object (`'object'`) or a
|
|
981
|
-
* positional array (`'positional'`). The array drops the keys but keeps the
|
|
982
|
-
* exact expression order, so {@link RelationShape.keys} maps positions back.
|
|
983
|
-
*/
|
|
984
|
-
private buildJsonRow;
|
|
985
|
-
/**
|
|
986
|
-
* Build the top-level relation shapes for a `with` clause, mirroring
|
|
987
|
-
* {@link buildSelectWithRelations}: same relation iteration order, same
|
|
988
|
-
* per-relation column resolution, same nested recursion.
|
|
989
|
-
*/
|
|
990
|
-
private buildRelationShapes;
|
|
991
|
-
/**
|
|
992
|
-
* Recursively describe one relation's positional layout: the camelCase key
|
|
993
|
-
* order (scalar columns first, then nested relation slots in the same order
|
|
994
|
-
* {@link buildRelationSubquery} appends them), the nested sub-shapes, and the
|
|
995
|
-
* cardinality (single object for belongsTo/hasOne, array for the rest).
|
|
996
|
-
*/
|
|
997
|
-
private buildRelationShape;
|
|
998
|
-
/**
|
|
999
|
-
* Build the row parser for a `with` clause. In object mode this is just
|
|
1000
|
-
* {@link parseNestedRow}. In positional mode it decodes each relation's
|
|
1001
|
-
* positional arrays into the object form first (shapes built once, not per
|
|
1002
|
-
* row), then delegates to parseNestedRow for date/snake-camel coercion.
|
|
1003
|
-
*/
|
|
1004
|
-
private makeNestedParser;
|
|
1005
|
-
/**
|
|
1006
|
-
* Return a shallow copy of a top-level row with each relation column decoded
|
|
1007
|
-
* from its positional array(s) into the object representation. Only relation
|
|
1008
|
-
* columns are positional — base scalar columns stay object-keyed — so the
|
|
1009
|
-
* result is exactly what the object encoding would have handed parseNestedRow.
|
|
1010
|
-
*/
|
|
1011
|
-
private decodePositionalRelations;
|
|
1012
|
-
/**
|
|
1013
|
-
* Decode one relation's positional JSON value. `json_agg` returns the value as
|
|
1014
|
-
* a JSON string at the top level (JSON.parse once); nested relation slots are
|
|
1015
|
-
* already-parsed arrays. A `'many'` value is an array of positional arrays; a
|
|
1016
|
-
* `'one'` value is a single positional array or null.
|
|
1017
|
-
*/
|
|
1018
|
-
private decodePositionalValue;
|
|
1019
|
-
/** Map one positional array back to a keyed object using the shape's key order. */
|
|
1020
|
-
private decodePositionalObject;
|
|
1021
|
-
/**
|
|
1022
|
-
* Build a SELECT clause that includes both base columns and nested relation subqueries.
|
|
1023
|
-
*
|
|
1024
|
-
* For each relation specified in the `with` clause, this method generates a correlated
|
|
1025
|
-
* subquery using PostgreSQL's `json_agg(json_build_object(...))` pattern. The result
|
|
1026
|
-
* is a single SQL SELECT clause that resolves the full object tree in one query --
|
|
1027
|
-
* no N+1 problem.
|
|
1028
|
-
*
|
|
1029
|
-
* **How it works:**
|
|
1030
|
-
* 1. Resolves the base columns for the root table (all columns, or a subset via `columnsList`).
|
|
1031
|
-
* 2. Iterates over each key in the `with` clause, looking up the relation definition.
|
|
1032
|
-
* 3. For each relation, delegates to {@link buildRelationSubquery} to generate a
|
|
1033
|
-
* correlated subquery that returns JSON (array for hasMany, object for belongsTo/hasOne).
|
|
1034
|
-
* 4. Each subquery is aliased as the relation name in the final SELECT.
|
|
1035
|
-
*
|
|
1036
|
-
* **aliasCounter:** A shared `{ n: number }` object is passed through all nesting levels.
|
|
1037
|
-
* Each call to `buildRelationSubquery` increments it to produce unique table aliases
|
|
1038
|
-
* (`t0`, `t1`, `t2`, ...) across arbitrarily deep relation trees, preventing alias
|
|
1039
|
-
* collisions in the generated SQL.
|
|
1040
|
-
*
|
|
1041
|
-
* **Example output:**
|
|
1042
|
-
* ```sql
|
|
1043
|
-
* "users"."id", "users"."name", "users"."email",
|
|
1044
|
-
* (SELECT COALESCE(json_agg(json_build_object('id', t0."id", 'title', t0."title")), '[]'::json)
|
|
1045
|
-
* FROM "posts" t0 WHERE t0."user_id" = "users"."id") AS "posts"
|
|
1046
|
-
* ```
|
|
1047
|
-
*
|
|
1048
|
-
* @param table - The root table name (e.g. `"users"`).
|
|
1049
|
-
* @param withClause - An object mapping relation names to their include specs
|
|
1050
|
-
* (`true` for default inclusion, or `WithOptions` for select/omit/where/orderBy/limit).
|
|
1051
|
-
* @param params - Shared parameter array for parameterized values (`$1`, `$2`, ...).
|
|
1052
|
-
* Nested where/limit values are pushed here to prevent SQL injection.
|
|
1053
|
-
* @param columnsList - Optional subset of columns to include in the SELECT. When `null`
|
|
1054
|
-
* or omitted, all columns from the table's schema metadata are used.
|
|
1055
|
-
* @param depth - Current nesting depth, passed through to {@link buildRelationSubquery}
|
|
1056
|
-
* for circular-relation detection. Defaults to `0` at the top level.
|
|
1057
|
-
* @param path - Breadcrumb trail of relation names traversed so far, used in error
|
|
1058
|
-
* messages when circular or too-deep nesting is detected.
|
|
1059
|
-
* @returns A complete SELECT clause string (without the `SELECT` keyword) containing
|
|
1060
|
-
* base columns and relation subqueries.
|
|
1061
|
-
*/
|
|
1062
|
-
private buildSelectWithRelations;
|
|
1063
|
-
/**
|
|
1064
|
-
* Generate a correlated subquery that returns JSON for a single relation.
|
|
1065
|
-
*
|
|
1066
|
-
* This is the core of Turbine's single-query nested relation strategy. For a given
|
|
1067
|
-
* relation (e.g. `posts` on a `users` query), it produces a self-contained SQL subquery
|
|
1068
|
-
* that PostgreSQL evaluates per parent row, returning either a JSON array (hasMany) or
|
|
1069
|
-
* a single JSON object (belongsTo / hasOne).
|
|
1070
|
-
*
|
|
1071
|
-
* ### Algorithm overview
|
|
1072
|
-
*
|
|
1073
|
-
* 1. **Alias generation:** Allocates a unique alias (`t0`, `t1`, ...) from the shared
|
|
1074
|
-
* `aliasCounter` so that deeply nested subqueries never collide.
|
|
1075
|
-
*
|
|
1076
|
-
* 2. **Column resolution:** Honors `select` / `omit` options to control which columns
|
|
1077
|
-
* appear in the output JSON.
|
|
1078
|
-
*
|
|
1079
|
-
* 3. **`json_build_object`:** Builds a JSON object for each row by mapping camelCase
|
|
1080
|
-
* field names to their column values:
|
|
1081
|
-
* ```sql
|
|
1082
|
-
* json_build_object('id', t0."id", 'title', t0."title", 'createdAt', t0."created_at")
|
|
1083
|
-
* ```
|
|
1084
|
-
*
|
|
1085
|
-
* 4. **`json_agg` wrapping (hasMany):** For one-to-many relations, wraps the
|
|
1086
|
-
* `json_build_object` call in `json_agg(...)` to aggregate all matching child rows
|
|
1087
|
-
* into a JSON array. Uses `COALESCE(..., '[]'::json)` so the result is never NULL.
|
|
1088
|
-
* For belongsTo / hasOne, no aggregation is used -- just the single JSON object
|
|
1089
|
-
* with `LIMIT 1`.
|
|
1090
|
-
*
|
|
1091
|
-
* 5. **Correlation (WHERE clause):** Links the subquery to the parent row:
|
|
1092
|
-
* - **hasMany:** `alias.foreignKey = parentRef.referenceKey`
|
|
1093
|
-
* (e.g. `t0."user_id" = "users"."id"` -- child FK points to parent PK)
|
|
1094
|
-
* - **belongsTo / hasOne:** `alias.referenceKey = parentRef.foreignKey`
|
|
1095
|
-
* (e.g. `t0."id" = "posts"."author_id"` -- parent FK points to child PK)
|
|
1096
|
-
*
|
|
1097
|
-
* 6. **Recursion:** If the spec includes a nested `with` clause, this method calls
|
|
1098
|
-
* itself recursively for each nested relation, passing the current alias as
|
|
1099
|
-
* `parentRef`. The nested subquery appears as an additional key in the
|
|
1100
|
-
* `json_build_object` call, wrapped in `COALESCE(..., '[]'::json)`.
|
|
1101
|
-
* Depth is incremented and capped at 10 to guard against circular relations.
|
|
1102
|
-
*
|
|
1103
|
-
* 7. **LIMIT / ORDER BY wrapping:** For hasMany relations with `limit` or `orderBy`,
|
|
1104
|
-
* the query is restructured into a two-level form:
|
|
1105
|
-
* ```sql
|
|
1106
|
-
* SELECT COALESCE(json_agg(json_build_object(...)), '[]'::json)
|
|
1107
|
-
* FROM (
|
|
1108
|
-
* SELECT t0.* FROM "posts" t0
|
|
1109
|
-
* WHERE t0."user_id" = "users"."id"
|
|
1110
|
-
* ORDER BY t0."created_at" DESC
|
|
1111
|
-
* LIMIT $1
|
|
1112
|
-
* ) t0i
|
|
1113
|
-
* ```
|
|
1114
|
-
* This ensures LIMIT and ORDER BY apply to the raw rows *before* `json_agg`
|
|
1115
|
-
* aggregation. Without the inner subquery, LIMIT would be meaningless because
|
|
1116
|
-
* `json_agg` produces a single aggregated row.
|
|
1117
|
-
*
|
|
1118
|
-
* 8. **Parameter threading:** All user-supplied values (where filters, limit) are
|
|
1119
|
-
* pushed to the shared `params` array with `$N` placeholders. No string
|
|
1120
|
-
* interpolation of user data ever occurs -- all identifiers go through
|
|
1121
|
-
* `this.q()` and all values are parameterized.
|
|
1122
|
-
*
|
|
1123
|
-
* ### Example output (hasMany with nested relation)
|
|
1124
|
-
* ```sql
|
|
1125
|
-
* SELECT COALESCE(json_agg(json_build_object(
|
|
1126
|
-
* 'id', t0."id",
|
|
1127
|
-
* 'title', t0."title",
|
|
1128
|
-
* 'comments', COALESCE((
|
|
1129
|
-
* SELECT COALESCE(json_agg(json_build_object('id', t1."id", 'body', t1."body")), '[]'::json)
|
|
1130
|
-
* FROM "comments" t1 WHERE t1."post_id" = t0."id"
|
|
1131
|
-
* ), '[]'::json)
|
|
1132
|
-
* )), '[]'::json) FROM "posts" t0 WHERE t0."user_id" = "users"."id"
|
|
1133
|
-
* ```
|
|
1134
|
-
*
|
|
1135
|
-
* @param relDef - The relation definition from schema metadata (contains `to`, `type`,
|
|
1136
|
-
* `foreignKey`, `referenceKey`).
|
|
1137
|
-
* @param spec - Either `true` (include with defaults) or a `WithOptions` object that
|
|
1138
|
-
* can specify `select`, `omit`, `where`, `orderBy`, `limit`, and nested `with`.
|
|
1139
|
-
* @param params - Shared parameter array. User-supplied values are pushed here and
|
|
1140
|
-
* referenced as `$1`, `$2`, etc. in the generated SQL.
|
|
1141
|
-
* @param parentRef - The alias (e.g. `"t0"`) or table name (e.g. `"users"`) of the
|
|
1142
|
-
* parent query. Used to build the correlated WHERE clause that ties
|
|
1143
|
-
* child rows to their parent row.
|
|
1144
|
-
* @param aliasCounter - Shared mutable counter (`{ n: number }`) for generating unique
|
|
1145
|
-
* table aliases (`t0`, `t1`, `t2`, ...) across all nesting levels.
|
|
1146
|
-
* Each call increments `n` by 1.
|
|
1147
|
-
* @param depth - Current nesting depth (starts at `0`). Incremented on each recursive
|
|
1148
|
-
* call. If it reaches 10, a {@link CircularRelationError} is thrown.
|
|
1149
|
-
* @param path - Breadcrumb trail of relation/table names traversed so far
|
|
1150
|
-
* (e.g. `["users", "posts", "comments"]`). Used in the error message
|
|
1151
|
-
* when circular or too-deep nesting is detected.
|
|
1152
|
-
* @returns A complete SQL subquery string (without surrounding parentheses) that
|
|
1153
|
-
* evaluates to a JSON array (hasMany) or a JSON object (belongsTo/hasOne).
|
|
1154
|
-
*/
|
|
1155
|
-
private buildRelationSubquery;
|
|
1156
|
-
/**
|
|
1157
|
-
* Build the json_agg subquery for a `manyToMany` relation, JOINing the target
|
|
1158
|
-
* table through a junction (join) table.
|
|
1159
|
-
*
|
|
1160
|
-
* Shape (no LIMIT/ORDER):
|
|
1161
|
-
* ```sql
|
|
1162
|
-
* SELECT COALESCE(json_agg(json_build_object(...)), '[]'::json)
|
|
1163
|
-
* FROM <target> <talias>
|
|
1164
|
-
* JOIN <junction> <jalias> ON <jalias>.<targetKey> = <talias>.<targetPK>
|
|
1165
|
-
* WHERE <jalias>.<sourceKey> = <parentRef>.<referenceKey>
|
|
1166
|
-
* ```
|
|
1167
|
-
*
|
|
1168
|
-
* With LIMIT/ORDER, the rows are wrapped in an inner subquery so the LIMIT
|
|
1169
|
-
* applies BEFORE aggregation (identical strategy to hasMany).
|
|
1170
|
-
*
|
|
1171
|
-
* Cardinality is always 'many' → empty-array fallback, never NULL.
|
|
1172
|
-
*
|
|
1173
|
-
* IMPORTANT: every `params.push` here MUST be mirrored, in the same order, in
|
|
1174
|
-
* {@link collectRelationSubqueryParams} or pipeline batching will desync.
|
|
1175
|
-
*/
|
|
1176
|
-
private buildManyToManySubquery;
|
|
1177
|
-
/**
|
|
1178
|
-
* Get the Postgres type for a column (e.g. 'jsonb', 'text', '_int4').
|
|
1179
|
-
* Used to detect JSONB/array columns for specialized operators.
|
|
1180
|
-
* Uses pre-computed Map for O(1) lookup instead of linear scan.
|
|
1181
|
-
*/
|
|
1182
|
-
/**
|
|
1183
|
-
* Case-insensitive json/jsonb column-type check. Postgres reports lowercase
|
|
1184
|
-
* udt_names, but SQLite/MySQL introspection surfaces the DECLARED type
|
|
1185
|
-
* (e.g. `JSON`), so every JSON-feature gate compares through this predicate
|
|
1186
|
-
* — build and collect sides alike, keeping the SQL-cache lockstep.
|
|
1187
|
-
*/
|
|
1188
|
-
private isJsonColumnType;
|
|
1189
|
-
private getColumnPgType;
|
|
1190
|
-
/**
|
|
1191
|
-
* Get the Postgres base element type for an array column.
|
|
1192
|
-
* E.g. '_text' → 'text', '_int4' → 'integer'
|
|
1193
|
-
*/
|
|
1194
|
-
private getArrayElementType;
|
|
1195
|
-
/**
|
|
1196
|
-
* Validate and enumerate the range comparisons (`gt`/`gte`/`lt`/`lte`) on a
|
|
1197
|
-
* JSON filter, in the fixed {@link JSON_RANGE_OPERATORS} order. Shared by
|
|
1198
|
-
* the SQL-build path ({@link buildJsonFilterClauses}) and the cache-hit
|
|
1199
|
-
* param-collect path ({@link collectJsonFilterParams}) so both always agree
|
|
1200
|
-
* on which params are pushed — and both throw identically for invalid
|
|
1201
|
-
* shapes, so a warmed cache can never skip validation.
|
|
1202
|
-
*/
|
|
1203
|
-
private jsonRangeEntries;
|
|
1204
|
-
/**
|
|
1205
|
-
* Build SQL clauses for JSONB filter operators on a column.
|
|
1206
|
-
* Supports: path, equals, contains, hasKey, gt, gte, lt, lte.
|
|
1207
|
-
*
|
|
1208
|
-
* The `path` param is bound at most once and its placeholder is shared by
|
|
1209
|
-
* every clause that extracts it (equals + range ops), so the param list
|
|
1210
|
-
* stays byte-identical to {@link collectJsonFilterParams}.
|
|
1211
|
-
*/
|
|
1212
|
-
private buildJsonFilterClauses;
|
|
1213
|
-
/**
|
|
1214
|
-
* Bind value for a JSON path parameter, encoded per dialect. PostgreSQL's
|
|
1215
|
-
* `#>>` takes a `text[]` (the segments as strings — or `nativeForm` when the
|
|
1216
|
-
* caller has a specific native binding, e.g. JsonFilter's raw path array).
|
|
1217
|
-
* Every other engine's JSON function (`json_extract` / `JSON_EXTRACT` /
|
|
1218
|
-
* `JSON_VALUE`) takes a `'$'`-rooted JSONPath STRING: binding the raw array
|
|
1219
|
-
* would arrive as `'["a"]'` (the driver shims JSON.stringify non-primitive
|
|
1220
|
-
* params) and fail at runtime with the engine's bad-JSON-path error. The
|
|
1221
|
-
* encoded path stays a bound parameter — never spliced into SQL text — so
|
|
1222
|
-
* the build/collect param mirrors stay in lockstep and injection-safe.
|
|
1223
|
-
*/
|
|
1224
|
-
private jsonPathParam;
|
|
1225
|
-
/**
|
|
1226
|
-
* Cast an extracted JSON path text value to a numeric type for range
|
|
1227
|
-
* comparison. PostgreSQL uses `(expr)::numeric` (exact — the right way to
|
|
1228
|
-
* compare JSON numbers, and `::float` would lose precision on big ints);
|
|
1229
|
-
* other dialects route through {@link Dialect.castAggregate} (SQLite/MySQL/
|
|
1230
|
-
* SQL Server have no `::` operator) as a float cast.
|
|
1231
|
-
*/
|
|
1232
|
-
private castJsonNumeric;
|
|
1233
|
-
/**
|
|
1234
|
-
* Build SQL clauses for Array filter operators on a column.
|
|
1235
|
-
* Supports: has, hasEvery, hasSome, isEmpty.
|
|
1236
|
-
*/
|
|
1237
|
-
private buildArrayFilterClauses;
|
|
1238
|
-
/**
|
|
1239
|
-
* Build SQL clauses for a pgvector distance WHERE filter:
|
|
1240
|
-
*
|
|
1241
|
-
* `"embedding" <-> $1::vector < $2`
|
|
1242
|
-
*
|
|
1243
|
-
* The query vector is bound as a `$n::vector` param (never interpolated), the
|
|
1244
|
-
* metric maps to an operator via a fixed allow-list, and each comparison
|
|
1245
|
-
* threshold (`lt`/`lte`/`gt`/`gte`) is its own bound param. Emits one clause
|
|
1246
|
-
* per supplied comparator (all ANDed). Param push order matches
|
|
1247
|
-
* {@link collectVectorFilterParams}.
|
|
1248
|
-
*/
|
|
1249
|
-
private buildVectorFilterClauses;
|
|
1250
|
-
/**
|
|
1251
|
-
* Build SQL clause for full-text search using to_tsvector @@ to_tsquery.
|
|
1252
|
-
* The config name is validated to prevent injection (only alphanumeric + underscore).
|
|
1253
|
-
*/
|
|
1254
|
-
private buildTextSearchClause;
|
|
1255
|
-
/**
|
|
1256
|
-
* Get the Postgres array type for a column (used by UNNEST in createMany).
|
|
1257
|
-
* Uses pre-computed Map for O(1) lookup instead of linear scan.
|
|
1258
|
-
*/
|
|
1259
|
-
private getColumnArrayType;
|
|
1260
493
|
}
|