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.
Files changed (68) hide show
  1. package/README.md +18 -16
  2. package/dist/cjs/cli/index.js +109 -16
  3. package/dist/cjs/cli/migrate.js +78 -3
  4. package/dist/cjs/cli/studio-ui.generated.js +1 -1
  5. package/dist/cjs/cli/studio.js +333 -22
  6. package/dist/cjs/cli/ui.js +7 -1
  7. package/dist/cjs/dialect.js +1 -1
  8. package/dist/cjs/generate.js +23 -2
  9. package/dist/cjs/index.js +2 -1
  10. package/dist/cjs/mssql.js +22 -5
  11. package/dist/cjs/powdb.js +41 -1
  12. package/dist/cjs/powql.js +80 -25
  13. package/dist/cjs/query/aggregates.js +683 -0
  14. package/dist/cjs/query/batched-loader.js +2 -0
  15. package/dist/cjs/query/builder.js +297 -4504
  16. package/dist/cjs/query/filters.js +12 -0
  17. package/dist/cjs/query/relations.js +1698 -0
  18. package/dist/cjs/query/where-compile.js +180 -0
  19. package/dist/cjs/query/where.js +1491 -0
  20. package/dist/cjs/query/writes.js +680 -0
  21. package/dist/cjs/schema-builder.js +6 -0
  22. package/dist/cjs/schema-metadata.js +4 -0
  23. package/dist/cjs/schema-sql.js +265 -3
  24. package/dist/cjs/sqlite.js +1 -1
  25. package/dist/cli/index.d.ts +8 -2
  26. package/dist/cli/index.js +111 -18
  27. package/dist/cli/migrate.d.ts +24 -1
  28. package/dist/cli/migrate.js +77 -3
  29. package/dist/cli/studio-ui.generated.js +1 -1
  30. package/dist/cli/studio.d.ts +46 -13
  31. package/dist/cli/studio.js +331 -23
  32. package/dist/cli/ui.js +7 -1
  33. package/dist/dialect.d.ts +15 -6
  34. package/dist/dialect.js +1 -1
  35. package/dist/generate.js +23 -2
  36. package/dist/index.d.ts +1 -1
  37. package/dist/index.js +1 -1
  38. package/dist/mssql.js +22 -5
  39. package/dist/powdb.d.ts +20 -0
  40. package/dist/powdb.js +40 -0
  41. package/dist/powql.d.ts +33 -1
  42. package/dist/powql.js +80 -25
  43. package/dist/query/aggregates.d.ts +74 -0
  44. package/dist/query/aggregates.js +641 -0
  45. package/dist/query/batched-loader.d.ts +6 -0
  46. package/dist/query/batched-loader.js +2 -0
  47. package/dist/query/builder.d.ts +62 -829
  48. package/dist/query/builder.js +302 -4509
  49. package/dist/query/deferred.d.ts +7 -0
  50. package/dist/query/filters.d.ts +7 -0
  51. package/dist/query/filters.js +11 -0
  52. package/dist/query/relations.d.ts +441 -0
  53. package/dist/query/relations.js +1627 -0
  54. package/dist/query/types.d.ts +15 -0
  55. package/dist/query/where-compile.d.ts +139 -0
  56. package/dist/query/where-compile.js +175 -0
  57. package/dist/query/where.d.ts +494 -0
  58. package/dist/query/where.js +1431 -0
  59. package/dist/query/writes.d.ts +131 -0
  60. package/dist/query/writes.js +626 -0
  61. package/dist/schema-builder.d.ts +18 -3
  62. package/dist/schema-builder.js +6 -0
  63. package/dist/schema-metadata.js +4 -0
  64. package/dist/schema-sql.d.ts +60 -3
  65. package/dist/schema-sql.js +261 -4
  66. package/dist/schema.d.ts +10 -0
  67. package/dist/sqlite.js +1 -1
  68. package/package.json +2 -2
@@ -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
- * Build a `SELECT * ... WHERE <predicate>` that re-fetches the row(s) matched
265
- * by a write's `where` clause. Used by the `'reselect'` result strategy to
266
- * return rows from non-RETURNING engines. Reuses the same parameterized WHERE
267
- * builder as reads, so no user value is interpolated.
268
- */
269
- private buildReselectByWhere;
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
- * Reject any write against a view (H4). Views are introspected with
488
- * `isView: true` and are read-only in every engine; a write raises a
489
- * {@link ValidationError} (E003) rather than emitting SQL Postgres would
490
- * reject (or, worse, silently applying to an updatable view).
491
- */
492
- private assertWritable;
493
- /**
494
- * Reject a write whose `data` names a `GENERATED ALWAYS AS (...) STORED`
495
- * column (H3). Postgres computes these from other columns and errors if you
496
- * try to write them; we fail early with a clear {@link ValidationError} (E003)
497
- * instead of surfacing a cryptic driver error. Undefined values are ignored
498
- * (they're stripped from the statement anyway).
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
- /** Mirrors {@link buildAliasWhere} param-push order for the cache-hit collect path. */
730
- private collectAliasWhereParams;
731
- /**
732
- * Value-invariant, shape-aware fingerprint for a relation `with` clause's
733
- * `where` filter. Must distinguish every SQL shape {@link buildAliasWhere}
734
- * can emit — equality vs null vs operator sets vs combinators — or two
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
- * Compile a `{ col }` reference to its quoted, prefix-matched SQL identifier.
748
- * NO param is bound: the referenced column is part of the SQL text (and of
749
- * the where fingerprint, see {@link fingerprintOperatorShape}).
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 columnRefSql;
469
+ private collectOrderByParams;
752
470
  /**
753
- * Build SQL clauses for a single operator object on a column.
754
- * Each operator key becomes its own clause, all ANDed together.
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
  }