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