effect-qb 0.15.0 → 0.16.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.
@@ -1,5 +1,6 @@
1
1
  import type * as Expression from "../scalar.js"
2
2
  import type * as ExpressionAst from "../expression-ast.js"
3
+ import type * as JsonPath from "../json/path.js"
3
4
 
4
5
  export type ColumnKey<
5
6
  TableName extends string,
@@ -7,15 +8,59 @@ export type ColumnKey<
7
8
  > = `${TableName}.${ColumnName}`
8
9
 
9
10
  export type ColumnKeyOfAst<Ast extends ExpressionAst.Any> =
10
- Ast extends ExpressionAst.ColumnNode<infer TableName extends string, infer ColumnName extends string>
11
+ [Ast] extends [ExpressionAst.ColumnNode<infer TableName extends string, infer ColumnName extends string>]
11
12
  ? ColumnKey<TableName, ColumnName>
12
13
  : never
13
14
 
15
+ type JsonPathKey<
16
+ Segments extends ExpressionAst.JsonSegmentTuple,
17
+ Current extends string = never,
18
+ Seen extends readonly unknown[] = []
19
+ > = Seen["length"] extends 8
20
+ ? never
21
+ : Segments extends readonly [
22
+ infer Segment,
23
+ ...infer Tail extends ExpressionAst.JsonSegmentTuple
24
+ ]
25
+ ? Segment extends JsonPath.KeySegment<infer Key extends string>
26
+ ? JsonPathKey<
27
+ Tail,
28
+ [Current] extends [never] ? Key : `${Current}.${Key}`,
29
+ readonly [...Seen, unknown]
30
+ >
31
+ : never
32
+ : Current
33
+
34
+ export type JsonPathPredicateKey<
35
+ Base extends Expression.Any,
36
+ Segments extends ExpressionAst.JsonSegmentTuple
37
+ > = [ColumnKeyOfExpression<Base>] extends [never]
38
+ ? never
39
+ : [JsonPathKey<Segments>] extends [never]
40
+ ? never
41
+ : `${ColumnKeyOfExpression<Base>}#json:${JsonPathKey<Segments>}`
42
+
43
+ export type PredicateKeyOfAst<Ast extends ExpressionAst.Any> =
44
+ [Ast] extends [ExpressionAst.ColumnNode<infer TableName extends string, infer ColumnName extends string>]
45
+ ? ColumnKey<TableName, ColumnName>
46
+ : [Ast] extends [ExpressionAst.CastNode<infer Value extends Expression.Any, infer Target extends Expression.DbType.Any>]
47
+ ? [Target] extends [Expression.DbTypeOf<Value>]
48
+ ? [Expression.DbTypeOf<Value>] extends [Target]
49
+ ? PredicateKeyOfExpression<Value>
50
+ : never
51
+ : never
52
+ : [Ast] extends [ExpressionAst.JsonAccessNode<infer Kind, infer Base extends Expression.Any, infer Segments extends ExpressionAst.JsonSegmentTuple>]
53
+ ? Kind extends "jsonGetText" | "jsonPathText" | "jsonAccessText" | "jsonTraverseText"
54
+ ? JsonPathPredicateKey<Base, Segments>
55
+ : never
56
+ : never
57
+
14
58
  type AstOf<Value extends Expression.Any> = Value extends {
15
59
  readonly [ExpressionAst.TypeId]: infer Ast extends ExpressionAst.Any
16
60
  } ? Ast : never
17
61
 
18
62
  export type ColumnKeyOfExpression<Value extends Expression.Any> = ColumnKeyOfAst<AstOf<Value>>
63
+ export type PredicateKeyOfExpression<Value extends Expression.Any> = PredicateKeyOfAst<AstOf<Value>>
19
64
 
20
65
  export type LiteralKey<Value> =
21
66
  Value extends string ? `string:${Value}` :
@@ -1,8 +1,8 @@
1
1
  import type * as Expression from "../scalar.js"
2
2
  import type * as ExpressionAst from "../expression-ast.js"
3
- import type { ColumnKeyOfExpression, ValueKey } from "./key.js"
3
+ import type { PredicateKeyOfExpression, ValueKey } from "./key.js"
4
4
  import type { AllFormula, AnyFormula, AtomFormula, FalseFormula, NotFormula, PredicateFormula, TrueFormula } from "./formula.js"
