turbine-orm 0.81.0 → 0.90.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 +26 -25
- package/dist/checkout.d.ts +27 -2
- package/dist/checkout.js +75 -4
- package/dist/cjs/checkout.d.ts +27 -2
- package/dist/cjs/checkout.js +76 -3
- package/dist/cjs/cli/code-first-tags.d.ts +62 -0
- package/dist/cjs/cli/code-first-tags.js +124 -0
- package/dist/cjs/cli/compile-query.js +9 -0
- package/dist/cjs/cli/config.d.ts +35 -6
- package/dist/cjs/cli/config.js +17 -1
- package/dist/cjs/cli/destructive.d.ts +7 -4
- package/dist/cjs/cli/destructive.js +224 -20
- package/dist/cjs/cli/error-catalog.js +36 -0
- package/dist/cjs/cli/error-text.d.ts +15 -0
- package/dist/cjs/cli/error-text.js +40 -0
- package/dist/cjs/cli/index.d.ts +62 -4
- package/dist/cjs/cli/index.js +568 -108
- package/dist/cjs/cli/local-server-guard.d.ts +53 -0
- package/dist/cjs/cli/local-server-guard.js +119 -0
- package/dist/cjs/cli/local-server-output.d.ts +41 -0
- package/dist/cjs/cli/local-server-output.js +103 -0
- package/dist/cjs/cli/mcp.d.ts +8 -0
- package/dist/cjs/cli/mcp.js +121 -32
- package/dist/cjs/cli/migrate.d.ts +113 -5
- package/dist/cjs/cli/migrate.js +270 -21
- package/dist/cjs/cli/observe.d.ts +6 -1
- package/dist/cjs/cli/observe.js +85 -55
- package/dist/cjs/cli/pii-predicate-guard.d.ts +43 -23
- package/dist/cjs/cli/pii-predicate-guard.js +98 -37
- package/dist/cjs/cli/prisma-resolve.js +17 -13
- package/dist/cjs/cli/prisma-schema.js +12 -4
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/studio.d.ts +14 -2
- package/dist/cjs/cli/studio.js +244 -69
- package/dist/cjs/cli/ui.d.ts +1 -1
- package/dist/cjs/client.d.ts +558 -85
- package/dist/cjs/client.js +1270 -395
- package/dist/cjs/connection-guard.d.ts +13 -0
- package/dist/cjs/connection-guard.js +28 -0
- package/dist/cjs/connection-url.d.ts +51 -16
- package/dist/cjs/connection-url.js +67 -34
- package/dist/cjs/diagnostics.d.ts +81 -0
- package/dist/cjs/diagnostics.js +147 -0
- package/dist/cjs/dialect.d.ts +449 -20
- package/dist/cjs/dialect.js +117 -76
- package/dist/cjs/engine-config.d.ts +7 -6
- package/dist/cjs/errors.d.ts +126 -6
- package/dist/cjs/errors.js +256 -13
- package/dist/cjs/generate.d.ts +57 -7
- package/dist/cjs/generate.js +635 -279
- package/dist/cjs/global-filters-config.d.ts +59 -0
- package/dist/cjs/global-filters-config.js +367 -0
- package/dist/cjs/index.d.ts +9 -4
- package/dist/cjs/index.js +10 -2
- package/dist/cjs/introspect.d.ts +31 -6
- package/dist/cjs/introspect.js +108 -50
- package/dist/cjs/log-codes.d.ts +12 -0
- package/dist/cjs/log-codes.js +77 -0
- package/dist/cjs/logger.d.ts +87 -0
- package/dist/cjs/logger.js +98 -0
- package/dist/cjs/mssql.d.ts +8 -5
- package/dist/cjs/mssql.js +352 -65
- package/dist/cjs/mysql.d.ts +3 -5
- package/dist/cjs/mysql.js +420 -70
- package/dist/cjs/nested-write.d.ts +3 -0
- package/dist/cjs/nested-write.js +179 -114
- package/dist/cjs/pipeline-submittable.d.ts +12 -0
- package/dist/cjs/pipeline-submittable.js +118 -44
- package/dist/cjs/pipeline.js +21 -26
- package/dist/cjs/powdb-introspect.d.ts +5 -2
- package/dist/cjs/powdb-introspect.js +20 -15
- package/dist/cjs/powdb-shared.d.ts +36 -5
- package/dist/cjs/powdb-shared.js +67 -8
- package/dist/cjs/powdb.d.ts +4 -0
- package/dist/cjs/powdb.js +183 -51
- package/dist/cjs/powql.d.ts +394 -51
- package/dist/cjs/powql.js +1829 -284
- package/dist/cjs/prisma-compat.d.ts +23 -15
- package/dist/cjs/prisma-compat.js +507 -372
- package/dist/cjs/query/aggregates.d.ts +17 -1
- package/dist/cjs/query/aggregates.js +261 -29
- package/dist/cjs/query/batched-loader.d.ts +25 -6
- package/dist/cjs/query/batched-loader.js +103 -20
- package/dist/cjs/query/builder.d.ts +325 -184
- package/dist/cjs/query/builder.js +1452 -490
- package/dist/cjs/query/column-ref.d.ts +61 -0
- package/dist/cjs/query/column-ref.js +73 -0
- package/dist/cjs/query/compound-unique.d.ts +21 -0
- package/dist/cjs/query/compound-unique.js +98 -18
- package/dist/cjs/query/deferred.d.ts +53 -16
- package/dist/cjs/query/expr.d.ts +217 -0
- package/dist/cjs/query/expr.js +859 -0
- package/dist/cjs/query/filters.d.ts +155 -53
- package/dist/cjs/query/filters.js +613 -76
- package/dist/cjs/query/index.d.ts +2 -0
- package/dist/cjs/query/index.js +7 -1
- package/dist/cjs/query/json-value.d.ts +43 -0
- package/dist/cjs/query/json-value.js +59 -0
- package/dist/cjs/query/normalize-args.d.ts +87 -0
- package/dist/cjs/query/normalize-args.js +527 -0
- package/dist/cjs/query/option-surface.d.ts +16 -4
- package/dist/cjs/query/option-surface.js +57 -14
- package/dist/cjs/query/relation-names.d.ts +14 -0
- package/dist/cjs/query/relation-names.js +77 -18
- package/dist/cjs/query/relations.d.ts +30 -3
- package/dist/cjs/query/relations.js +219 -63
- package/dist/cjs/query/row-lock.d.ts +49 -0
- package/dist/cjs/query/row-lock.js +138 -0
- package/dist/cjs/query/sql-comment.d.ts +161 -0
- package/dist/cjs/query/sql-comment.js +381 -0
- package/dist/cjs/query/stream-idle.d.ts +132 -0
- package/dist/cjs/query/stream-idle.js +212 -0
- package/dist/cjs/query/table-client.d.ts +101 -0
- package/dist/cjs/query/table-client.js +74 -0
- package/dist/cjs/query/types.d.ts +677 -298
- package/dist/cjs/query/utils.d.ts +105 -6
- package/dist/cjs/query/utils.js +426 -19
- package/dist/cjs/query/warn-registry.d.ts +18 -0
- package/dist/cjs/query/warn-registry.js +18 -0
- package/dist/cjs/query/where-compile.d.ts +138 -14
- package/dist/cjs/query/where-compile.js +513 -29
- package/dist/cjs/query/where.d.ts +159 -59
- package/dist/cjs/query/where.js +716 -246
- package/dist/cjs/query/writes.d.ts +112 -50
- package/dist/cjs/query/writes.js +1498 -200
- package/dist/cjs/query-events.d.ts +4 -2
- package/dist/cjs/query-events.js +3 -1
- package/dist/cjs/realtime.d.ts +7 -3
- package/dist/cjs/realtime.js +13 -2
- package/dist/cjs/schema-builder.d.ts +55 -4
- package/dist/cjs/schema-builder.js +382 -18
- package/dist/cjs/schema-metadata.d.ts +21 -1
- package/dist/cjs/schema-metadata.js +29 -7
- package/dist/cjs/schema-sql.d.ts +75 -5
- package/dist/cjs/schema-sql.js +497 -45
- package/dist/cjs/schema.d.ts +26 -5
- package/dist/cjs/schema.js +110 -13
- package/dist/cjs/sql-placeholders.d.ts +56 -0
- package/dist/cjs/sql-placeholders.js +204 -0
- package/dist/cjs/sqlite.d.ts +75 -20
- package/dist/cjs/sqlite.js +593 -95
- package/dist/cjs/supplied-pool-types.d.ts +48 -0
- package/dist/cjs/supplied-pool-types.js +256 -0
- package/dist/cjs/transaction-control.d.ts +51 -0
- package/dist/cjs/transaction-control.js +316 -0
- package/dist/cjs/transaction-state.d.ts +342 -0
- package/dist/cjs/transaction-state.js +707 -0
- package/dist/cjs/typed-sql.d.ts +37 -3
- package/dist/cjs/typed-sql.js +102 -8
- package/dist/cli/code-first-tags.d.ts +62 -0
- package/dist/cli/code-first-tags.js +120 -0
- package/dist/cli/compile-query.js +9 -0
- package/dist/cli/config.d.ts +35 -6
- package/dist/cli/config.js +16 -1
- package/dist/cli/destructive.d.ts +7 -4
- package/dist/cli/destructive.js +224 -20
- package/dist/cli/error-catalog.js +36 -0
- package/dist/cli/error-text.d.ts +15 -0
- package/dist/cli/error-text.js +37 -0
- package/dist/cli/index.d.ts +62 -4
- package/dist/cli/index.js +569 -112
- package/dist/cli/local-server-guard.d.ts +53 -0
- package/dist/cli/local-server-guard.js +113 -0
- package/dist/cli/local-server-output.d.ts +41 -0
- package/dist/cli/local-server-output.js +98 -0
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +121 -34
- package/dist/cli/migrate.d.ts +113 -5
- package/dist/cli/migrate.js +261 -21
- package/dist/cli/observe.d.ts +6 -1
- package/dist/cli/observe.js +84 -54
- package/dist/cli/pii-predicate-guard.d.ts +43 -23
- package/dist/cli/pii-predicate-guard.js +100 -35
- package/dist/cli/prisma-resolve.js +15 -11
- package/dist/cli/prisma-schema.js +12 -4
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/studio.d.ts +14 -2
- package/dist/cli/studio.js +246 -71
- package/dist/cli/ui.d.ts +1 -1
- package/dist/client.d.ts +558 -85
- package/dist/client.js +1275 -400
- package/dist/connection-guard.d.ts +13 -0
- package/dist/connection-guard.js +27 -0
- package/dist/connection-url.d.ts +51 -16
- package/dist/connection-url.js +63 -34
- package/dist/diagnostics.d.ts +81 -0
- package/dist/diagnostics.js +144 -0
- package/dist/dialect.d.ts +449 -20
- package/dist/dialect.js +115 -43
- package/dist/engine-config.d.ts +7 -6
- package/dist/errors.d.ts +126 -6
- package/dist/errors.js +252 -12
- package/dist/generate.d.ts +57 -7
- package/dist/generate.js +635 -281
- package/dist/global-filters-config.d.ts +59 -0
- package/dist/global-filters-config.js +361 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.js +4 -2
- package/dist/introspect.d.ts +31 -6
- package/dist/introspect.js +106 -50
- package/dist/log-codes.d.ts +12 -0
- package/dist/log-codes.js +74 -0
- package/dist/logger.d.ts +87 -0
- package/dist/logger.js +93 -0
- package/dist/mssql.d.ts +8 -5
- package/dist/mssql.js +354 -67
- package/dist/mysql.d.ts +3 -5
- package/dist/mysql.js +423 -73
- package/dist/nested-write.d.ts +3 -0
- package/dist/nested-write.js +177 -112
- package/dist/pipeline-submittable.d.ts +12 -0
- package/dist/pipeline-submittable.js +117 -44
- package/dist/pipeline.js +21 -26
- package/dist/powdb-introspect.d.ts +5 -2
- package/dist/powdb-introspect.js +21 -16
- package/dist/powdb-shared.d.ts +36 -5
- package/dist/powdb-shared.js +65 -8
- package/dist/powdb.d.ts +4 -0
- package/dist/powdb.js +183 -51
- package/dist/powql.d.ts +394 -51
- package/dist/powql.js +1836 -291
- package/dist/prisma-compat.d.ts +23 -15
- package/dist/prisma-compat.js +508 -373
- package/dist/query/aggregates.d.ts +17 -1
- package/dist/query/aggregates.js +263 -32
- package/dist/query/batched-loader.d.ts +25 -6
- package/dist/query/batched-loader.js +101 -21
- package/dist/query/builder.d.ts +325 -184
- package/dist/query/builder.js +1458 -495
- package/dist/query/column-ref.d.ts +61 -0
- package/dist/query/column-ref.js +66 -0
- package/dist/query/compound-unique.d.ts +21 -0
- package/dist/query/compound-unique.js +97 -19
- package/dist/query/deferred.d.ts +53 -16
- package/dist/query/expr.d.ts +217 -0
- package/dist/query/expr.js +848 -0
- package/dist/query/filters.d.ts +155 -53
- package/dist/query/filters.js +597 -72
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.js +4 -0
- package/dist/query/json-value.d.ts +43 -0
- package/dist/query/json-value.js +54 -0
- package/dist/query/normalize-args.d.ts +87 -0
- package/dist/query/normalize-args.js +517 -0
- package/dist/query/option-surface.d.ts +16 -4
- package/dist/query/option-surface.js +56 -13
- package/dist/query/relation-names.d.ts +14 -0
- package/dist/query/relation-names.js +77 -19
- package/dist/query/relations.d.ts +30 -3
- package/dist/query/relations.js +220 -67
- package/dist/query/row-lock.d.ts +49 -0
- package/dist/query/row-lock.js +131 -0
- package/dist/query/sql-comment.d.ts +161 -0
- package/dist/query/sql-comment.js +374 -0
- package/dist/query/stream-idle.d.ts +132 -0
- package/dist/query/stream-idle.js +203 -0
- package/dist/query/table-client.d.ts +101 -0
- package/dist/query/table-client.js +70 -0
- package/dist/query/types.d.ts +677 -298
- package/dist/query/utils.d.ts +105 -6
- package/dist/query/utils.js +414 -18
- package/dist/query/warn-registry.d.ts +18 -0
- package/dist/query/warn-registry.js +18 -0
- package/dist/query/where-compile.d.ts +138 -14
- package/dist/query/where-compile.js +508 -31
- package/dist/query/where.d.ts +159 -59
- package/dist/query/where.js +711 -248
- package/dist/query/writes.d.ts +112 -50
- package/dist/query/writes.js +1496 -202
- package/dist/query-events.d.ts +4 -2
- package/dist/query-events.js +3 -1
- package/dist/realtime.d.ts +7 -3
- package/dist/realtime.js +13 -2
- package/dist/schema-builder.d.ts +55 -4
- package/dist/schema-builder.js +381 -18
- package/dist/schema-metadata.d.ts +21 -1
- package/dist/schema-metadata.js +28 -7
- package/dist/schema-sql.d.ts +75 -5
- package/dist/schema-sql.js +494 -44
- package/dist/schema.d.ts +26 -5
- package/dist/schema.js +109 -13
- package/dist/sql-placeholders.d.ts +56 -0
- package/dist/sql-placeholders.js +200 -0
- package/dist/sqlite.d.ts +75 -20
- package/dist/sqlite.js +597 -99
- package/dist/supplied-pool-types.d.ts +48 -0
- package/dist/supplied-pool-types.js +247 -0
- package/dist/transaction-control.d.ts +51 -0
- package/dist/transaction-control.js +311 -0
- package/dist/transaction-state.d.ts +342 -0
- package/dist/transaction-state.js +696 -0
- package/dist/typed-sql.d.ts +37 -3
- package/dist/typed-sql.js +100 -8
- package/package.json +7 -4
- package/skills/turbine-orm/SKILL.md +56 -6
|
@@ -16,10 +16,11 @@
|
|
|
16
16
|
*
|
|
17
17
|
* This module removes the drift BY CONSTRUCTION:
|
|
18
18
|
* - {@link walkWhere} is the ONE enumeration. It sorts keys canonically,
|
|
19
|
-
* skips `undefined
|
|
20
|
-
* relation filters, and yields a
|
|
21
|
-
*
|
|
22
|
-
* combinator structure
|
|
19
|
+
* skips `undefined` values, applies the one combinator-shape rule
|
|
20
|
+
* ({@link combinatorBranches}), dispatches relation filters, and yields a
|
|
21
|
+
* flat, ordered {@link WhereEvent} stream. All three consumers iterate this
|
|
22
|
+
* same stream, so their key order, combinator structure and view of
|
|
23
|
+
* `undefined` can never diverge.
|
|
23
24
|
* - {@link classifyScalarForSql} is the ONE scalar-shape decision the SQL
|
|
24
25
|
* paths use. `buildWhereClause` and `collectWhereParams` BOTH call it with
|
|
25
26
|
* the same `(rawColumn, value)`, so they always take the same branch and
|
|
@@ -37,10 +38,19 @@
|
|
|
37
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
39
|
exports.MAX_WHERE_DEPTH = void 0;
|
|
39
40
|
exports.assertWhereDepth = assertWhereDepth;
|
|
41
|
+
exports.relationFilterValueProblem = relationFilterValueProblem;
|
|
42
|
+
exports.relationCardinalityProblem = relationCardinalityProblem;
|
|
43
|
+
exports.normalizeToOneRelationFilter = normalizeToOneRelationFilter;
|
|
44
|
+
exports.combinatorBranches = combinatorBranches;
|
|
40
45
|
exports.walkWhere = walkWhere;
|
|
46
|
+
exports.foldWhere = foldWhere;
|
|
47
|
+
exports.metadataWhereHost = metadataWhereHost;
|
|
48
|
+
exports.foldWhereFor = foldWhereFor;
|
|
41
49
|
exports.classifyScalarForSql = classifyScalarForSql;
|
|
42
50
|
exports.fingerprintScalarToken = fingerprintScalarToken;
|
|
43
51
|
const errors_js_1 = require("../errors.js");
|
|
52
|
+
const schema_js_1 = require("../schema.js");
|
|
53
|
+
const column_ref_js_1 = require("./column-ref.js");
|
|
44
54
|
const filters_js_1 = require("./filters.js");
|
|
45
55
|
const utils_js_1 = require("./utils.js");
|
|
46
56
|
/**
|
|
@@ -83,18 +93,246 @@ function assertWhereDepth(depth, clause = 'where') {
|
|
|
83
93
|
}
|
|
84
94
|
/** True when a normalized relation filter carries at least one cardinality key. */
|
|
85
95
|
function isRelationFilterObj(filterObj) {
|
|
86
|
-
return (
|
|
96
|
+
return filters_js_1.RELATION_FILTER_WRAPPERS.some((wrapper) => wrapper in filterObj);
|
|
87
97
|
}
|
|
88
98
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
99
|
+
* The refusal for a relation filter not made of the relation-filter keys. The
|
|
100
|
+
* SQL is built from those keys alone, so anything else beside them (a typo such
|
|
101
|
+
* as `sum` for `some`, or `iz` for `is`) would be dropped and the filter would
|
|
102
|
+
* match more rows than written; inside a global filter that is a tenancy
|
|
103
|
+
* condition silently not applied. Same rule and wording as the PowDB engine.
|
|
104
|
+
*/
|
|
105
|
+
function relationFilterShapeError(table, key, rel, detail) {
|
|
106
|
+
return new errors_js_1.ValidationError(`Invalid relation filter "${key}" on "${table}": ${detail}. ` +
|
|
107
|
+
(rel.type === 'belongsTo' || rel.type === 'hasOne'
|
|
108
|
+
? 'A to-one relation takes `{ is: {...} }`, `{ isNot: {...} }`, a bare where object, or `is: null`.'
|
|
109
|
+
: 'A to-many relation takes `{ some: {...} }`, `{ none: {...} }` or `{ every: {...} }`.'));
|
|
110
|
+
}
|
|
111
|
+
function unrecognizedKeys(keys) {
|
|
112
|
+
return `unrecognized key ${keys.map((k) => `"${k}"`).join(', ')}`;
|
|
113
|
+
}
|
|
114
|
+
/** Throws unless every key of a relation filter is a relation-filter key. */
|
|
115
|
+
function assertRelationFilterKeys(table, key, rel, filterObj) {
|
|
116
|
+
for (const k in filterObj) {
|
|
117
|
+
if (filters_js_1.RELATION_FILTER_WRAPPER_SET.has(k))
|
|
118
|
+
continue;
|
|
119
|
+
const unknown = Object.keys(filterObj).filter((name) => !filters_js_1.RELATION_FILTER_WRAPPER_SET.has(name));
|
|
120
|
+
throw relationFilterShapeError(table, key, rel, unrecognizedKeys(unknown));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Why a relation-filter key's value cannot be read, or `null` when it can.
|
|
125
|
+
* `some` / `none` / `every` take a where object; `is` / `isNot` take a where
|
|
126
|
+
* object or `null`. Any other value (`null` on a quantifier, a scalar, an
|
|
127
|
+
* array) used to compile to no condition at all, so a filter, or a tenant
|
|
128
|
+
* global filter, built from a missing value matched every row. Shared with the
|
|
129
|
+
* PowDB engine so both refuse the same values in the same words.
|
|
130
|
+
*/
|
|
131
|
+
function relationFilterValueProblem(wrapper, value) {
|
|
132
|
+
if (value === undefined || (0, filters_js_1.isPlainRecord)(value))
|
|
133
|
+
return null;
|
|
134
|
+
const toOne = wrapper === 'is' || wrapper === 'isNot';
|
|
135
|
+
if (toOne && value === null)
|
|
136
|
+
return null;
|
|
137
|
+
return (`\`${wrapper}\` takes a where object${toOne ? ' or null' : ''}, got ${(0, filters_js_1.describeOperand)(value)}` +
|
|
138
|
+
(value === null ? '; `some: {}` matches rows with a related row, `none: {}` rows with none' : ''));
|
|
139
|
+
}
|
|
140
|
+
/** Throws unless every relation-filter key holds a value the compiler reads (see {@link relationFilterValueProblem}). */
|
|
141
|
+
function assertRelationFilterValues(table, key, rel, filterObj) {
|
|
142
|
+
for (const wrapper of filters_js_1.RELATION_FILTER_WRAPPERS) {
|
|
143
|
+
const problem = relationFilterValueProblem(wrapper, filterObj[wrapper]);
|
|
144
|
+
if (problem !== null)
|
|
145
|
+
throw relationFilterShapeError(table, key, rel, problem);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Why a relation filter reads a to-one key on a to-many relation, or `null`
|
|
150
|
+
* when it does not. `posts: { is: {...} }` compiled to "at least one post
|
|
151
|
+
* matches" and `isNot` to "no post matches", which is not what either word says
|
|
152
|
+
* about a list of rows, and the typed API never offers them there. The
|
|
153
|
+
* quantifiers on a to-one relation are left alone: with at most one related
|
|
154
|
+
* row, `some` / `none` / `every` each have one reading. Shared with the PowDB
|
|
155
|
+
* engine so both refuse the same filters in the same words.
|
|
156
|
+
*/
|
|
157
|
+
function relationCardinalityProblem(relDef, filterObj) {
|
|
158
|
+
if (relDef.type !== 'hasMany' && relDef.type !== 'manyToMany')
|
|
159
|
+
return null;
|
|
160
|
+
for (const wrapper of ['is', 'isNot']) {
|
|
161
|
+
const value = filterObj[wrapper];
|
|
162
|
+
if (value === undefined)
|
|
163
|
+
continue;
|
|
164
|
+
const instead = value === null
|
|
165
|
+
? wrapper === 'is'
|
|
166
|
+
? '`none: {}` for rows with no related row'
|
|
167
|
+
: '`some: {}` for rows with at least one'
|
|
168
|
+
: wrapper === 'is'
|
|
169
|
+
? '`some` (at least one related row matches) or `every` (all of them do)'
|
|
170
|
+
: '`none` (no related row matches)';
|
|
171
|
+
return `\`${wrapper}\` applies to a to-one relation and this one is to-many; write ${instead}`;
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* A bare where object on a to-one relation is an implicit `is`
|
|
177
|
+
* (`{ author: { name: 'x' } }` is `{ author: { is: { name: 'x' } } }`). To-many
|
|
178
|
+
* relations still require a quantifier. The one rule, used by the SQL engines'
|
|
179
|
+
* hosts and by PowDB.
|
|
180
|
+
*/
|
|
181
|
+
function normalizeToOneRelationFilter(relDef, filterObj) {
|
|
182
|
+
if ((relDef.type === 'belongsTo' || relDef.type === 'hasOne') &&
|
|
183
|
+
!filters_js_1.RELATION_FILTER_WRAPPERS.some((wrapper) => wrapper in filterObj)) {
|
|
184
|
+
return { is: filterObj };
|
|
185
|
+
}
|
|
186
|
+
return filterObj;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* THE combinator-shape rule, shared by every walker over a where (or having)
|
|
190
|
+
* clause: the SQL walk below, `having`, and the `globalFilters` check at
|
|
191
|
+
* construction. Returns the branches `value` stands for, or throws
|
|
192
|
+
* {@link ValidationError} (E003).
|
|
193
|
+
*
|
|
194
|
+
* - `AND` and `NOT` take one where object (a single branch) or an array of
|
|
195
|
+
* them; `OR` takes an array. This is Prisma's shape.
|
|
196
|
+
* - Anything else is refused by name: `OR: { ... }`, a string, a number,
|
|
197
|
+
* `null`, and an array holding one of those. A combinator the compiler
|
|
198
|
+
* cannot read would compile to nothing, so a mistyped filter, or a
|
|
199
|
+
* tenant filter built as `{ OR: { orgId } }`, would match every row.
|
|
200
|
+
* - An `undefined` member is absent and is dropped. When every member was
|
|
201
|
+
* `undefined` the combinator itself is absent and this returns `null`, the
|
|
202
|
+
* same reading the args seam gives it.
|
|
203
|
+
*
|
|
204
|
+
* An array written empty is returned empty; what that means is the caller's
|
|
205
|
+
* rule (in a where clause: `OR: []` matches nothing, `AND: []` and `NOT: []`
|
|
206
|
+
* apply no condition). Returns the input array by reference when it holds no
|
|
207
|
+
* `undefined`, so the common path allocates nothing.
|
|
208
|
+
*/
|
|
209
|
+
function combinatorBranches(key, value, table, clause = 'where') {
|
|
210
|
+
if (value === undefined)
|
|
211
|
+
return null;
|
|
212
|
+
if (Array.isArray(value)) {
|
|
213
|
+
let holes = 0;
|
|
214
|
+
for (let i = 0; i < value.length; i++) {
|
|
215
|
+
const branch = value[i];
|
|
216
|
+
if (branch === undefined)
|
|
217
|
+
holes++;
|
|
218
|
+
else if (!(0, filters_js_1.isPlainRecord)(branch))
|
|
219
|
+
throw combinatorShapeError(key, table, clause, branch, i);
|
|
220
|
+
}
|
|
221
|
+
if (holes === 0)
|
|
222
|
+
return value;
|
|
223
|
+
if (holes === value.length)
|
|
224
|
+
return null;
|
|
225
|
+
return value.filter((branch) => branch !== undefined);
|
|
226
|
+
}
|
|
227
|
+
if (key !== 'OR' && (0, filters_js_1.isPlainRecord)(value))
|
|
228
|
+
return [value];
|
|
229
|
+
throw combinatorShapeError(key, table, clause, value);
|
|
230
|
+
}
|
|
231
|
+
function combinatorShapeError(key, table, clause, received, index) {
|
|
232
|
+
const takes = key === 'OR' ? `an array of ${clause} objects` : `a ${clause} object or an array of them`;
|
|
233
|
+
const got = index === undefined ? `received ${(0, filters_js_1.describeOperand)(received)}` : `element ${index} is ${(0, filters_js_1.describeOperand)(received)}`;
|
|
234
|
+
const hint = key === 'OR' && index === undefined && (0, filters_js_1.isPlainRecord)(received)
|
|
235
|
+
? ' Wrap the object in an array: `OR: [{ ... }]`.'
|
|
236
|
+
: ' A combinator the query cannot read would otherwise apply no condition at all.';
|
|
237
|
+
return new errors_js_1.ValidationError(`\`${key}\` in the ${clause} clause on table "${table}" takes ${takes}; ${got}.${hint}`);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* The event `OR: []` compiles to: a predicate on the table's own key that no
|
|
241
|
+
* row satisfies (`<pk> IN ()`, bound as an empty list), so an `OR` built from
|
|
242
|
+
* an empty list matches nothing, as in Prisma, instead of everything. It
|
|
243
|
+
* reuses the `in` operator every engine already binds as one length-independent
|
|
244
|
+
* placeholder, so the statement keeps one fixed shape and the three consumers
|
|
245
|
+
* stay in step with no special case.
|
|
246
|
+
*/
|
|
247
|
+
function matchNothingEvent(host) {
|
|
248
|
+
const meta = host.tableMeta;
|
|
249
|
+
for (const column of [...(meta.primaryKey ?? []), ...(meta.allColumns ?? [])]) {
|
|
250
|
+
if ((0, filters_js_1.operandColumnKind)(host.getColumnPgType(column)) !== 'scalar')
|
|
251
|
+
continue;
|
|
252
|
+
return { kind: 'scalar', key: meta.reverseColumnMap?.[column] ?? column, value: { in: [] } };
|
|
253
|
+
}
|
|
254
|
+
throw new errors_js_1.ValidationError(`\`OR: []\` on table "${meta.name}" matches no row, and expressing that needs one plain column of the ` +
|
|
255
|
+
'table, but its schema metadata lists none. Regenerate the client (`npx turbine generate`), or leave ' +
|
|
256
|
+
'the empty `OR` out of the query.');
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* The members of an `OR` that are not absent (see {@link isAbsentBranch}),
|
|
260
|
+
* by reference when none is.
|
|
261
|
+
*/
|
|
262
|
+
function presentOrBranches(host, branches) {
|
|
263
|
+
for (let i = 0; i < branches.length; i++) {
|
|
264
|
+
if (isAbsentBranch(host, branches[i], 0))
|
|
265
|
+
return branches.filter((branch) => !isAbsentBranch(host, branch, 0));
|
|
266
|
+
}
|
|
267
|
+
return branches;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Whether an `OR` member is absent below the args seam: it names keys, and
|
|
271
|
+
* every one holds `undefined`, a combinator made only of such members, or a
|
|
272
|
+
* relation filter whose keys all hold `undefined`. The seam removes such a
|
|
273
|
+
* member, so the walk drops it too and an internal caller that skips the seam
|
|
274
|
+
* gets the same `OR`. A member written empty (`{}`) is not absent: it applies
|
|
275
|
+
* no condition, which makes its `OR` TRUE.
|
|
276
|
+
*/
|
|
277
|
+
function isAbsentBranch(host, branch, depth) {
|
|
278
|
+
if (depth > exports.MAX_WHERE_DEPTH)
|
|
279
|
+
return false;
|
|
280
|
+
let named = false;
|
|
281
|
+
for (const key of Object.keys(branch)) {
|
|
282
|
+
named = true;
|
|
283
|
+
const value = branch[key];
|
|
284
|
+
if (value === undefined)
|
|
285
|
+
continue;
|
|
286
|
+
if (key === 'OR' || key === 'AND' || key === 'NOT') {
|
|
287
|
+
if (Array.isArray(value)) {
|
|
288
|
+
if (value.length > 0 &&
|
|
289
|
+
value.every((m) => m === undefined || ((0, filters_js_1.isPlainRecord)(m) && isAbsentBranch(host, m, depth + 1)))) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
else if ((0, filters_js_1.isPlainRecord)(value) && isAbsentBranch(host, value, depth + 1)) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
if ((0, filters_js_1.isPlainRecord)(value) && (0, utils_js_1.resolveRelation)(host.tableMeta.relations, key) && holdsOnlyUndefined(value))
|
|
299
|
+
continue;
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
return named;
|
|
303
|
+
}
|
|
304
|
+
/** True for an object that names keys and holds `undefined` under every one. */
|
|
305
|
+
function holdsOnlyUndefined(value) {
|
|
306
|
+
let named = false;
|
|
307
|
+
for (const key of Object.keys(value)) {
|
|
308
|
+
if (value[key] !== undefined)
|
|
309
|
+
return false;
|
|
310
|
+
named = true;
|
|
311
|
+
}
|
|
312
|
+
return named;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* THE canonical WHERE enumeration. Yields events in sorted-key order,
|
|
316
|
+
* dispatching combinators and relation filters, so every consumer
|
|
91
317
|
* (fingerprint, SQL build, param collect) walks identically.
|
|
92
318
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
319
|
+
* `undefined`: the args seam is the authority and removes it, with every
|
|
320
|
+
* object it emptied, before any of this runs. This walk is the backstop for
|
|
321
|
+
* callers below the seam and reads only `undefined` itself: a key holding it
|
|
322
|
+
* is skipped, a combinator member holding it is dropped, and a relation filter
|
|
323
|
+
* whose wrapper keys all hold it is absent. Values are never looked inside, so
|
|
324
|
+
* a JSON operand is data whatever its shape.
|
|
325
|
+
*
|
|
326
|
+
* Combinators follow {@link combinatorBranches}. `AND` and `NOT` with no
|
|
327
|
+
* branches apply no condition; `OR` with none matches no row
|
|
328
|
+
* ({@link matchNothingEvent}), and an `OR` member made only of `undefined` is
|
|
329
|
+
* dropped ({@link isAbsentBranch}). A `NOT` array negates each branch (`NOT a AND
|
|
330
|
+
* NOT b`), as Prisma does; a `NOT` object negates the object as a whole. A key
|
|
331
|
+
* that names a relation takes only the relation-filter keys (a to-one relation
|
|
332
|
+
* also takes a bare where object, read as `is`), each holding a value the
|
|
333
|
+
* compiler reads ({@link relationFilterValueProblem}), and anything else is
|
|
334
|
+
* refused (E003). Only when the key also names a column, and its object names
|
|
335
|
+
* no relation-filter key, is it that column's filter.
|
|
98
336
|
*/
|
|
99
337
|
function walkWhere(host, where) {
|
|
100
338
|
const events = [];
|
|
@@ -102,22 +340,35 @@ function walkWhere(host, where) {
|
|
|
102
340
|
const value = where[key];
|
|
103
341
|
if (value === undefined)
|
|
104
342
|
continue;
|
|
105
|
-
if (key === 'OR') {
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
343
|
+
if (key === 'OR' || key === 'AND' || key === 'NOT') {
|
|
344
|
+
const branches = combinatorBranches(key, value, host.tableMeta.name);
|
|
345
|
+
if (branches === null)
|
|
108
346
|
continue;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
347
|
+
if (key === 'OR') {
|
|
348
|
+
if (branches.length === 0) {
|
|
349
|
+
events.push(matchNothingEvent(host));
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
const present = presentOrBranches(host, branches);
|
|
353
|
+
// Every member held only `undefined`: the `OR` itself is absent.
|
|
354
|
+
if (present.length > 0)
|
|
355
|
+
events.push({ kind: 'or', conditions: present });
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
else if (branches.length === 0) {
|
|
359
|
+
// `AND: []` and `NOT: []` apply no condition.
|
|
360
|
+
}
|
|
361
|
+
else if (key === 'AND') {
|
|
362
|
+
events.push(Array.isArray(value)
|
|
363
|
+
? { kind: 'and', conditions: branches }
|
|
364
|
+
: { kind: 'and', conditions: branches, fixedArity: true });
|
|
365
|
+
}
|
|
366
|
+
else if (!Array.isArray(value)) {
|
|
367
|
+
events.push({ kind: 'not', condition: branches[0] });
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
events.push({ kind: 'and', conditions: branches.map((branch) => ({ NOT: branch })) });
|
|
371
|
+
}
|
|
121
372
|
continue;
|
|
122
373
|
}
|
|
123
374
|
// Resolved rather than looked up, so a relation filter accepts the
|
|
@@ -126,17 +377,250 @@ function walkWhere(host, where) {
|
|
|
126
377
|
// these events), so resolving here cannot drift between them; the emitted
|
|
127
378
|
// event carries the DECLARED name, which is what reaches the fingerprint.
|
|
128
379
|
const resolvedRel = (0, utils_js_1.resolveRelation)(host.tableMeta.relations, key);
|
|
129
|
-
if (resolvedRel &&
|
|
130
|
-
const
|
|
380
|
+
if (resolvedRel && (0, filters_js_1.isPlainRecord)(value)) {
|
|
381
|
+
const present = (0, filters_js_1.dropUndefinedKeys)(value);
|
|
382
|
+
if (present !== value && Object.keys(present).length === 0)
|
|
383
|
+
continue;
|
|
384
|
+
const filterObj = host.normalizeRelationFilter(resolvedRel.def, present);
|
|
131
385
|
if (isRelationFilterObj(filterObj)) {
|
|
386
|
+
assertRelationFilterKeys(host.tableMeta.name, key, resolvedRel.def, filterObj);
|
|
387
|
+
assertRelationFilterValues(host.tableMeta.name, key, resolvedRel.def, filterObj);
|
|
388
|
+
const cardinality = relationCardinalityProblem(resolvedRel.def, filterObj);
|
|
389
|
+
if (cardinality !== null)
|
|
390
|
+
throw relationFilterShapeError(host.tableMeta.name, key, resolvedRel.def, cardinality);
|
|
132
391
|
events.push({ kind: 'relation', key: resolvedRel.name, relDef: resolvedRel.def, filterObj });
|
|
133
392
|
continue;
|
|
134
393
|
}
|
|
394
|
+
// A to-many relation's object names none of its quantifiers. It is a
|
|
395
|
+
// column filter only when the key also names a column.
|
|
396
|
+
if ((0, utils_js_1.resolveColumnName)(host.tableMeta, key) === undefined) {
|
|
397
|
+
const keys = Object.keys(filterObj);
|
|
398
|
+
const detail = keys.length === 0 ? 'the filter object is empty' : unrecognizedKeys(keys);
|
|
399
|
+
throw relationFilterShapeError(host.tableMeta.name, key, resolvedRel.def, detail);
|
|
400
|
+
}
|
|
135
401
|
}
|
|
136
402
|
events.push({ kind: 'scalar', key, value });
|
|
137
403
|
}
|
|
138
404
|
return events;
|
|
139
405
|
}
|
|
406
|
+
function foldWhere(host, where, hostFor, depth = 0, options) {
|
|
407
|
+
assertWhereDepth(depth);
|
|
408
|
+
const parts = [];
|
|
409
|
+
for (const event of walkWhere(host, where)) {
|
|
410
|
+
switch (event.kind) {
|
|
411
|
+
case 'or':
|
|
412
|
+
parts.push(foldAny(event.conditions.map((cond) => foldWhere(host, cond, hostFor, depth + 1, options))));
|
|
413
|
+
break;
|
|
414
|
+
case 'and':
|
|
415
|
+
for (const cond of event.conditions)
|
|
416
|
+
parts.push(foldWhere(host, cond, hostFor, depth + 1, options));
|
|
417
|
+
break;
|
|
418
|
+
case 'not':
|
|
419
|
+
parts.push(negateFold(foldWhere(host, event.condition, hostFor, depth + 1, options)));
|
|
420
|
+
break;
|
|
421
|
+
case 'relation':
|
|
422
|
+
parts.push(foldRelationFilter(host, event.relDef, event.filterObj, hostFor, depth + 1, options));
|
|
423
|
+
break;
|
|
424
|
+
case 'scalar':
|
|
425
|
+
parts.push(foldScalar(host, event.key, event.value, options));
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return foldAll(parts);
|
|
430
|
+
}
|
|
431
|
+
/** AND of folded parts; an `'empty'` part contributes no SQL and is skipped. */
|
|
432
|
+
function foldAll(parts) {
|
|
433
|
+
let out = 'empty';
|
|
434
|
+
for (const part of parts) {
|
|
435
|
+
if (part === 'false')
|
|
436
|
+
return 'false';
|
|
437
|
+
if (part === 'row')
|
|
438
|
+
out = 'row';
|
|
439
|
+
else if (part === 'true' && out === 'empty')
|
|
440
|
+
out = 'true';
|
|
441
|
+
}
|
|
442
|
+
return out;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* OR of folded branches. A branch that compiles to nothing applies no
|
|
446
|
+
* condition, so it is TRUE for every row and so is the `OR` (the compiler
|
|
447
|
+
* writes it as a constant TRUE).
|
|
448
|
+
*/
|
|
449
|
+
function foldAny(parts) {
|
|
450
|
+
let out = 'false';
|
|
451
|
+
for (const part of parts) {
|
|
452
|
+
if (part === 'true' || part === 'empty')
|
|
453
|
+
return 'true';
|
|
454
|
+
if (part === 'row')
|
|
455
|
+
out = 'row';
|
|
456
|
+
}
|
|
457
|
+
return out;
|
|
458
|
+
}
|
|
459
|
+
function negateFold(fold) {
|
|
460
|
+
return fold === 'true' ? 'false' : fold === 'false' ? 'true' : fold;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* A relation filter's branches, as the compiler emits them: `some` / `is` is
|
|
464
|
+
* EXISTS over the sub-where, `none` / `isNot` NOT EXISTS, and `every` NOT
|
|
465
|
+
* EXISTS over its negation (nothing at all when its sub-where is empty).
|
|
466
|
+
*/
|
|
467
|
+
function foldRelationFilter(host, relDef, filterObj, hostFor, depth, options) {
|
|
468
|
+
const target = hostFor(relDef.to);
|
|
469
|
+
if (!target)
|
|
470
|
+
return 'empty';
|
|
471
|
+
const sub = (wrapper) => foldWhere(target, filterObj[wrapper], hostFor, depth, options);
|
|
472
|
+
// Every row has exactly one related row: a NOT NULL foreign key the engine
|
|
473
|
+
// enforces, to a target whose global filter cannot hide that row.
|
|
474
|
+
const notNull = options?.notNull;
|
|
475
|
+
const alwaysRelated = notNull?.foreignKeys === true &&
|
|
476
|
+
relDef.type === 'belongsTo' &&
|
|
477
|
+
!notNull.hasGlobalFilter(relDef.to) &&
|
|
478
|
+
(0, schema_js_1.normalizeKeyColumns)(relDef.foreignKey).every((column) => columnIsNotNull(host.tableMeta, column));
|
|
479
|
+
const parts = [];
|
|
480
|
+
for (const wrapper of filters_js_1.RELATION_FILTER_WRAPPERS) {
|
|
481
|
+
const value = filterObj[wrapper];
|
|
482
|
+
if (value === undefined)
|
|
483
|
+
continue;
|
|
484
|
+
const exists = wrapper === 'some' || wrapper === 'is';
|
|
485
|
+
const notExists = wrapper === 'none' || wrapper === 'isNot';
|
|
486
|
+
if (value === null) {
|
|
487
|
+
// `is: null` is NOT EXISTS and `isNot: null` EXISTS.
|
|
488
|
+
parts.push(alwaysRelated ? (wrapper === 'is' ? 'false' : 'true') : 'row');
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
const inner = sub(wrapper);
|
|
492
|
+
const matchesAll = inner === 'empty' || inner === 'true';
|
|
493
|
+
if (exists)
|
|
494
|
+
parts.push(inner === 'false' ? 'false' : alwaysRelated && matchesAll ? 'true' : 'row');
|
|
495
|
+
else if (notExists)
|
|
496
|
+
parts.push(inner === 'false' ? 'true' : alwaysRelated && matchesAll ? 'false' : 'row');
|
|
497
|
+
else
|
|
498
|
+
parts.push(matchesAll ? inner : 'row');
|
|
499
|
+
}
|
|
500
|
+
return foldAll(parts);
|
|
501
|
+
}
|
|
502
|
+
/** Whether the schema says `column` of `meta` never holds NULL. Unknown nullability is not NOT NULL. */
|
|
503
|
+
function columnIsNotNull(meta, column) {
|
|
504
|
+
for (const col of meta.columns) {
|
|
505
|
+
if (col.name === column)
|
|
506
|
+
return col.nullable === false;
|
|
507
|
+
}
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* A column's filter folds as an operator object holding an empty `in` /
|
|
512
|
+
* `notIn` list, and, with {@link WhereFoldOptions.notNull} on a NOT NULL
|
|
513
|
+
* column, as the shapes listed on {@link foldWhere}.
|
|
514
|
+
*/
|
|
515
|
+
function foldScalar(host, key, value, options) {
|
|
516
|
+
if (value !== null && !(0, filters_js_1.isPlainRecord)(value))
|
|
517
|
+
return 'row';
|
|
518
|
+
const column = (0, utils_js_1.resolveColumnName)(host.tableMeta, key);
|
|
519
|
+
if (column === undefined)
|
|
520
|
+
return 'row';
|
|
521
|
+
const notNull = options?.notNull !== undefined && columnIsNotNull(host.tableMeta, column);
|
|
522
|
+
if (value === null)
|
|
523
|
+
return notNull ? 'false' : 'row';
|
|
524
|
+
const kind = classifyScalarForSql(host, column, value).kind;
|
|
525
|
+
if (kind === 'array')
|
|
526
|
+
return notNull ? foldArrayFilter(value) : 'row';
|
|
527
|
+
if (kind !== 'operator')
|
|
528
|
+
return 'row';
|
|
529
|
+
const json = (0, filters_js_1.operandColumnKind)(host.getColumnPgType(column)) === 'json';
|
|
530
|
+
return foldOperator(value, json, 0, notNull && !json ? { host, column } : undefined);
|
|
531
|
+
}
|
|
532
|
+
/** On a NOT NULL array column: `hasEvery: []` holds for every row and `hasSome: []` for none. */
|
|
533
|
+
function foldArrayFilter(filter) {
|
|
534
|
+
const parts = [];
|
|
535
|
+
for (const key of Object.keys(filter)) {
|
|
536
|
+
const value = filter[key];
|
|
537
|
+
if (value === undefined)
|
|
538
|
+
continue;
|
|
539
|
+
if ((key === 'hasEvery' || key === 'hasSome') && Array.isArray(value) && value.length === 0) {
|
|
540
|
+
parts.push(key === 'hasEvery' ? 'true' : 'false');
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
parts.push('row');
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return foldAll(parts);
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* The operator object's clauses, ANDed: `in: []` is FALSE and `notIn: []` TRUE
|
|
550
|
+
* on every engine (an empty `= ANY` / `IN (subquery)` is false even for a NULL
|
|
551
|
+
* column), and a `not` operator object (non-JSON columns) negates its fold.
|
|
552
|
+
* With `notNull` (a NOT NULL, non-JSON column): `not: null` is TRUE and
|
|
553
|
+
* `equals: null` FALSE, an empty `contains` / `startsWith` / `endsWith` is
|
|
554
|
+
* TRUE (`LIKE '%%'`), and a comparison with the column itself is TRUE for
|
|
555
|
+
* `equals` / `gte` / `lte` and FALSE for `not` / `gt` / `lt`.
|
|
556
|
+
*/
|
|
557
|
+
function foldOperator(op, json, depth, notNull) {
|
|
558
|
+
const parts = [];
|
|
559
|
+
for (const key of Object.keys(op)) {
|
|
560
|
+
const value = op[key];
|
|
561
|
+
if (value === undefined || key === 'mode')
|
|
562
|
+
continue;
|
|
563
|
+
if ((key === 'in' || key === 'notIn') && Array.isArray(value) && value.length === 0) {
|
|
564
|
+
parts.push(key === 'in' ? 'false' : 'true');
|
|
565
|
+
}
|
|
566
|
+
else if (key === 'not' && !json && depth < exports.MAX_WHERE_DEPTH && (0, filters_js_1.isWhereOperator)(value)) {
|
|
567
|
+
parts.push(negateFold(foldOperator(value, json, depth + 1, notNull)));
|
|
568
|
+
}
|
|
569
|
+
else if (notNull) {
|
|
570
|
+
parts.push(foldNotNullComparison(key, value, notNull));
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
parts.push('row');
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return foldAll(parts);
|
|
577
|
+
}
|
|
578
|
+
const SELF_COMPARISON_TRUE = new Set(['equals', 'gte', 'lte']);
|
|
579
|
+
const SELF_COMPARISON_FALSE = new Set(['not', 'gt', 'lt']);
|
|
580
|
+
function foldNotNullComparison(key, value, at) {
|
|
581
|
+
if (value === null) {
|
|
582
|
+
if (key === 'not')
|
|
583
|
+
return 'true';
|
|
584
|
+
if (key === 'equals')
|
|
585
|
+
return 'false';
|
|
586
|
+
return 'row';
|
|
587
|
+
}
|
|
588
|
+
if ((key === 'contains' || key === 'startsWith' || key === 'endsWith') && value === '')
|
|
589
|
+
return 'true';
|
|
590
|
+
if ((0, column_ref_js_1.isColumnRef)(value) && (0, utils_js_1.resolveColumnName)(at.host.tableMeta, value.col) === at.column) {
|
|
591
|
+
if (SELF_COMPARISON_TRUE.has(key))
|
|
592
|
+
return 'true';
|
|
593
|
+
if (SELF_COMPARISON_FALSE.has(key))
|
|
594
|
+
return 'false';
|
|
595
|
+
}
|
|
596
|
+
return 'row';
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* A {@link WhereHost} over plain table metadata, for callers with no SQL
|
|
600
|
+
* builder (PowDB, construction-time checks). Same column-type and to-one rules
|
|
601
|
+
* as the SQL engines' scoped hosts.
|
|
602
|
+
*/
|
|
603
|
+
function metadataWhereHost(meta) {
|
|
604
|
+
return {
|
|
605
|
+
tableMeta: meta,
|
|
606
|
+
normalizeRelationFilter: normalizeToOneRelationFilter,
|
|
607
|
+
getColumnPgType: (column) => meta.dialectTypes?.[column] ?? meta.pgTypes?.[column] ?? 'text',
|
|
608
|
+
isJsonColumnType: (colType) => (0, filters_js_1.operandColumnKind)(colType) === 'json',
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* {@link foldWhere} for `table` in `tables`, with plain metadata hosts. Also
|
|
613
|
+
* validates the whole clause shape at every depth (combinators, relation-filter
|
|
614
|
+
* keys and values), so a construction-time check can run it on a static filter.
|
|
615
|
+
*/
|
|
616
|
+
function foldWhereFor(tables, table, where, options) {
|
|
617
|
+
const hostFor = (name) => {
|
|
618
|
+
const meta = Object.hasOwn(tables, name) ? tables[name] : undefined;
|
|
619
|
+
return meta ? metadataWhereHost(meta) : undefined;
|
|
620
|
+
};
|
|
621
|
+
const host = hostFor(table);
|
|
622
|
+
return host ? foldWhere(host, where, hostFor, 0, options) : 'row';
|
|
623
|
+
}
|
|
140
624
|
/**
|
|
141
625
|
* Column-aware SQL classification of a scalar WHERE value. Reproduces the SQL
|
|
142
626
|
* builder's linear fall-through: a JSON/array-shaped value on a non-JSON/array
|