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
|
@@ -7,12 +7,19 @@
|
|
|
7
7
|
* and execution rather than filter-shape bookkeeping.
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.MAX_NAMED_ORDER_KEYS = exports.VECTOR_DISTANCE_COMPARATORS = exports.VECTOR_METRIC_OPERATORS = exports.TEXT_SEARCH_KEYS = exports.ARRAY_UNIQUE_KEYS = exports.ARRAY_OPERATOR_KEYS = exports.JSONB_UNIQUE_KEYS = exports.JSON_FILTER_KEYS = exports.JSON_STRING_OPERATORS = exports.JSON_RANGE_OPERATORS = exports.JSONB_OPERATOR_KEYS = exports.UPDATE_OPERATOR_KEYS = exports.RELATION_FILTER_WRAPPER_SET = exports.RELATION_FILTER_WRAPPERS = exports.COLUMN_REF_OPERATORS = void 0;
|
|
10
|
+
exports.MAX_NAMED_ORDER_KEYS = exports.VECTOR_DISTANCE_COMPARATORS = exports.VECTOR_METRIC_OPERATORS = exports.TEXT_SEARCH_KEYS = exports.ARRAY_UNIQUE_KEYS = exports.ARRAY_OPERATOR_KEYS = exports.JSONB_UNIQUE_KEYS = exports.JSON_FILTER_KEYS = exports.JSON_STRING_OPERATORS = exports.JSON_RANGE_OPERATORS = exports.JSONB_OPERATOR_KEYS = exports.UPDATE_OPERATOR_KEYS = exports.isColumnRef = exports.RELATION_FILTER_WRAPPER_SET = exports.RELATION_FILTER_WRAPPERS = exports.COLUMN_REF_OPERATORS = void 0;
|
|
11
11
|
exports.isWhereOperator = isWhereOperator;
|
|
12
|
+
exports.isPlainRecord = isPlainRecord;
|
|
13
|
+
exports.setOwn = setOwn;
|
|
12
14
|
exports.isUnmatchedPlainObject = isUnmatchedPlainObject;
|
|
13
|
-
exports.
|
|
15
|
+
exports.dropUndefinedKeys = dropUndefinedKeys;
|
|
16
|
+
exports.operandColumnKind = operandColumnKind;
|
|
17
|
+
exports.describeOperand = describeOperand;
|
|
18
|
+
exports.assertOperatorOperands = assertOperatorOperands;
|
|
19
|
+
exports.assertJsonNotOperand = assertJsonNotOperand;
|
|
20
|
+
exports.assertEqualityOperand = assertEqualityOperand;
|
|
21
|
+
exports.cursorFieldValue = cursorFieldValue;
|
|
14
22
|
exports.fingerprintOperatorShape = fingerprintOperatorShape;
|
|
15
|
-
exports.assertBindableEqualsOperand = assertBindableEqualsOperand;
|
|
16
23
|
exports.sortedKeys = sortedKeys;
|
|
17
24
|
exports.sortedEntries = sortedEntries;
|
|
18
25
|
exports.fingerprintJsonFilterShape = fingerprintJsonFilterShape;
|
|
@@ -34,7 +41,12 @@ exports.dedupeOrderEntries = dedupeOrderEntries;
|
|
|
34
41
|
exports.dedupeColumnList = dedupeColumnList;
|
|
35
42
|
exports.normalizeOrderBy = normalizeOrderBy;
|
|
36
43
|
exports.isEmptyOrderBy = isEmptyOrderBy;
|
|
44
|
+
exports.assertJsonFilterKeys = assertJsonFilterKeys;
|
|
45
|
+
exports.holdsUndefinedValue = holdsUndefinedValue;
|
|
46
|
+
exports.findUndefinedPath = findUndefinedPath;
|
|
47
|
+
exports.assertArrayFilter = assertArrayFilter;
|
|
37
48
|
const errors_js_1 = require("../errors.js");
|
|
49
|
+
const column_ref_js_1 = require("./column-ref.js");
|
|
38
50
|
const types_js_1 = require("./types.js");
|
|
39
51
|
const utils_js_1 = require("./utils.js");
|
|
40
52
|
// ---------------------------------------------------------------------------
|
|
@@ -53,19 +65,40 @@ function isWhereOperator(value) {
|
|
|
53
65
|
return keys.length > 0 && keys.every((k) => utils_js_1.OPERATOR_KEYS.has(k));
|
|
54
66
|
}
|
|
55
67
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* Class instances
|
|
59
|
-
*
|
|
68
|
+
* THE plain-object test for argument trees: an object literal (or a
|
|
69
|
+
* prototype-less object), never an array, Date, Buffer, typed array or other
|
|
70
|
+
* class instance. Class instances are values a driver binds (a Decimal, an
|
|
71
|
+
* interval wrapper), so no walker over caller arguments may descend into one.
|
|
60
72
|
*/
|
|
61
|
-
function
|
|
62
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value)
|
|
63
|
-
return false;
|
|
64
|
-
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value))
|
|
73
|
+
function isPlainRecord(value) {
|
|
74
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
65
75
|
return false;
|
|
66
76
|
const proto = Object.getPrototypeOf(value);
|
|
67
77
|
return proto === Object.prototype || proto === null;
|
|
68
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* `target[key] = value`, always as an OWN property. A plain assignment of the
|
|
81
|
+
* key `__proto__` replaces the object's prototype instead, so a caller key
|
|
82
|
+
* spelled that way (`JSON.parse` produces it as an ordinary own key) would
|
|
83
|
+
* vanish from a rebuilt argument object rather than reach the check that
|
|
84
|
+
* refuses it, and a filter would silently stop filtering.
|
|
85
|
+
*/
|
|
86
|
+
function setOwn(target, key, value) {
|
|
87
|
+
if (key === '__proto__') {
|
|
88
|
+
Object.defineProperty(target, key, { value, writable: true, enumerable: true, configurable: true });
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
target[key] = value;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* {@link isPlainRecord} read at an equality fallthrough: a plain object that
|
|
96
|
+
* matched no known filter shape is a misspelled operator, while a class
|
|
97
|
+
* instance (Buffer for bytea, a Decimal wrapper) is a legitimate bind value.
|
|
98
|
+
*/
|
|
99
|
+
function isUnmatchedPlainObject(value) {
|
|
100
|
+
return isPlainRecord(value);
|
|
101
|
+
}
|
|
69
102
|
/**
|
|
70
103
|
* Operator keys that accept a {@link ColumnRef} (`{ col: 'otherField' }`)
|
|
71
104
|
* value for column-to-column comparison. `in`/`notIn` and the LIKE operators
|
|
@@ -76,54 +109,410 @@ exports.COLUMN_REF_OPERATORS = new Set(['equals', 'not', 'gt', 'gte', 'lt', 'lte
|
|
|
76
109
|
* THE relation-filter wrappers: the keys whose body is a clause against the
|
|
77
110
|
* relation's TARGET table rather than against this one.
|
|
78
111
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* `RELATION_QUANTIFIERS`
|
|
86
|
-
* `k === 'is' || k === 'isNot'` tests, which is the copy most likely to drift
|
|
87
|
-
* because half of it does not read as a list and a grep for the list does not
|
|
88
|
-
* find it.
|
|
89
|
-
*
|
|
90
|
-
* The failure mode is not cosmetic: a wrapper the SQL compiler treats as a
|
|
91
|
-
* relation filter but a WALKER does not is a wrapper whose body reaches the
|
|
92
|
-
* builder unwalked. That is precisely the operand-position channel this
|
|
93
|
-
* release closed elsewhere.
|
|
112
|
+
* Everything that walks a where clause has to agree about them: a wrapper the
|
|
113
|
+
* SQL compiler treats as a relation filter but a walker does not is a wrapper
|
|
114
|
+
* whose body reaches the builder unwalked. The SQL walk (`walkWhere` in
|
|
115
|
+
* where-compile.ts), the bare to-one normalization (`normalizeRelationFilter`
|
|
116
|
+
* in where.ts) and the PII predicate guard (`cli/pii-predicate-guard.ts`) all
|
|
117
|
+
* read this list. `prisma-compat.ts` still carries its own copy
|
|
118
|
+
* (`RELATION_QUANTIFIERS` plus inline `is` / `isNot` tests).
|
|
94
119
|
*
|
|
95
120
|
* `query/` is the right home rather than `cli/` because the direction of the
|
|
96
121
|
* dependency is fixed: `cli/` and the prisma-compat shim may import from the
|
|
97
122
|
* query path, and the query path may never import from `cli/`
|
|
98
123
|
* (`scripts/check-import-cycles.mjs`).
|
|
99
|
-
*
|
|
100
|
-
* ## STATUS: a landing spot, not yet a deduplication. Read this before trusting it.
|
|
101
|
-
*
|
|
102
|
-
* NOTHING IMPORTS THIS YET. The three copies listed above are all still in
|
|
103
|
-
* place and still hand-synced; declaring the canonical home did not by itself
|
|
104
|
-
* move any of them onto it. It is kept, rather than deleted as unused, because
|
|
105
|
-
* `cli/pii-predicate-guard.ts` names this module as where its own copy wants to
|
|
106
|
-
* go, and deleting the destination is the one change that makes converging
|
|
107
|
-
* harder. Wiring the copies up is a separate change: each one sits on a
|
|
108
|
-
* different walk, so each has to be re-tested on its own.
|
|
109
|
-
*
|
|
110
|
-
* So do not read this constant as evidence that the walkers agree. The comment
|
|
111
|
-
* on each copy is still the only thing holding them in step.
|
|
112
124
|
*/
|
|
113
125
|
exports.RELATION_FILTER_WRAPPERS = ['some', 'none', 'every', 'is', 'isNot'];
|
|
114
126
|
/** {@link RELATION_FILTER_WRAPPERS} as a membership set, for the walkers. */
|
|
115
127
|
exports.RELATION_FILTER_WRAPPER_SET = new Set(exports.RELATION_FILTER_WRAPPERS);
|
|
128
|
+
// The column-reference check lives with its constructor; re-exported so the
|
|
129
|
+
// where modules keep one import site for every operand-shape guard.
|
|
130
|
+
var column_ref_js_2 = require("./column-ref.js");
|
|
131
|
+
Object.defineProperty(exports, "isColumnRef", { enumerable: true, get: function () { return column_ref_js_2.isColumnRef; } });
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// `undefined` means absent
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
/**
|
|
136
|
+
* Recursion bound for nested `not` operator objects. Past it the operand walk
|
|
137
|
+
* refuses the filter by name instead of recursing further.
|
|
138
|
+
*/
|
|
139
|
+
const OPERATOR_MAX_DEPTH = 64;
|
|
116
140
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
141
|
+
* `value` without its `undefined`-valued keys, one level deep. Returns the
|
|
142
|
+
* input BY REFERENCE when there are none, so the common path allocates
|
|
143
|
+
* nothing. Symbol-keyed properties (internal brands) survive a copy.
|
|
144
|
+
*
|
|
145
|
+
* This is the where walkers' backstop only. The args seam
|
|
146
|
+
* (`pruneUndefinedArgs` in normalize-args.ts) is the authority on `undefined`:
|
|
147
|
+
* it removes undefined keys at every where position and objects that became
|
|
148
|
+
* empty because of them. The walkers see what the seam missed (internal
|
|
149
|
+
* callers below it), skip a key whose value is `undefined`, and never look
|
|
150
|
+
* inside a value, so a JSON operand is data however it is shaped.
|
|
151
|
+
*/
|
|
152
|
+
function dropUndefinedKeys(value) {
|
|
153
|
+
const keys = Object.keys(value);
|
|
154
|
+
for (const key of keys) {
|
|
155
|
+
if (value[key] !== undefined)
|
|
156
|
+
continue;
|
|
157
|
+
const out = copySymbols(value);
|
|
158
|
+
for (const k of keys)
|
|
159
|
+
if (value[k] !== undefined)
|
|
160
|
+
setOwn(out, k, value[k]);
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
return value;
|
|
164
|
+
}
|
|
165
|
+
/** A fresh object carrying `source`'s own symbol-keyed properties (brands). */
|
|
166
|
+
function copySymbols(source) {
|
|
167
|
+
const out = Object.getPrototypeOf(source) === null ? Object.create(null) : {};
|
|
168
|
+
for (const sym of Object.getOwnPropertySymbols(source)) {
|
|
169
|
+
const descriptor = Object.getOwnPropertyDescriptor(source, sym);
|
|
170
|
+
if (descriptor)
|
|
171
|
+
Object.defineProperty(out, sym, descriptor);
|
|
172
|
+
}
|
|
173
|
+
return out;
|
|
174
|
+
}
|
|
175
|
+
/** {@link OperandColumnKind} for a column type token (case-insensitive). */
|
|
176
|
+
function operandColumnKind(columnType) {
|
|
177
|
+
if (columnType === undefined)
|
|
178
|
+
return 'scalar';
|
|
179
|
+
const t = columnType.toLowerCase();
|
|
180
|
+
if (t === 'json' || t === 'jsonb')
|
|
181
|
+
return 'json';
|
|
182
|
+
if (t.startsWith('_'))
|
|
183
|
+
return 'array';
|
|
184
|
+
if (t === 'vector')
|
|
185
|
+
return 'vector';
|
|
186
|
+
return 'scalar';
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Built-in containers whose instances serialize to `{}` (or nothing useful)
|
|
190
|
+
* when a driver JSON-encodes them, so binding one compares against `'{}'`.
|
|
121
191
|
*/
|
|
122
|
-
|
|
123
|
-
|
|
192
|
+
const NON_SCALAR_BUILTINS = [
|
|
193
|
+
Map,
|
|
194
|
+
Set,
|
|
195
|
+
WeakMap,
|
|
196
|
+
WeakSet,
|
|
197
|
+
Promise,
|
|
198
|
+
RegExp,
|
|
199
|
+
Error,
|
|
200
|
+
];
|
|
201
|
+
/**
|
|
202
|
+
* A class instance the driver can bind as one value: one that says how to
|
|
203
|
+
* render itself (`toPostgres`, `toJSON`, or its own `toString`, as a Decimal
|
|
204
|
+
* or an interval wrapper does), and is not a built-in container.
|
|
205
|
+
*/
|
|
206
|
+
function isBindableInstance(value) {
|
|
207
|
+
if (NON_SCALAR_BUILTINS.some((ctor) => value instanceof ctor))
|
|
208
|
+
return false;
|
|
209
|
+
const v = value;
|
|
210
|
+
return (typeof v.toPostgres === 'function' ||
|
|
211
|
+
typeof v.toJSON === 'function' ||
|
|
212
|
+
(typeof v.toString === 'function' && v.toString !== Object.prototype.toString));
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* A value the driver binds as ONE scalar: a primitive, a Date, a Buffer or
|
|
216
|
+
* typed array, or a class instance that renders itself (see
|
|
217
|
+
* {@link isBindableInstance}). A plain object, an array, a Map or Set is not,
|
|
218
|
+
* and neither is a function, a symbol or `undefined`.
|
|
219
|
+
*/
|
|
220
|
+
function isBindableScalar(value) {
|
|
221
|
+
switch (typeof value) {
|
|
222
|
+
case 'string':
|
|
223
|
+
case 'number':
|
|
224
|
+
case 'bigint':
|
|
225
|
+
case 'boolean':
|
|
226
|
+
return true;
|
|
227
|
+
case 'object':
|
|
228
|
+
break;
|
|
229
|
+
default:
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
if (value === null || Array.isArray(value))
|
|
233
|
+
return false;
|
|
234
|
+
// An Invalid Date has no SQL value: drivers send it as `NaN` text, a server
|
|
235
|
+
// error, a thrown RangeError or, inside a JSON-encoded list, `null`, which
|
|
236
|
+
// matched nothing and so negated to every row.
|
|
237
|
+
if (value instanceof Date)
|
|
238
|
+
return !Number.isNaN(value.getTime());
|
|
239
|
+
if (ArrayBuffer.isView(value))
|
|
240
|
+
return true;
|
|
241
|
+
if ((0, column_ref_js_1.isColumnRef)(value) || isPlainRecord(value))
|
|
242
|
+
return false;
|
|
243
|
+
return isBindableInstance(value);
|
|
244
|
+
}
|
|
245
|
+
/** A non-null value operand that fits a column of `kind`. */
|
|
246
|
+
function fitsColumn(value, kind) {
|
|
247
|
+
if (isBindableScalar(value))
|
|
248
|
+
return true;
|
|
249
|
+
if (kind === 'json') {
|
|
250
|
+
if (value === undefined || typeof value === 'function' || typeof value === 'symbol')
|
|
251
|
+
return false;
|
|
252
|
+
if (value instanceof Date)
|
|
253
|
+
return false;
|
|
254
|
+
return !(typeof value === 'object' && value !== null && NON_SCALAR_BUILTINS.some((c) => value instanceof c));
|
|
255
|
+
}
|
|
256
|
+
if (kind === 'array' || kind === 'vector') {
|
|
257
|
+
return Array.isArray(value) && value.every((el) => el === null || isBindableScalar(el) || Array.isArray(el));
|
|
258
|
+
}
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
/** Short, value-free description of an operand for an error message. */
|
|
262
|
+
function describeOperand(value) {
|
|
263
|
+
if (value === null)
|
|
264
|
+
return 'null';
|
|
265
|
+
if (value === undefined)
|
|
266
|
+
return 'undefined';
|
|
267
|
+
if (Array.isArray(value))
|
|
268
|
+
return 'an array';
|
|
269
|
+
if ((0, column_ref_js_1.isColumnRef)(value))
|
|
270
|
+
return 'a column reference';
|
|
271
|
+
if (value instanceof Date && Number.isNaN(value.getTime()))
|
|
272
|
+
return 'an Invalid Date';
|
|
273
|
+
if (isPlainRecord(value)) {
|
|
274
|
+
const keys = Object.keys(value);
|
|
275
|
+
return keys.length === 0 ? 'an empty object' : `an object with keys ${keys.map((k) => `"${k}"`).join(', ')}`;
|
|
276
|
+
}
|
|
277
|
+
if (typeof value === 'object') {
|
|
278
|
+
const name = value.constructor?.name;
|
|
279
|
+
return typeof name === 'string' && name.length > 0 ? `a ${name} instance` : 'an object';
|
|
280
|
+
}
|
|
281
|
+
return `a ${typeof value}`;
|
|
282
|
+
}
|
|
283
|
+
const SCALAR_WORDS = 'a string, number, bigint, boolean, Date or Buffer';
|
|
284
|
+
function acceptedFor(kind) {
|
|
285
|
+
if (kind === 'array')
|
|
286
|
+
return `an array (or ${SCALAR_WORDS})`;
|
|
287
|
+
if (kind === 'vector')
|
|
288
|
+
return 'an array of numbers';
|
|
289
|
+
if (kind === 'json')
|
|
290
|
+
return 'a JSON value';
|
|
291
|
+
return SCALAR_WORDS;
|
|
292
|
+
}
|
|
293
|
+
/** `on "<column>" for table "<table>"`, for refusal messages. Built only on the throw path. */
|
|
294
|
+
function operandSite(ctx) {
|
|
295
|
+
return `on "${ctx.column}" for table "${ctx.table}"`;
|
|
296
|
+
}
|
|
297
|
+
function refuseOperand(key, value, ctx, accepted) {
|
|
298
|
+
throw new errors_js_1.ValidationError(`Invalid operand for '${key}' ${operandSite(ctx)}: received ${describeOperand(value)}. '${key}' takes ${accepted}.`);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Validate every operand of an operator object against its column, and throw
|
|
302
|
+
* {@link ValidationError} (E003) naming the column, the operator, what was
|
|
303
|
+
* received and what is accepted.
|
|
304
|
+
*
|
|
305
|
+
* An operator object must state at least one comparison. One made only of
|
|
306
|
+
* `mode` (or of operators whose value is `undefined`) compares nothing, so it
|
|
307
|
+
* is refused rather than compiled to no predicate: inside a relation filter
|
|
308
|
+
* that silence turned `deleteMany` into a delete of every row. The args seam
|
|
309
|
+
* removes the object when `undefined` emptied it, so Prisma's optional-search
|
|
310
|
+
* idiom (`{ contains: q, mode: 'insensitive' }` with `q` undefined) still
|
|
311
|
+
* reads as "no filter".
|
|
312
|
+
*
|
|
313
|
+
* Called by the SQL build AND the cache-hit param collect with the same
|
|
314
|
+
* arguments, so a warmed template can never bind an operand the cold build
|
|
315
|
+
* refuses. Recurses into a `not` operator object, bounded by `depth`. Runs on
|
|
316
|
+
* every operator object of every query, so it reads only the keys present and
|
|
317
|
+
* builds a message only when it throws.
|
|
318
|
+
*/
|
|
319
|
+
function assertOperatorOperands(op, ctx, depth = 0) {
|
|
320
|
+
if (depth > OPERATOR_MAX_DEPTH) {
|
|
321
|
+
throw new errors_js_1.ValidationError(`\`not\` operators on "${ctx.column}" nest too deeply; flatten the filter.`);
|
|
322
|
+
}
|
|
323
|
+
const mode = op.mode;
|
|
324
|
+
if (mode !== undefined && mode !== 'default' && mode !== 'insensitive') {
|
|
325
|
+
throw new errors_js_1.ValidationError(`Invalid mode ${typeof mode === 'string' ? JSON.stringify(mode) : describeOperand(mode)} ${operandSite(ctx)}. ` +
|
|
326
|
+
"Use 'insensitive' or 'default'.");
|
|
327
|
+
}
|
|
328
|
+
// The classifier sends only operator keys here; `mode` was checked above.
|
|
329
|
+
let comparisons = 0;
|
|
330
|
+
for (const key of Object.keys(op)) {
|
|
331
|
+
const value = op[key];
|
|
332
|
+
if (value === undefined)
|
|
333
|
+
continue;
|
|
334
|
+
switch (key) {
|
|
335
|
+
case 'equals':
|
|
336
|
+
case 'not':
|
|
337
|
+
comparisons++;
|
|
338
|
+
if (value !== null && typeof value === 'object')
|
|
339
|
+
assertObjectEqualityOperand(key, value, ctx, depth);
|
|
340
|
+
else if (value !== null && !fitsColumn(value, ctx.kind)) {
|
|
341
|
+
refuseOperand(key, value, ctx, `${acceptedFor(ctx.kind)}, null, or col('field')`);
|
|
342
|
+
}
|
|
343
|
+
break;
|
|
344
|
+
case 'gt':
|
|
345
|
+
case 'gte':
|
|
346
|
+
case 'lt':
|
|
347
|
+
case 'lte':
|
|
348
|
+
comparisons++;
|
|
349
|
+
if (value === null) {
|
|
350
|
+
refuseOperand(key, value, ctx, `${acceptedFor(ctx.kind)} or col('field'); a comparison with null matches no row, use equals: null or not: null`);
|
|
351
|
+
}
|
|
352
|
+
if ((0, column_ref_js_1.isColumnRef)(value))
|
|
353
|
+
break;
|
|
354
|
+
if (ctx.kind !== 'json' && (0, column_ref_js_1.isPlainColumnRefShape)(value)) {
|
|
355
|
+
throw new errors_js_1.ValidationError((0, column_ref_js_1.plainColumnRefMessage)(value, key, ctx.column, ctx.table));
|
|
356
|
+
}
|
|
357
|
+
if (!fitsColumn(value, ctx.kind))
|
|
358
|
+
refuseOperand(key, value, ctx, `${acceptedFor(ctx.kind)} or col('field')`);
|
|
359
|
+
break;
|
|
360
|
+
case 'in':
|
|
361
|
+
case 'notIn':
|
|
362
|
+
comparisons++;
|
|
363
|
+
assertListOperand(key, value, ctx);
|
|
364
|
+
break;
|
|
365
|
+
case 'contains':
|
|
366
|
+
case 'startsWith':
|
|
367
|
+
case 'endsWith':
|
|
368
|
+
comparisons++;
|
|
369
|
+
if (typeof value !== 'string')
|
|
370
|
+
refuseOperand(key, value, ctx, 'a string');
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (comparisons === 0) {
|
|
375
|
+
throw new errors_js_1.ValidationError(`The ${depth > 0 ? '`not` operator object' : 'filter'} ${operandSite(ctx)} names no operator: received ` +
|
|
376
|
+
`${describeOperand(op)}. Add one (for example { contains: 'x', mode: 'insensitive' }), or leave ` +
|
|
377
|
+
`"${ctx.column}" out of the filter to apply no condition to it.`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/** An object at `equals` / `not`: a column reference, or a `not` operator object to negate. */
|
|
381
|
+
function assertObjectEqualityOperand(key, value, ctx, depth) {
|
|
382
|
+
if ((0, column_ref_js_1.isColumnRef)(value)) {
|
|
383
|
+
if (ctx.kind === 'json') {
|
|
384
|
+
refuseOperand(key, value, ctx, `${acceptedFor('json')}; column references are not supported on JSON columns`);
|
|
385
|
+
}
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (ctx.kind === 'json') {
|
|
389
|
+
if (key === 'not')
|
|
390
|
+
assertJsonNotOperand(value, ctx);
|
|
391
|
+
if (!fitsColumn(value, 'json'))
|
|
392
|
+
refuseOperand(key, value, ctx, `${acceptedFor('json')} or null`);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if ((0, column_ref_js_1.isPlainColumnRefShape)(value)) {
|
|
396
|
+
throw new errors_js_1.ValidationError((0, column_ref_js_1.plainColumnRefMessage)(value, key, ctx.column, ctx.table));
|
|
397
|
+
}
|
|
398
|
+
if (key === 'not' && isPlainRecord(value)) {
|
|
399
|
+
if (Object.keys(value).length === 0) {
|
|
400
|
+
refuseOperand(key, value, ctx, `a value, null, a column reference or an operator object such as { contains: 'x' }`);
|
|
401
|
+
}
|
|
402
|
+
if (!isWhereOperator(value)) {
|
|
403
|
+
refuseOperand(key, value, ctx, `${acceptedFor(ctx.kind)}, null, col('field'), or an operator object`);
|
|
404
|
+
}
|
|
405
|
+
assertOperatorOperands(value, ctx, depth + 1);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (!fitsColumn(value, ctx.kind)) {
|
|
409
|
+
refuseOperand(key, value, ctx, `${acceptedFor(ctx.kind)}, null, or col('field')` +
|
|
410
|
+
(isPlainRecord(value) ? ' (an object is a JSON value, accepted only on json/jsonb columns)' : ''));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* On a JSON column `not` takes a JSON VALUE (`not: { a: 1 }` compares
|
|
415
|
+
* documents), so `not: { contains: {...} }` was compared as the literal document
|
|
416
|
+
* `{"contains": ...}` and matched nearly every row, while the same spelling on a
|
|
417
|
+
* text column negates the filter. An object made only of filter-operator keys
|
|
418
|
+
* is therefore refused, except `not: { equals: X }`, which negates the
|
|
419
|
+
* whole-document equality. A literal document with such keys stays expressible.
|
|
420
|
+
* Every engine runs it (PowDB too), so the spelling means one thing everywhere.
|
|
421
|
+
*/
|
|
422
|
+
function assertJsonNotOperand(value, ctx) {
|
|
423
|
+
if (!isPlainRecord(value))
|
|
424
|
+
return;
|
|
425
|
+
const keys = Object.keys(value).filter((k) => value[k] !== undefined);
|
|
426
|
+
if (keys.length === 0 || (keys.length === 1 && keys[0] === 'equals'))
|
|
427
|
+
return;
|
|
428
|
+
if (!keys.every((k) => exports.JSON_FILTER_KEYS.has(k) || utils_js_1.OPERATOR_KEYS.has(k)))
|
|
429
|
+
return;
|
|
430
|
+
const named = keys.map((k) => `"${k}"`).join(', ');
|
|
431
|
+
throw new errors_js_1.ValidationError(`Invalid \`not\` ${operandSite(ctx)}, a JSON column: an object whose keys are all filter operators (${named}) ` +
|
|
432
|
+
'would be compared as a literal JSON document and match nearly every row. To negate the filter, write ' +
|
|
433
|
+
`\`NOT: { ${ctx.column}: { ${keys[0]}: ... } }\`. To compare against a document that has these keys, ` +
|
|
434
|
+
'pass it as `not: jsonValue({ ... })` or `not: { equals: { ... } }`.');
|
|
435
|
+
}
|
|
436
|
+
/** `in` / `notIn`: an array whose elements fit the column (or are null). */
|
|
437
|
+
function assertListOperand(key, value, ctx) {
|
|
438
|
+
if (!Array.isArray(value))
|
|
439
|
+
refuseOperand(key, value, ctx, 'an array of values');
|
|
440
|
+
const elementKind = ctx.kind === 'array' ? 'array' : ctx.kind;
|
|
441
|
+
for (const element of value) {
|
|
442
|
+
if (element === null || fitsColumn(element, elementKind))
|
|
443
|
+
continue;
|
|
444
|
+
throw new errors_js_1.ValidationError(`Invalid element in '${key}' ${operandSite(ctx)}: received ${describeOperand(element)}. ` +
|
|
445
|
+
`Every element must be ${acceptedFor(elementKind)} (or null).`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Validate a BARE value (implicit equality, `{ id: 5 }`) against its column.
|
|
450
|
+
* Shared by the build and collect paths of every where scope.
|
|
451
|
+
*/
|
|
452
|
+
function assertEqualityOperand(value, ctx) {
|
|
453
|
+
if (value === null)
|
|
454
|
+
return;
|
|
455
|
+
const where = `on "${ctx.column}" for table "${ctx.table}"`;
|
|
456
|
+
if ((0, column_ref_js_1.isColumnRef)(value)) {
|
|
457
|
+
throw new errors_js_1.ValidationError(`A column reference ${where} needs an operator: write { equals: col(${JSON.stringify(value.col)}) }` +
|
|
458
|
+
(ctx.kind === 'json' ? ', on a non-JSON column (references are not supported on JSON columns).' : '.'));
|
|
459
|
+
}
|
|
460
|
+
if (ctx.kind === 'json') {
|
|
461
|
+
if (typeof value === 'function' || typeof value === 'symbol') {
|
|
462
|
+
throw new errors_js_1.ValidationError(`Invalid value ${where}: received ${describeOperand(value)}. Pass a JSON value.`);
|
|
463
|
+
}
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if ((0, column_ref_js_1.isPlainColumnRefShape)(value)) {
|
|
467
|
+
throw new errors_js_1.ValidationError((0, column_ref_js_1.plainColumnRefMessage)(value, 'equals', ctx.column, ctx.table));
|
|
468
|
+
}
|
|
469
|
+
if (isPlainRecord(value)) {
|
|
470
|
+
const keys = Object.keys(value);
|
|
471
|
+
throw new errors_js_1.ValidationError(keys.length === 0
|
|
472
|
+
? `Empty filter object ${where}. Provide a value or an operator like { gt: 1 }.`
|
|
473
|
+
: `Unknown operator${keys.length > 1 ? 's' : ''} ${keys.map((k) => `"${k}"`).join(', ')} ${where}. ` +
|
|
474
|
+
`Supported operators: ${[...utils_js_1.OPERATOR_KEYS].join(', ')}.`);
|
|
475
|
+
}
|
|
476
|
+
if (fitsColumn(value, ctx.kind))
|
|
477
|
+
return;
|
|
478
|
+
throw new errors_js_1.ValidationError(`Invalid value ${where}: received ${describeOperand(value)}. A bare value takes ${acceptedFor(ctx.kind)}` +
|
|
479
|
+
(Array.isArray(value) ? '; to match any of several values use { in: [...] }.' : '.'));
|
|
480
|
+
}
|
|
481
|
+
/** `{ equals: v }` written as an object literal with that one key. */
|
|
482
|
+
function isEqualsWrapper(value) {
|
|
483
|
+
if (!isPlainRecord(value))
|
|
124
484
|
return false;
|
|
125
485
|
const keys = Object.keys(value);
|
|
126
|
-
return keys.length === 1 && keys[0] === '
|
|
486
|
+
return keys.length === 1 && keys[0] === 'equals';
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* One `cursor` field's value, checked and unwrapped. A cursor value is the
|
|
490
|
+
* value the last row holds in that column, so it takes the bare-equality rule
|
|
491
|
+
* (`{ equals: v }` is the same as `v`); an operator object, a list on a scalar
|
|
492
|
+
* column or a column reference is refused (E003) in terms of cursors, not
|
|
493
|
+
* filters. NULL is a row's value only in a column that can hold it: in a NOT
|
|
494
|
+
* NULL column (the primary key included) no row has it, so it is refused
|
|
495
|
+
* rather than placing the page nowhere. Shared by every engine's cursor path.
|
|
496
|
+
*/
|
|
497
|
+
function cursorFieldValue(field, written, ctx) {
|
|
498
|
+
const value = isEqualsWrapper(written) ? written.equals : written;
|
|
499
|
+
const site = `\`cursor\` field "${field}" on table "${ctx.table}"`;
|
|
500
|
+
if (value === null) {
|
|
501
|
+
if (ctx.nullable)
|
|
502
|
+
return null;
|
|
503
|
+
throw new errors_js_1.ValidationError(`Invalid ${site}: null. "${ctx.column}" is NOT NULL, so no row holds null there and the cursor names no ` +
|
|
504
|
+
"position. Pass the last row's value, or leave `cursor` out for the first page.");
|
|
505
|
+
}
|
|
506
|
+
const columnRef = (0, column_ref_js_1.isColumnRef)(value) || (0, column_ref_js_1.isPlainColumnRefShape)(value);
|
|
507
|
+
const operatorShaped = isPlainRecord(value) && ctx.kind !== 'json';
|
|
508
|
+
const list = Array.isArray(value) && ctx.kind === 'scalar';
|
|
509
|
+
if (columnRef || operatorShaped || list) {
|
|
510
|
+
throw new errors_js_1.ValidationError(`Invalid ${site}: received ${describeOperand(value)}. A cursor value is the value the last row holds in ` +
|
|
511
|
+
`"${field}" (for example \`cursor: { ${field}: 42 }\`), written bare or as \`{ equals: 42 }\`. ` +
|
|
512
|
+
'To filter rows, use `where`.');
|
|
513
|
+
}
|
|
514
|
+
assertEqualityOperand(value, ctx);
|
|
515
|
+
return value;
|
|
127
516
|
}
|
|
128
517
|
/**
|
|
129
518
|
* Fingerprint the SHAPE of a where-operator object. Null-valued `equals` /
|
|
@@ -132,43 +521,35 @@ function isColumnRef(value) {
|
|
|
132
521
|
* warmed by `{ not: 5 }` would serve `{ not: null }` with a desynced param list.
|
|
133
522
|
*
|
|
134
523
|
* Column references ({@link ColumnRef}) compile the referenced column into the
|
|
135
|
-
* SQL TEXT (no param bound), so the referenced field name is part of the
|
|
136
|
-
|
|
137
|
-
*
|
|
524
|
+
* SQL TEXT (no param bound), so the referenced field name is part of the
|
|
525
|
+
* shape: `{ equals: col('a') }` and `{ equals: col('b') }` must never share a
|
|
526
|
+
* cache entry. The name is JSON-encoded so exotic field names cannot collide
|
|
138
527
|
* with other fingerprint tokens.
|
|
528
|
+
*
|
|
529
|
+
* A `not` operator object compiles to `NOT (<inner>)`, so its whole inner shape
|
|
530
|
+
* is part of the token, with the inner object's EFFECTIVE mode (its own, else
|
|
531
|
+
* the inherited one). Keys holding `undefined` are skipped, as the builders skip them.
|
|
139
532
|
*/
|
|
140
|
-
function fingerprintOperatorShape(value) {
|
|
533
|
+
function fingerprintOperatorShape(value, inheritedMode, depth = 0) {
|
|
141
534
|
const obj = value;
|
|
535
|
+
const effectiveMode = obj.mode !== undefined ? obj.mode : inheritedMode;
|
|
142
536
|
const opKeys = Object.keys(obj)
|
|
143
|
-
.filter((k) => k !== 'mode')
|
|
537
|
+
.filter((k) => k !== 'mode' && obj[k] !== undefined)
|
|
144
538
|
.map((k) => {
|
|
145
539
|
const v = obj[k];
|
|
146
540
|
if ((k === 'equals' || k === 'not') && v === null)
|
|
147
541
|
return `${k}:null`;
|
|
148
|
-
if (exports.COLUMN_REF_OPERATORS.has(k) && isColumnRef(v))
|
|
542
|
+
if (exports.COLUMN_REF_OPERATORS.has(k) && (0, column_ref_js_1.isColumnRef)(v))
|
|
149
543
|
return `${k}:col(${JSON.stringify(v.col)})`;
|
|
544
|
+
if (k === 'not' && depth < OPERATOR_MAX_DEPTH && isWhereOperator(v)) {
|
|
545
|
+
return `not:${fingerprintOperatorShape(v, effectiveMode, depth + 1)}`;
|
|
546
|
+
}
|
|
150
547
|
return k;
|
|
151
548
|
})
|
|
152
549
|
.sort();
|
|
153
|
-
const modeStr =
|
|
550
|
+
const modeStr = effectiveMode === 'insensitive' ? ':i' : '';
|
|
154
551
|
return `op(${opKeys.join(',')}${modeStr})`;
|
|
155
552
|
}
|
|
156
|
-
/**
|
|
157
|
-
* Guard for the value of an `equals` operator reaching the plain-equality
|
|
158
|
-
* operator path. A plain object literal can only legitimately be an equality
|
|
159
|
-
* value on a json/jsonb column, and those route to the JSONB filter branch
|
|
160
|
-
* BEFORE the operator branch, so any plain object that reaches here is a
|
|
161
|
-
* mistake (e.g. `{ equals: { foo: 1 } }` on a text column). Shared by the
|
|
162
|
-
* SQL-build path and the cache-hit param-collect path so a warmed cache can
|
|
163
|
-
* never skip the check.
|
|
164
|
-
*/
|
|
165
|
-
function assertBindableEqualsOperand(value, column) {
|
|
166
|
-
if (!isUnmatchedPlainObject(value))
|
|
167
|
-
return;
|
|
168
|
-
throw new errors_js_1.ValidationError(`Plain-object value for operator 'equals' on ${column}: ` +
|
|
169
|
-
`objects are only valid 'equals' values on JSON (json/jsonb) columns, ` +
|
|
170
|
-
`where 'equals' is the JSONB containment filter.`);
|
|
171
|
-
}
|
|
172
553
|
/**
|
|
173
554
|
* Object keys in sorted order, mirroring the canonical order used by every
|
|
174
555
|
* cache fingerprint. The SQL-build and cache-hit param-collect paths MUST
|
|
@@ -325,7 +706,9 @@ exports.ARRAY_UNIQUE_KEYS = new Set(['has', 'hasEvery', 'hasSome', 'isEmpty']);
|
|
|
325
706
|
* concrete value is part of the shape; the other operators are value-invariant.
|
|
326
707
|
*/
|
|
327
708
|
function fingerprintArrayFilterShape(filter) {
|
|
328
|
-
const keys = Object.keys(filter)
|
|
709
|
+
const keys = Object.keys(filter)
|
|
710
|
+
.filter((k) => filter[k] !== undefined)
|
|
711
|
+
.sort();
|
|
329
712
|
const suffix = filter.isEmpty === undefined ? '' : `:empty=${filter.isEmpty ? 'true' : 'false'}`;
|
|
330
713
|
return `${keys.join(',')}${suffix}`;
|
|
331
714
|
}
|
|
@@ -669,24 +1052,34 @@ function assertDroppedDirection(key, value, table) {
|
|
|
669
1052
|
* has a meaning that degrades as the list grows (every added column makes the
|
|
670
1053
|
* result strictly less distinct, converging on the plain query), so a long list
|
|
671
1054
|
* is self-limiting in a way a long sort is not.
|
|
1055
|
+
*
|
|
1056
|
+
* Every element must be a field name: anything else (an `undefined` hole, a
|
|
1057
|
+
* number) is E003 here, the first place either caller reads the list.
|
|
672
1058
|
*/
|
|
673
|
-
function dedupeColumnList(meta, columns) {
|
|
1059
|
+
function dedupeColumnList(meta, columns, label = 'distinct') {
|
|
1060
|
+
for (let i = 0; i < columns.length; i++) {
|
|
1061
|
+
const key = columns[i];
|
|
1062
|
+
if (typeof key === 'string' && key.length > 0)
|
|
1063
|
+
continue;
|
|
1064
|
+
throw new errors_js_1.ValidationError(`\`${label}\`${meta?.name ? ` on table "${meta.name}"` : ''} takes field names, but element ${i} is ` +
|
|
1065
|
+
`${key === undefined ? 'undefined' : describeOperand(key)}. Pass each field as a string, or leave the ` +
|
|
1066
|
+
`list out to apply no ${label}.`);
|
|
1067
|
+
}
|
|
674
1068
|
if (columns.length < 2)
|
|
675
1069
|
return null;
|
|
676
1070
|
const seen = new Map();
|
|
677
1071
|
let dropped;
|
|
678
1072
|
const kept = [];
|
|
679
1073
|
for (const key of columns) {
|
|
680
|
-
const column =
|
|
681
|
-
const first =
|
|
682
|
-
if (first !== undefined
|
|
1074
|
+
const column = resolvedOrderColumn(meta, key);
|
|
1075
|
+
const first = seen.get(column);
|
|
1076
|
+
if (first !== undefined) {
|
|
683
1077
|
if (dropped === undefined)
|
|
684
1078
|
dropped = [];
|
|
685
1079
|
dropped.push({ key, first, resolved: column });
|
|
686
1080
|
continue;
|
|
687
1081
|
}
|
|
688
|
-
|
|
689
|
-
seen.set(column, key);
|
|
1082
|
+
seen.set(column, key);
|
|
690
1083
|
kept.push(key);
|
|
691
1084
|
}
|
|
692
1085
|
return dropped ? { columns: kept, dropped } : null;
|
|
@@ -717,3 +1110,147 @@ function isEmptyOrderBy(orderBy) {
|
|
|
717
1110
|
}
|
|
718
1111
|
return orderBy === undefined || orderBy === null;
|
|
719
1112
|
}
|
|
1113
|
+
// ---------------------------------------------------------------------------
|
|
1114
|
+
// JSON and array filter keys, and `undefined` inside a filter
|
|
1115
|
+
// ---------------------------------------------------------------------------
|
|
1116
|
+
/**
|
|
1117
|
+
* Refuse a {@link JsonFilter} carrying a key that is not a JSON operator, and
|
|
1118
|
+
* refuse a filter that selects a `path` but never compares it.
|
|
1119
|
+
*
|
|
1120
|
+
* Both shapes used to compile to NOTHING. `buildJsonFilterClauses` only ever
|
|
1121
|
+
* emitted a clause for a key it recognized, so `{ path: ['title'],
|
|
1122
|
+
* string_contains: 'x' }`, the Prisma spelling, and an easy typo besides -
|
|
1123
|
+
* produced an empty clause list, the predicate vanished, and the query
|
|
1124
|
+
* returned EVERY row. Inside an `AND` it silently dropped that conjunct, so a
|
|
1125
|
+
* tenant scope written this way widened to the whole table. The equivalent
|
|
1126
|
+
* typo on a scalar column has always thrown; this closes the inconsistency.
|
|
1127
|
+
*
|
|
1128
|
+
* Called from both the SQL-build path and the cache-hit param-collect path, so
|
|
1129
|
+
* a warmed SQL cache cannot skip the check.
|
|
1130
|
+
*/
|
|
1131
|
+
function assertJsonFilterKeys(filter, column) {
|
|
1132
|
+
const obj = filter;
|
|
1133
|
+
const present = Object.keys(obj).filter((k) => obj[k] !== undefined);
|
|
1134
|
+
for (const key of present) {
|
|
1135
|
+
if ((0, column_ref_js_1.isColumnRef)(obj[key])) {
|
|
1136
|
+
throw new errors_js_1.ValidationError(`A column reference cannot be the '${key}' operand of a JSON filter on ${column}: on a json/jsonb ` +
|
|
1137
|
+
'column the operand is a JSON value. Compare the column with col() only on non-JSON columns.');
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
for (const key of present) {
|
|
1141
|
+
if (exports.JSON_FILTER_KEYS.has(key))
|
|
1142
|
+
continue;
|
|
1143
|
+
const suggestion = JSON_PRISMA_SPELLINGS[key];
|
|
1144
|
+
throw new errors_js_1.ValidationError(`Unknown JSON filter operator "${key}" on ${column}.` +
|
|
1145
|
+
(suggestion ? ` Did you mean \`${suggestion}\`?` : '') +
|
|
1146
|
+
` Supported operators: ${[...exports.JSON_FILTER_KEYS].sort().join(', ')}.`);
|
|
1147
|
+
}
|
|
1148
|
+
// `mode` and `path` are modifiers, not comparisons: a filter made only of
|
|
1149
|
+
// those (or of keys holding `undefined`) compares nothing, which is exactly
|
|
1150
|
+
// the silent no-op shape above.
|
|
1151
|
+
if (present.length === 0) {
|
|
1152
|
+
throw new errors_js_1.ValidationError(`JSON filter on ${column} names no comparison. Add one of: ${[...exports.JSON_FILTER_KEYS]
|
|
1153
|
+
.filter((k) => k !== 'path' && k !== 'mode')
|
|
1154
|
+
.sort()
|
|
1155
|
+
.join(', ')}.`);
|
|
1156
|
+
}
|
|
1157
|
+
if (present.every((k) => k === 'path' || k === 'mode')) {
|
|
1158
|
+
throw new errors_js_1.ValidationError(`JSON filter on ${column} selects a \`path\` but has no comparison. ` +
|
|
1159
|
+
`Add one of: ${[...exports.JSON_FILTER_KEYS]
|
|
1160
|
+
.filter((k) => k !== 'path' && k !== 'mode')
|
|
1161
|
+
.sort()
|
|
1162
|
+
.join(', ')}.`);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* The Prisma spelling of each JSON operator, so a migrator who writes the
|
|
1167
|
+
* name they already know gets told the turbine one instead of a bare list.
|
|
1168
|
+
*/
|
|
1169
|
+
const JSON_PRISMA_SPELLINGS = {
|
|
1170
|
+
string_contains: 'stringContains',
|
|
1171
|
+
string_starts_with: 'stringStartsWith',
|
|
1172
|
+
string_ends_with: 'stringEndsWith',
|
|
1173
|
+
array_contains: 'contains',
|
|
1174
|
+
startsWith: 'stringStartsWith',
|
|
1175
|
+
endsWith: 'stringEndsWith',
|
|
1176
|
+
};
|
|
1177
|
+
/**
|
|
1178
|
+
* Whether {@link findUndefinedPath} would find anything, without building the
|
|
1179
|
+
* path strings. Same walk, same leaves, same plain-object rule, same depth bound.
|
|
1180
|
+
*/
|
|
1181
|
+
function holdsUndefinedValue(value, depth) {
|
|
1182
|
+
if (value === undefined)
|
|
1183
|
+
return true;
|
|
1184
|
+
if (typeof value !== 'object' || value === null || depth > 64)
|
|
1185
|
+
return false;
|
|
1186
|
+
if (Array.isArray(value)) {
|
|
1187
|
+
for (let i = 0; i < value.length; i++)
|
|
1188
|
+
if (holdsUndefinedValue(value[i], depth + 1))
|
|
1189
|
+
return true;
|
|
1190
|
+
return false;
|
|
1191
|
+
}
|
|
1192
|
+
const proto = Object.getPrototypeOf(value);
|
|
1193
|
+
if (proto !== Object.prototype && proto !== null)
|
|
1194
|
+
return false;
|
|
1195
|
+
for (const key of Object.keys(value)) {
|
|
1196
|
+
if (holdsUndefinedValue(value[key], depth + 1))
|
|
1197
|
+
return true;
|
|
1198
|
+
}
|
|
1199
|
+
return false;
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* The key path of the first `undefined` inside a where object (a global filter),
|
|
1203
|
+
* or `null`.
|
|
1204
|
+
* Walks plain objects and arrays; values of any other kind (Dates, Buffers,
|
|
1205
|
+
* column references) are leaves. Key NAMES only, never values, so the path is
|
|
1206
|
+
* safe in an error message.
|
|
1207
|
+
*/
|
|
1208
|
+
function findUndefinedPath(value, path, depth) {
|
|
1209
|
+
if (value === undefined)
|
|
1210
|
+
return path || '(root)';
|
|
1211
|
+
if (typeof value !== 'object' || value === null || depth > 64)
|
|
1212
|
+
return null;
|
|
1213
|
+
if (Array.isArray(value)) {
|
|
1214
|
+
for (let i = 0; i < value.length; i++) {
|
|
1215
|
+
const hit = findUndefinedPath(value[i], `${path}[${i}]`, depth + 1);
|
|
1216
|
+
if (hit !== null)
|
|
1217
|
+
return hit;
|
|
1218
|
+
}
|
|
1219
|
+
return null;
|
|
1220
|
+
}
|
|
1221
|
+
const proto = Object.getPrototypeOf(value);
|
|
1222
|
+
if (proto !== Object.prototype && proto !== null)
|
|
1223
|
+
return null;
|
|
1224
|
+
for (const key of Object.keys(value)) {
|
|
1225
|
+
const hit = findUndefinedPath(value[key], path ? `${path}.${key}` : key, depth + 1);
|
|
1226
|
+
if (hit !== null)
|
|
1227
|
+
return hit;
|
|
1228
|
+
}
|
|
1229
|
+
return null;
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Refuse an {@link ArrayFilter} that would compile to less than it says: a key
|
|
1233
|
+
* that is not an array operator (it would be dropped, so `{ has: 'a', hsa: 'b' }`
|
|
1234
|
+
* filtered on `has` alone), an `isEmpty` that is not a boolean (it compiled to
|
|
1235
|
+
* nothing), or no comparison at all. Called by the SQL build and the cache-hit
|
|
1236
|
+
* param collect alike.
|
|
1237
|
+
*/
|
|
1238
|
+
function assertArrayFilter(filter, column) {
|
|
1239
|
+
const obj = filter;
|
|
1240
|
+
let comparisons = 0;
|
|
1241
|
+
for (const key of Object.keys(obj)) {
|
|
1242
|
+
const value = obj[key];
|
|
1243
|
+
if (value === undefined)
|
|
1244
|
+
continue;
|
|
1245
|
+
if (!exports.ARRAY_OPERATOR_KEYS.has(key)) {
|
|
1246
|
+
throw new errors_js_1.ValidationError(`Unknown array filter operator "${key}" on ${column}. Supported operators: ${[...exports.ARRAY_OPERATOR_KEYS].join(', ')}.`);
|
|
1247
|
+
}
|
|
1248
|
+
if (key === 'isEmpty' && typeof value !== 'boolean') {
|
|
1249
|
+
throw new errors_js_1.ValidationError(`\`isEmpty\` on ${column} takes true or false, received ${describeOperand(value)}.`);
|
|
1250
|
+
}
|
|
1251
|
+
comparisons++;
|
|
1252
|
+
}
|
|
1253
|
+
if (comparisons === 0) {
|
|
1254
|
+
throw new errors_js_1.ValidationError(`Array filter on ${column} names no comparison. Add one of: ${[...exports.ARRAY_OPERATOR_KEYS].join(', ')}.`);
|
|
1255
|
+
}
|
|
1256
|
+
}
|