5
- import type { EqColumnAtom, EqLiteralAtom, NeqLiteralAtom, NonNullAtom, NullAtom, UnknownAtom } from "./atom.js"
5
+ import type { EqColumnAtom, EqLiteralAtom, LiteralSetAtom, NeqLiteralAtom, NonNullAtom, NullAtom, UnknownAtom } from "./atom.js"
6
6
 
7
7
  type AstOf<Value extends Expression.Any> = Value extends {
8
8
  readonly [ExpressionAst.TypeId]: infer Ast extends ExpressionAst.Any
@@ -20,9 +20,9 @@ type AtomOf<Atom extends import("./atom.js").PredicateAtom> = AtomFormula<Atom>
20
20
  type FactOf<Atom extends import("./atom.js").PredicateAtom> = AtomFormula<Atom>
21
21
 
22
22
  type NonNullFactsOfExpression<Value extends Expression.Any> =
23
- [ColumnKeyOfExpression<Value>] extends [never]
23
+ [PredicateKeyOfExpression<Value>] extends [never]
24
24
  ? never
25
- : FactOf<NonNullAtom<ColumnKeyOfExpression<Value>>>
25
+ : FactOf<NonNullAtom<PredicateKeyOfExpression<Value>>>
26
26
 
27
27
  type CombineFacts<
28
28
  Left extends PredicateFormula,
@@ -45,8 +45,8 @@ type FormulaOfEq<
45
45
  Left extends Expression.Any,
46
46
  Right extends Expression.Any
47
47
  > =
48
- [ColumnKeyOfExpression<Left>] extends [never]
49
- ? [ColumnKeyOfExpression<Right>] extends [never]
48
+ [PredicateKeyOfExpression<Left>] extends [never]
49
+ ? [PredicateKeyOfExpression<Right>] extends [never]
50
50
  ? LiteralValueOfExpression<Left> extends infer LeftLiteral
51
51
  ? LiteralValueOfExpression<Right> extends infer RightLiteral
52
52
  ? [LeftLiteral] extends [never]
@@ -67,24 +67,24 @@ type FormulaOfEq<
67
67
  ? UnknownTag<"eq:unsupported">
68
68
  : LeftLiteral extends null
69
69
  ? False
70
- : AtomOf<EqLiteralAtom<ColumnKeyOfExpression<Right>, ValueKey<LeftLiteral>>>
70
+ : AtomOf<EqLiteralAtom<PredicateKeyOfExpression<Right>, ValueKey<LeftLiteral>>>
71
71
  : UnknownTag<"eq:unsupported">
72
- : [ColumnKeyOfExpression<Right>] extends [never]
72
+ : [PredicateKeyOfExpression<Right>] extends [never]
73
73
  ? LiteralValueOfExpression<Right> extends infer RightLiteral
74
74
  ? [RightLiteral] extends [never]
75
75
  ? UnknownTag<"eq:unsupported">
76
76
  : RightLiteral extends null
77
77
  ? False
78
- : AtomOf<EqLiteralAtom<ColumnKeyOfExpression<Left>, ValueKey<RightLiteral>>>
78
+ : AtomOf<EqLiteralAtom<PredicateKeyOfExpression<Left>, ValueKey<RightLiteral>>>
79
79
  : UnknownTag<"eq:unsupported">
80
- : AtomOf<import("./atom.js").EqColumnAtom<ColumnKeyOfExpression<Left>, ColumnKeyOfExpression<Right>>>
80
+ : AtomOf<import("./atom.js").EqColumnAtom<PredicateKeyOfExpression<Left>, PredicateKeyOfExpression<Right>>>
81
81
 
82
82
  type FormulaOfNeq<
83
83
  Left extends Expression.Any,
84
84
  Right extends Expression.Any
85
85
  > =
86
- [ColumnKeyOfExpression<Left>] extends [never]
87
- ? [ColumnKeyOfExpression<Right>] extends [never]
86
+ [PredicateKeyOfExpression<Left>] extends [never]
87
+ ? [PredicateKeyOfExpression<Right>] extends [never]
88
88
  ? LiteralValueOfExpression<Left> extends infer LeftLiteral
89
89
  ? LiteralValueOfExpression<Right> extends infer RightLiteral
90
90
  ? [LeftLiteral] extends [never]
@@ -105,15 +105,15 @@ type FormulaOfNeq<
105
105
  ? UnknownTag<"neq:unsupported">
106
106
  : LeftLiteral extends null
107
107
  ? False
108
- : AtomOf<NeqLiteralAtom<ColumnKeyOfExpression<Right>, ValueKey<LeftLiteral>>>
108
+ : AtomOf<NeqLiteralAtom<PredicateKeyOfExpression<Right>, ValueKey<LeftLiteral>>>
109
109
  : UnknownTag<"neq:unsupported">
110
- : [ColumnKeyOfExpression<Right>] extends [never]
110
+ : [PredicateKeyOfExpression<Right>] extends [never]
111
111
  ? LiteralValueOfExpression<Right> extends infer RightLiteral
112
112
  ? [RightLiteral] extends [never]
113
113
  ? UnknownTag<"neq:unsupported">
114
114
  : RightLiteral extends null
115
115
  ? False
116
- : AtomOf<NeqLiteralAtom<ColumnKeyOfExpression<Left>, ValueKey<RightLiteral>>>
116
+ : AtomOf<NeqLiteralAtom<PredicateKeyOfExpression<Left>, ValueKey<RightLiteral>>>
117
117
  : UnknownTag<"neq:unsupported">
118
118
  : CombineFacts<NonNullFactsOfExpression<Left>, NonNullFactsOfExpression<Right>>
119
119
 
@@ -127,23 +127,23 @@ type FormulaOfIsNotDistinctFrom<
127
127
  ? [RightLiteral] extends [never]
128
128
  ? UnknownTag<"isNotDistinctFrom:unsupported">
129
129
  : RightLiteral extends null
130
- ? [ColumnKeyOfExpression<Left>] extends [never]
130
+ ? [PredicateKeyOfExpression<Left>] extends [never]
131
131
  ? UnknownTag<"isNotDistinctFrom:unsupported">
132
- : AtomOf<NullAtom<ColumnKeyOfExpression<Left>>>
132
+ : AtomOf<NullAtom<PredicateKeyOfExpression<Left>>>
133
133
  : UnknownTag<"isNotDistinctFrom:unsupported">
134
134
  : LeftLiteral extends null
135
- ? [ColumnKeyOfExpression<Right>] extends [never]
135
+ ? [PredicateKeyOfExpression<Right>] extends [never]
136
136
  ? UnknownTag<"isNotDistinctFrom:unsupported">
137
- : AtomOf<NullAtom<ColumnKeyOfExpression<Right>>>
137
+ : AtomOf<NullAtom<PredicateKeyOfExpression<Right>>>
138
138
  : RightLiteral extends null
139
- ? [ColumnKeyOfExpression<Left>] extends [never]
139
+ ? [PredicateKeyOfExpression<Left>] extends [never]
140
140
  ? UnknownTag<"isNotDistinctFrom:unsupported">
141
- : AtomOf<NullAtom<ColumnKeyOfExpression<Left>>>
142
- : [ColumnKeyOfExpression<Left>] extends [never]
143
- ? [ColumnKeyOfExpression<Right>] extends [never]
141
+ : AtomOf<NullAtom<PredicateKeyOfExpression<Left>>>
142
+ : [PredicateKeyOfExpression<Left>] extends [never]
143
+ ? [PredicateKeyOfExpression<Right>] extends [never]
144
144
  ? CombineFacts<NonNullFactsOfExpression<Left>, NonNullFactsOfExpression<Right>>
145
- : AtomOf<EqLiteralAtom<ColumnKeyOfExpression<Right>, ValueKey<LeftLiteral>>>
146
- : AtomOf<EqLiteralAtom<ColumnKeyOfExpression<Left>, ValueKey<RightLiteral>>>
145
+ : AtomOf<EqLiteralAtom<PredicateKeyOfExpression<Right>, ValueKey<LeftLiteral>>>
146
+ : AtomOf<EqLiteralAtom<PredicateKeyOfExpression<Left>, ValueKey<RightLiteral>>>
147
147
  : UnknownTag<"isNotDistinctFrom:unsupported">
148
148
  : UnknownTag<"isNotDistinctFrom:unsupported">
149
149
 
@@ -157,9 +157,12 @@ type AndFormulas<
157
157
 
158
158
  type FormulaTupleOf<
159
159
  Values extends readonly Expression.Any[]
160
- > = {
161
- readonly [K in keyof Values]: Values[K] extends Expression.Any ? FormulaOfExpression<Values[K]> : never
162
- } & readonly PredicateFormula[]
160
+ > = Values extends readonly [
161
+ infer Head extends Expression.Any,
162
+ ...infer Tail extends readonly Expression.Any[]
163
+ ]
164
+ ? readonly [FormulaOfExpression<Head>, ...FormulaTupleOf<Tail>]
165
+ : readonly []
163
166
 
164
167
  type AllFormulaOfValues<
165
168
  Values extends readonly Expression.Any[]
@@ -167,7 +170,53 @@ type AllFormulaOfValues<
167
170
 
168
171
  type AnyFormulaOfValues<
169
172
  Values extends readonly Expression.Any[]
170
- > = import("./formula.js").NormalizeBooleanConstants<AnyFormula<FormulaTupleOf<Values>>>
173
+ > = FormulaTupleOf<Values> extends infer Formulas extends readonly PredicateFormula[]
174
+ ? [CompactOrLiteralSet<Formulas>] extends [infer Compact extends PredicateFormula]
175
+ ? [Compact] extends [never]
176
+ ? import("./formula.js").NormalizeBooleanConstants<AnyFormula<Formulas>>
177
+ : Compact
178
+ : import("./formula.js").NormalizeBooleanConstants<AnyFormula<Formulas>>
179
+ : import("./formula.js").NormalizeBooleanConstants<AnyFormula<FormulaTupleOf<Values>>>
180
+
181
+ type LiteralSetDetails<Formula extends PredicateFormula> =
182
+ Formula extends AtomFormula<EqLiteralAtom<infer Key extends string, infer Value extends string>>
183
+ ? readonly [Key, Value]
184
+ : Formula extends AtomFormula<LiteralSetAtom<infer Key extends string, infer Values extends string>>
185
+ ? readonly [Key, Values]
186
+ : never
187
+
188
+ type IsUnion<Value, Candidate = Value> =
189
+ [Value] extends [never]
190
+ ? false
191
+ : Value extends unknown
192
+ ? [Candidate] extends [Value] ? false : true
193
+ : false
194
+
195
+ type CompactOrLiteralSet<
196
+ Items extends readonly PredicateFormula[],
197
+ Key extends string = never,
198
+ Values extends string = never,
199
+ Seen extends readonly unknown[] = []
200
+ > = Seen["length"] extends 20
201
+ ? never
202
+ : Items extends readonly [
203
+ infer Head extends PredicateFormula,
204
+ ...infer Tail extends readonly PredicateFormula[]
205
+ ]
206
+ ? LiteralSetDetails<Head> extends readonly [infer HeadKey extends string, infer HeadValues extends string]
207
+ ? IsUnion<HeadKey> extends true
208
+ ? never
209
+ : [Key] extends [never]
210
+ ? CompactOrLiteralSet<Tail, HeadKey, HeadValues, readonly [...Seen, unknown]>
211
+ : [HeadKey] extends [Key]
212
+ ? [Key] extends [HeadKey]
213
+ ? CompactOrLiteralSet<Tail, Key, Values | HeadValues, readonly [...Seen, unknown]>
214
+ : never
215
+ : never
216
+ : never
217
+ : [Key] extends [never]
218
+ ? never
219
+ : AtomOf<LiteralSetAtom<Key, Values>>
171
220
 
172
221
  type FormulaOfInValues<
173
222
  Left extends Expression.Any,
@@ -180,6 +229,38 @@ type FormulaOfInValues<
180
229
  ? FormulaOfInValues<Left, Tail, [...Current, FormulaOfEq<Left, Head>]>
181
230
  : Current
182
231
 
232
+ type LiteralSetValuesOf<
233
+ Values extends readonly Expression.Any[],
234
+ Current extends string = never,
235
+ Seen extends readonly unknown[] = []
236
+ > = Seen["length"] extends 20
237
+ ? string
238
+ : Values extends readonly [
239
+ infer Head extends Expression.Any,
240
+ ...infer Tail extends readonly Expression.Any[]
241
+ ]
242
+ ? LiteralValueOfExpression<Head> extends infer Literal
243
+ ? [Literal] extends [never]
244
+ ? never
245
+ : Literal extends null
246
+ ? never
247
+ : LiteralSetValuesOf<Tail, Current | ValueKey<Literal>, readonly [...Seen, unknown]>
248
+ : never
249
+ : Current
250
+
251
+ type FormulaOfIn<
252
+ Left extends Expression.Any,
253
+ Values extends readonly Expression.Any[]
254
+ > = [PredicateKeyOfExpression<Left>] extends [never]
255
+ ? OrFormulas<FormulaOfInValues<Left, Values>>
256
+ : LiteralSetValuesOf<Values> extends infer ValueSet extends string
257
+ ? [ValueSet] extends [never]
258
+ ? OrFormulas<FormulaOfInValues<Left, Values>>
259
+ : string extends ValueSet
260
+ ? CombineFacts<NonNullFactsOfExpression<Left>, UnknownTag<"in:literal-set-too-large">>
261
+ : AtomOf<LiteralSetAtom<PredicateKeyOfExpression<Left>, ValueSet>>
262
+ : OrFormulas<FormulaOfInValues<Left, Values>>
263
+
183
264
  type FormulaOfNotInValues<
184
265
  Left extends Expression.Any,
185
266
  Values extends readonly Expression.Any[],
@@ -200,7 +281,7 @@ type FormulaOfVariadic<
200
281
  ? AnyFormulaOfValues<Values>
201
282
  : Kind extends "in"
202
283
  ? Values extends readonly [infer Left extends Expression.Any, ...infer Tail extends readonly Expression.Any[]]
203
- ? OrFormulas<FormulaOfInValues<Left, Tail>>
284
+ ? FormulaOfIn<Left, Tail>
204
285
  : False
205
286
  : Kind extends "notIn"
206
287
  ? Values extends readonly [infer Left extends Expression.Any, ...infer Tail extends readonly Expression.Any[]]
@@ -218,13 +299,13 @@ type FormulaOfUnary<
218
299
  Kind extends ExpressionAst.UnaryKind,
219
300
  Inner extends Expression.Any
220
301
  > = Kind extends "isNull"
221
- ? [ColumnKeyOfExpression<Inner>] extends [never]
302
+ ? [PredicateKeyOfExpression<Inner>] extends [never]
222
303
  ? UnknownTag<"isNull:unsupported">
223
- : AtomOf<NullAtom<ColumnKeyOfExpression<Inner>>>
304
+ : AtomOf<NullAtom<PredicateKeyOfExpression<Inner>>>
224
305
  : Kind extends "isNotNull"
225
- ? [ColumnKeyOfExpression<Inner>] extends [never]
306
+ ? [PredicateKeyOfExpression<Inner>] extends [never]
226
307
  ? UnknownTag<"isNotNull:unsupported">
227
- : AtomOf<NonNullAtom<ColumnKeyOfExpression<Inner>>>
308
+ : AtomOf<NonNullAtom<PredicateKeyOfExpression<Inner>>>
228
309
  : Kind extends "not"
229
310
  ? import("./formula.js").Not<FormulaOfExpression<Inner>>
230
311
  : UnknownTag<`unary:${Kind}`>
@@ -4,6 +4,7 @@ import type { PredicateAtom } from "./atom.js"
4
4
  import type {
5
5
  EqColumnAtom,
6
6
  EqLiteralAtom,
7
+ LiteralSetAtom,
7
8
  NeqLiteralAtom,
8
9
  NonNullAtom,
9
10
  NullAtom,
@@ -24,6 +25,7 @@ export interface RuntimeContext {
24
25
  readonly nullKeys: ReadonlySet<string>
25
26
  readonly eqLiterals: ReadonlyMap<string, string>
26
27
  readonly neqLiterals: ReadonlyMap<string, ReadonlySet<string>>
28
+ readonly literalSets: ReadonlyMap<string, ReadonlySet<string>>
27
29
  readonly sourceNames: ReadonlySet<string>
28
30
  readonly contradiction: boolean
29
31
  readonly unknown: boolean
@@ -34,6 +36,7 @@ type MutableContext = {
34
36
  nullKeys: Set<string>
35
37
  eqLiterals: Map<string, string>
36
38
  neqLiterals: Map<string, Set<string>>
39
+ literalSets: Map<string, Set<string>>
37
40
  sourceNames: Set<string>
38
41
  contradiction: boolean
39
42
  unknown: boolean
@@ -72,6 +75,7 @@ const emptyContext = (): MutableContext => ({
72
75
  nullKeys: new Set(),
73
76
  eqLiterals: new Map(),
74
77
  neqLiterals: new Map(),
78
+ literalSets: new Map(),
75
79
  sourceNames: new Set(),
76
80
  contradiction: false,
77
81
  unknown: false
@@ -84,6 +88,9 @@ const cloneContext = (context: MutableContext): MutableContext => ({
84
88
  neqLiterals: new Map(
85
89
  Array.from(context.neqLiterals.entries(), ([key, values]) => [key, new Set(values)])
86
90
  ),
91
+ literalSets: new Map(
92
+ Array.from(context.literalSets.entries(), ([key, values]) => [key, new Set(values)])
93
+ ),
87
94
  sourceNames: new Set(context.sourceNames),
88
95
  contradiction: context.contradiction,
89
96
  unknown: context.unknown
@@ -123,7 +130,12 @@ const addEqLiteral = (context: MutableContext, key: string, value: string): void
123
130
  if (neqValues?.has(value)) {
124
131
  context.contradiction = true
125
132
  }
133
+ const existingSet = context.literalSets.get(key)
134
+ if (existingSet !== undefined && !existingSet.has(value)) {
135
+ context.contradiction = true
136
+ }
126
137
  context.eqLiterals.set(key, value)
138
+ context.literalSets.set(key, new Set([value]))
127
139
  }
128
140
 
129
141
  const addNeqLiteral = (context: MutableContext, key: string, value: string): void => {
@@ -136,6 +148,24 @@ const addNeqLiteral = (context: MutableContext, key: string, value: string): voi
136
148
  context.neqLiterals.set(key, values)
137
149
  }
138
150
 
151
+ const addLiteralSet = (context: MutableContext, key: string, values: ReadonlySet<string>): void => {
152
+ addNonNull(context, key)
153
+ const existingEq = context.eqLiterals.get(key)
154
+ if (existingEq !== undefined && !values.has(existingEq)) {
155
+ context.contradiction = true
156
+ }
157
+ const existing = context.literalSets.get(key)
158
+ context.literalSets.set(
159
+ key,
160
+ existing === undefined
161
+ ? new Set(values)
162
+ : new Set(Array.from(existing).filter((value) => values.has(value)))
163
+ )
164
+ if (context.literalSets.get(key)?.size === 0) {
165
+ context.contradiction = true
166
+ }
167
+ }
168
+
139
169
  const applyEqColumn = (context: MutableContext, left: string, right: string): void => {
140
170
  const leftValue = context.eqLiterals.get(left)
141
171
  const rightValue = context.eqLiterals.get(right)
@@ -176,6 +206,9 @@ const applyAtom = (context: MutableContext, atom: PredicateAtom): void => {
176
206
  case "neq-literal":
177
207
  addNeqLiteral(context, atom.key, atom.value)
178
208
  return
209
+ case "literal-set":
210
+ addLiteralSet(context, atom.key, new Set(atom.values))
211
+ return
179
212
  case "eq-column":
180
213
  applyEqColumn(context, atom.left, atom.right)
181
214
  return
@@ -199,6 +232,9 @@ const applyNegativeAtom = (context: MutableContext, atom: PredicateAtom): void =
199
232
  case "neq-literal":
200
233
  addEqLiteral(context, atom.key, atom.value)
201
234
  return
235
+ case "literal-set":
236
+ addNonNull(context, atom.key)
237
+ return
202
238
  case "eq-column":
203
239
  addNonNull(context, atom.left)
204
240
  addNonNull(context, atom.right)
@@ -240,6 +276,21 @@ const intersectNeqLiterals = (
240
276
  return result
241
277
  }
242
278
 
279
+ const unionLiteralSets = (
280
+ left: ReadonlyMap<string, ReadonlySet<string>>,
281
+ right: ReadonlyMap<string, ReadonlySet<string>>
282
+ ): Map<string, Set<string>> => {
283
+ const result = new Map<string, Set<string>>()
284
+ for (const [key, leftValues] of left) {
285
+ const rightValues = right.get(key)
286
+ if (rightValues === undefined) {
287
+ continue
288
+ }
289
+ result.set(key, new Set([...leftValues, ...rightValues]))
290
+ }
291
+ return result
292
+ }
293
+
243
294
  const intersectContexts = (left: MutableContext, right: MutableContext): MutableContext => {
244
295
  if (left.contradiction) {
245
296
  return cloneContext(right)
@@ -252,6 +303,7 @@ const intersectContexts = (left: MutableContext, right: MutableContext): Mutable
252
303
  nullKeys: new Set(Array.from(left.nullKeys).filter((key) => right.nullKeys.has(key))),
253
304
  eqLiterals: intersectEqLiterals(left.eqLiterals, right.eqLiterals),
254
305
  neqLiterals: intersectNeqLiterals(left.neqLiterals, right.neqLiterals),
306
+ literalSets: unionLiteralSets(left.literalSets, right.literalSets),
255
307
  sourceNames: new Set(Array.from(left.sourceNames).filter((name) => right.sourceNames.has(name))),
256
308
  contradiction: false,
257
309
  unknown: left.unknown || right.unknown
@@ -337,6 +389,53 @@ const columnKeyOfExpression = (value: Expression.Any): string | undefined => {
337
389
  return ast.kind === "column" ? `${ast.tableName}.${ast.columnName}` : undefined
338
390
  }
339
391
 
392
+ const sameDbType = (left: Expression.DbType.Any, right: Expression.DbType.Any): boolean =>
393
+ left.dialect === right.dialect && left.kind === right.kind
394
+
395
+ const jsonPathPredicateKeyOfExpression = (value: Expression.Any): string | undefined => {
396
+ const ast = astOf(value)
397
+ switch (ast.kind) {
398
+ case "jsonGetText":
399
+ case "jsonPathText":
400
+ case "jsonAccessText":
401
+ case "jsonTraverseText": {
402
+ const jsonAst = ast as ExpressionAst.JsonAccessNode
403
+ const segments = jsonAst.segments
404
+ if (segments.length === 0 || segments.length > 8) {
405
+ return undefined
406
+ }
407
+ const path: Array<string> = []
408
+ for (const segment of segments) {
409
+ if (typeof segment !== "object" || segment === null || segment.kind !== "key") {
410
+ return undefined
411
+ }
412
+ path.push(segment.key)
413
+ }
414
+ if (path.length === 0) {
415
+ return undefined
416
+ }
417
+ const baseKey = columnKeyOfExpression(jsonAst.base)
418
+ return baseKey === undefined ? undefined : `${baseKey}#json:${path.join(".")}`
419
+ }
420
+ default:
421
+ return undefined
422
+ }
423
+ }
424
+
425
+ const predicateKeyOfExpression = (value: Expression.Any): string | undefined =>
426
+ columnKeyOfExpression(value) ?? castPredicateKeyOfExpression(value) ?? jsonPathPredicateKeyOfExpression(value)
427
+
428
+ const castPredicateKeyOfExpression = (value: Expression.Any): string | undefined => {
429
+ const ast = astOf(value)
430
+ if (ast.kind !== "cast") {
431
+ return undefined
432
+ }
433
+ const source = ast.value as Expression.Any
434
+ return sameDbType(source[Expression.TypeId].dbType, ast.target)
435
+ ? predicateKeyOfExpression(source)
436
+ : undefined
437
+ }
438
+
340
439
  const valueKeyOfLiteral = (value: unknown): string => {
341
440
  if (typeof value === "string") {
342
441
  return `string:${value}`
@@ -357,7 +456,7 @@ const valueKeyOfLiteral = (value: unknown): string => {
357
456
  }
358
457
 
359
458
  const nonNullFactsOfExpression = (value: Expression.Any): PredicateFormula | undefined => {
360
- const key = columnKeyOfExpression(value)
459
+ const key = predicateKeyOfExpression(value)
361
460
  return key === undefined ? undefined : atomFormula<NonNullAtom<string>>({ kind: "is-not-null", key })
362
461
  }
363
462
 
@@ -375,8 +474,8 @@ const combineFacts = (
375
474
  }
376
475
 
377
476
  const formulaOfEq = (left: Expression.Any, right: Expression.Any): PredicateFormula => {
378
- const leftKey = columnKeyOfExpression(left)
379
- const rightKey = columnKeyOfExpression(right)
477
+ const leftKey = predicateKeyOfExpression(left)
478
+ const rightKey = predicateKeyOfExpression(right)
380
479
  const leftAst = astOf(left)
381
480
  const rightAst = astOf(right)
382
481
  const leftLiteral = leftAst.kind === "literal" ? leftAst.value : undefined
@@ -428,8 +527,8 @@ const formulaOfEq = (left: Expression.Any, right: Expression.Any): PredicateForm
428
527
  }
429
528
 
430
529
  const formulaOfNeq = (left: Expression.Any, right: Expression.Any): PredicateFormula => {
431
- const leftKey = columnKeyOfExpression(left)
432
- const rightKey = columnKeyOfExpression(right)
530
+ const leftKey = predicateKeyOfExpression(left)
531
+ const rightKey = predicateKeyOfExpression(right)
433
532
  const leftAst = astOf(left)
434
533
  const rightAst = astOf(right)
435
534
  const leftLiteral = leftAst.kind === "literal" ? leftAst.value : undefined
@@ -477,8 +576,8 @@ const formulaOfNeq = (left: Expression.Any, right: Expression.Any): PredicateFor
477
576
  }
478
577
 
479
578
  const formulaOfIsNotDistinctFrom = (left: Expression.Any, right: Expression.Any): PredicateFormula => {
480
- const leftKey = columnKeyOfExpression(left)
481
- const rightKey = columnKeyOfExpression(right)
579
+ const leftKey = predicateKeyOfExpression(left)
580
+ const rightKey = predicateKeyOfExpression(right)
482
581
  const leftAst = astOf(left)
483
582
  const rightAst = astOf(right)
484
583
  const leftLiteral = leftAst.kind === "literal" ? leftAst.value : undefined
@@ -587,13 +686,13 @@ export const formulaOfExpression = (value: Expression.Any): PredicateFormula =>
587
686
  }
588
687
  return unknownTag("literal:non-boolean")
589
688
  case "isNull": {
590
- const key = columnKeyOfExpression(ast.value)
689
+ const key = predicateKeyOfExpression(ast.value)
591
690
  return key === undefined
592
691
  ? unknownTag("isNull:unsupported")
593
692
  : atomFormula<NullAtom<string>>({ kind: "is-null", key })
594
693
  }
595
694
  case "isNotNull": {
596
- const key = columnKeyOfExpression(ast.value)
695
+ const key = predicateKeyOfExpression(ast.value)
597
696
  return key === undefined
598
697
  ? unknownTag("isNotNull:unsupported")
599
698
  : atomFormula<NonNullAtom<string>>({ kind: "is-not-null", key })
@@ -614,8 +713,16 @@ export const formulaOfExpression = (value: Expression.Any): PredicateFormula =>
614
713
  return anyFormula(ast.values.map((value: Expression.Any) => formulaOfExpression(value)))
615
714
  case "in": {
616
715
  const [left, ...rest] = ast.values
617
- return left === undefined
618
- ? falseFormula()
716
+ if (left === undefined) {
717
+ return falseFormula()
718
+ }
719
+ const key = predicateKeyOfExpression(left)
720
+ const literalValues = rest.map((entry: Expression.Any) => {
721
+ const entryAst = astOf(entry)
722
+ return entryAst.kind === "literal" && entryAst.value !== null ? valueKeyOfLiteral(entryAst.value) : undefined
723
+ })
724
+ return key !== undefined && literalValues.every((entry): entry is string => entry !== undefined)
725
+ ? atomFormula<LiteralSetAtom<string, string>>({ kind: "literal-set", key, values: literalValues })
619
726
  : anyFormula(rest.map((value: Expression.Any) => formulaOfEq(left, value)))
620
727
  }
621
728
  case "notIn": {