Skip to content

Commit 910dec0

Browse files
committed
Add EXCLUDE to partiql-eval
1 parent 5617ef6 commit 910dec0

File tree

6 files changed

+729
-257
lines changed

6 files changed

+729
-257
lines changed

partiql-eval/src/main/kotlin/org/partiql/eval/internal/Compiler.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package org.partiql.eval.internal
22

33
import org.partiql.eval.internal.operator.Operator
4+
import org.partiql.eval.internal.operator.rel.RelExclude
45
import org.partiql.eval.internal.operator.rel.RelFilter
56
import org.partiql.eval.internal.operator.rel.RelJoinInner
67
import org.partiql.eval.internal.operator.rel.RelJoinLeft
78
import org.partiql.eval.internal.operator.rel.RelJoinOuterFull
89
import org.partiql.eval.internal.operator.rel.RelJoinRight
910
import org.partiql.eval.internal.operator.rel.RelProject
1011
import org.partiql.eval.internal.operator.rel.RelScan
12+
import org.partiql.eval.internal.operator.rel.compileExcludeItems
1113
import org.partiql.eval.internal.operator.rex.ExprCase
1214
import org.partiql.eval.internal.operator.rex.ExprCollection
1315
import org.partiql.eval.internal.operator.rex.ExprLiteral
@@ -82,6 +84,12 @@ internal object Compiler {
8284
return RelFilter(input, condition)
8385
}
8486

