@cerbos/orm-prisma 4.0.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/README.md +611 -460
  2. package/lib/collections.d.ts +30 -0
  3. package/lib/collections.d.ts.map +1 -0
  4. package/lib/collections.js +421 -0
  5. package/lib/collections.js.map +1 -0
  6. package/lib/comparison.d.ts +24 -0
  7. package/lib/comparison.d.ts.map +1 -0
  8. package/lib/comparison.js +731 -0
  9. package/lib/comparison.js.map +1 -0
  10. package/lib/errors.d.ts +13 -0
  11. package/lib/errors.d.ts.map +1 -0
  12. package/lib/errors.js +17 -0
  13. package/lib/errors.js.map +1 -0
  14. package/lib/evaluate.d.ts +5 -0
  15. package/lib/evaluate.d.ts.map +1 -0
  16. package/lib/evaluate.js +118 -0
  17. package/lib/evaluate.js.map +1 -0
  18. package/lib/fields.d.ts +34 -0
  19. package/lib/fields.d.ts.map +1 -0
  20. package/lib/fields.js +253 -0
  21. package/lib/fields.js.map +1 -0
  22. package/lib/functions.d.ts +5 -0
  23. package/lib/functions.d.ts.map +1 -0
  24. package/lib/functions.js +408 -0
  25. package/lib/functions.js.map +1 -0
  26. package/lib/hierarchy.d.ts +6 -0
  27. package/lib/hierarchy.d.ts.map +1 -0
  28. package/lib/hierarchy.js +266 -0
  29. package/lib/hierarchy.js.map +1 -0
  30. package/lib/index.d.ts +91 -8
  31. package/lib/index.d.ts.map +1 -1
  32. package/lib/index.js +34 -2301
  33. package/lib/index.js.map +1 -1
  34. package/lib/literals.d.ts +20 -0
  35. package/lib/literals.d.ts.map +1 -0
  36. package/lib/literals.js +290 -0
  37. package/lib/literals.js.map +1 -0
  38. package/lib/mapping.d.ts +95 -0
  39. package/lib/mapping.d.ts.map +1 -0
  40. package/lib/mapping.js +316 -0
  41. package/lib/mapping.js.map +1 -0
  42. package/lib/narrowing.d.ts +12 -0
  43. package/lib/narrowing.d.ts.map +1 -0
  44. package/lib/narrowing.js +32 -0
  45. package/lib/narrowing.js.map +1 -0
  46. package/lib/plan.d.ts +49 -0
  47. package/lib/plan.d.ts.map +1 -0
  48. package/lib/plan.js +86 -0
  49. package/lib/plan.js.map +1 -0
  50. package/lib/presence.d.ts +25 -0
  51. package/lib/presence.d.ts.map +1 -0
  52. package/lib/presence.js +82 -0
  53. package/lib/presence.js.map +1 -0
  54. package/lib/regex.d.ts +33 -0
  55. package/lib/regex.d.ts.map +1 -0
  56. package/lib/regex.js +480 -0
  57. package/lib/regex.js.map +1 -0
  58. package/lib/relations.d.ts +58 -0
  59. package/lib/relations.d.ts.map +1 -0
  60. package/lib/relations.js +175 -0
  61. package/lib/relations.js.map +1 -0
  62. package/lib/rewrite.d.ts +36 -0
  63. package/lib/rewrite.d.ts.map +1 -0
  64. package/lib/rewrite.js +366 -0
  65. package/lib/rewrite.js.map +1 -0
  66. package/lib/solve.d.ts +20 -0
  67. package/lib/solve.d.ts.map +1 -0
  68. package/lib/solve.js +77 -0
  69. package/lib/solve.js.map +1 -0
  70. package/lib/strings.d.ts +12 -0
  71. package/lib/strings.d.ts.map +1 -0
  72. package/lib/strings.js +110 -0
  73. package/lib/strings.js.map +1 -0
  74. package/lib/ternary.d.ts +12 -0
  75. package/lib/ternary.d.ts.map +1 -0
  76. package/lib/ternary.js +192 -0
  77. package/lib/ternary.js.map +1 -0
  78. package/lib/timestamp.d.ts +45 -0
  79. package/lib/timestamp.d.ts.map +1 -0
  80. package/lib/timestamp.js +225 -0
  81. package/lib/timestamp.js.map +1 -0
  82. package/lib/translate.d.ts +20 -0
  83. package/lib/translate.d.ts.map +1 -0
  84. package/lib/translate.js +287 -0
  85. package/lib/translate.js.map +1 -0
  86. package/lib/types.d.ts +29 -0
  87. package/lib/types.d.ts.map +1 -0
  88. package/lib/types.js +980 -0
  89. package/lib/types.js.map +1 -0
  90. package/lib/value.d.ts +8 -0
  91. package/lib/value.d.ts.map +1 -0
  92. package/lib/value.js +7 -0
  93. package/lib/value.js.map +1 -0
  94. package/package.json +31 -18
  95. package/src/adversarial.test.ts +488 -488
  96. package/src/collections.ts +738 -0
  97. package/src/comparison.ts +1157 -0
  98. package/src/corpus.ts +597 -0
  99. package/src/errors.ts +12 -0
  100. package/src/evaluate.ts +170 -0
  101. package/src/fields.ts +373 -0
  102. package/src/functions.ts +548 -0
  103. package/src/generated/prisma-adversarial/browser.ts +20 -0
  104. package/src/generated/prisma-adversarial/client.ts +20 -0
  105. package/src/generated/prisma-adversarial/commonInputTypes.ts +116 -127
  106. package/src/generated/prisma-adversarial/internal/class.ts +44 -4
  107. package/src/generated/prisma-adversarial/internal/prismaNamespace.ts +350 -3
  108. package/src/generated/prisma-adversarial/internal/prismaNamespaceBrowser.ts +49 -2
  109. package/src/generated/prisma-adversarial/models/AdversarialBoolListElement.ts +1272 -0
  110. package/src/generated/prisma-adversarial/models/AdversarialInner.ts +1369 -0
  111. package/src/generated/prisma-adversarial/models/AdversarialNumberListElement.ts +1316 -0
  112. package/src/generated/prisma-adversarial/models/AdversarialParent.ts +1474 -0
  113. package/src/generated/prisma-adversarial/models/AdversarialResource.ts +547 -75
  114. package/src/generated/prisma-adversarial/models.ts +4 -0
  115. package/src/generated/prisma-adversarial-mysql/browser.ts +64 -0
  116. package/src/generated/{prisma → prisma-adversarial-mysql}/client.ts +23 -18
  117. package/src/generated/{prisma → prisma-adversarial-mysql}/commonInputTypes.ts +167 -92
  118. package/src/generated/prisma-adversarial-mysql/internal/class.ts +284 -0
  119. package/src/generated/prisma-adversarial-mysql/internal/prismaNamespace.ts +1518 -0
  120. package/src/generated/prisma-adversarial-mysql/internal/prismaNamespaceBrowser.ts +274 -0
  121. package/src/generated/prisma-adversarial-mysql/models/AdversarialBoolListElement.ts +1157 -0
  122. package/src/generated/prisma-adversarial-mysql/models/AdversarialCategory.ts +1286 -0
  123. package/src/generated/prisma-adversarial-mysql/models/AdversarialInner.ts +1247 -0
  124. package/src/generated/prisma-adversarial-mysql/models/AdversarialLabel.ts +1157 -0
  125. package/src/generated/prisma-adversarial-mysql/models/AdversarialNumberListElement.ts +1201 -0
  126. package/src/generated/prisma-adversarial-mysql/models/AdversarialParent.ts +1352 -0
  127. package/src/generated/prisma-adversarial-mysql/models/AdversarialResource.ts +2021 -0
  128. package/src/generated/prisma-adversarial-mysql/models/AdversarialSubCategory.ts +1286 -0
  129. package/src/generated/prisma-adversarial-mysql/models/AdversarialTag.ts +1190 -0
  130. package/src/generated/prisma-adversarial-mysql/models.ts +20 -0
  131. package/src/generated/prisma-adversarial-mysql-v6/index.d.ts +13822 -0
  132. package/src/generated/prisma-adversarial-pg/browser.ts +64 -0
  133. package/src/generated/prisma-adversarial-pg/client.ts +86 -0
  134. package/src/generated/prisma-adversarial-pg/commonInputTypes.ts +345 -0
  135. package/src/generated/prisma-adversarial-pg/enums.ts +15 -0
  136. package/src/generated/prisma-adversarial-pg/internal/class.ts +284 -0
  137. package/src/generated/prisma-adversarial-pg/internal/prismaNamespace.ts +1542 -0
  138. package/src/generated/{prisma → prisma-adversarial-pg}/internal/prismaNamespaceBrowser.ts +72 -43
  139. package/src/generated/prisma-adversarial-pg/models/AdversarialBoolListElement.ts +1275 -0
  140. package/src/generated/prisma-adversarial-pg/models/AdversarialCategory.ts +1404 -0
  141. package/src/generated/prisma-adversarial-pg/models/AdversarialInner.ts +1371 -0
  142. package/src/generated/prisma-adversarial-pg/models/AdversarialLabel.ts +1275 -0
  143. package/src/generated/prisma-adversarial-pg/models/AdversarialNumberListElement.ts +1319 -0
  144. package/src/generated/prisma-adversarial-pg/models/AdversarialParent.ts +1476 -0
  145. package/src/generated/prisma-adversarial-pg/models/AdversarialResource.ts +2139 -0
  146. package/src/generated/prisma-adversarial-pg/models/AdversarialSubCategory.ts +1404 -0
  147. package/src/generated/prisma-adversarial-pg/models/AdversarialTag.ts +1310 -0
  148. package/src/generated/prisma-adversarial-pg/models.ts +20 -0
  149. package/src/generated/prisma-adversarial-pg-v6/client.d.ts +1 -0
  150. package/src/generated/prisma-adversarial-pg-v6/default.d.ts +1 -0
  151. package/src/generated/prisma-adversarial-pg-v6/edge.d.ts +1 -0
  152. package/src/generated/prisma-adversarial-pg-v6/index.d.ts +14918 -0
  153. package/src/generated/prisma-adversarial-pg-v6/runtime/index-browser.d.ts +370 -0
  154. package/src/generated/prisma-adversarial-pg-v6/runtime/library.d.ts +3982 -0
  155. package/src/generated/prisma-adversarial-pg-v6/wasm.d.ts +1 -0
  156. package/src/generated/prisma-adversarial-v6/index.d.ts +6515 -304
  157. package/src/hierarchy.ts +405 -0
  158. package/src/index.ts +134 -3325
  159. package/src/literals.ts +363 -0
  160. package/src/mapping.ts +474 -0
  161. package/src/narrowing.ts +33 -0
  162. package/src/plan.ts +125 -0
  163. package/src/presence.ts +114 -0
  164. package/src/regex.ts +596 -0
  165. package/src/relations.ts +226 -0
  166. package/src/rewrite.ts +524 -0
  167. package/src/solve.ts +96 -0
  168. package/src/strings.ts +152 -0
  169. package/src/ternary.ts +359 -0
  170. package/src/test-setup.adversarial-url.ts +21 -0
  171. package/src/test-setup.adversarial.mysql.v6.ts +13 -0
  172. package/src/test-setup.adversarial.mysql.v7.ts +12 -0
  173. package/src/test-setup.adversarial.pg.v6.ts +13 -0
  174. package/src/test-setup.adversarial.pg.v7.ts +9 -0
  175. package/src/test-setup.adversarial.v7.ts +1 -0
  176. package/src/timestamp.ts +308 -0
  177. package/src/translate.ts +419 -0
  178. package/src/translator.test.ts +766 -0
  179. package/src/types.ts +1310 -0
  180. package/src/value.ts +16 -0
  181. package/src/generated/prisma/browser.ts +0 -59
  182. package/src/generated/prisma/internal/class.ts +0 -274
  183. package/src/generated/prisma/internal/prismaNamespace.ts +0 -1403
  184. package/src/generated/prisma/models/Category.ts +0 -1412
  185. package/src/generated/prisma/models/Label.ts +0 -1276
  186. package/src/generated/prisma/models/NestedResource.ts +0 -1562
  187. package/src/generated/prisma/models/NextLevelNestedResource.ts +0 -1337
  188. package/src/generated/prisma/models/Resource.ts +0 -2196
  189. package/src/generated/prisma/models/SubCategory.ts +0 -1412
  190. package/src/generated/prisma/models/Tag.ts +0 -1276
  191. package/src/generated/prisma/models/User.ts +0 -1503
  192. package/src/generated/prisma/models.ts +0 -19
  193. package/src/generated/prisma-v6/index.d.ts +0 -13726
  194. package/src/index.test.ts +0 -8885
  195. package/src/test-setup.v6.ts +0 -5
  196. package/src/test-setup.v7.ts +0 -7
  197. /package/src/generated/{prisma → prisma-adversarial-mysql}/enums.ts +0 -0
  198. /package/src/generated/{prisma-v6 → prisma-adversarial-mysql-v6}/client.d.ts +0 -0
  199. /package/src/generated/{prisma-v6 → prisma-adversarial-mysql-v6}/default.d.ts +0 -0
  200. /package/src/generated/{prisma-v6 → prisma-adversarial-mysql-v6}/edge.d.ts +0 -0
  201. /package/src/generated/{prisma-v6 → prisma-adversarial-mysql-v6}/runtime/index-browser.d.ts +0 -0
  202. /package/src/generated/{prisma-v6 → prisma-adversarial-mysql-v6}/runtime/library.d.ts +0 -0
  203. /package/src/generated/{prisma-v6 → prisma-adversarial-mysql-v6}/wasm.d.ts +0 -0
