-
Notifications
You must be signed in to change notification settings - Fork 62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add EXCLUDE
to partiql-eval
#1320
Conversation
@@ -0,0 +1,251 @@ | |||
package org.partiql.eval.internal.exclude |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These tests are the same as the commented out tests from partiql-eval/src/test/kotlin/org/partiql/lang/eval/EvaluatingCompilerExcludeTests.kt
@@ -264,6 +264,35 @@ class PartiQLEngineDefaultTest { | |||
assertEquals(expected, output) | |||
} | |||
|
|||
@OptIn(PartiQLValueExperimental::class) | |||
@Test | |||
fun testExclude() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test succeeds when @Disabled
is removed from L29.
Conformance comparison report
Number passing in both: 5384 Number failing in both: 434 Number passing in Base (d64784c) but now fail: 0 Number failing in Base (d64784c) but now pass: 0 |
672a09d
to
910dec0
Compare
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## partiql-eval #1320 +/- ##
===============================================
Coverage ? 49.26%
Complexity ? 1046
===============================================
Files ? 166
Lines ? 13396
Branches ? 2504
===============================================
Hits ? 6600
Misses ? 6139
Partials ? 657
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
910dec0
to
5bc681e
Compare
} | ||
} | ||
|
||
@OptIn(PartiQLValueExperimental::class) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than having a bunch of top-level functions, could you please place these in the appropriate spots? Compile related functions in the Compiler, and evaluation related items wihtin the RelExclude operator?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed locations of the top-level functions in 9e9a040
partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExclude.kt
Outdated
Show resolved
Hide resolved
partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExclude.kt
Outdated
Show resolved
Hide resolved
partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExclude.kt
Outdated
Show resolved
Hide resolved
val collWithRemoved = when (coll) { | ||
is BagValue -> coll | ||
is ListValue, is SexpValue -> coll.filterIndexed { index, _ -> | ||
!indexesToRemove.contains(index) | ||
} | ||
} | ||
val finalColl = collWithRemoved.mapIndexed { index, element -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the difference between collWithRemoved and finalColl? I think you should be applying the exclusions with a mapIndexedNotNull and return null if the index is skipped otherwise apply the exlcusion. This way you don't traverse the collection more than once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
collWithRemoved
would remove the indexes at that level (i.e. the leaf nodes with ExcludeStep.CollIndex
). finalColl
would remove the values at deeper nested levels (i.e. the branch nodes).
I think you should be applying the exclusions with a mapIndexedNotNull and return null if the index is skipped otherwise apply the exlcusion. This way you don't traverse the collection more than once.
Good point. I will change to use mapIndexedNotNull
so only one iteration of coll
is necessary.
// apply collection wildcard exclusions for lists, bags, and sexps | ||
val collectionWildcardKey = ExcludeStep.CollWildcard | ||
branches.find { | ||
it.step == collectionWildcardKey | ||
}?.let { | ||
expr = excludeOnPartiQLValue(expr, it) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have an example of what this is for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll edit the comment to clarify. This basically applies all the collection wildcard exclude steps that have deeper exclude steps (e.g. t.a.b.c[*].field_x
in
SELECT *
EXCLUDE
t.a.b.c[*].field_x
FROM [{
'a': {
'b': {
'c': [
{ -- c[0]; field_x to be removed
'field_x': 0,
'field_y': 0
},
{ -- c[1]; field_x to be removed
'field_x': 1,
'field_y': 1
},
{ -- c[2]; field_x to be removed
'field_x': 2,
'field_y': 2
}
]
}
}
}] AS t
When we get to the c
list, the collection wildcard step will apply the remainder of the step (i.e. .field_x
) on each of the elements of c
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ported above example to another test:
Lines 249 to 296 in 50de032
SuccessTestCase( | |
input = """ | |
SELECT * | |
EXCLUDE | |
t.a.b.c[*].field_x | |
FROM [{ | |
'a': { | |
'b': { | |
'c': [ | |
{ -- c[0]; field_x to be removed | |
'field_x': 0, | |
'field_y': 0 | |
}, | |
{ -- c[1]; field_x to be removed | |
'field_x': 1, | |
'field_y': 1 | |
}, | |
{ -- c[2]; field_x to be removed | |
'field_x': 2, | |
'field_y': 2 | |
} | |
] | |
} | |
} | |
}] AS t | |
""".trimIndent(), | |
expected = bagValue( | |
structValue( | |
"a" to structValue( | |
"b" to structValue( | |
"c" to bagValue( // TODO: should be ListValue; currently, Rex.ExprCollection doesn't return lists | |
structValue( | |
"field_y" to int32Value(0) | |
), | |
structValue( | |
"field_y" to int32Value(1) | |
), | |
structValue( | |
"field_y" to int32Value(2) | |
) | |
) | |
) | |
) | |
) | |
) | |
) | |
) | |
} |
Though the inner collection should be a list rather than bag. I believe this is a bug (or not yet implemented) in Rex.ExprCollection
which currently always returning a bag.
partiql-eval/src/main/kotlin/org/partiql/eval/internal/exclude/CompiledExcludeItem.kt
Outdated
Show resolved
Hide resolved
partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExclude.kt
Outdated
Show resolved
Hide resolved
partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExclude.kt
Outdated
Show resolved
Hide resolved
while (true) { | ||
val row = input.next() ?: return null | ||
val newRecord = exclusions.fold(row) { curRecord, expr -> | ||
excludeOnRecord(curRecord, expr) | ||
} | ||
return newRecord | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need the loop because you aren't processing multiple input rows.
while (true) { | |
val row = input.next() ?: return null | |
val newRecord = exclusions.fold(row) { curRecord, expr -> | |
excludeOnRecord(curRecord, expr) | |
} | |
return newRecord | |
} | |
val record = input.next() ?: return null | |
val result = exclusions.fold(record) { curr, path -> curr.exlcude(path) } | |
return result |
Something I've noticed which is a total nitpicky thing is that in Kotlin (unlike rust) functions don't need unique names so you can overload the name because the parameters are different.
Instead of
excludeOnRecord(record: Record. ...)
excludeOnStructValue(structValue: StructValue<*>, ...)
excludeOnCollValue(collValue: CollectionValue<*>, ...)
excludeOnPartiQLValue(initialPartiQLValue: PartiQLValue, ...)
You can do
exclude(record: Record, ...)
exclude(value: PartiQLValue, ...) // usually declare the base before the others, but it doesn't matter
exclude(struct: StructValue<*>, ...)
exclude(collection: CollectionValue<*>, ...)
This is only a style thing to cut out verbosity, but I can see how the naming is carried over from Rust.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need the loop because you aren't processing multiple input rows.
Ah, true. I will change it do process just one row.
For naming of the exclude
, I'll change all of the excludeOn...
to exclude
. This will be more consistent with how the TypeUtils
models the similar functions for exclude on static type.
val subsumedPaths = newPaths | ||
.groupBy(keySelector = { it.root }, valueTransform = { it.steps }) // combine exclude paths with the same resolved root before subsumption | ||
.map { (root, allSteps) -> | ||
val nonRedundant = ExcludeRepr.toExcludeRepr(allSteps.flatten()).removeRedundantSteps() | ||
relOpExcludePath(root, nonRedundant.toPlanRepr()) | ||
} | ||
val op = relOpExclude(input, subsumedPaths) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this in case the AST representations aren't equivalent, but perhaps they resolve to the same?
SELECT * EXCLUDE t.a.*, "t".a FROM { 'a': { 'x': 1 } ... } AS t
I think the only time the root (which is a var) would differ is based on the case-sensitivity of the identifier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, the ast representations differed since the root (identifier) of t.a.*
was case-insensitive while "t".a
was case-sensitive. They would resolve to the same in this example and should be under the same exclude path root in the resolved plan.
partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeUtils.kt
Outdated
Show resolved
Hide resolved
partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExclude.kt
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work!
Relevant Issues
EXCLUDE
RFC -- RFC-0051: EXCLUDE Clause partiql-docs#51EXCLUDE
implementation inEvaluatingCompiler
-- Implement EXCLUDE inEvaluatingCompiler
#1249EXCLUDE
toPhysicalPlanCompiler
-- Add exclude physical plan compiler impl #1280Description
EXCLUDE
to thepartiql-eval
evaluatorEXCLUDE
in the plan representation. This change in modeling was done to better align with the redundant path elimination and evaluation approachesOther Information
Updated Unreleased Section in CHANGELOG: [NO]
Any backward-incompatible changes? [NO]
Any new external dependencies? [NO]
Do your changes comply with the Contributing Guidelines
and Code Style Guidelines? [YES]
License Information
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.