effect-qb 0.13.0 → 0.14.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 (54) hide show
  1. package/README.md +6 -1431
  2. package/dist/mysql.js +1678 -355
  3. package/dist/postgres/metadata.js +2724 -0
  4. package/dist/postgres.js +7197 -5433
  5. package/package.json +8 -10
  6. package/src/internal/column-state.ts +84 -10
  7. package/src/internal/column.ts +556 -34
  8. package/src/internal/datatypes/define.ts +0 -30
  9. package/src/internal/executor.ts +45 -11
  10. package/src/internal/expression-ast.ts +4 -0
  11. package/src/internal/expression.ts +1 -1
  12. package/src/internal/implication-runtime.ts +171 -0
  13. package/src/internal/mysql-query.ts +7173 -0
  14. package/src/internal/mysql-renderer.ts +2 -2
  15. package/src/internal/plan.ts +14 -4
  16. package/src/internal/{query-factory.ts → postgres-query.ts} +619 -167
  17. package/src/internal/postgres-renderer.ts +2 -2
  18. package/src/internal/postgres-schema-model.ts +144 -0
  19. package/src/internal/predicate-analysis.ts +10 -0
  20. package/src/internal/predicate-context.ts +112 -36
  21. package/src/internal/predicate-formula.ts +31 -19
  22. package/src/internal/predicate-normalize.ts +177 -106
  23. package/src/internal/predicate-runtime.ts +676 -0
  24. package/src/internal/query.ts +455 -39
  25. package/src/internal/renderer.ts +2 -2
  26. package/src/internal/runtime-schema.ts +74 -20
  27. package/src/internal/schema-ddl.ts +55 -0
  28. package/src/internal/schema-derivation.ts +93 -21
  29. package/src/internal/schema-expression.ts +44 -0
  30. package/src/internal/sql-expression-renderer.ts +95 -31
  31. package/src/internal/table-options.ts +87 -7
  32. package/src/internal/table.ts +104 -41
  33. package/src/mysql/column.ts +1 -0
  34. package/src/mysql/datatypes/index.ts +17 -2
  35. package/src/mysql/function/core.ts +1 -0
  36. package/src/mysql/function/index.ts +1 -0
  37. package/src/mysql/private/query.ts +1 -13
  38. package/src/mysql/query.ts +5 -0
  39. package/src/postgres/cast.ts +31 -0
  40. package/src/postgres/column.ts +26 -0
  41. package/src/postgres/datatypes/index.ts +40 -5
  42. package/src/postgres/function/core.ts +12 -0
  43. package/src/postgres/function/index.ts +2 -1
  44. package/src/postgres/function/json.ts +499 -2
  45. package/src/postgres/metadata.ts +31 -0
  46. package/src/postgres/private/query.ts +1 -13
  47. package/src/postgres/query.ts +5 -2
  48. package/src/postgres/schema-expression.ts +16 -0
  49. package/src/postgres/schema-management.ts +204 -0
  50. package/src/postgres/schema.ts +35 -0
  51. package/src/postgres/table.ts +307 -41
  52. package/src/postgres/type.ts +4 -0
  53. package/src/postgres.ts +14 -0
  54. package/CHANGELOG.md +0 -134