package/lib/types.js ADDED
@@ -0,0 +1,980 @@
1
+ "use strict";
2
+ // A plan-to-plan rewrite that settles comparisons whose outcome the mapped column types already
3
+ // decide: CEL's heterogeneous equality, and the no-overload errors a mistyped operand raises.
4
+ // Runs before constant folding, so every constant it produces folds away like any other.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.settleTypeMismatches = settleTypeMismatches;
7
+ exports.formatCelDouble = formatCelDouble;
8
+ exports.presence = presence;
9
+ const evaluate_1 = require("./evaluate");
10
+ const literals_1 = require("./literals");
11
+ const mapping_1 = require("./mapping");
12
+ const regex_1 = require("./regex");
13
+ const rewrite_1 = require("./rewrite");
14
+ const timestamp_1 = require("./timestamp");
15
+ const plan_1 = require("./plan");
16
+ const narrowing_1 = require("./narrowing");
17
+ const ORDERING_OPERATORS = new Set(["lt", "le", "gt", "ge"]);
18
+ const STRING_OPERATORS = new Set(["contains", "startsWith", "endsWith", "matches"]);
19
+ const HIERARCHY_OPERATORS = new Set(["overlaps", "ancestorOf", "descendentOf"]);
20
+ const LIST_OPERATORS = new Set(["filter", "map", "except"]);
21
+ /**
22
+ * Replaces every leaf whose outcome the mapped value types settle with the constant that decides
23
+ * the same rows, then leaves the rest of the plan to the translator.
24
+ *
25
+ * CEL's `&&`, `||` and `!` are Kleene's strong three-valued logic with an error in the role of
26
+ * UNKNOWN, and `exists`/`all` are its disjunction and conjunction over the elements. Kleene logic
27
+ * is regular — a formula that is TRUE with an UNKNOWN operand is TRUE whatever that operand is —
28
+ * so under an even number of negations a leaf that is only ever FALSE or an error decides every
29
+ * row exactly as a constant FALSE does, and under an odd number a leaf that is only ever TRUE or
30
+ * an error decides every row exactly as a constant TRUE does. A leaf that can be both TRUE and
31
+ * FALSE, or that sits where its polarity is not fixed (a ternary condition, an operand of `==`,
32
+ * the body of `exists_one`), is left for the translator to lower or refuse.
33
+ */
34
+ function settleTypeMismatches(expr, context, positive = true) {
35
+ if (!(0, plan_1.isOperatorOperand)(expr))
36
+ return expr;
37
+ switch (expr.operator) {
38
+ case "and":
39
+ case "or":
40
+ return {
41
+ operator: expr.operator,
42
+ operands: expr.operands.map((operand) => settleTypeMismatches(operand, context, positive)),
43
+ };
44
+ case "not":
45
+ return {
46
+ operator: "not",
47
+ operands: expr.operands.map((operand) => settleTypeMismatches(operand, context, !positive)),
48
+ };
49
+ case "exists":
50
+ case "all":
51
+ return settleLambdaBody(expr, context, positive);
52
+ }
53
+ const cast = unwrapBooleanComparison(expr) ??
54
+ distributeTernary(expr, context) ??
55
+ rewriteCastComparison(expr, context, positive) ??
56
+ rewriteConcatenation(expr, context) ??
57
+ (0, timestamp_1.rewriteTemporalComparison)(expr, positive) ??
58
+ splitDivision(expr, context);
59
+ if (cast !== undefined)
60
+ return settleTypeMismatches(cast, context, positive);
61
+ const outcomes = leafOutcomes(expr, context);
62
+ if (outcomes === undefined)
63
+ return rewriteLiteralLists(expr, context);
64
+ // Definite once every column is present, and an error otherwise: the presence tests carry the
65
+ // error as SQL UNKNOWN, which decides the row as CEL's error does under either polarity.
66
+ if (outcomes.missing !== undefined && outcomes.true !== outcomes.false) {
67
+ const present = outcomes.missing.length === 1
68
+ ? outcomes.missing[0]
69
+ : { operator: "and", operands: outcomes.missing };
70
+ if (outcomes.missing.length === 0)
71
+ return { value: outcomes.true === true };
72
+ return outcomes.true ? present : { operator: "not", operands: [present] };
73
+ }
74
+ if (positive && !outcomes.true)
75
+ return { value: false };
76
+ if (!positive && !outcomes.false)
77
+ return { value: true };
78
+ // What is left is a leaf that is TRUE (positive) or FALSE (negative) once the relation and its
79
+ // column are present, and an error otherwise. The error rows must not satisfy the leaf's
80
+ // polarity: `reached` is FALSE or UNKNOWN on them, so it stands for the TRUE leaf under an even
81
+ // number of negations, and its negation for the FALSE leaf under an odd number, where that
82
+ // negation meets the enclosing one and is TRUE only for a present value (Kleene logic is
83
+ // regular, so an error leaf decides no more rows than the value its polarity can least use).
84
+ if (outcomes.reached !== undefined) {
85
+ return positive ? outcomes.reached : { operator: "not", operands: [outcomes.reached] };
86
+ }
87
+ if (!outcomes.error && !(outcomes.true && outcomes.false)) {
88
+ return { value: outcomes.true === true };
89
+ }
90
+ return expr;
91
+ }
92
+ /** `exists(c, P)` and `all(c, P)` are monotone in P, so the body keeps the macro's polarity. */
93
+ function settleLambdaBody(expr, context, positive) {
94
+ const [collection, lambda] = expr.operands;
95
+ if (collection === undefined ||
96
+ !(0, plan_1.isNamedOperand)(collection) ||
97
+ lambda === undefined ||
98
+ !(0, plan_1.isOperatorOperand)(lambda) ||
99
+ lambda.operator !== "lambda" ||
100
+ // A two-variable comprehension binds a second variable; rebuilding the lambda below would
101
+ // drop it. It is left whole for the translator to refuse.
102
+ lambda.operands.length !== 2) {
103
+ return expr;
104
+ }
105
+ const [body, variable] = lambda.operands;
106
+ if (body === undefined || variable === undefined || !(0, plan_1.isNamedOperand)(variable)) {
107
+ return expr;
108
+ }
109
+ const scoped = scopedContext(context, collection.name, variable.name);
110
+ if (scoped === undefined)
111
+ return expr;
112
+ return {
113
+ operator: expr.operator,
114
+ operands: [
115
+ collection,
116
+ { operator: "lambda", operands: [settleTypeMismatches(body, scoped, positive), variable] },
117
+ ],
118
+ };
119
+ }
120
+ /** The context a lambda body over `collectionName` resolves its element references in. */
121
+ function scopedContext(context, collectionName, variableName) {
122
+ const { relations } = (0, mapping_1.resolveFieldReference)(collectionName, context);
123
+ if (!relations || relations.length === 0)
124
+ return undefined;
125
+ return (0, mapping_1.enterLambdaScope)(context, collectionName, {
126
+ variableName,
127
+ relationModel: relations[relations.length - 1].model,
128
+ nullableFields: new Set(),
129
+ unknownFilters: [],
130
+ });
131
+ }
132
+ // -- casts ---------------------------------------------------------------------------------------
133
+ /**
134
+ * `cmp(c ? a : b, x)` as `(c && cmp(a, x)) || (!c && cmp(b, x))`, when that lets a branch settle.
135
+ * A ternary evaluates only the branch it selects, so the comparison distributes over it; the
136
+ * expansion is exact only when `c` itself cannot be an error, so `c` must be a boolean column that
137
+ * is never missing. Any other ternary is left to the translator's own lowering.
138
+ */
139
+ function distributeTernary(expr, context) {
140
+ if (!plan_1.COMPARISON_OPERATORS.has(expr.operator) || expr.operands.length !== 2)
141
+ return undefined;
142
+ const index = expr.operands.findIndex((operand) => (0, plan_1.isOperatorOperand)(operand) && operand.operator === "if" && operand.operands.length === 3);
143
+ if (index === -1)
144
+ return undefined;
145
+ const ternary = expr.operands[index];
146
+ if (ternary === undefined || !(0, plan_1.isOperatorOperand)(ternary))
147
+ return undefined;
148
+ const [condition, whenTrue, whenFalse] = (0, narrowing_1.triple)(ternary.operands);
149
+ if (!(0, plan_1.isNamedOperand)(condition))
150
+ return undefined;
151
+ const fieldRef = (0, mapping_1.resolveFieldReference)(condition.name, context);
152
+ if (scalarType(fieldRef) !== "boolean" || fieldRef.relations?.length)
153
+ return undefined;
154
+ // A missing condition is an error that selects neither branch; its presence test keeps it one.
155
+ const conditionPresent = mayBeMissing(fieldRef) ? presence(condition, fieldRef) : undefined;
156
+ // A relation reached in a branch would be required around the whole expansion under an
157
+ // enclosing negation (see negateRequiringHops), where CEL never evaluates the unselected branch.
158
+ const reachesRelation = (operand) => (0, plan_1.isNamedOperand)(operand)
159
+ ? ((0, mapping_1.resolveFieldReference)(operand.name, context).relations?.length ?? 0) > 0
160
+ : (0, plan_1.isOperatorOperand)(operand) && operand.operands.some(reachesRelation);
161
+ if (expr.operands.some(reachesRelation))
162
+ return undefined;
163
+ const branch = (value) => (0, rewrite_1.constantFoldExpression)({
164
+ operator: expr.operator,
165
+ operands: expr.operands.map((operand, i) => (i === index ? value : operand)),
166
+ });
167
+ const [onTrue, onFalse] = [branch(whenTrue), branch(whenFalse)];
168
+ const settles = (leaf) => (0, plan_1.isValueOperand)(leaf) || ((0, plan_1.isOperatorOperand)(leaf) && leafOutcomes(leaf, context) !== undefined);
169
+ if (!settles(onTrue) && !settles(onFalse))
170
+ return undefined;
171
+ return {
172
+ operator: "or",
173
+ operands: [
174
+ { operator: "and", operands: [condition, onTrue] },
175
+ { operator: "and", operands: [{ operator: "not", operands: [condition] }, onFalse] },
176
+ ...(conditionPresent === undefined
177
+ ? []
178
+ : [{ operator: "not", operands: [conditionPresent] }]),
179
+ ],
180
+ };
181
+ }
182
+ /** Operators whose value is a boolean (or an error), never anything else. */
183
+ const BOOLEAN_OPERATORS = new Set([
184
+ "and",
185
+ "or",
186
+ "not",
187
+ "eq",
188
+ "ne",
189
+ "lt",
190
+ "le",
191
+ "gt",
192
+ "ge",
193
+ "in",
194
+ "contains",
195
+ "startsWith",
196
+ "endsWith",
197
+ "matches",
198
+ "exists",
199
+ "all",
200
+ "exists_one",
201
+ "hasIntersection",
202
+ ]);
203
+ /**
204
+ * `b == true`, `b != false` (and the mirrors) over a boolean-valued `b` are `b`; `b == false` and
205
+ * `b != true` are `!b`. An error in `b` is an error either way.
206
+ */
207
+ function unwrapBooleanComparison(expr) {
208
+ if ((expr.operator !== "eq" && expr.operator !== "ne") || expr.operands.length !== 2) {
209
+ return undefined;
210
+ }
211
+ const [first, second] = (0, narrowing_1.pair)(expr.operands);
212
+ const [inner, literal] = (0, plan_1.isValueOperand)(first) ? [second, first] : [first, second];
213
+ if (!(0, plan_1.isValueOperand)(literal) ||
214
+ !(0, narrowing_1.isBoolean)(literal.value) ||
215
+ !(0, plan_1.isOperatorOperand)(inner) ||
216
+ !BOOLEAN_OPERATORS.has(inner.operator)) {
217
+ return undefined;
218
+ }
219
+ const same = (expr.operator === "eq") === literal.value;
220
+ return same ? inner : { operator: "not", operands: [inner] };
221
+ }
222
+ /**
223
+ * `string(column) == "lit"` and `int(column) == k` (and their `!=` forms), solved for the column
224
+ * the cast wraps: a Prisma filter has no cast, but these conversions are invertible.
225
+ *
226
+ * - `string(b)` of a boolean is `"true"` or `"false"`, for a column or any boolean-valued
227
+ * expression (see renderedBooleanComparison).
228
+ * - `string(d)` of a number is cel-go's shortest `%g` rendering, so exactly one double prints as a
229
+ * given literal — or none does, and the comparison is settled like any type mismatch.
230
+ * - `int(d)` truncates toward zero, and raises an error outside (-2^63, 2^63). A threshold is a
231
+ * half-open interval that would need that bound spelled out, which an `Int` column rejects, so
232
+ * only `==` outside a `!` and `!=` under one are translated: the interval `==` solves to already
233
+ * excludes every value that overflows, and the error then decides the row as the constant does.
234
+ *
235
+ * A column the caller sends as an explicit null is left alone: the cast of a null VALUE is an
236
+ * error, where the comparison it would be rewritten to is definite.
237
+ */
238
+ function rewriteCastComparison(expr, context, positive) {
239
+ const { operator } = expr;
240
+ if ((operator !== "eq" && operator !== "ne") || expr.operands.length !== 2)
241
+ return undefined;
242
+ const [first, second] = (0, narrowing_1.pair)(expr.operands);
243
+ const [cast, literal] = (0, plan_1.isOperatorOperand)(first) ? [first, second] : [second, first];
244
+ if (!(0, plan_1.isOperatorOperand)(cast) ||
245
+ cast.operands.length !== 1 ||
246
+ !(0, plan_1.isValueOperand)(literal) ||
247
+ literal.value === null) {
248
+ return undefined;
249
+ }
250
+ const column = cast.operands[0];
251
+ const value = literal.value;
252
+ if (cast.operator === "string" && (0, narrowing_1.isString)(value) && !(0, plan_1.isNamedOperand)(column)) {
253
+ if (isBooleanValued(column, context)) {
254
+ return renderedBooleanComparison(column, value, operator);
255
+ }
256
+ // string(int(x)) renders the int in plain decimal, so the comparison is int(x) == k for the
257
+ // one k that renders as the literal, solved below on the next pass. A literal no int renders
258
+ // as, or one past 2^53, is left alone: the int's overflow error has no bound an Int column
259
+ // accepts.
260
+ const rendered = (0, plan_1.isOperatorOperand)(column) && column.operator === "int" ? Number(value) : NaN;
261
+ return /^(0|-?[1-9][0-9]*)$/.test(value) && Number.isSafeInteger(rendered)
262
+ ? { operator, operands: [column, { value: rendered }] }
263
+ : undefined;
264
+ }
265
+ if (!(0, plan_1.isNamedOperand)(column))
266
+ return undefined;
267
+ const fieldRef = (0, mapping_1.resolveFieldReference)(column.name, context);
268
+ if (fieldRef.nullAttributeRepresentation === "explicit")
269
+ return undefined;
270
+ const type = scalarType(fieldRef);
271
+ // A column reached through a to-one relation is solved for only as a boolean rendering: the
272
+ // comparison it becomes is one the translator already guards through the hop.
273
+ if (fieldRef.relations?.length && (cast.operator !== "string" || type !== "boolean")) {
274
+ return undefined;
275
+ }
276
+ const compare = (op, v) => ({
277
+ operator: op,
278
+ operands: [column, { value: v }],
279
+ });
280
+ const equality = (v) => compare(operator, v);
281
+ // No value of the column prints as the literal: the comparison is settled by its type alone.
282
+ const unmatched = {
283
+ operator,
284
+ operands: [column, { operator: "struct", operands: [] }],
285
+ };
286
+ if (cast.operator === "string" && (0, narrowing_1.isString)(value)) {
287
+ if (type === "boolean") {
288
+ return value === "true" || value === "false" ? equality(value === "true") : unmatched;
289
+ }
290
+ if (type === "number") {
291
+ const parsed = Number(value);
292
+ return value.trim() !== "" && formatCelDouble(parsed) === value
293
+ ? equality(parsed)
294
+ : unmatched;
295
+ }
296
+ return undefined;
297
+ }
298
+ if (cast.operator === "int" && type === "number" && (0, narrowing_1.isNumber)(value)) {
299
+ if ((operator === "eq") !== positive)
300
+ return undefined;
301
+ if (!Number.isInteger(value))
302
+ return unmatched;
303
+ const range = {
304
+ operator: "and",
305
+ operands: value > 0
306
+ ? [compare("ge", value), compare("lt", value + 1)]
307
+ : value < 0
308
+ ? [compare("gt", value - 1), compare("le", value)]
309
+ : [compare("gt", -1), compare("lt", 1)],
310
+ };
311
+ return operator === "eq" ? range : { operator: "not", operands: [range] };
312
+ }
313
+ return undefined;
314
+ }
315
+ /**
316
+ * `string(b) == "lit"` (and `!=`) over a boolean-valued expression `b`: CEL renders a boolean as
317
+ * `"true"` or `"false"`, so the comparison is `b`, `!b`, or for any other literal false wherever
318
+ * `b` evaluates, and an error wherever `b` is one — which `b && !b` is, exactly.
319
+ */
320
+ function renderedBooleanComparison(expression, literal, operator) {
321
+ const negated = { operator: "not", operands: [expression] };
322
+ const holds = literal === "true"
323
+ ? expression
324
+ : literal === "false"
325
+ ? negated
326
+ : { operator: "and", operands: [expression, negated] };
327
+ return operator === "eq" ? holds : { operator: "not", operands: [holds] };
328
+ }
329
+ /**
330
+ * Whether `expr` is a boolean, or an error, on every row: a boolean operator, a boolean literal,
331
+ * a boolean column, or a ternary whose arms both are.
332
+ */
333
+ function isBooleanValued(expr, context) {
334
+ if ((0, plan_1.isValueOperand)(expr))
335
+ return (0, narrowing_1.isBoolean)(expr.value);
336
+ if ((0, plan_1.isNamedOperand)(expr)) {
337
+ return scalarType((0, mapping_1.resolveFieldReference)(expr.name, context)) === "boolean";
338
+ }
339
+ if (BOOLEAN_OPERATORS.has(expr.operator))
340
+ return true;
341
+ return (expr.operator === "if" &&
342
+ expr.operands.length === 3 &&
343
+ expr.operands.slice(1).every((arm) => isBooleanValued(arm, context)));
344
+ }
345
+ /**
346
+ * A comparison whose only column appears as `x / x` or `x / ±0`, split on the sign of `x`. CEL
347
+ * numbers are doubles, so neither division raises an error: `x / x` is 1, or NaN at zero, and
348
+ * `x / ±0` is an infinity signed by both operands, or NaN at zero. Each arm substitutes that
349
+ * constant, leaving a comparison between constants that folds; the arms' guards are plain
350
+ * comparisons on `x`, UNKNOWN on a NULL column, as the missing attribute's error requires.
351
+ */
352
+ function splitDivision(expr, context) {
353
+ if (!plan_1.COMPARISON_OPERATORS.has(expr.operator))
354
+ return undefined;
355
+ const column = divisionColumn(expr);
356
+ if (column === undefined)
357
+ return undefined;
358
+ const fieldRef = (0, mapping_1.resolveFieldReference)(column.name, context);
359
+ if (fieldRef.valueType !== "number" ||
360
+ (fieldRef.relations?.length ?? 0) > 0 ||
361
+ fieldRef.nullAttributeRepresentation === "explicit") {
362
+ return undefined;
363
+ }
364
+ const sign = (op) => ({
365
+ operator: op,
366
+ operands: [column, { value: 0 }],
367
+ });
368
+ const arms = [];
369
+ for (const [guard, x] of [
370
+ ["gt", 1],
371
+ ["lt", -1],
372
+ ["eq", 0],
373
+ ]) {
374
+ const substituted = substituteDivision(expr, column.name, x);
375
+ if (substituted === undefined)
376
+ return undefined;
377
+ arms.push({ operator: "and", operands: [sign(guard), substituted] });
378
+ }
379
+ return { operator: "or", operands: arms };
380
+ }
381
+ /** The one column of a comparison when every reference to it sits in `x / x` or `x / ±0`. */
382
+ function divisionColumn(expr) {
383
+ let column;
384
+ let divided = false;
385
+ const visit = (node) => {
386
+ if ((0, plan_1.isNamedOperand)(node)) {
387
+ if (column !== undefined && column.name !== node.name)
388
+ return false;
389
+ column = node;
390
+ return false;
391
+ }
392
+ if (!(0, plan_1.isOperatorOperand)(node))
393
+ return true;
394
+ if (node.operator === "lambda")
395
+ return false;
396
+ const division = columnDivision(node);
397
+ if (division !== undefined) {
398
+ const { numerator } = division;
399
+ if (column !== undefined && column.name !== numerator.name)
400
+ return false;
401
+ column = numerator;
402
+ divided = true;
403
+ return true;
404
+ }
405
+ return node.operands.every(visit);
406
+ };
407
+ return visit(expr) && divided ? column : undefined;
408
+ }
409
+ /** `x / x` or `x / 0` over a column: the division whose quotient the column's value decides. */
410
+ function columnDivision(node) {
411
+ if (node.operator !== "div" || node.operands.length !== 2)
412
+ return undefined;
413
+ const [numerator, denominator] = (0, narrowing_1.pair)(node.operands);
414
+ if (!(0, plan_1.isNamedOperand)(numerator))
415
+ return undefined;
416
+ if ((0, plan_1.isNamedOperand)(denominator)) {
417
+ return denominator.name === numerator.name ? { numerator, divisor: undefined } : undefined;
418
+ }
419
+ return (0, plan_1.isValueOperand)(denominator) && (0, narrowing_1.isNumber)(denominator.value) && denominator.value === 0
420
+ ? { numerator, divisor: denominator.value }
421
+ : undefined;
422
+ }
423
+ /**
424
+ * `expr` with every division of the column replaced by its value when the column has the sign of
425
+ * `x`, or undefined when the column is still referenced outside one.
426
+ */
427
+ function substituteDivision(expr, name, x) {
428
+ if ((0, plan_1.isNamedOperand)(expr))
429
+ return undefined;
430
+ if (!(0, plan_1.isOperatorOperand)(expr))
431
+ return expr;
432
+ const division = columnDivision(expr);
433
+ if (division !== undefined)
434
+ return { value: x / (division.divisor ?? x) };
435
+ const operands = [];
436
+ for (const operand of expr.operands) {
437
+ const substituted = substituteDivision(operand, name, x);
438
+ if (substituted === undefined)
439
+ return undefined;
440
+ operands.push(substituted);
441
+ }
442
+ return { operator: expr.operator, operands };
443
+ }
444
+ /** Past this many code points, a literal is not split across a concatenation of columns. */
445
+ const MAX_CONCATENATION_SPLITS = 256;
446
+ /** Past this many ways to split the literal, the concatenation is left to the translator. */
447
+ const MAX_CONCATENATION_ARMS = 256;
448
+ /**
449
+ * `a + "sep" + b == "lit"` over string columns and string literals, with at least two columns:
450
+ * one arm per way of splitting the literal into consecutive pieces, each column taking one piece
451
+ * (`a == prefix && b == suffix`) and each literal term matching its own text in place. Each arm
452
+ * also ORs in `!startsWith(column, "")` for every column — FALSE for a present value, UNKNOWN for a
453
+ * NULL one — so a missing operand leaves the whole comparison UNKNOWN under both polarities, as
454
+ * the error `+` raises on it does in CEL.
455
+ */
456
+ function rewriteConcatenation(expr, context) {
457
+ const { operator } = expr;
458
+ if ((operator !== "eq" && operator !== "ne") || expr.operands.length !== 2)
459
+ return undefined;
460
+ const [first, second] = (0, narrowing_1.pair)(expr.operands);
461
+ const [sum, literal] = (0, plan_1.isOperatorOperand)(first) ? [first, second] : [second, first];
462
+ if (!(0, plan_1.isOperatorOperand)(sum) ||
463
+ sum.operator !== "add" ||
464
+ !(0, plan_1.isValueOperand)(literal) ||
465
+ !(0, narrowing_1.isString)(literal.value)) {
466
+ return undefined;
467
+ }
468
+ const terms = concatenationTerms(sum);
469
+ if (terms === undefined)
470
+ return undefined;
471
+ const columns = terms.filter(plan_1.isNamedOperand);
472
+ if (columns.length < 2 ||
473
+ columns.some((column) => {
474
+ const fieldRef = (0, mapping_1.resolveFieldReference)(column.name, context);
475
+ return fieldRef.valueType !== "string" || (fieldRef.relations?.length ?? 0) > 0;
476
+ })) {
477
+ return undefined;
478
+ }
479
+ const codePoints = Array.from(literal.value);
480
+ if (codePoints.length > MAX_CONCATENATION_SPLITS)
481
+ return undefined;
482
+ const arms = [];
483
+ const split = (index, offset, pieces) => {
484
+ if (index === terms.length) {
485
+ if (offset !== codePoints.length)
486
+ return true;
487
+ arms.push(pieces.length === 1 ? pieces[0] : { operator: "and", operands: pieces });
488
+ return arms.length <= MAX_CONCATENATION_ARMS;
489
+ }
490
+ const term = terms[index];
491
+ if ("value" in term) {
492
+ const text = Array.from(term.value);
493
+ const matches = text.every((point, i) => codePoints[offset + i] === point);
494
+ return !matches || split(index + 1, offset + text.length, pieces);
495
+ }
496
+ const last = index === terms.length - 1;
497
+ for (let end = last ? codePoints.length : offset; end <= codePoints.length; end++) {
498
+ const piece = codePoints.slice(offset, end).join("");
499
+ const equality = { operator: "eq", operands: [term, { value: piece }] };
500
+ if (!split(index + 1, end, [...pieces, equality]))
501
+ return false;
502
+ }
503
+ return true;
504
+ };
505
+ if (!split(0, 0, []))
506
+ return undefined;
507
+ for (const column of columns) {
508
+ arms.push({
509
+ operator: "not",
510
+ operands: [{ operator: "startsWith", operands: [column, { value: "" }] }],
511
+ });
512
+ }
513
+ const equality = { operator: "or", operands: arms };
514
+ return operator === "eq" ? equality : { operator: "not", operands: [equality] };
515
+ }
516
+ /** The terms of a nested `add` of string columns and string literals, left to right. */
517
+ function concatenationTerms(expr) {
518
+ if ((0, plan_1.isNamedOperand)(expr))
519
+ return [expr];
520
+ if ((0, plan_1.isValueOperand)(expr)) {
521
+ return (0, narrowing_1.isString)(expr.value) ? [{ value: expr.value }] : undefined;
522
+ }
523
+ if (!(0, plan_1.isOperatorOperand)(expr) || expr.operator !== "add" || expr.operands.length !== 2) {
524
+ return undefined;
525
+ }
526
+ const left = concatenationTerms(expr.operands[0]);
527
+ const right = concatenationTerms(expr.operands[1]);
528
+ return left && right && [...left, ...right];
529
+ }
530
+ /**
531
+ * CEL's string() of a double: cel-go's `fmt.Sprintf("%g", d)`, the shortest digits that
532
+ * round-trip, in exponent form below 1e-4 or from 1e+06.
533
+ */
534
+ function formatCelDouble(x) {
535
+ if (Number.isNaN(x))
536
+ return "NaN";
537
+ if (x === Infinity)
538
+ return "+Inf";
539
+ if (x === -Infinity)
540
+ return "-Inf";
541
+ if (x === 0)
542
+ return Object.is(x, -0) ? "-0" : "0";
543
+ const sign = x < 0 ? "-" : "";
544
+ const exponential = Math.abs(x).toExponential();
545
+ const exponentAt = exponential.indexOf("e");
546
+ const mantissa = exponential.slice(0, exponentAt);
547
+ const exponentText = exponential.slice(exponentAt + 1);
548
+ const digits = mantissa.replace(".", "");
549
+ const exponent = Number(exponentText);
550
+ if (exponent < -4 || exponent >= 6) {
551
+ const fraction = digits.length > 1 ? `.${digits.slice(1)}` : "";
552
+ const magnitude = String(Math.abs(exponent)).padStart(2, "0");
553
+ return `${sign}${digits[0]}${fraction}e${exponent < 0 ? "-" : "+"}${magnitude}`;
554
+ }
555
+ const point = exponent + 1;
556
+ if (point <= 0)
557
+ return `${sign}0.${"0".repeat(-point)}${digits}`;
558
+ if (point >= digits.length)
559
+ return `${sign}${digits}${"0".repeat(point - digits.length)}`;
560
+ return `${sign}${digits.slice(0, point)}.${digits.slice(point)}`;
561
+ }
562
+ // -- the leaves ----------------------------------------------------------------------------------
563
+ function leafOutcomes(expr, context) {
564
+ const { operator, operands } = expr;
565
+ // A pattern RE2 rejects fails on every row it is evaluated for.
566
+ if (operator === "matches" && operands.length === 2) {
567
+ const pattern = operands[1];
568
+ if ((0, plan_1.isValueOperand)(pattern) && (0, narrowing_1.isString)(pattern.value) && (0, regex_1.isInvalidPattern)(pattern.value)) {
569
+ return { error: true };
570
+ }
571
+ }
572
+ // A list where a boolean is required: CEL's logical operators raise a no-overload error on it,
573
+ // and a condition that is not a boolean denies.
574
+ if (LIST_OPERATORS.has(operator))
575
+ return { error: true };
576
+ if (plan_1.COMPARISON_OPERATORS.has(operator) && operands.length === 2) {
577
+ const [left, right] = (0, narrowing_1.pair)(operands);
578
+ // NaN equals nothing and orders against nothing — not even a string, on the current PDP.
579
+ const nan = [left, right].find((o) => (0, plan_1.isValueOperand)(o) && Number.isNaN(o.value));
580
+ if (nan !== undefined) {
581
+ const other = nan === left ? right : left;
582
+ if (!(0, plan_1.isNamedOperand)(other) && !(0, plan_1.isValueOperand)(other))
583
+ return undefined;
584
+ return definite(operator === "ne", [other], context);
585
+ }
586
+ const nullComparison = omittedNullComparison(operator, left, right, context);
587
+ if (nullComparison !== undefined)
588
+ return nullComparison;
589
+ const size = [left, right].find((o) => (0, plan_1.isOperatorOperand)(o) && o.operator === "size");
590
+ if (size !== undefined) {
591
+ // size() has overloads for strings, lists and maps only.
592
+ const [subject] = size.operands;
593
+ const type = subject && scalarFieldType(subject, context);
594
+ return type === "number" || type === "boolean" ? { error: true } : undefined;
595
+ }
596
+ // A bare date-time column is the RFC 3339 STRING the application sends, and a string never
597
+ // orders against a timestamp: no overload, so an error on every row. Equality across the two
598
+ // types is heterogeneous, and false.
599
+ if (comparesRawTemporalWithTimestamp(left, right, context)) {
600
+ return ORDERING_OPERATORS.has(operator)
601
+ ? { error: true }
602
+ : definite(operator !== "eq", [left, right], context);
603
+ }
604
+ const leftType = operandType(left, context);
605
+ const rightType = operandType(right, context);
606
+ if (leftType === undefined ||
607
+ rightType === undefined ||
608
+ leftType.type === rightType.type ||
609
+ leftType.type === "null" ||
610
+ rightType.type === "null") {
611
+ return undefined;
612
+ }
613
+ if (ORDERING_OPERATORS.has(operator))
614
+ return { error: true };
615
+ return definite(operator !== "eq", [left, right], context);
616
+ }
617
+ if (STRING_OPERATORS.has(operator) && operands.length === 2) {
618
+ const mistyped = operands.some((operand) => {
619
+ const type = operandType(operand, context)?.type;
620
+ return type !== undefined && type !== "string" && type !== "null";
621
+ });
622
+ return mistyped ? { error: true } : undefined;
623
+ }
624
+ if (HIERARCHY_OPERATORS.has(operator)) {
625
+ // hierarchy() parses a string; any other scalar is a no-overload error.
626
+ const mistyped = operands.some((operand) => {
627
+ if (!(0, plan_1.isOperatorOperand)(operand) || operand.operator !== "hierarchy")
628
+ return false;
629
+ const [subject] = operand.operands;
630
+ const type = subject && scalarFieldType(subject, context);
631
+ return type === "number" || type === "boolean";
632
+ });
633
+ return mistyped ? { error: true } : undefined;
634
+ }
635
+ if (operator === "in" && operands.length === 2) {
636
+ const [member, collection] = (0, narrowing_1.pair)(operands);
637
+ // A literal member against a mapped list whose elements can never equal it.
638
+ const memberType = literalType(member);
639
+ const list = collectionElementType(collection, context);
640
+ if (memberType !== undefined && memberType !== "null" && list !== undefined) {
641
+ return memberType === list.type ? undefined : { false: true, error: list.mayBeMissing };
642
+ }
643
+ // A field member against a literal list none of whose elements it can equal.
644
+ const field = operandType(member, context);
645
+ const elements = literalElements(collection);
646
+ if (field !== undefined && elements !== undefined) {
647
+ return elements.every((element) => isMismatch(field.type, element))
648
+ ? definite(false, [member], context)
649
+ : undefined;
650
+ }
651
+ return undefined;
652
+ }
653
+ if (operator === "hasIntersection" && operands.length === 2) {
654
+ const [left, right] = (0, narrowing_1.pair)(operands);
655
+ for (const [list, literal] of [
656
+ [left, right],
657
+ [right, left],
658
+ ]) {
659
+ const elementType = collectionElementType(list, context);
660
+ const elements = literalElements(literal);
661
+ if (elementType !== undefined && elements !== undefined) {
662
+ return elements.every((element) => isMismatch(elementType.type, element))
663
+ ? { false: true, error: elementType.mayBeMissing }
664
+ : undefined;
665
+ }
666
+ }
667
+ }
668
+ return undefined;
669
+ }
670
+ /** Whether one operand is a bare `dateTime` column and the other a `timestamp(...)` call. */
671
+ function comparesRawTemporalWithTimestamp(left, right, context) {
672
+ return [
673
+ [left, right],
674
+ [right, left],
675
+ ].some(([column, other]) => (0, plan_1.isNamedOperand)(column) &&
676
+ (0, plan_1.isOperatorOperand)(other) &&
677
+ other.operator === "timestamp" &&
678
+ (0, mapping_1.resolveFieldReference)(column.name, context).valueType === "dateTime");
679
+ }
680
+ /**
681
+ * `x == null` / `x != null` over a column the caller omits when NULL: a present value is never
682
+ * null, and an absent one is a missing-attribute error, so neither outcome can select the NULL
683
+ * rows an `IS NULL` would.
684
+ *
685
+ * A column read through to-one relations (`parent.x`) is also missing when a relation on the way
686
+ * is absent. Its presence test is `reached` rather than `missing`: a relation filter is not
687
+ * guaranteed to be UNKNOWN on the rows it fails, so it only answers where polarity is fixed.
688
+ */
689
+ function omittedNullComparison(operator, left, right, context) {
690
+ if (operator !== "eq" && operator !== "ne")
691
+ return undefined;
692
+ const [field, literal] = (0, plan_1.isNamedOperand)(left) ? [left, right] : [right, left];
693
+ if (!(0, plan_1.isNamedOperand)(field) || !(0, plan_1.isValueOperand)(literal) || literal.value !== null) {
694
+ return undefined;
695
+ }
696
+ const fieldRef = (0, mapping_1.resolveFieldReference)(field.name, context);
697
+ const relations = fieldRef.relations ?? [];
698
+ if (relations.some((relation) => relation.type !== "one"))
699
+ return undefined;
700
+ const convention = fieldRef.nullAttributeRepresentation ?? context.nullRepresentation;
701
+ if (convention !== "omitted")
702
+ return undefined;
703
+ const test = presence(field, fieldRef);
704
+ const presenceTest = test === undefined ? {} : relations.length > 0 ? { reached: test } : { missing: [test] };
705
+ return {
706
+ [operator === "eq" ? "false" : "true"]: true,
707
+ error: true,
708
+ ...presenceTest,
709
+ };
710
+ }
711
+ /**
712
+ * A leaf that is `value` whenever the named operands among `operands` are present, and a
713
+ * missing-attribute error when one is not.
714
+ */
715
+ function definite(value, operands, context) {
716
+ const missing = [];
717
+ let unknowable = false;
718
+ const visit = (operand) => {
719
+ if ((0, literals_1.literalValue)(operand) !== undefined)
720
+ return;
721
+ // Arithmetic raises the error of any column it reads; another expression's errors are not
722
+ // known here, so its leaf is left as it stands.
723
+ if ((0, plan_1.isOperatorOperand)(operand)) {
724
+ if (evaluate_1.ARITHMETIC_OPERATORS.has(operand.operator))
725
+ operand.operands.forEach(visit);
726
+ else
727
+ unknowable = true;
728
+ return;
729
+ }
730
+ if (!(0, plan_1.isNamedOperand)(operand))
731
+ return;
732
+ const fieldRef = (0, mapping_1.resolveFieldReference)(operand.name, context);
733
+ if (!mayBeMissing(fieldRef))
734
+ return;
735
+ const test = presence(operand, fieldRef);
736
+ if (test === undefined)
737
+ unknowable = true;
738
+ else
739
+ missing.push(test);
740
+ };
741
+ operands.forEach(visit);
742
+ return {
743
+ [value ? "true" : "false"]: true,
744
+ error: unknowable || missing.length > 0,
745
+ missing: unknowable ? undefined : missing,
746
+ };
747
+ }
748
+ /**
749
+ * A predicate TRUE for a present value of the column and UNKNOWN for a NULL one, spelled with
750
+ * the plain comparisons the translator leaves to SQL's three-valued logic. Undefined for a column
751
+ * whose type is not declared.
752
+ */
753
+ function presence(column, fieldRef) {
754
+ const compare = (operator, value) => ({
755
+ operator,
756
+ operands: [column, { value }],
757
+ });
758
+ switch (scalarType(fieldRef)) {
759
+ case "string":
760
+ return compare("startsWith", "");
761
+ case "number":
762
+ return { operator: "or", operands: [compare("gt", 0), compare("le", 0)] };
763
+ case "boolean":
764
+ return { operator: "or", operands: [compare("eq", true), compare("eq", false)] };
765
+ default:
766
+ return undefined;
767
+ }
768
+ }
769
+ /**
770
+ * Drops, from a literal list compared by element equality, every element that can never equal
771
+ * the other side's elements: each one contributes a definite false to CEL's membership test, so
772
+ * the list without it decides every row the same way.
773
+ */
774
+ function rewriteLiteralLists(expr, context) {
775
+ const { operator, operands } = expr;
776
+ if (operands.length !== 2)
777
+ return expr;
778
+ const [first, second] = (0, narrowing_1.pair)(operands);
779
+ if (operator === "in") {
780
+ const field = operandType(first, context);
781
+ if (field === undefined)
782
+ return expr;
783
+ const pruned = pruneLiteralList(second, field.type);
784
+ return pruned === undefined ? expr : { operator, operands: [first, pruned] };
785
+ }
786
+ if (operator === "hasIntersection") {
787
+ const leftList = collectionElementType(first, context);
788
+ if (leftList !== undefined) {
789
+ const pruned = pruneLiteralList(second, leftList.type);
790
+ return pruned === undefined ? expr : { operator, operands: [first, pruned] };
791
+ }
792
+ const rightList = collectionElementType(second, context);
793
+ if (rightList !== undefined) {
794
+ const pruned = pruneLiteralList(first, rightList.type);
795
+ return pruned === undefined ? expr : { operator, operands: [pruned, second] };
796
+ }
797
+ }
798
+ return expr;
799
+ }
800
+ /** The literal list without its mismatched elements, or undefined when nothing is dropped. */
801
+ function pruneLiteralList(operand, type) {
802
+ if (!(0, plan_1.isValueOperand)(operand) || !Array.isArray(operand.value))
803
+ return undefined;
804
+ const kept = operand.value.filter((element) => !isMismatch(type, element));
805
+ if (kept.length === operand.value.length || kept.length === 0)
806
+ return undefined;
807
+ return { value: kept };
808
+ }
809
+ function valueType(value) {
810
+ if (value === null)
811
+ return "null";
812
+ if (Array.isArray(value))
813
+ return "list";
814
+ switch ((0, narrowing_1.typeofValue)(value)) {
815
+ case "string":
816
+ return "string";
817
+ case "number":
818
+ return "number";
819
+ case "boolean":
820
+ return "boolean";
821
+ case "object":
822
+ return "map";
823
+ default:
824
+ return undefined;
825
+ }
826
+ }
827
+ function isMismatch(type, element) {
828
+ const elementType = valueType(element);
829
+ return elementType !== undefined && elementType !== "null" && elementType !== type;
830
+ }
831
+ /** The CEL type of a literal operand: a value, or a `list(...)`/`struct(...)` constructor. */
832
+ function literalType(operand) {
833
+ if ((0, plan_1.isValueOperand)(operand))
834
+ return valueType(operand.value);
835
+ if ((0, plan_1.isOperatorOperand)(operand)) {
836
+ if (operand.operator === "list")
837
+ return "list";
838
+ if (operand.operator === "struct")
839
+ return "map";
840
+ }
841
+ return undefined;
842
+ }
843
+ /** The elements of a literal list operand, with nested constructors standing in for their type. */
844
+ function literalElements(operand) {
845
+ if ((0, plan_1.isValueOperand)(operand)) {
846
+ return Array.isArray(operand.value) ? operand.value : undefined;
847
+ }
848
+ if ((0, plan_1.isOperatorOperand)(operand) && operand.operator === "list") {
849
+ const elements = [];
850
+ for (const element of operand.operands) {
851
+ if ((0, plan_1.isValueOperand)(element)) {
852
+ elements.push(element.value);
853
+ continue;
854
+ }
855
+ const type = literalType(element);
856
+ if (type === "list")
857
+ elements.push([]);
858
+ else if (type === "map")
859
+ elements.push({});
860
+ else
861
+ return undefined;
862
+ }
863
+ return elements;
864
+ }
865
+ return undefined;
866
+ }
867
+ function operandType(operand, context) {
868
+ const literal = literalType(operand);
869
+ if (literal !== undefined)
870
+ return { type: literal, mayBeMissing: false };
871
+ if ((0, plan_1.isOperatorOperand)(operand))
872
+ return arithmeticType(operand, context);
873
+ if (!(0, plan_1.isNamedOperand)(operand))
874
+ return undefined;
875
+ const fieldRef = (0, mapping_1.resolveFieldReference)(operand.name, context);
876
+ const type = scalarType(fieldRef);
877
+ if (type === undefined)
878
+ return undefined;
879
+ // A relation-backed reference that is not a to-one scalar is a collection, not a scalar.
880
+ if (fieldRef.relations?.some((relation) => relation.type !== "one"))
881
+ return undefined;
882
+ return { type, mayBeMissing: mayBeMissing(fieldRef) };
883
+ }
884
+ /**
885
+ * The type of `a + b`, `a - b`, `a * b` or `a / b` when both operands are known numbers, or of
886
+ * `a + b` when both are strings: the result's type, missing when either operand is. Any other
887
+ * pairing is left unknown, as a no-overload error is only certain of the typed ones.
888
+ */
889
+ function arithmeticType(expr, context) {
890
+ if (!evaluate_1.ARITHMETIC_OPERATORS.has(expr.operator) || expr.operands.length !== 2)
891
+ return undefined;
892
+ const [left, right] = expr.operands.map((operand) => operandType(operand, context));
893
+ if (left === undefined || right === undefined || left.type !== right.type)
894
+ return undefined;
895
+ if (left.type !== "number" && (left.type !== "string" || expr.operator !== "add")) {
896
+ return undefined;
897
+ }
898
+ return { type: left.type, mayBeMissing: left.mayBeMissing || right.mayBeMissing };
899
+ }
900
+ function scalarFieldType(operand, context) {
901
+ return operandType(operand, context)?.type;
902
+ }
903
+ /** Date-time columns are RFC 3339 strings in CEL but not in the store, so they stay unknown. */
904
+ function scalarType(fieldRef) {
905
+ switch (fieldRef.valueType) {
906
+ case "string":
907
+ case "number":
908
+ case "boolean":
909
+ return fieldRef.valueType;
910
+ default:
911
+ return undefined;
912
+ }
913
+ }
914
+ /**
915
+ * Whether evaluating the reference can raise a missing-attribute error: a to-one hop that may be
916
+ * absent, or a column that may be NULL. A column sent as an explicit null holds a null VALUE,
917
+ * which heterogeneous equality compares definitely. Only `nullable: false` says a column is never
918
+ * NULL: an undeclared mapping is not evidence of NOT NULL, and settling a comparison as if it
919
+ * were would admit the NULL rows under a negation.
920
+ */
921
+ function mayBeMissing(fieldRef) {
922
+ if (fieldRef.relations?.some((relation) => relation.type === "one"))
923
+ return true;
924
+ if (fieldRef.nullAttributeRepresentation === "explicit")
925
+ return false;
926
+ return fieldRef.nullable !== false;
927
+ }
928
+ /**
929
+ * The element type of a mapped list: a relation-backed list reference, or `map()` projecting one
930
+ * element column. Undefined when the mapping does not declare it.
931
+ */
932
+ function collectionElementType(operand, context) {
933
+ if ((0, plan_1.isNamedOperand)(operand)) {
934
+ const fieldRef = (0, mapping_1.resolveFieldReference)(operand.name, context);
935
+ const relations = fieldRef.relations;
936
+ if (!relations || relations.length === 0)
937
+ return undefined;
938
+ const last = relations[relations.length - 1];
939
+ if (last.type !== "many" || last.field === undefined)
940
+ return undefined;
941
+ const declared = (0, mapping_1.lookupMapping)(context.mapper, operand.name)?.valueType ??
942
+ elementFieldValueType(context, operand.name, last.field);
943
+ const type = declared && scalarType({ path: [], valueType: declared });
944
+ if (type === undefined)
945
+ return undefined;
946
+ return { type, mayBeMissing: relations.some((relation) => relation.type === "one") };
947
+ }
948
+ if ((0, plan_1.isOperatorOperand)(operand) && operand.operator === "map") {
949
+ const [collection, lambda] = operand.operands;
950
+ if (collection === undefined ||
951
+ !(0, plan_1.isNamedOperand)(collection) ||
952
+ lambda === undefined ||
953
+ !(0, plan_1.isOperatorOperand)(lambda) ||
954
+ lambda.operator !== "lambda") {
955
+ return undefined;
956
+ }
957
+ const [projection, variable] = lambda.operands;
958
+ if (projection === undefined ||
959
+ !(0, plan_1.isNamedOperand)(projection) ||
960
+ variable === undefined ||
961
+ !(0, plan_1.isNamedOperand)(variable)) {
962
+ return undefined;
963
+ }
964
+ const scoped = scopedContext(context, collection.name, variable.name);
965
+ if (scoped === undefined)
966
+ return undefined;
967
+ const type = scalarType((0, mapping_1.resolveFieldReference)(projection.name, scoped));
968
+ if (type === undefined)
969
+ return undefined;
970
+ // A projected element column can be missing on an element, which errors the whole map().
971
+ return { type, mayBeMissing: true };
972
+ }
973
+ return undefined;
974
+ }
975
+ /** The declared valueType of the projected element column of a relation-backed list. */
976
+ function elementFieldValueType(context, reference, field) {
977
+ const relation = (0, mapping_1.lookupMapping)(context.mapper, reference)?.relation;
978
+ return relation?.fields?.[field]?.valueType;
979
+ }
980
+ //# sourceMappingURL=types.js.map