effect-qb 0.12.3 → 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.
- package/README.md +6 -1283
- package/dist/mysql.js +6376 -4978
- package/dist/postgres/metadata.js +2724 -0
- package/dist/postgres.js +5475 -3636
- package/package.json +13 -8
- package/src/internal/column-state.ts +88 -6
- package/src/internal/column.ts +569 -34
- package/src/internal/datatypes/define.ts +0 -30
- package/src/internal/executor.ts +45 -11
- package/src/internal/expression-ast.ts +15 -0
- package/src/internal/expression.ts +3 -1
- package/src/internal/implication-runtime.ts +171 -0
- package/src/internal/mysql-query.ts +7173 -0
- package/src/internal/mysql-renderer.ts +2 -2
- package/src/internal/plan.ts +14 -4
- package/src/internal/{query-factory.ts → postgres-query.ts} +669 -230
- package/src/internal/postgres-renderer.ts +2 -2
- package/src/internal/postgres-schema-model.ts +144 -0
- package/src/internal/predicate-analysis.ts +10 -0
- package/src/internal/predicate-context.ts +112 -36
- package/src/internal/predicate-formula.ts +31 -19
- package/src/internal/predicate-normalize.ts +177 -106
- package/src/internal/predicate-runtime.ts +676 -0
- package/src/internal/query.ts +471 -41
- package/src/internal/renderer.ts +2 -2
- package/src/internal/runtime-schema.ts +74 -20
- package/src/internal/schema-ddl.ts +55 -0
- package/src/internal/schema-derivation.ts +93 -21
- package/src/internal/schema-expression.ts +44 -0
- package/src/internal/sql-expression-renderer.ts +123 -35
- package/src/internal/table-options.ts +88 -7
- package/src/internal/table.ts +106 -42
- package/src/mysql/column.ts +3 -1
- package/src/mysql/datatypes/index.ts +17 -2
- package/src/mysql/executor.ts +20 -17
- package/src/mysql/function/aggregate.ts +6 -0
- package/src/mysql/function/core.ts +5 -0
- package/src/mysql/function/index.ts +20 -0
- package/src/mysql/function/json.ts +4 -0
- package/src/mysql/function/string.ts +6 -0
- package/src/mysql/function/temporal.ts +103 -0
- package/src/mysql/function/window.ts +7 -0
- package/src/mysql/private/query.ts +1 -0
- package/src/mysql/query.ts +6 -26
- package/src/mysql.ts +2 -0
- package/src/postgres/cast.ts +31 -0
- package/src/postgres/column.ts +27 -1
- package/src/postgres/datatypes/index.ts +40 -5
- package/src/postgres/executor.ts +19 -17
- package/src/postgres/function/aggregate.ts +6 -0
- package/src/postgres/function/core.ts +16 -0
- package/src/postgres/function/index.ts +20 -0
- package/src/postgres/function/json.ts +501 -0
- package/src/postgres/function/string.ts +6 -0
- package/src/postgres/function/temporal.ts +107 -0
- package/src/postgres/function/window.ts +7 -0
- package/src/postgres/metadata.ts +31 -0
- package/src/postgres/private/query.ts +1 -0
- package/src/postgres/query.ts +6 -28
- package/src/postgres/schema-expression.ts +16 -0
- package/src/postgres/schema-management.ts +204 -0
- package/src/postgres/schema.ts +35 -0
- package/src/postgres/table.ts +307 -41
- package/src/postgres/type.ts +4 -0
- package/src/postgres.ts +16 -0
|
@@ -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
|