turbine-orm 0.50.0 → 0.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -66
- package/dist/adapters/cockroachdb.d.ts +5 -5
- package/dist/adapters/cockroachdb.js +10 -10
- package/dist/adapters/index.d.ts +5 -5
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/yugabytedb.d.ts +7 -7
- package/dist/adapters/yugabytedb.js +10 -10
- package/dist/cjs/adapters/cockroachdb.d.ts +5 -5
- package/dist/cjs/adapters/cockroachdb.js +10 -10
- package/dist/cjs/adapters/index.d.ts +5 -5
- package/dist/cjs/adapters/index.js +7 -7
- package/dist/cjs/adapters/yugabytedb.d.ts +7 -7
- package/dist/cjs/adapters/yugabytedb.js +10 -10
- package/dist/cjs/cli/config.d.ts +13 -2
- package/dist/cjs/cli/config.js +3 -2
- package/dist/cjs/cli/destructive.d.ts +1 -1
- package/dist/cjs/cli/destructive.js +1 -1
- package/dist/cjs/cli/index.d.ts +10 -10
- package/dist/cjs/cli/index.js +49 -45
- package/dist/cjs/cli/loader.d.ts +7 -7
- package/dist/cjs/cli/loader.js +9 -9
- package/dist/cjs/cli/mcp.js +4 -4
- package/dist/cjs/cli/migrate.d.ts +5 -5
- package/dist/cjs/cli/migrate.js +11 -11
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/ui.d.ts +2 -2
- package/dist/cjs/cli/ui.js +2 -2
- package/dist/cjs/client.d.ts +49 -38
- package/dist/cjs/client.js +57 -56
- package/dist/cjs/dialect.d.ts +62 -18
- package/dist/cjs/dialect.js +40 -2
- package/dist/cjs/errors.d.ts +5 -5
- package/dist/cjs/errors.js +11 -11
- package/dist/cjs/generate.d.ts +6 -6
- package/dist/cjs/generate.js +31 -29
- package/dist/cjs/index-advisor.d.ts +5 -5
- package/dist/cjs/index-advisor.js +0 -0
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +7 -7
- package/dist/cjs/introspect.d.ts +35 -9
- package/dist/cjs/introspect.js +83 -32
- package/dist/cjs/mssql.d.ts +11 -11
- package/dist/cjs/mssql.js +64 -29
- package/dist/cjs/mysql.d.ts +8 -8
- package/dist/cjs/mysql.js +61 -23
- package/dist/cjs/nested-write.d.ts +21 -2
- package/dist/cjs/nested-write.js +51 -14
- package/dist/cjs/optional-peer-import.cjs +7 -7
- package/dist/cjs/optional-peer-import.d.cts +7 -7
- package/dist/cjs/pipeline-submittable.d.ts +2 -2
- package/dist/cjs/pipeline-submittable.js +6 -6
- package/dist/cjs/pipeline.d.ts +1 -1
- package/dist/cjs/pipeline.js +4 -4
- package/dist/cjs/powdb-introspect.d.ts +1 -1
- package/dist/cjs/powdb-introspect.js +1 -1
- package/dist/cjs/powdb.d.ts +28 -28
- package/dist/cjs/powdb.js +66 -66
- package/dist/cjs/powql.d.ts +27 -27
- package/dist/cjs/powql.js +73 -52
- package/dist/cjs/query/aggregates.d.ts +1 -1
- package/dist/cjs/query/aggregates.js +5 -5
- package/dist/cjs/query/batched-loader.d.ts +11 -11
- package/dist/cjs/query/batched-loader.js +24 -24
- package/dist/cjs/query/builder.d.ts +39 -21
- package/dist/cjs/query/builder.js +99 -57
- package/dist/cjs/query/compound-unique.d.ts +1 -1
- package/dist/cjs/query/compound-unique.js +0 -0
- package/dist/cjs/query/deferred.d.ts +12 -6
- package/dist/cjs/query/deferred.js +1 -1
- package/dist/cjs/query/filters.d.ts +31 -11
- package/dist/cjs/query/filters.js +67 -14
- package/dist/cjs/query/index.d.ts +1 -1
- package/dist/cjs/query/index.js +1 -1
- package/dist/cjs/query/relations.d.ts +9 -9
- package/dist/cjs/query/relations.js +164 -57
- package/dist/cjs/query/types.d.ts +86 -35
- package/dist/cjs/query/types.js +1 -1
- package/dist/cjs/query/utils.d.ts +27 -10
- package/dist/cjs/query/utils.js +86 -14
- package/dist/cjs/query/where.d.ts +47 -28
- package/dist/cjs/query/where.js +130 -31
- package/dist/cjs/query/writes.d.ts +24 -5
- package/dist/cjs/query/writes.js +102 -13
- package/dist/cjs/realtime.d.ts +7 -7
- package/dist/cjs/realtime.js +9 -9
- package/dist/cjs/schema-builder.d.ts +18 -7
- package/dist/cjs/schema-builder.js +17 -10
- package/dist/cjs/schema-metadata.d.ts +3 -3
- package/dist/cjs/schema-metadata.js +9 -9
- package/dist/cjs/schema-sql.d.ts +9 -9
- package/dist/cjs/schema-sql.js +20 -20
- package/dist/cjs/schema.d.ts +19 -9
- package/dist/cjs/schema.js +6 -6
- package/dist/cjs/serverless.d.ts +15 -15
- package/dist/cjs/serverless.js +16 -16
- package/dist/cjs/sqlite.d.ts +8 -8
- package/dist/cjs/sqlite.js +53 -22
- package/dist/cjs/typed-sql.d.ts +4 -4
- package/dist/cjs/typed-sql.js +5 -5
- package/dist/cli/config.d.ts +13 -2
- package/dist/cli/config.js +3 -2
- package/dist/cli/destructive.d.ts +1 -1
- package/dist/cli/destructive.js +1 -1
- package/dist/cli/index.d.ts +10 -10
- package/dist/cli/index.js +49 -45
- package/dist/cli/loader.d.ts +7 -7
- package/dist/cli/loader.js +9 -9
- package/dist/cli/mcp.js +4 -4
- package/dist/cli/migrate.d.ts +5 -5
- package/dist/cli/migrate.js +11 -11
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/ui.d.ts +2 -2
- package/dist/cli/ui.js +2 -2
- package/dist/client.d.ts +49 -38
- package/dist/client.js +57 -56
- package/dist/dialect.d.ts +62 -18
- package/dist/dialect.js +40 -2
- package/dist/errors.d.ts +5 -5
- package/dist/errors.js +11 -11
- package/dist/generate.d.ts +6 -6
- package/dist/generate.js +31 -29
- package/dist/index-advisor.d.ts +5 -5
- package/dist/index-advisor.js +0 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -7
- package/dist/introspect.d.ts +35 -9
- package/dist/introspect.js +82 -32
- package/dist/mssql.d.ts +11 -11
- package/dist/mssql.js +64 -29
- package/dist/mysql.d.ts +8 -8
- package/dist/mysql.js +61 -23
- package/dist/nested-write.d.ts +21 -2
- package/dist/nested-write.js +51 -14
- package/dist/optional-peer-import.cjs +7 -7
- package/dist/optional-peer-import.d.cts +7 -7
- package/dist/pipeline-submittable.d.ts +2 -2
- package/dist/pipeline-submittable.js +6 -6
- package/dist/pipeline.d.ts +1 -1
- package/dist/pipeline.js +4 -4
- package/dist/powdb-introspect.d.ts +1 -1
- package/dist/powdb-introspect.js +1 -1
- package/dist/powdb.d.ts +28 -28
- package/dist/powdb.js +66 -66
- package/dist/powql.d.ts +27 -27
- package/dist/powql.js +73 -52
- package/dist/query/aggregates.d.ts +1 -1
- package/dist/query/aggregates.js +5 -5
- package/dist/query/batched-loader.d.ts +11 -11
- package/dist/query/batched-loader.js +24 -24
- package/dist/query/builder.d.ts +39 -21
- package/dist/query/builder.js +100 -58
- package/dist/query/compound-unique.d.ts +1 -1
- package/dist/query/compound-unique.js +0 -0
- package/dist/query/deferred.d.ts +12 -6
- package/dist/query/deferred.js +1 -1
- package/dist/query/filters.d.ts +31 -11
- package/dist/query/filters.js +66 -13
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +1 -1
- package/dist/query/relations.d.ts +9 -9
- package/dist/query/relations.js +165 -58
- package/dist/query/types.d.ts +86 -35
- package/dist/query/types.js +1 -1
- package/dist/query/utils.d.ts +27 -10
- package/dist/query/utils.js +84 -14
- package/dist/query/where.d.ts +47 -28
- package/dist/query/where.js +129 -32
- package/dist/query/writes.d.ts +24 -5
- package/dist/query/writes.js +101 -13
- package/dist/realtime.d.ts +7 -7
- package/dist/realtime.js +9 -9
- package/dist/schema-builder.d.ts +18 -7
- package/dist/schema-builder.js +17 -10
- package/dist/schema-metadata.d.ts +3 -3
- package/dist/schema-metadata.js +9 -9
- package/dist/schema-sql.d.ts +9 -9
- package/dist/schema-sql.js +20 -20
- package/dist/schema.d.ts +19 -9
- package/dist/schema.js +6 -6
- package/dist/serverless.d.ts +15 -15
- package/dist/serverless.js +16 -16
- package/dist/sqlite.d.ts +8 -8
- package/dist/sqlite.js +53 -22
- package/dist/typed-sql.d.ts +4 -4
- package/dist/typed-sql.js +5 -5
- package/package.json +2 -2
|
@@ -94,7 +94,7 @@ interface ColumnRefContext {
|
|
|
94
94
|
* The operator's own RAW (unquoted) column name. The `column` argument the
|
|
95
95
|
* operator builders receive is already quoted on the build side and raw on
|
|
96
96
|
* the collect side, so the temporal bind rewrite resolves the column's type
|
|
97
|
-
* from here instead
|
|
97
|
+
* from here instead, the one value both sides pass identically.
|
|
98
98
|
*/
|
|
99
99
|
rawColumn: string;
|
|
100
100
|
}
|
|
@@ -105,7 +105,7 @@ interface ColumnRefContext {
|
|
|
105
105
|
* `with`-clause `where` filters (against a per-subquery alias, `t0.col`) compile
|
|
106
106
|
* an arbitrary target table's where against a column qualifier. They differ ONLY
|
|
107
107
|
* in that qualifier, the correlation parent handed to `buildRelationFilter`, and
|
|
108
|
-
* the unknown-column error wording
|
|
108
|
+
* the unknown-column error wording, so a single scoped build/collect/fingerprint
|
|
109
109
|
* trio, driven by the SAME canonical {@link walkWhere} the top level uses, serves
|
|
110
110
|
* both. See `buildScopedWhere` / `collectScopedWhereParams` / `fingerprintScopedWhere`.
|
|
111
111
|
*/
|
|
@@ -114,7 +114,7 @@ interface WhereScope {
|
|
|
114
114
|
meta: TableMetadata;
|
|
115
115
|
/** The target table name (used for host binding + error messages). */
|
|
116
116
|
table: string;
|
|
117
|
-
/** SQL prefix before `q(col)
|
|
117
|
+
/** SQL prefix before `q(col)`, `"target".` for EXISTS sub-wheres, `t0.` for aliases. */
|
|
118
118
|
qualifier: string;
|
|
119
119
|
/** The `parentTable` correlation argument for nested `buildRelationFilter` calls. */
|
|
120
120
|
relationParent: string;
|
|
@@ -168,7 +168,7 @@ export declare function collectScalarParams(qi: BuilderCtx, key: string, value:
|
|
|
168
168
|
* Param-collect mirror of {@link buildRelationFilter} for one relation-filter
|
|
169
169
|
* object (`{ some/every/none/is/isNot }`, already normalized). Pushes, per
|
|
170
170
|
* present branch and in the canonical order some→none→every→is→isNot, the
|
|
171
|
-
* branch's sub-where params THEN the target table's global-filter params
|
|
171
|
+
* branch's sub-where params THEN the target table's global-filter params -
|
|
172
172
|
* exactly the order buildRelationFilter emits. When no global filter applies
|
|
173
173
|
* the gf calls are no-ops, so this stays byte-identical to the pre-0.28 path.
|
|
174
174
|
* Shared by every collect site that mirrors buildRelationFilter
|
|
@@ -226,7 +226,7 @@ export declare function targetGlobalFilterAlias(qi: BuilderCtx, targetTable: str
|
|
|
226
226
|
export declare function collectTargetGlobalFilterAlias(qi: BuilderCtx, targetTable: string, params: unknown[]): void;
|
|
227
227
|
/**
|
|
228
228
|
* SQL clause for `targetTable`'s global filter rendered against the bare
|
|
229
|
-
* (unaliased) table name
|
|
229
|
+
* (unaliased) table name, the form used inside relation-filter `EXISTS`
|
|
230
230
|
* subqueries. Pushes its params; `''` when none. Mirror:
|
|
231
231
|
* {@link collectTargetGlobalFilterExists}.
|
|
232
232
|
*/
|
|
@@ -247,7 +247,7 @@ export declare function globalFilterCacheSegment(qi: BuilderCtx): string;
|
|
|
247
247
|
/**
|
|
248
248
|
* True when the USER-supplied `where` compiles to no predicate (`{}`,
|
|
249
249
|
* `{ id: undefined }`, `{ OR: [{ a: undefined }] }`, …). This is the exact
|
|
250
|
-
* signal the empty-`where` guard needs
|
|
250
|
+
* signal the empty-`where` guard needs, the compiled emptiness, NOT the
|
|
251
251
|
* fingerprint (which is non-empty for an all-undefined `OR`/`AND`). It ignores
|
|
252
252
|
* any configured global filter, so a global filter never lets an unguarded
|
|
253
253
|
* mass mutation through.
|
|
@@ -269,7 +269,7 @@ export declare function buildWhereClause(qi: BuilderCtx, where: Record<string, u
|
|
|
269
269
|
*/
|
|
270
270
|
export declare function buildScalarClause(qi: BuilderCtx, key: string, value: unknown, params: unknown[], andClauses: string[]): void;
|
|
271
271
|
/**
|
|
272
|
-
* A {@link WhereHost} with no relations
|
|
272
|
+
* A {@link WhereHost} with no relations, used to fingerprint a sub-where
|
|
273
273
|
* whose target table is unknown (`schema.tables[t]` miss). `walkWhere` reads
|
|
274
274
|
* only `tableMeta.relations`, so every key falls to the scalar path, matching
|
|
275
275
|
* the pre-unification `meta?.relations` short-circuit.
|
|
@@ -283,7 +283,7 @@ export declare function aliasWhereScope(qi: BuilderCtx, targetTable: string, met
|
|
|
283
283
|
/**
|
|
284
284
|
* Compile a scoped sub-where to SQL. Serves BOTH the relation-filter EXISTS
|
|
285
285
|
* body ({@link buildSubWhereForRelation}) and the relation `with`-clause
|
|
286
|
-
* `where` ({@link buildAliasWhere})
|
|
286
|
+
* `where` ({@link buildAliasWhere}), the emitted SQL is byte-identical to the
|
|
287
287
|
* former hand-mirrored walkers, since it renders the same clauses in the same
|
|
288
288
|
* ({@link walkWhere}-canonical) key order.
|
|
289
289
|
*/
|
|
@@ -292,8 +292,8 @@ export declare function buildScopedWhere(qi: BuilderCtx, scope: WhereScope, wher
|
|
|
292
292
|
* Emit the SQL clause(s) for one scalar key of a scoped sub-where. Reproduces
|
|
293
293
|
* the null / JSON / array / operator / equality fall-through both former
|
|
294
294
|
* walkers shared (relation sub-wheres and alias wheres carry no vector or
|
|
295
|
-
* text-search scalar surface, so
|
|
296
|
-
*
|
|
295
|
+
* text-search scalar surface, so, unlike the top-level {@link buildScalarClause}
|
|
296
|
+
* - those shapes are not special-cased here and keep their historical
|
|
297
297
|
* equality-guard behavior).
|
|
298
298
|
*/
|
|
299
299
|
export declare function buildScopedScalarClause(qi: BuilderCtx, scope: WhereScope, field: string, value: unknown, params: unknown[], clauses: string[]): void;
|
|
@@ -340,7 +340,7 @@ export declare function pgTypeForColumn(_qi: BuilderCtx, meta: TableMetadata, co
|
|
|
340
340
|
* the UTC-component literal, so a predicate matches the value a write of the
|
|
341
341
|
* same `Date` stored.
|
|
342
342
|
*
|
|
343
|
-
* This is a VALUE transform only
|
|
343
|
+
* This is a VALUE transform only, it never changes the emitted SQL, so the
|
|
344
344
|
* SQL-template cache is unaffected, and it is applied on the cache-hit
|
|
345
345
|
* param-collect path as well as the build path.
|
|
346
346
|
*
|
|
@@ -353,13 +353,13 @@ export declare function coerceWhereOperand(qi: BuilderCtx, meta: TableMetadata,
|
|
|
353
353
|
* Introspection stores each column's `udt_name` in `pgTypes` and every
|
|
354
354
|
* database enum in `schema.enums` (typname → labels); a column whose type
|
|
355
355
|
* matches an enum key needs an explicit `::"EnumName"` cast on its write
|
|
356
|
-
* binds
|
|
356
|
+
* binds, bulk-insert forms like `UNNEST($1::text[])` otherwise type the
|
|
357
357
|
* value as text and Postgres refuses the implicit text→enum coercion
|
|
358
358
|
* ("column X is of type Y but expression is of type text").
|
|
359
359
|
*
|
|
360
360
|
* Postgres-only by construction: gated on the active dialect being
|
|
361
361
|
* `postgresql` AND on `schema.enums` having entries (only PG introspection
|
|
362
|
-
* produces them
|
|
362
|
+
* produces them, `defineSchema` and the other engines leave it empty), so
|
|
363
363
|
* SQLite/MySQL/MSSQL/PowDB output is byte-identical.
|
|
364
364
|
*/
|
|
365
365
|
export declare function enumTypeForColumn(qi: BuilderCtx, column: string): string | null;
|
|
@@ -382,7 +382,7 @@ export declare function assertBindableEqualityValue(qi: BuilderCtx, rawColumn: s
|
|
|
382
382
|
/**
|
|
383
383
|
* Build the user-supplied `where` filter of a relation `with` clause against
|
|
384
384
|
* the relation's table alias. Supports the same scalar surface as the
|
|
385
|
-
* top-level WHERE builder
|
|
385
|
+
* top-level WHERE builder, equality, IS NULL, operator objects (incl.
|
|
386
386
|
* `mode: 'insensitive'`), and OR/AND/NOT combinators. Unknown operator
|
|
387
387
|
* objects throw via {@link assertBindableEqualityValue}.
|
|
388
388
|
*
|
|
@@ -395,7 +395,7 @@ export declare function collectAliasWhereParams(qi: BuilderCtx, targetTable: str
|
|
|
395
395
|
/**
|
|
396
396
|
* Value-invariant, shape-aware fingerprint for a relation `with` clause's
|
|
397
397
|
* `where` filter. Must distinguish every SQL shape {@link buildAliasWhere}
|
|
398
|
-
* can emit
|
|
398
|
+
* can emit, equality vs null vs operator sets vs combinators, or two
|
|
399
399
|
* differently-shaped wheres would share one cached SQL string.
|
|
400
400
|
*/
|
|
401
401
|
export declare function fingerprintAliasWhere(qi: BuilderCtx, where: Record<string, unknown>, targetTable?: string): string;
|
|
@@ -444,7 +444,7 @@ export declare function requireArrayColumns(qi: BuilderCtx): void;
|
|
|
444
444
|
* Resolve a {@link VectorMetric} to its pgvector distance operator from a
|
|
445
445
|
* fixed allow-list, validating the target column is actually a `vector`
|
|
446
446
|
* column. Throws {@link ValidationError} for an unknown metric or a
|
|
447
|
-
* non-vector column
|
|
447
|
+
* non-vector column, a user-supplied string can never become a SQL operator.
|
|
448
448
|
*/
|
|
449
449
|
export declare function vectorOperator(qi: BuilderCtx, field: string, rawColumn: string, metric: string): string;
|
|
450
450
|
/**
|
|
@@ -456,8 +456,8 @@ export declare function vectorOperator(qi: BuilderCtx, field: string, rawColumn:
|
|
|
456
456
|
*/
|
|
457
457
|
export declare function pushVectorParam(qi: BuilderCtx, field: string, _rawColumn: string, to: unknown, params: unknown[]): string;
|
|
458
458
|
/**
|
|
459
|
-
* Prisma-compat: a plain object on a to-one relation key
|
|
460
|
-
* `where: { vendor: { name: { contains: 'x' } } }
|
|
459
|
+
* Prisma-compat: a plain object on a to-one relation key -
|
|
460
|
+
* `where: { vendor: { name: { contains: 'x' } } }`, is an implicit `is`
|
|
461
461
|
* filter. Normalize it to `{ is: obj }` so all downstream handling (SQL,
|
|
462
462
|
* params, fingerprint) sees one canonical shape. To-many relations still
|
|
463
463
|
* require an explicit `some`/`every`/`none` (a bare object there is
|
|
@@ -468,7 +468,7 @@ export declare function normalizeRelationFilter(_qi: BuilderCtx, relDef: Relatio
|
|
|
468
468
|
* Case-insensitive json/jsonb column-type check. Postgres reports lowercase
|
|
469
469
|
* udt_names, but SQLite/MySQL introspection surfaces the DECLARED type
|
|
470
470
|
* (e.g. `JSON`), so every JSON-feature gate compares through this predicate
|
|
471
|
-
*
|
|
471
|
+
* - build and collect sides alike, keeping the SQL-cache lockstep.
|
|
472
472
|
*/
|
|
473
473
|
export declare function isJsonColumnType(_qi: BuilderCtx, colType: string): boolean;
|
|
474
474
|
export declare function getColumnPgType(qi: BuilderCtx, column: string): string;
|
|
@@ -478,13 +478,32 @@ export declare function getColumnPgType(qi: BuilderCtx, column: string): string;
|
|
|
478
478
|
*/
|
|
479
479
|
export declare function getArrayElementType(_qi: BuilderCtx, pgType: string): string;
|
|
480
480
|
/**
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
*
|
|
486
|
-
*
|
|
481
|
+
* Refuse a {@link JsonFilter} carrying a key that is not a JSON operator, and
|
|
482
|
+
* refuse a filter that selects a `path` but never compares it.
|
|
483
|
+
*
|
|
484
|
+
* Both shapes used to compile to NOTHING. `buildJsonFilterClauses` only ever
|
|
485
|
+
* emitted a clause for a key it recognized, so `{ path: ['title'],
|
|
486
|
+
* string_contains: 'x' }`, the Prisma spelling, and an easy typo besides -
|
|
487
|
+
* produced an empty clause list, the predicate vanished, and the query
|
|
488
|
+
* returned EVERY row. Inside an `AND` it silently dropped that conjunct, so a
|
|
489
|
+
* tenant scope written this way widened to the whole table. The equivalent
|
|
490
|
+
* typo on a scalar column has always thrown; this closes the inconsistency.
|
|
491
|
+
*
|
|
492
|
+
* Called from both the SQL-build path and the cache-hit param-collect path, so
|
|
493
|
+
* a warmed SQL cache cannot skip the check.
|
|
487
494
|
*/
|
|
495
|
+
export declare function assertJsonFilterKeys(filter: JsonFilter, column: string): void;
|
|
496
|
+
/**
|
|
497
|
+
* Validate and enumerate the substring comparisons on a JSON filter, in the
|
|
498
|
+
* fixed {@link JSON_STRING_OPERATORS} order. Shared by the build and collect
|
|
499
|
+
* paths exactly like {@link jsonRangeEntries}, so both agree on the params
|
|
500
|
+
* pushed and both throw identically on an invalid shape.
|
|
501
|
+
*/
|
|
502
|
+
export declare function jsonStringEntries(filter: JsonFilter, column: string): {
|
|
503
|
+
op: string;
|
|
504
|
+
pattern: (escaped: string) => string;
|
|
505
|
+
value: string;
|
|
506
|
+
}[];
|
|
488
507
|
export declare function jsonRangeEntries(_qi: BuilderCtx, filter: JsonFilter, column: string): {
|
|
489
508
|
sqlOp: string;
|
|
490
509
|
value: number | string;
|
|
@@ -500,19 +519,19 @@ export declare function jsonRangeEntries(_qi: BuilderCtx, filter: JsonFilter, co
|
|
|
500
519
|
export declare function buildJsonFilterClauses(qi: BuilderCtx, column: string, filter: JsonFilter, params: unknown[]): string[];
|
|
501
520
|
/**
|
|
502
521
|
* Bind value for a JSON path parameter, encoded per dialect. PostgreSQL's
|
|
503
|
-
* `#>>` takes a `text[]` (the segments as strings
|
|
522
|
+
* `#>>` takes a `text[]` (the segments as strings, or `nativeForm` when the
|
|
504
523
|
* caller has a specific native binding, e.g. JsonFilter's raw path array).
|
|
505
524
|
* Every other engine's JSON function (`json_extract` / `JSON_EXTRACT` /
|
|
506
525
|
* `JSON_VALUE`) takes a `'$'`-rooted JSONPath STRING: binding the raw array
|
|
507
526
|
* would arrive as `'["a"]'` (the driver shims JSON.stringify non-primitive
|
|
508
527
|
* params) and fail at runtime with the engine's bad-JSON-path error. The
|
|
509
|
-
* encoded path stays a bound parameter
|
|
528
|
+
* encoded path stays a bound parameter, never spliced into SQL text, so
|
|
510
529
|
* the build/collect param mirrors stay in lockstep and injection-safe.
|
|
511
530
|
*/
|
|
512
531
|
export declare function jsonPathParam(qi: BuilderCtx, path: readonly (string | number)[], nativeForm?: unknown): unknown;
|
|
513
532
|
/**
|
|
514
533
|
* Cast an extracted JSON path text value to a numeric type for range
|
|
515
|
-
* comparison. PostgreSQL uses `(expr)::numeric` (exact
|
|
534
|
+
* comparison. PostgreSQL uses `(expr)::numeric` (exact, the right way to
|
|
516
535
|
* compare JSON numbers, and `::float` would lose precision on big ints);
|
|
517
536
|
* other dialects route through {@link Dialect.castAggregate} (SQLite/MySQL/
|
|
518
537
|
* SQL Server have no `::` operator) as a float cast.
|
package/dist/cjs/query/where.js
CHANGED
|
@@ -65,6 +65,8 @@ exports.normalizeRelationFilter = normalizeRelationFilter;
|
|
|
65
65
|
exports.isJsonColumnType = isJsonColumnType;
|
|
66
66
|
exports.getColumnPgType = getColumnPgType;
|
|
67
67
|
exports.getArrayElementType = getArrayElementType;
|
|
68
|
+
exports.assertJsonFilterKeys = assertJsonFilterKeys;
|
|
69
|
+
exports.jsonStringEntries = jsonStringEntries;
|
|
68
70
|
exports.jsonRangeEntries = jsonRangeEntries;
|
|
69
71
|
exports.buildJsonFilterClauses = buildJsonFilterClauses;
|
|
70
72
|
exports.jsonPathParam = jsonPathParam;
|
|
@@ -237,7 +239,7 @@ function collectScalarParams(qi, key, value, params) {
|
|
|
237
239
|
* Param-collect mirror of {@link buildRelationFilter} for one relation-filter
|
|
238
240
|
* object (`{ some/every/none/is/isNot }`, already normalized). Pushes, per
|
|
239
241
|
* present branch and in the canonical order some→none→every→is→isNot, the
|
|
240
|
-
* branch's sub-where params THEN the target table's global-filter params
|
|
242
|
+
* branch's sub-where params THEN the target table's global-filter params -
|
|
241
243
|
* exactly the order buildRelationFilter emits. When no global filter applies
|
|
242
244
|
* the gf calls are no-ops, so this stays byte-identical to the pre-0.28 path.
|
|
243
245
|
* Shared by every collect site that mirrors buildRelationFilter
|
|
@@ -255,7 +257,7 @@ function collectRelationFilterParams(qi, relDef, filterObj, params) {
|
|
|
255
257
|
}
|
|
256
258
|
if (filterObj.every !== undefined && filterObj.every !== null) {
|
|
257
259
|
// gf is only emitted (build) when the `every` sub-where compiles to a
|
|
258
|
-
// filter
|
|
260
|
+
// filter, otherwise `every` is trivially true and no subquery is built.
|
|
259
261
|
if (buildSubWhereForRelation(qi, target, filterObj.every, []) !== null) {
|
|
260
262
|
collectRelFilterParams(qi, target, filterObj.every, params);
|
|
261
263
|
collectTargetGlobalFilterExists(qi, target, params);
|
|
@@ -326,6 +328,7 @@ function collectOperatorParams(qi, column, op, params, refCtx) {
|
|
|
326
328
|
* comparison values in {@link JSON_RANGE_OPERATORS} order.
|
|
327
329
|
*/
|
|
328
330
|
function collectJsonFilterParams(qi, filter, params, column) {
|
|
331
|
+
assertJsonFilterKeys(filter, column);
|
|
329
332
|
let pathPushed = false;
|
|
330
333
|
const pushPathOnce = () => {
|
|
331
334
|
if (!pathPushed) {
|
|
@@ -351,6 +354,10 @@ function collectJsonFilterParams(qi, filter, params, column) {
|
|
|
351
354
|
pushPathOnce();
|
|
352
355
|
params.push(value);
|
|
353
356
|
}
|
|
357
|
+
for (const { pattern, value } of jsonStringEntries(filter, column)) {
|
|
358
|
+
pushPathOnce();
|
|
359
|
+
params.push(pattern((0, utils_js_1.escapeLike)(value)));
|
|
360
|
+
}
|
|
354
361
|
}
|
|
355
362
|
/** Collect params from array filter. Mirrors buildArrayFilterClauses. */
|
|
356
363
|
function collectArrayFilterParams(qi, filter, params) {
|
|
@@ -406,7 +413,7 @@ function resolveGlobalFilter(qi, table, skip = qi.currentSkip) {
|
|
|
406
413
|
return null;
|
|
407
414
|
const obj = resolved;
|
|
408
415
|
// An all-undefined filter (e.g. `{ tenantId: undefined }`) contributes
|
|
409
|
-
// nothing
|
|
416
|
+
// nothing, treat it as absent so it never emits a dangling clause.
|
|
410
417
|
if (Object.keys(obj).every((k) => obj[k] === undefined))
|
|
411
418
|
return null;
|
|
412
419
|
return obj;
|
|
@@ -451,7 +458,7 @@ function collectTargetGlobalFilterAlias(qi, targetTable, params) {
|
|
|
451
458
|
}
|
|
452
459
|
/**
|
|
453
460
|
* SQL clause for `targetTable`'s global filter rendered against the bare
|
|
454
|
-
* (unaliased) table name
|
|
461
|
+
* (unaliased) table name, the form used inside relation-filter `EXISTS`
|
|
455
462
|
* subqueries. Pushes its params; `''` when none. Mirror:
|
|
456
463
|
* {@link collectTargetGlobalFilterExists}.
|
|
457
464
|
*/
|
|
@@ -513,7 +520,7 @@ function globalFilterCacheSegment(qi) {
|
|
|
513
520
|
/**
|
|
514
521
|
* True when the USER-supplied `where` compiles to no predicate (`{}`,
|
|
515
522
|
* `{ id: undefined }`, `{ OR: [{ a: undefined }] }`, …). This is the exact
|
|
516
|
-
* signal the empty-`where` guard needs
|
|
523
|
+
* signal the empty-`where` guard needs, the compiled emptiness, NOT the
|
|
517
524
|
* fingerprint (which is non-empty for an all-undefined `OR`/`AND`). It ignores
|
|
518
525
|
* any configured global filter, so a global filter never lets an unguarded
|
|
519
526
|
* mass mutation through.
|
|
@@ -635,7 +642,7 @@ function buildScalarClause(qi, key, value, params, andClauses) {
|
|
|
635
642
|
}
|
|
636
643
|
}
|
|
637
644
|
/**
|
|
638
|
-
* A {@link WhereHost} with no relations
|
|
645
|
+
* A {@link WhereHost} with no relations, used to fingerprint a sub-where
|
|
639
646
|
* whose target table is unknown (`schema.tables[t]` miss). `walkWhere` reads
|
|
640
647
|
* only `tableMeta.relations`, so every key falls to the scalar path, matching
|
|
641
648
|
* the pre-unification `meta?.relations` short-circuit.
|
|
@@ -687,7 +694,7 @@ function aliasWhereScope(qi, targetTable, meta, alias) {
|
|
|
687
694
|
/**
|
|
688
695
|
* Compile a scoped sub-where to SQL. Serves BOTH the relation-filter EXISTS
|
|
689
696
|
* body ({@link buildSubWhereForRelation}) and the relation `with`-clause
|
|
690
|
-
* `where` ({@link buildAliasWhere})
|
|
697
|
+
* `where` ({@link buildAliasWhere}), the emitted SQL is byte-identical to the
|
|
691
698
|
* former hand-mirrored walkers, since it renders the same clauses in the same
|
|
692
699
|
* ({@link walkWhere}-canonical) key order.
|
|
693
700
|
*/
|
|
@@ -728,8 +735,8 @@ function buildScopedWhere(qi, scope, where, params) {
|
|
|
728
735
|
* Emit the SQL clause(s) for one scalar key of a scoped sub-where. Reproduces
|
|
729
736
|
* the null / JSON / array / operator / equality fall-through both former
|
|
730
737
|
* walkers shared (relation sub-wheres and alias wheres carry no vector or
|
|
731
|
-
* text-search scalar surface, so
|
|
732
|
-
*
|
|
738
|
+
* text-search scalar surface, so, unlike the top-level {@link buildScalarClause}
|
|
739
|
+
* - those shapes are not special-cased here and keep their historical
|
|
733
740
|
* equality-guard behavior).
|
|
734
741
|
*/
|
|
735
742
|
function buildScopedScalarClause(qi, scope, field, value, params, clauses) {
|
|
@@ -929,7 +936,7 @@ function buildRelationFilter(qi, _relName, relDef, filterObj, params, parentTabl
|
|
|
929
936
|
// DOMAIN of correlated rows in EVERY branch: `some`/`none`/`is`/`isNot`
|
|
930
937
|
// ignore filtered-out rows, and `every` quantifies over only the surviving
|
|
931
938
|
// rows ("every NON-deleted related row matches P"). It is ANDed into the
|
|
932
|
-
// correlation and its params pushed AFTER the per-branch filter
|
|
939
|
+
// correlation and its params pushed AFTER the per-branch filter, mirrored
|
|
933
940
|
// exactly in collectWhereParams' relation-filter branch. `qt` is the bare
|
|
934
941
|
// target table, matching the `FROM ${qt}` here (see targetGlobalFilterExists).
|
|
935
942
|
const gfAnd = () => {
|
|
@@ -964,10 +971,10 @@ function buildRelationFilter(qi, _relName, relDef, filterObj, params, parentTabl
|
|
|
964
971
|
clauses.push(`NOT EXISTS (SELECT 1 FROM ${qt} WHERE ${correlation}${gf} AND NOT (${filterClause}))`);
|
|
965
972
|
}
|
|
966
973
|
else {
|
|
967
|
-
// "every" with empty filter = true (all match trivially)
|
|
974
|
+
// "every" with empty filter = true (all match trivially), gf irrelevant.
|
|
968
975
|
}
|
|
969
976
|
}
|
|
970
|
-
// "is": EXISTS
|
|
977
|
+
// "is": EXISTS, for to-one relations (same SQL as "some").
|
|
971
978
|
// `is: null` = "no related row" (Prisma semantics) → NOT EXISTS.
|
|
972
979
|
if (filterObj.is !== undefined) {
|
|
973
980
|
if (filterObj.is === null) {
|
|
@@ -980,7 +987,7 @@ function buildRelationFilter(qi, _relName, relDef, filterObj, params, parentTabl
|
|
|
980
987
|
clauses.push(`EXISTS (SELECT 1 FROM ${qt} WHERE ${correlation}${filterAnd}${gfAnd()})`);
|
|
981
988
|
}
|
|
982
989
|
}
|
|
983
|
-
// "isNot": NOT EXISTS
|
|
990
|
+
// "isNot": NOT EXISTS, for to-one relations (same SQL as "none").
|
|
984
991
|
// `isNot: null` = "a related row exists" → EXISTS.
|
|
985
992
|
if (filterObj.isNot !== undefined) {
|
|
986
993
|
if (filterObj.isNot === null) {
|
|
@@ -1021,7 +1028,7 @@ function pgTypeForColumn(_qi, meta, column) {
|
|
|
1021
1028
|
* the UTC-component literal, so a predicate matches the value a write of the
|
|
1022
1029
|
* same `Date` stored.
|
|
1023
1030
|
*
|
|
1024
|
-
* This is a VALUE transform only
|
|
1031
|
+
* This is a VALUE transform only, it never changes the emitted SQL, so the
|
|
1025
1032
|
* SQL-template cache is unaffected, and it is applied on the cache-hit
|
|
1026
1033
|
* param-collect path as well as the build path.
|
|
1027
1034
|
*
|
|
@@ -1042,13 +1049,13 @@ function coerceWhereOperand(qi, meta, column, value) {
|
|
|
1042
1049
|
* Introspection stores each column's `udt_name` in `pgTypes` and every
|
|
1043
1050
|
* database enum in `schema.enums` (typname → labels); a column whose type
|
|
1044
1051
|
* matches an enum key needs an explicit `::"EnumName"` cast on its write
|
|
1045
|
-
* binds
|
|
1052
|
+
* binds, bulk-insert forms like `UNNEST($1::text[])` otherwise type the
|
|
1046
1053
|
* value as text and Postgres refuses the implicit text→enum coercion
|
|
1047
1054
|
* ("column X is of type Y but expression is of type text").
|
|
1048
1055
|
*
|
|
1049
1056
|
* Postgres-only by construction: gated on the active dialect being
|
|
1050
1057
|
* `postgresql` AND on `schema.enums` having entries (only PG introspection
|
|
1051
|
-
* produces them
|
|
1058
|
+
* produces them, `defineSchema` and the other engines leave it empty), so
|
|
1052
1059
|
* SQLite/MySQL/MSSQL/PowDB output is byte-identical.
|
|
1053
1060
|
*/
|
|
1054
1061
|
function enumTypeForColumn(qi, column) {
|
|
@@ -1059,7 +1066,7 @@ function enumTypeForColumn(qi, column) {
|
|
|
1059
1066
|
return null;
|
|
1060
1067
|
// Cross-schema guard (N-5): introspection records pgTypeSchema ONLY when
|
|
1061
1068
|
// the column's type lives OUTSIDE the introspected schema. A same-named
|
|
1062
|
-
// enum in another schema must not get this schema's cast
|
|
1069
|
+
// enum in another schema must not get this schema's cast, search_path
|
|
1063
1070
|
// would resolve `::"status"` to the wrong type. Skipping the cast restores
|
|
1064
1071
|
// the pre-cast behavior for such columns. Columns without pgTypeSchema
|
|
1065
1072
|
// (same-schema types, defineSchema/legacy metadata) keep the cast.
|
|
@@ -1104,7 +1111,7 @@ function assertBindableEqualityValue(qi, rawColumn, value, columnPgType, table)
|
|
|
1104
1111
|
/**
|
|
1105
1112
|
* Build the user-supplied `where` filter of a relation `with` clause against
|
|
1106
1113
|
* the relation's table alias. Supports the same scalar surface as the
|
|
1107
|
-
* top-level WHERE builder
|
|
1114
|
+
* top-level WHERE builder, equality, IS NULL, operator objects (incl.
|
|
1108
1115
|
* `mode: 'insensitive'`), and OR/AND/NOT combinators. Unknown operator
|
|
1109
1116
|
* objects throw via {@link assertBindableEqualityValue}.
|
|
1110
1117
|
*
|
|
@@ -1123,7 +1130,7 @@ function collectAliasWhereParams(qi, targetTable, targetMeta, where, params) {
|
|
|
1123
1130
|
/**
|
|
1124
1131
|
* Value-invariant, shape-aware fingerprint for a relation `with` clause's
|
|
1125
1132
|
* `where` filter. Must distinguish every SQL shape {@link buildAliasWhere}
|
|
1126
|
-
* can emit
|
|
1133
|
+
* can emit, equality vs null vs operator sets vs combinators, or two
|
|
1127
1134
|
* differently-shaped wheres would share one cached SQL string.
|
|
1128
1135
|
*/
|
|
1129
1136
|
function fingerprintAliasWhere(qi, where, targetTable) {
|
|
@@ -1245,18 +1252,18 @@ function buildOperatorClauses(qi, column, op, params, refCtx) {
|
|
|
1245
1252
|
params.push(qi.inParam(cv(op.notIn)));
|
|
1246
1253
|
clauses.push(qi.inClause(column, qi.p(params.length), true));
|
|
1247
1254
|
}
|
|
1248
|
-
const
|
|
1255
|
+
const insensitive = op.mode === 'insensitive';
|
|
1249
1256
|
if (op.contains !== undefined) {
|
|
1250
1257
|
params.push(`%${(0, utils_js_1.escapeLike)(op.contains)}%`);
|
|
1251
|
-
clauses.push(
|
|
1258
|
+
clauses.push(buildLikeClause(qi, column, qi.p(params.length), insensitive));
|
|
1252
1259
|
}
|
|
1253
1260
|
if (op.startsWith !== undefined) {
|
|
1254
1261
|
params.push(`${(0, utils_js_1.escapeLike)(op.startsWith)}%`);
|
|
1255
|
-
clauses.push(
|
|
1262
|
+
clauses.push(buildLikeClause(qi, column, qi.p(params.length), insensitive));
|
|
1256
1263
|
}
|
|
1257
1264
|
if (op.endsWith !== undefined) {
|
|
1258
1265
|
params.push(`%${(0, utils_js_1.escapeLike)(op.endsWith)}`);
|
|
1259
|
-
clauses.push(
|
|
1266
|
+
clauses.push(buildLikeClause(qi, column, qi.p(params.length), insensitive));
|
|
1260
1267
|
}
|
|
1261
1268
|
return clauses;
|
|
1262
1269
|
}
|
|
@@ -1291,7 +1298,7 @@ function requireArrayColumns(qi) {
|
|
|
1291
1298
|
* Resolve a {@link VectorMetric} to its pgvector distance operator from a
|
|
1292
1299
|
* fixed allow-list, validating the target column is actually a `vector`
|
|
1293
1300
|
* column. Throws {@link ValidationError} for an unknown metric or a
|
|
1294
|
-
* non-vector column
|
|
1301
|
+
* non-vector column, a user-supplied string can never become a SQL operator.
|
|
1295
1302
|
*/
|
|
1296
1303
|
function vectorOperator(qi, field, rawColumn, metric) {
|
|
1297
1304
|
if (!qi.dialect.supportsVector) {
|
|
@@ -1338,8 +1345,8 @@ function pushVectorParam(qi, field, _rawColumn, to, params) {
|
|
|
1338
1345
|
return `${qi.p(params.length)}::vector`;
|
|
1339
1346
|
}
|
|
1340
1347
|
/**
|
|
1341
|
-
* Prisma-compat: a plain object on a to-one relation key
|
|
1342
|
-
* `where: { vendor: { name: { contains: 'x' } } }
|
|
1348
|
+
* Prisma-compat: a plain object on a to-one relation key -
|
|
1349
|
+
* `where: { vendor: { name: { contains: 'x' } } }`, is an implicit `is`
|
|
1343
1350
|
* filter. Normalize it to `{ is: obj }` so all downstream handling (SQL,
|
|
1344
1351
|
* params, fingerprint) sees one canonical shape. To-many relations still
|
|
1345
1352
|
* require an explicit `some`/`every`/`none` (a bare object there is
|
|
@@ -1360,7 +1367,7 @@ function normalizeRelationFilter(_qi, relDef, filterObj) {
|
|
|
1360
1367
|
* Case-insensitive json/jsonb column-type check. Postgres reports lowercase
|
|
1361
1368
|
* udt_names, but SQLite/MySQL introspection surfaces the DECLARED type
|
|
1362
1369
|
* (e.g. `JSON`), so every JSON-feature gate compares through this predicate
|
|
1363
|
-
*
|
|
1370
|
+
* - build and collect sides alike, keeping the SQL-cache lockstep.
|
|
1364
1371
|
*/
|
|
1365
1372
|
function isJsonColumnType(_qi, colType) {
|
|
1366
1373
|
const t = colType.toLowerCase();
|
|
@@ -1397,9 +1404,92 @@ function getArrayElementType(_qi, pgType) {
|
|
|
1397
1404
|
* JSON filter, in the fixed {@link JSON_RANGE_OPERATORS} order. Shared by
|
|
1398
1405
|
* the SQL-build path ({@link buildJsonFilterClauses}) and the cache-hit
|
|
1399
1406
|
* param-collect path ({@link collectJsonFilterParams}) so both always agree
|
|
1400
|
-
* on which params are pushed
|
|
1407
|
+
* on which params are pushed, and both throw identically for invalid
|
|
1401
1408
|
* shapes, so a warmed cache can never skip validation.
|
|
1402
1409
|
*/
|
|
1410
|
+
/**
|
|
1411
|
+
* One LIKE comparison, honoring `mode: 'insensitive'` through the dialect and
|
|
1412
|
+
* always carrying the `ESCAPE '\'` clause that pairs with {@link escapeLike}.
|
|
1413
|
+
*
|
|
1414
|
+
* Shared by the scalar `contains` / `startsWith` / `endsWith` operators and by
|
|
1415
|
+
* the JSON substring operators, so the two can never drift into escaping their
|
|
1416
|
+
* operands the same way but comparing them differently.
|
|
1417
|
+
*/
|
|
1418
|
+
function buildLikeClause(qi, column, paramRef, insensitive) {
|
|
1419
|
+
const base = insensitive ? qi.dialect.buildInsensitiveLike(column, paramRef) : `${column} LIKE ${paramRef}`;
|
|
1420
|
+
return `${base} ESCAPE '\\'`;
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* Refuse a {@link JsonFilter} carrying a key that is not a JSON operator, and
|
|
1424
|
+
* refuse a filter that selects a `path` but never compares it.
|
|
1425
|
+
*
|
|
1426
|
+
* Both shapes used to compile to NOTHING. `buildJsonFilterClauses` only ever
|
|
1427
|
+
* emitted a clause for a key it recognized, so `{ path: ['title'],
|
|
1428
|
+
* string_contains: 'x' }`, the Prisma spelling, and an easy typo besides -
|
|
1429
|
+
* produced an empty clause list, the predicate vanished, and the query
|
|
1430
|
+
* returned EVERY row. Inside an `AND` it silently dropped that conjunct, so a
|
|
1431
|
+
* tenant scope written this way widened to the whole table. The equivalent
|
|
1432
|
+
* typo on a scalar column has always thrown; this closes the inconsistency.
|
|
1433
|
+
*
|
|
1434
|
+
* Called from both the SQL-build path and the cache-hit param-collect path, so
|
|
1435
|
+
* a warmed SQL cache cannot skip the check.
|
|
1436
|
+
*/
|
|
1437
|
+
function assertJsonFilterKeys(filter, column) {
|
|
1438
|
+
const obj = filter;
|
|
1439
|
+
const present = Object.keys(obj).filter((k) => obj[k] !== undefined);
|
|
1440
|
+
for (const key of present) {
|
|
1441
|
+
if (filters_js_1.JSON_FILTER_KEYS.has(key))
|
|
1442
|
+
continue;
|
|
1443
|
+
const suggestion = JSON_PRISMA_SPELLINGS[key];
|
|
1444
|
+
throw new errors_js_1.ValidationError(`[turbine] Unknown JSON filter operator "${key}" on ${column}.` +
|
|
1445
|
+
(suggestion ? ` Did you mean \`${suggestion}\`?` : '') +
|
|
1446
|
+
` Supported operators: ${[...filters_js_1.JSON_FILTER_KEYS].sort().join(', ')}.`);
|
|
1447
|
+
}
|
|
1448
|
+
// `mode` and `path` are modifiers, not comparisons: a filter made only of
|
|
1449
|
+
// those compares nothing, which is exactly the silent no-op shape above.
|
|
1450
|
+
if (present.length > 0 && present.every((k) => k === 'path' || k === 'mode')) {
|
|
1451
|
+
throw new errors_js_1.ValidationError(`[turbine] JSON filter on ${column} selects a \`path\` but has no comparison. ` +
|
|
1452
|
+
`Add one of: ${[...filters_js_1.JSON_FILTER_KEYS]
|
|
1453
|
+
.filter((k) => k !== 'path' && k !== 'mode')
|
|
1454
|
+
.sort()
|
|
1455
|
+
.join(', ')}.`);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* The Prisma spelling of each JSON operator, so a migrator who writes the
|
|
1460
|
+
* name they already know gets told the turbine one instead of a bare list.
|
|
1461
|
+
*/
|
|
1462
|
+
const JSON_PRISMA_SPELLINGS = {
|
|
1463
|
+
string_contains: 'stringContains',
|
|
1464
|
+
string_starts_with: 'stringStartsWith',
|
|
1465
|
+
string_ends_with: 'stringEndsWith',
|
|
1466
|
+
array_contains: 'contains',
|
|
1467
|
+
startsWith: 'stringStartsWith',
|
|
1468
|
+
endsWith: 'stringEndsWith',
|
|
1469
|
+
};
|
|
1470
|
+
/**
|
|
1471
|
+
* Validate and enumerate the substring comparisons on a JSON filter, in the
|
|
1472
|
+
* fixed {@link JSON_STRING_OPERATORS} order. Shared by the build and collect
|
|
1473
|
+
* paths exactly like {@link jsonRangeEntries}, so both agree on the params
|
|
1474
|
+
* pushed and both throw identically on an invalid shape.
|
|
1475
|
+
*/
|
|
1476
|
+
function jsonStringEntries(filter, column) {
|
|
1477
|
+
const entries = [];
|
|
1478
|
+
for (const [op, pattern] of Object.entries(filters_js_1.JSON_STRING_OPERATORS)) {
|
|
1479
|
+
const value = filter[op];
|
|
1480
|
+
if (value === undefined)
|
|
1481
|
+
continue;
|
|
1482
|
+
if (filter.path === undefined) {
|
|
1483
|
+
throw new errors_js_1.ValidationError(`[turbine] JSON operator '${op}' on ${column} requires a \`path\` ` +
|
|
1484
|
+
`(e.g. { path: ['meta', 'title'], ${op}: ${JSON.stringify(value)} }).`);
|
|
1485
|
+
}
|
|
1486
|
+
if (typeof value !== 'string') {
|
|
1487
|
+
throw new errors_js_1.ValidationError(`[turbine] JSON operator '${op}' on ${column} requires a string, got ${JSON.stringify(value)}.`);
|
|
1488
|
+
}
|
|
1489
|
+
entries.push({ op, pattern, value });
|
|
1490
|
+
}
|
|
1491
|
+
return entries;
|
|
1492
|
+
}
|
|
1403
1493
|
function jsonRangeEntries(_qi, filter, column) {
|
|
1404
1494
|
const entries = [];
|
|
1405
1495
|
for (const [op, sqlOp] of Object.entries(filters_js_1.JSON_RANGE_OPERATORS)) {
|
|
@@ -1430,6 +1520,7 @@ function jsonRangeEntries(_qi, filter, column) {
|
|
|
1430
1520
|
* stays byte-identical to {@link collectJsonFilterParams}.
|
|
1431
1521
|
*/
|
|
1432
1522
|
function buildJsonFilterClauses(qi, column, filter, params) {
|
|
1523
|
+
assertJsonFilterKeys(filter, column);
|
|
1433
1524
|
const clauses = [];
|
|
1434
1525
|
// Lazily bind the path once; reuse the same $N in every extraction clause.
|
|
1435
1526
|
let pathParamIdx = null;
|
|
@@ -1470,17 +1561,25 @@ function buildJsonFilterClauses(qi, column, filter, params) {
|
|
|
1470
1561
|
const lhs = typeof value === 'number' ? castJsonNumeric(qi, extract) : extract;
|
|
1471
1562
|
clauses.push(`${lhs} ${sqlOp} ${qi.p(params.length)}`);
|
|
1472
1563
|
}
|
|
1564
|
+
// Substring comparisons on the extracted path. The operand is LIKE-escaped
|
|
1565
|
+
// exactly like the scalar `contains` family, so a value containing `%` or
|
|
1566
|
+
// `_` matches literally instead of turning into a wildcard.
|
|
1567
|
+
for (const { pattern, value } of jsonStringEntries(filter, column)) {
|
|
1568
|
+
const extract = pathExtract();
|
|
1569
|
+
params.push(pattern((0, utils_js_1.escapeLike)(value)));
|
|
1570
|
+
clauses.push(buildLikeClause(qi, extract, qi.p(params.length), filter.mode === 'insensitive'));
|
|
1571
|
+
}
|
|
1473
1572
|
return clauses;
|
|
1474
1573
|
}
|
|
1475
1574
|
/**
|
|
1476
1575
|
* Bind value for a JSON path parameter, encoded per dialect. PostgreSQL's
|
|
1477
|
-
* `#>>` takes a `text[]` (the segments as strings
|
|
1576
|
+
* `#>>` takes a `text[]` (the segments as strings, or `nativeForm` when the
|
|
1478
1577
|
* caller has a specific native binding, e.g. JsonFilter's raw path array).
|
|
1479
1578
|
* Every other engine's JSON function (`json_extract` / `JSON_EXTRACT` /
|
|
1480
1579
|
* `JSON_VALUE`) takes a `'$'`-rooted JSONPath STRING: binding the raw array
|
|
1481
1580
|
* would arrive as `'["a"]'` (the driver shims JSON.stringify non-primitive
|
|
1482
1581
|
* params) and fail at runtime with the engine's bad-JSON-path error. The
|
|
1483
|
-
* encoded path stays a bound parameter
|
|
1582
|
+
* encoded path stays a bound parameter, never spliced into SQL text, so
|
|
1484
1583
|
* the build/collect param mirrors stay in lockstep and injection-safe.
|
|
1485
1584
|
*/
|
|
1486
1585
|
function jsonPathParam(qi, path, nativeForm) {
|
|
@@ -1492,7 +1591,7 @@ function jsonPathParam(qi, path, nativeForm) {
|
|
|
1492
1591
|
}
|
|
1493
1592
|
/**
|
|
1494
1593
|
* Cast an extracted JSON path text value to a numeric type for range
|
|
1495
|
-
* comparison. PostgreSQL uses `(expr)::numeric` (exact
|
|
1594
|
+
* comparison. PostgreSQL uses `(expr)::numeric` (exact, the right way to
|
|
1496
1595
|
* compare JSON numbers, and `::float` would lose precision on big ints);
|
|
1497
1596
|
* other dialects route through {@link Dialect.castAggregate} (SQLite/MySQL/
|
|
1498
1597
|
* SQL Server have no `::` operator) as a float cast.
|
|
@@ -20,10 +20,10 @@ import type { BuilderCtx } from './where.js';
|
|
|
20
20
|
*
|
|
21
21
|
* Two value shapes need rewriting, both a JS `Date` on a temporal column:
|
|
22
22
|
*
|
|
23
|
-
* 1. A time-of-day column (`time` / `timetz`)
|
|
23
|
+
* 1. A time-of-day column (`time` / `timetz`), the driver serializes a `Date`
|
|
24
24
|
* as a full ISO timestamp with the process offset and Postgres answers
|
|
25
25
|
* `22007 invalid input syntax for type time`. Rewritten on every engine.
|
|
26
|
-
* 2. A zone-less `date` / `timestamp` column on PostgreSQL
|
|
26
|
+
* 2. A zone-less `date` / `timestamp` column on PostgreSQL, the driver's
|
|
27
27
|
* local-offset serialization stores the PROCESS's calendar fields, so in a
|
|
28
28
|
* non-UTC process the stored value is wrong and (because the read path
|
|
29
29
|
* interprets an offset-less value as UTC) does not round-trip. Rewritten to
|
|
@@ -85,17 +85,36 @@ export declare function piiColumns(_qi: BuilderCtx, meta: TableMetadata): Set<st
|
|
|
85
85
|
export declare function piiFields(_qi: BuilderCtx, meta: TableMetadata): string[];
|
|
86
86
|
/**
|
|
87
87
|
* The `RETURNING` / `OUTPUT` selection for a write on this table. A table with
|
|
88
|
-
* no PII column returns `'*'` (every column
|
|
88
|
+
* no PII column returns `'*'` (every column, byte-identical SQL to before);
|
|
89
89
|
* a table WITH PII columns returns an explicit quoted list of every non-PII
|
|
90
90
|
* column so the PII values never leave the database on a write. A PII-tagged
|
|
91
91
|
* PRIMARY KEY column is kept in the projection regardless (the returned row
|
|
92
|
-
* must stay addressable): tag sensitive data, not keys
|
|
92
|
+
* must stay addressable): tag sensitive data, not keys, a PII PK is
|
|
93
93
|
* documented out of scope for stripping. Writes accept no `select`/`includePii`
|
|
94
94
|
* (unlike reads), so this is the whole write-return policy at the SQL level;
|
|
95
95
|
* {@link parseWriteRow} remains as a defense-in-depth strip (a no-op once the
|
|
96
96
|
* SQL already excludes the columns). Derived purely from static per-table
|
|
97
97
|
* schema metadata, so the write SQL cache needs no extra key segment.
|
|
98
98
|
*/
|
|
99
|
+
/**
|
|
100
|
+
* Return `data` with every `updatedAt`-tagged column the caller did not name
|
|
101
|
+
* set to `now`, or the original object when the table has none.
|
|
102
|
+
*
|
|
103
|
+
* Prisma's `@updatedAt` has no turbine equivalent, so a migrated application
|
|
104
|
+
* had to remember the field on every single update, and the value is usually
|
|
105
|
+
* load-bearing in the response body, so forgetting it is a silent staleness
|
|
106
|
+
* bug rather than a crash. The tag is opt-in per column and never inferred
|
|
107
|
+
* from a column's name, so a schema that does not use it emits byte-identical
|
|
108
|
+
* SQL and an application already managing its own timestamp is untouched.
|
|
109
|
+
*
|
|
110
|
+
* The timestamp is generated CLIENT-side (like Prisma) rather than as a SQL
|
|
111
|
+
* `now()`, so it flows through the same temporal coercion as any other bound
|
|
112
|
+
* `Date` and lands in UTC on every engine.
|
|
113
|
+
*
|
|
114
|
+
* An explicit value always wins, including an explicit `null`: naming the
|
|
115
|
+
* column is a statement of intent.
|
|
116
|
+
*/
|
|
117
|
+
export declare function applyUpdatedAtColumns(qi: BuilderCtx, data: Record<string, unknown>): Record<string, unknown>;
|
|
99
118
|
export declare function writeReturningColumns(qi: BuilderCtx): ReturningSelection;
|
|
100
119
|
/**
|
|
101
120
|
* String form of {@link writeReturningColumns} for a `SELECT` list (the
|
|
@@ -132,7 +151,7 @@ export declare function assertNoGeneratedColumns(qi: BuilderCtx, data: Record<st
|
|
|
132
151
|
*
|
|
133
152
|
* Supports plain values and atomic operator objects ({ set, increment,
|
|
134
153
|
* decrement, multiply, divide }). An operator object is detected ONLY when
|
|
135
|
-
* it has EXACTLY one key that is one of the 5 operator keys
|
|
154
|
+
* it has EXACTLY one key that is one of the 5 operator keys, this avoids
|
|
136
155
|
* misinterpreting JSON column values like `{ set: 'x' }` as operators
|
|
137
156
|
* (real operator objects always have exactly one key, and a plain JSON
|
|
138
157
|
* payload that happens to have a single `set` key is extremely unusual).
|