87+
override fun visitRelOpExclude(node: Rel.Op.Exclude, ctx: Unit): Operator {
88+
val input = visitRel(node.input, ctx)
89+
val compiledExcludeExprs = compileExcludeItems(node.items)
90+
return RelExclude(input, compiledExcludeExprs)
91+
}
92+
8593
override fun visitRex(node: Rex, ctx: Unit): Operator.Expr {
8694
return super.visitRexOp(node.op, ctx) as Operator.Expr
8795
}
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
package org.partiql.eval.internal.exclude
2+
3+
/**
4+
* Internal representation of an `EXCLUDE` expr step.
5+
*/
6+
internal sealed class ExcludeStep {
7+
internal data class StructField(val attr: String, val caseSensitivity: ExcludeFieldCase) : ExcludeStep()
8+
internal object StructWildcard : ExcludeStep()
9+
internal data class CollIndex(val index: Int) : ExcludeStep()
10+
internal object CollWildcard : ExcludeStep()
11+
}
12+
13+
/**
14+
* Internal representation of an `EXCLUDE` struct attribute case-sensitivity.
15+
*/
16+
internal enum class ExcludeFieldCase {
17+
INSENSITIVE, SENSITIVE
18+
}
19+
20+
/**
21+
* Represents all the compiled `EXCLUDE` paths that start with the same [CompiledExcludeItem.root]. This variant of
22+
* [ExcludeNode] represents the top-level root node of the exclude tree.
23+
*
24+
* Notably, redundant paths (i.e. exclude paths that exclude values already excluded by other paths) will be removed.
25+
*/
26+
internal data class CompiledExcludeItem(
27+
val root: Int,
28+
override val leaves: MutableSet<ExcludeLeaf>,
29+
override val branches: MutableSet<ExcludeBranch>
30+
) : ExcludeNode(leaves, branches) {
31+
companion object {
32+
fun empty(root: Int): CompiledExcludeItem {
33+
return CompiledExcludeItem(root, mutableSetOf(), mutableSetOf())
34+
}
35+
}
36+
}
37+
38+
/**
39+
* Represent all the `EXCLUDE` paths that start with the same [ExcludeBranch.step] that also have additional steps
40+
* (i.e. final step is at a deeper level). This variant of [ExcludeNode] represents inner nodes (i.e. non-top-level)
41+
* nodes of the exclude tree.
42+
*/
43+
internal data class ExcludeBranch(
44+
val step: ExcludeStep,
45+
override val leaves: MutableSet<ExcludeLeaf>,
46+
override val branches: MutableSet<ExcludeBranch>
47+
) : ExcludeNode(leaves, branches) {
48+
companion object {
49+
fun empty(step: ExcludeStep): ExcludeBranch {
50+
return ExcludeBranch(step, mutableSetOf(), mutableSetOf())
51+
}
52+
}
53+
}
54+
55+
/**
56+
* Represents all the `EXCLUDE` paths that have a final exclude step at the current level. This variant of [ExcludeNode]
57+
* represents the leaves in our exclude tree.
58+
*/
59+
internal data class ExcludeLeaf(
60+
val step: ExcludeStep,
61+
) : ExcludeNode(mutableSetOf(), mutableSetOf())
62+
63+
/**
64+
* A tree representation of the exclude paths that will eliminate redundant paths (i.e. exclude paths that exclude
65+
* values already excluded by other paths).
66+
*
67+
* The idea behind this tree representation is that at a current level (i.e. path step index), we keep track of the
68+
* - Exclude paths that have a final exclude step at the current level. This set of struct attributes and collection
69+
* indexes to remove at the current level is modeled as a set of leaves (i.e. [ExcludeLeaf]).
70+
* - Exclude paths that have additional steps (their final step is at a deeper level). This is modeled as a set of
71+
* branches [ExcludeBranch] to group all exclude paths that share the same current step.
72+
*
73+
* For example, let's say we have exclude paths
74+
* a.b, -- assuming root resolves to 0
75+
* x.y.z1, -- assuming root resolves to 1
76+
* x.y.z2 -- assuming root resolves to 1
77+
* ^ ^ ^
78+
* Level 1 2 3
79+
*
80+
* These exclude paths would be converted to the following [CompiledExcludeItem]s in [ExcludeNode]s:
81+
* ```
82+
* // For demonstration purposes, the syntax '<string>' corresponds to the exclude struct attribute step of <string>
83+
* CompiledExcludeItem( // Root 0 (i.e. 'a')
84+
* root = 0,
85+
* leaves = mutableSetOf(
86+
* ExcludeLeaf(step = 'b') // Exclude 'b' at level 2
87+
* ),
88+
* branches = mutableSetOf() // No further exclusions
89+
* ),
90+
* CompiledExcludeItem( // Root 1 (i.e. 'x')
91+
* root = 1,
92+
* leaves = mutableSetOf(), // No exclusions at level 2
93+
* branches = mutableSetOf(
94+
* ExcludeBranch(
95+
* step = 'y',
96+
* leaves = mutableSetOf(
97+
* ExcludeLeaf(step = 'z1'), // Exclude 'z1` at level 3
98+
* ExcludeLeaf(step = 'z2') // Exclude `z2` at level 3
99+
* )
100+
* branches = mutableSetOf() // No further exclusions
101+
* )
102+
* )
103+
* )
104+
*/
105+
internal sealed class ExcludeNode(
106+
open val leaves: MutableSet<ExcludeLeaf>,
107+
open val branches: MutableSet<ExcludeBranch>
108+
) {
109+
private fun addLeaf(step: ExcludeStep) {
110+
when (step) {
111+
is ExcludeStep.StructField -> {
112+
if (leaves.contains(ExcludeLeaf(ExcludeStep.StructWildcard))) {
113+
// leaves contain wildcard; do not add; e.g. a.* and a.b -> keep a.*
114+
} else {
115+
// add to leaves
116+
leaves.add(ExcludeLeaf(step))
117+
// remove from branches; e.g. a.b.c and a.b -> keep a.b
118+
branches.removeIf { subBranch ->
119+
step == subBranch.step
120+
}
121+
}
122+
}
123+
is ExcludeStep.StructWildcard -> {
124+
leaves.add(ExcludeLeaf(step))
125+
// remove all struct attribute exclude steps from leaves
126+
leaves.removeIf { subLeaf ->
127+
subLeaf.step is ExcludeStep.StructField
128+
}
129+
// remove all struct attribute/wildcard exclude steps from branches
130+
branches.removeIf { subBranch ->
131+
subBranch.step is ExcludeStep.StructField || subBranch.step is ExcludeStep.StructWildcard
132+
}
133+
}
134+
is ExcludeStep.CollIndex -> {
135+
if (leaves.contains(ExcludeLeaf(ExcludeStep.CollWildcard))) {
136+
// leaves contains wildcard; do not add; e.g a[*] and a[1] -> keep a[*]
137+
} else {
138+
// add to leaves
139+
leaves.add(ExcludeLeaf(step))
140+
// remove from branches; e.g. a.b[2].c and a.b[2] -> keep a.b[2]
141+
branches.removeIf { subBranch ->
142+
step == subBranch.step
143+
}
144+
}
145+
}
146+
is ExcludeStep.CollWildcard -> {
147+
leaves.add(ExcludeLeaf(step))
148+
// remove all collection index exclude steps from leaves
149+
leaves.removeIf { subLeaf ->
150+
subLeaf.step is ExcludeStep.CollIndex
151+
}
152+
// remove all collection index/wildcard exclude steps from branches
153+
branches.removeIf { subBranch ->
154+
subBranch.step is ExcludeStep.CollIndex || subBranch.step is ExcludeStep.CollWildcard
155+
}
156+
}
157+
}
158+
}
159+
160+
private fun addBranch(steps: List<ExcludeStep>) {
161+
val head = steps.first()
162+
val tail = steps.drop(1)
163+
when (head) {
164+
is ExcludeStep.StructField -> {
165+
if (leaves.contains(ExcludeLeaf(ExcludeStep.StructWildcard)) || leaves.contains(
166+
ExcludeLeaf(head)
167+
)
168+
) {
169+
// leaves contains struct wildcard or attr; do not add to branches
170+
// e.g. a.* and a.b.c -> a.*
171+
} else {
172+
val existingBranch = branches.find { subBranch ->
173+
head == subBranch.step
174+
} ?: ExcludeBranch.empty(head)
175+
branches.remove(existingBranch)
176+
existingBranch.addNode(tail)
177+
branches.add(existingBranch)
178+
}
179+
}
180+
is ExcludeStep.StructWildcard -> {
181+
if (leaves.any { it.step is ExcludeStep.StructWildcard }) {
182+
// struct wildcard in leaves; do nothing
183+
} else {
184+
val existingBranch = branches.find { subBranch ->
185+
head == subBranch.step
186+
} ?: ExcludeBranch.empty(head)
187+
branches.remove(existingBranch)
188+
existingBranch.addNode(tail)
189+
branches.add(existingBranch)
190+
}
191+
}
192+
is ExcludeStep.CollIndex -> {
193+
if (leaves.contains(ExcludeLeaf(ExcludeStep.CollWildcard)) || leaves.contains(
194+
ExcludeLeaf(head)
195+
)
196+
) {
197+
// leaves contains collection wildcard or index; do not add to branches
198+
// e.g. a[*] and a[*][1] -> a[*]
199+
} else {
200+
val existingBranch = branches.find { subBranch ->
201+
head == subBranch.step
202+
} ?: ExcludeBranch.empty(head)
203+
branches.remove(existingBranch)
204+
existingBranch.addNode(tail)
205+
branches.add(existingBranch)
206+
}
207+
}
208+
is ExcludeStep.CollWildcard -> {
209+
if (leaves.any { it.step is ExcludeStep.CollWildcard }) {
210+
// collection wildcard in leaves; do nothing
211+
} else {
212+
val existingBranch = branches.find { subBranch ->
213+
head == subBranch.step
214+
} ?: ExcludeBranch.empty(head)
215+
branches.remove(existingBranch)
216+
existingBranch.addNode(tail)
217+
branches.add(existingBranch)
218+
}
219+
}
220+
}
221+
}
222+
223+
internal fun addNode(steps: List<ExcludeStep>) {
224+
when (steps.size) {
225+
1 -> this.addLeaf(steps.first())
226+
else -> this.addBranch(steps)
227+
}
228+
}
229+
}

0 commit comments

Comments
 (0)