@@ -0,0 +1,676 @@
1
+ import * as Expression from "./expression.js"
2
+ import * as ExpressionAst from "./expression-ast.js"
3
+ import type { PredicateAtom } from "./predicate-atom.js"
4
+ import type {
5
+ EqColumnAtom,
6
+ EqLiteralAtom,
7
+ NeqLiteralAtom,
8
+ NonNullAtom,
9
+ NullAtom,
10
+ UnknownAtom
11
+ } from "./predicate-atom.js"
12
+ import type {
13
+ AllFormula,
14
+ AnyFormula,
15
+ AtomFormula,
16
+ FalseFormula,
17
+ NotFormula,
18
+ PredicateFormula,
19
+ TrueFormula
20
+ } from "./predicate-formula.js"
21
+
22
+ export interface RuntimeContext {
23
+ readonly nonNullKeys: ReadonlySet<string>
24
+ readonly nullKeys: ReadonlySet<string>
25
+ readonly eqLiterals: ReadonlyMap<string, string>
26
+ readonly neqLiterals: ReadonlyMap<string, ReadonlySet<string>>
27
+ readonly sourceNames: ReadonlySet<string>
28
+ readonly contradiction: boolean
29
+ readonly unknown: boolean
30
+ }
31
+
32
+ type MutableContext = {
33
+ nonNullKeys: Set<string>
34
+ nullKeys: Set<string>
35
+ eqLiterals: Map<string, string>
36
+ neqLiterals: Map<string, Set<string>>
37
+ sourceNames: Set<string>
38
+ contradiction: boolean
39
+ unknown: boolean
40
+ }
41
+
42
+ type Frame = {
43
+ readonly formula: PredicateFormula
44
+ readonly polarity: "positive" | "negative"
45
+ }
46
+
47
+ type AstBackedExpression = Expression.Any & {
48
+ readonly [ExpressionAst.TypeId]: ExpressionAst.Any
49
+ }
50
+
51
+ export const trueFormula = (): TrueFormula => ({ kind: "true" })
52
+ export const falseFormula = (): FalseFormula => ({ kind: "false" })
53
+ export const atomFormula = <Atom extends PredicateAtom>(atom: Atom): AtomFormula<Atom> => ({ kind: "atom", atom })
54
+ export const allFormula = (items: readonly PredicateFormula[]): PredicateFormula =>
55
+ normalizeFormula({ kind: "all", items } satisfies AllFormula<readonly PredicateFormula[]>)
56
+ export const anyFormula = (items: readonly PredicateFormula[]): PredicateFormula =>
57
+ normalizeFormula({ kind: "any", items } satisfies AnyFormula<readonly PredicateFormula[]>)
58
+ export const notFormula = (item: PredicateFormula): PredicateFormula =>
59
+ normalizeFormula({ kind: "not", item } satisfies NotFormula<PredicateFormula>)
60
+
61
+ export const andFormula = (left: PredicateFormula, right: PredicateFormula): PredicateFormula =>
62
+ allFormula([left, right])
63
+
64
+ export const orFormula = (left: PredicateFormula, right: PredicateFormula): PredicateFormula =>
65
+ anyFormula([left, right])
66
+
67
+ const unknownTag = <Tag extends string>(tag: Tag): AtomFormula<UnknownAtom<Tag>> =>
68
+ atomFormula({ kind: "unknown", tag })
69
+
70
+ const emptyContext = (): MutableContext => ({
71
+ nonNullKeys: new Set(),
72
+ nullKeys: new Set(),
73
+ eqLiterals: new Map(),
74
+ neqLiterals: new Map(),
75
+ sourceNames: new Set(),
76
+ contradiction: false,
77
+ unknown: false
78
+ })
79
+
80
+ const cloneContext = (context: MutableContext): MutableContext => ({
81
+ nonNullKeys: new Set(context.nonNullKeys),
82
+ nullKeys: new Set(context.nullKeys),
83
+ eqLiterals: new Map(context.eqLiterals),
84
+ neqLiterals: new Map(
85
+ Array.from(context.neqLiterals.entries(), ([key, values]) => [key, new Set(values)])
86
+ ),
87
+ sourceNames: new Set(context.sourceNames),
88
+ contradiction: context.contradiction,
89
+ unknown: context.unknown
90
+ })
91
+
92
+ const freezeContext = (context: MutableContext): RuntimeContext => context
93
+
94
+ const sourceNameOfKey = (key: string): string => key.split(".", 1)[0] ?? key
95
+
96
+ const addSourceName = (context: MutableContext, key: string): void => {
97
+ context.sourceNames.add(sourceNameOfKey(key))
98
+ }
99
+
100
+ const addNonNull = (context: MutableContext, key: string): void => {
101
+ addSourceName(context, key)
102
+ if (context.nullKeys.has(key)) {
103
+ context.contradiction = true
104
+ }
105
+ context.nonNullKeys.add(key)
106
+ }
107
+
108
+ const addNull = (context: MutableContext, key: string): void => {
109
+ addSourceName(context, key)
110
+ if (context.nonNullKeys.has(key)) {
111
+ context.contradiction = true
112
+ }
113
+ context.nullKeys.add(key)
114
+ }
115
+
116
+ const addEqLiteral = (context: MutableContext, key: string, value: string): void => {
117
+ addNonNull(context, key)
118
+ const existing = context.eqLiterals.get(key)
119
+ if (existing !== undefined && existing !== value) {
120
+ context.contradiction = true
121
+ }
122
+ const neqValues = context.neqLiterals.get(key)
123
+ if (neqValues?.has(value)) {
124
+ context.contradiction = true
125
+ }
126
+ context.eqLiterals.set(key, value)
127
+ }
128
+
129
+ const addNeqLiteral = (context: MutableContext, key: string, value: string): void => {
130
+ addNonNull(context, key)
131
+ if (context.eqLiterals.get(key) === value) {
132
+ context.contradiction = true
133
+ }
134
+ const values = context.neqLiterals.get(key) ?? new Set<string>()
135
+ values.add(value)
136
+ context.neqLiterals.set(key, values)
137
+ }
138
+
139
+ const applyEqColumn = (context: MutableContext, left: string, right: string): void => {
140
+ const leftValue = context.eqLiterals.get(left)
141
+ const rightValue = context.eqLiterals.get(right)
142
+ if (leftValue === undefined && rightValue === undefined) {
143
+ addNonNull(context, left)
144
+ addNonNull(context, right)
145
+ return
146
+ }
147
+ if (leftValue === undefined && rightValue !== undefined) {
148
+ addNonNull(context, left)
149
+ addEqLiteral(context, left, rightValue)
150
+ return
151
+ }
152
+ if (leftValue !== undefined && rightValue === undefined) {
153
+ addNonNull(context, right)
154
+ addEqLiteral(context, right, leftValue)
155
+ return
156
+ }
157
+ if (leftValue === rightValue) {
158
+ addEqLiteral(context, left, leftValue!)
159
+ addEqLiteral(context, right, rightValue!)
160
+ return
161
+ }
162
+ context.contradiction = true
163
+ }
164
+
165
+ const applyAtom = (context: MutableContext, atom: PredicateAtom): void => {
166
+ switch (atom.kind) {
167
+ case "is-null":
168
+ addNull(context, atom.key)
169
+ return
170
+ case "is-not-null":
171
+ addNonNull(context, atom.key)
172
+ return
173
+ case "eq-literal":
174
+ addEqLiteral(context, atom.key, atom.value)
175
+ return
176
+ case "neq-literal":
177
+ addNeqLiteral(context, atom.key, atom.value)
178
+ return
179
+ case "eq-column":
180
+ applyEqColumn(context, atom.left, atom.right)
181
+ return
182
+ case "unknown":
183
+ context.unknown = true
184
+ return
185
+ }
186
+ }
187
+
188
+ const applyNegativeAtom = (context: MutableContext, atom: PredicateAtom): void => {
189
+ switch (atom.kind) {
190
+ case "is-null":
191
+ addNonNull(context, atom.key)
192
+ return
193
+ case "is-not-null":
194
+ addNull(context, atom.key)
195
+ return
196
+ case "eq-literal":
197
+ addNeqLiteral(context, atom.key, atom.value)
198
+ return
199
+ case "neq-literal":
200
+ addEqLiteral(context, atom.key, atom.value)
201
+ return
202
+ case "eq-column":
203
+ addNonNull(context, atom.left)
204
+ addNonNull(context, atom.right)
205
+ return
206
+ case "unknown":
207
+ context.unknown = true
208
+ return
209
+ }
210
+ }
211
+
212
+ const intersectEqLiterals = (
213
+ left: ReadonlyMap<string, string>,
214
+ right: ReadonlyMap<string, string>
215
+ ): Map<string, string> => {
216
+ const result = new Map<string, string>()
217
+ for (const [key, value] of left) {
218
+ if (right.get(key) === value) {
219
+ result.set(key, value)
220
+ }
221
+ }
222
+ return result
223
+ }
224
+
225
+ const intersectNeqLiterals = (
226
+ left: ReadonlyMap<string, ReadonlySet<string>>,
227
+ right: ReadonlyMap<string, ReadonlySet<string>>
228
+ ): Map<string, Set<string>> => {
229
+ const result = new Map<string, Set<string>>()
230
+ for (const [key, leftValues] of left) {
231
+ const rightValues = right.get(key)
232
+ if (rightValues === undefined) {
233
+ continue
234
+ }
235
+ const next = new Set(Array.from(leftValues).filter((value) => rightValues.has(value)))
236
+ if (next.size > 0) {
237
+ result.set(key, next)
238
+ }
239
+ }
240
+ return result
241
+ }
242
+
243
+ const intersectContexts = (left: MutableContext, right: MutableContext): MutableContext => {
244
+ if (left.contradiction) {
245
+ return cloneContext(right)
246
+ }
247
+ if (right.contradiction) {
248
+ return cloneContext(left)
249
+ }
250
+ return {
251
+ nonNullKeys: new Set(Array.from(left.nonNullKeys).filter((key) => right.nonNullKeys.has(key))),
252
+ nullKeys: new Set(Array.from(left.nullKeys).filter((key) => right.nullKeys.has(key))),
253
+ eqLiterals: intersectEqLiterals(left.eqLiterals, right.eqLiterals),
254
+ neqLiterals: intersectNeqLiterals(left.neqLiterals, right.neqLiterals),
255
+ sourceNames: new Set(Array.from(left.sourceNames).filter((name) => right.sourceNames.has(name))),
256
+ contradiction: false,
257
+ unknown: left.unknown || right.unknown
258
+ }
259
+ }
260
+
261
+ const analyzeBranchSet = (
262
+ context: MutableContext,
263
+ items: readonly PredicateFormula[],
264
+ polarity: "positive" | "negative"
265
+ ): MutableContext => {
266
+ let current: MutableContext | undefined
267
+ for (const item of items) {
268
+ const branch = analyzeStack(cloneContext(context), [{ formula: item, polarity }])
269
+ if (branch.contradiction) {
270
+ continue
271
+ }
272
+ current = current === undefined ? branch : intersectContexts(current, branch)
273
+ }
274
+ if (current === undefined) {
275
+ const next = cloneContext(context)
276
+ next.contradiction = true
277
+ return next
278
+ }
279
+ return current
280
+ }
281
+
282
+ const analyzeStack = (context: MutableContext, stack: readonly Frame[]): MutableContext => {
283
+ const queue = [...stack]
284
+ while (queue.length > 0 && !context.contradiction) {
285
+ const frame = queue.shift()!
286
+ switch (frame.formula.kind) {
287
+ case "true":
288
+ if (frame.polarity === "negative") {
289
+ context.contradiction = true
290
+ }
291
+ break
292
+ case "false":
293
+ if (frame.polarity === "positive") {
294
+ context.contradiction = true
295
+ }
296
+ break
297
+ case "atom":
298
+ if (frame.polarity === "positive") {
299
+ applyAtom(context, frame.formula.atom)
300
+ } else {
301
+ applyNegativeAtom(context, frame.formula.atom)
302
+ }
303
+ break
304
+ case "not":
305
+ queue.unshift({
306
+ formula: frame.formula.item,
307
+ polarity: frame.polarity === "positive" ? "negative" : "positive"
308
+ })
309
+ break
310
+ case "all":
311
+ if (frame.polarity === "positive") {
312
+ queue.unshift(...frame.formula.items.map((formula) => ({ formula, polarity: "positive" as const })))
313
+ } else {
314
+ context = analyzeBranchSet(context, frame.formula.items, "negative")
315
+ }
316
+ break
317
+ case "any":
318
+ if (frame.polarity === "positive") {
319
+ context = analyzeBranchSet(context, frame.formula.items, "positive")
320
+ } else {
321
+ queue.unshift(...frame.formula.items.map((formula) => ({ formula, polarity: "negative" as const })))
322
+ }
323
+ break
324
+ }
325
+ }
326
+ return context
327
+ }
328
+
329
+ export const analyzeFormula = (formula: PredicateFormula): RuntimeContext =>
330
+ freezeContext(analyzeStack(emptyContext(), [{ formula, polarity: "positive" }]))
331
+
332
+ const astOf = (value: Expression.Any): ExpressionAst.Any =>
333
+ (value as AstBackedExpression)[ExpressionAst.TypeId]
334
+
335
+ const columnKeyOfExpression = (value: Expression.Any): string | undefined => {
336
+ const ast = astOf(value)
337
+ return ast.kind === "column" ? `${ast.tableName}.${ast.columnName}` : undefined
338
+ }
339
+
340
+ const valueKeyOfLiteral = (value: unknown): string => {
341
+ if (typeof value === "string") {
342
+ return `string:${value}`
343
+ }
344
+ if (typeof value === "number") {
345
+ return `number:${value}`
346
+ }
347
+ if (typeof value === "boolean") {
348
+ return `boolean:${value}`
349
+ }
350
+ if (value === null) {
351
+ return "null"
352
+ }
353
+ if (value instanceof Date) {
354
+ return `date:${value.toISOString()}`
355
+ }
356
+ return "unknown"
357
+ }
358
+
359
+ const nonNullFactsOfExpression = (value: Expression.Any): PredicateFormula | undefined => {
360
+ const key = columnKeyOfExpression(value)
361
+ return key === undefined ? undefined : atomFormula<NonNullAtom<string>>({ kind: "is-not-null", key })
362
+ }
363
+
364
+ const combineFacts = (
365
+ left: PredicateFormula | undefined,
366
+ right: PredicateFormula | undefined
367
+ ): PredicateFormula => {
368
+ if (left === undefined) {
369
+ return right ?? trueFormula()
370
+ }
371
+ if (right === undefined) {
372
+ return left
373
+ }
374
+ return andFormula(left, right)
375
+ }
376
+
377
+ const formulaOfEq = (left: Expression.Any, right: Expression.Any): PredicateFormula => {
378
+ const leftKey = columnKeyOfExpression(left)
379
+ const rightKey = columnKeyOfExpression(right)
380
+ const leftAst = astOf(left)
381
+ const rightAst = astOf(right)
382
+ const leftLiteral = leftAst.kind === "literal" ? leftAst.value : undefined
383
+ const rightLiteral = rightAst.kind === "literal" ? rightAst.value : undefined
384
+
385
+ if (leftKey === undefined && rightKey === undefined) {
386
+ if (leftAst.kind !== "literal" || rightAst.kind !== "literal") {
387
+ return unknownTag("eq:unsupported")
388
+ }
389
+ if (leftLiteral === null || rightLiteral === null) {
390
+ return falseFormula()
391
+ }
392
+ return Object.is(leftLiteral, rightLiteral) ? trueFormula() : falseFormula()
393
+ }
394
+
395
+ if (leftKey === undefined) {
396
+ if (leftAst.kind !== "literal") {
397
+ return unknownTag("eq:unsupported")
398
+ }
399
+ if (leftLiteral === null) {
400
+ return falseFormula()
401
+ }
402
+ return atomFormula<EqLiteralAtom<string, string>>({
403
+ kind: "eq-literal",
404
+ key: rightKey!,
405
+ value: valueKeyOfLiteral(leftLiteral)
406
+ })
407
+ }
408
+
409
+ if (rightKey === undefined) {
410
+ if (rightAst.kind !== "literal") {
411
+ return unknownTag("eq:unsupported")
412
+ }
413
+ if (rightLiteral === null) {
414
+ return falseFormula()
415
+ }
416
+ return atomFormula<EqLiteralAtom<string, string>>({
417
+ kind: "eq-literal",
418
+ key: leftKey,
419
+ value: valueKeyOfLiteral(rightLiteral)
420
+ })
421
+ }
422
+
423
+ return atomFormula<EqColumnAtom<string, string>>({
424
+ kind: "eq-column",
425
+ left: leftKey,
426
+ right: rightKey
427
+ })
428
+ }
429
+
430
+ const formulaOfNeq = (left: Expression.Any, right: Expression.Any): PredicateFormula => {
431
+ const leftKey = columnKeyOfExpression(left)
432
+ const rightKey = columnKeyOfExpression(right)
433
+ const leftAst = astOf(left)
434
+ const rightAst = astOf(right)
435
+ const leftLiteral = leftAst.kind === "literal" ? leftAst.value : undefined
436
+ const rightLiteral = rightAst.kind === "literal" ? rightAst.value : undefined
437
+
438
+ if (leftKey === undefined && rightKey === undefined) {
439
+ if (leftAst.kind !== "literal" || rightAst.kind !== "literal") {
440
+ return unknownTag("neq:unsupported")
441
+ }
442
+ if (leftLiteral === null || rightLiteral === null) {
443
+ return falseFormula()
444
+ }
445
+ return Object.is(leftLiteral, rightLiteral) ? falseFormula() : trueFormula()
446
+ }
447
+
448
+ if (leftKey === undefined) {
449
+ if (leftAst.kind !== "literal") {
450
+ return unknownTag("neq:unsupported")
451
+ }
452
+ if (leftLiteral === null) {
453
+ return falseFormula()
454
+ }
455
+ return atomFormula<NeqLiteralAtom<string, string>>({
456
+ kind: "neq-literal",
457
+ key: rightKey!,
458
+ value: valueKeyOfLiteral(leftLiteral)
459
+ })
460
+ }
461
+
462
+ if (rightKey === undefined) {
463
+ if (rightAst.kind !== "literal") {
464
+ return unknownTag("neq:unsupported")
465
+ }
466
+ if (rightLiteral === null) {
467
+ return falseFormula()
468
+ }
469
+ return atomFormula<NeqLiteralAtom<string, string>>({
470
+ kind: "neq-literal",
471
+ key: leftKey,
472
+ value: valueKeyOfLiteral(rightLiteral)
473
+ })
474
+ }
475
+
476
+ return combineFacts(nonNullFactsOfExpression(left), nonNullFactsOfExpression(right))
477
+ }
478
+
479
+ const formulaOfIsNotDistinctFrom = (left: Expression.Any, right: Expression.Any): PredicateFormula => {
480
+ const leftKey = columnKeyOfExpression(left)
481
+ const rightKey = columnKeyOfExpression(right)
482
+ const leftAst = astOf(left)
483
+ const rightAst = astOf(right)
484
+ const leftLiteral = leftAst.kind === "literal" ? leftAst.value : undefined
485
+ const rightLiteral = rightAst.kind === "literal" ? rightAst.value : undefined
486
+
487
+ if (leftAst.kind === "literal" && rightAst.kind === "literal") {
488
+ return Object.is(leftLiteral, rightLiteral) ? trueFormula() : falseFormula()
489
+ }
490
+ if (leftAst.kind === "literal" && leftLiteral === null && rightKey !== undefined) {
491
+ return atomFormula<NullAtom<string>>({ kind: "is-null", key: rightKey })
492
+ }
493
+ if (rightAst.kind === "literal" && rightLiteral === null && leftKey !== undefined) {
494
+ return atomFormula<NullAtom<string>>({ kind: "is-null", key: leftKey })
495
+ }
496
+ if (leftAst.kind === "literal" && rightKey !== undefined) {
497
+ return atomFormula<EqLiteralAtom<string, string>>({
498
+ kind: "eq-literal",
499
+ key: rightKey,
500
+ value: valueKeyOfLiteral(leftLiteral)
501
+ })
502
+ }
503
+ if (rightAst.kind === "literal" && leftKey !== undefined) {
504
+ return atomFormula<EqLiteralAtom<string, string>>({
505
+ kind: "eq-literal",
506
+ key: leftKey,
507
+ value: valueKeyOfLiteral(rightLiteral)
508
+ })
509
+ }
510
+ return unknownTag("isNotDistinctFrom:unsupported")
511
+ }
512
+
513
+ export const normalizeFormula = (formula: PredicateFormula): PredicateFormula => {
514
+ switch (formula.kind) {
515
+ case "all": {
516
+ const items: PredicateFormula[] = []
517
+ for (const item of formula.items) {
518
+ const normalized = normalizeFormula(item)
519
+ if (normalized.kind === "true") {
520
+ continue
521
+ }
522
+ if (normalized.kind === "false") {
523
+ return falseFormula()
524
+ }
525
+ if (normalized.kind === "all") {
526
+ items.push(...normalized.items)
527
+ } else {
528
+ items.push(normalized)
529
+ }
530
+ }
531
+ if (items.length === 0) {
532
+ return trueFormula()
533
+ }
534
+ if (items.length === 1) {
535
+ return items[0]!
536
+ }
537
+ return { kind: "all", items }
538
+ }
539
+ case "any": {
540
+ const items: PredicateFormula[] = []
541
+ for (const item of formula.items) {
542
+ const normalized = normalizeFormula(item)
543
+ if (normalized.kind === "false") {
544
+ continue
545
+ }
546
+ if (normalized.kind === "true") {
547
+ return trueFormula()
548
+ }
549
+ if (normalized.kind === "any") {
550
+ items.push(...normalized.items)
551
+ } else {
552
+ items.push(normalized)
553
+ }
554
+ }
555
+ if (items.length === 0) {
556
+ return falseFormula()
557
+ }
558
+ if (items.length === 1) {
559
+ return items[0]!
560
+ }
561
+ return { kind: "any", items }
562
+ }
563
+ case "not": {
564
+ const item = normalizeFormula(formula.item)
565
+ if (item.kind === "true") {
566
+ return falseFormula()
567
+ }
568
+ if (item.kind === "false") {
569
+ return trueFormula()
570
+ }
571
+ return { kind: "not", item }
572
+ }
573
+ default:
574
+ return formula
575
+ }
576
+ }
577
+
578
+ export const formulaOfExpression = (value: Expression.Any): PredicateFormula => {
579
+ const ast = astOf(value)
580
+ switch (ast.kind) {
581
+ case "literal":
582
+ if (ast.value === true) {
583
+ return trueFormula()
584
+ }
585
+ if (ast.value === false) {
586
+ return falseFormula()
587
+ }
588
+ return unknownTag("literal:non-boolean")
589
+ case "isNull": {
590
+ const key = columnKeyOfExpression(ast.value)
591
+ return key === undefined
592
+ ? unknownTag("isNull:unsupported")
593
+ : atomFormula<NullAtom<string>>({ kind: "is-null", key })
594
+ }
595
+ case "isNotNull": {
596
+ const key = columnKeyOfExpression(ast.value)
597
+ return key === undefined
598
+ ? unknownTag("isNotNull:unsupported")
599
+ : atomFormula<NonNullAtom<string>>({ kind: "is-not-null", key })
600
+ }
601
+ case "not":
602
+ return notFormula(formulaOfExpression(ast.value))
603
+ case "eq":
604
+ return formulaOfEq(ast.left, ast.right)
605
+ case "neq":
606
+ return formulaOfNeq(ast.left, ast.right)
607
+ case "isNotDistinctFrom":
608
+ return formulaOfIsNotDistinctFrom(ast.left, ast.right)
609
+ case "isDistinctFrom":
610
+ return notFormula(formulaOfIsNotDistinctFrom(ast.left, ast.right))
611
+ case "and":
612
+ return allFormula(ast.values.map((value: Expression.Any) => formulaOfExpression(value)))
613
+ case "or":
614
+ return anyFormula(ast.values.map((value: Expression.Any) => formulaOfExpression(value)))
615
+ case "in": {
616
+ const [left, ...rest] = ast.values
617
+ return left === undefined
618
+ ? falseFormula()
619
+ : anyFormula(rest.map((value: Expression.Any) => formulaOfEq(left, value)))
620
+ }
621
+ case "notIn": {
622
+ const [left, ...rest] = ast.values
623
+ return left === undefined
624
+ ? trueFormula()
625
+ : allFormula(rest.map((value: Expression.Any) => formulaOfNeq(left, value)))
626
+ }
627
+ case "between":
628
+ return combineFacts(
629
+ ast.values.reduce<PredicateFormula | undefined>(
630
+ (current: PredicateFormula | undefined, entry: Expression.Any) => combineFacts(current, nonNullFactsOfExpression(entry)),
631
+ undefined
632
+ ),
633
+ unknownTag("variadic:between")
634
+ )
635
+ case "lt":
636
+ case "lte":
637
+ case "gt":
638
+ case "gte":
639
+ case "like":
640
+ case "ilike":
641
+ case "contains":
642
+ case "containedBy":
643
+ case "overlaps":
644
+ return combineFacts(nonNullFactsOfExpression(ast.left), nonNullFactsOfExpression(ast.right))
645
+ default:
646
+ return unknownTag(`expr:${ast.kind}`)
647
+ }
648
+ }
649
+
650
+ export const formulaOfPredicate = (value: Expression.Any | boolean): PredicateFormula =>
651
+ value === true
652
+ ? trueFormula()
653
+ : value === false
654
+ ? falseFormula()
655
+ : formulaOfExpression(value)
656
+
657
+ export const assumeFormulaTrue = (assumptions: PredicateFormula, formula: PredicateFormula): PredicateFormula =>
658
+ assumptions.kind === "true" ? formula : andFormula(assumptions, formula)
659
+
660
+ export const assumeFormulaFalse = (assumptions: PredicateFormula, formula: PredicateFormula): PredicateFormula =>
661
+ assumptions.kind === "true" ? notFormula(formula) : andFormula(assumptions, notFormula(formula))
662
+
663
+ export const contradictsFormula = (assumptions: PredicateFormula, formula: PredicateFormula): boolean =>
664
+ analyzeFormula(andFormula(assumptions, formula)).contradiction
665
+
666
+ export const impliesFormula = (assumptions: PredicateFormula, formula: PredicateFormula): boolean =>
667
+ analyzeFormula(andFormula(assumptions, notFormula(formula))).contradiction
668
+
669
+ export const guaranteedNonNullKeys = (assumptions: PredicateFormula): ReadonlySet<string> =>
670
+ analyzeFormula(assumptions).nonNullKeys
671
+
672
+ export const guaranteedNullKeys = (assumptions: PredicateFormula): ReadonlySet<string> =>
673
+ analyzeFormula(assumptions).nullKeys
674
+
675
+ export const guaranteedSourceNames = (assumptions: PredicateFormula): ReadonlySet<string> =>
676
+ analyzeFormula(assumptions).sourceNames