Skip to content

Commit 91443cd

Browse files
committed
Updates UndefinedVariable class, deprecates methods, and updates tests
1 parent 7fc183e commit 91443cd

File tree

4 files changed

+136
-80
lines changed

4 files changed

+136
-80
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ Thank you to all who have contributed!
3131

3232
### Changed
3333
- Change `StaticType.AnyOfType`'s `.toString` to not perform `.flatten()`
34-
- Function resolution logic: Now the function resolver would match all possible candidate (based on if the argument can be coerced to the Signature parameter type). If there are multiple match it will first attempt to pick the one requires the least cast, then pick the function with the highest precedence.
3534
- **Behavioral change**: The COUNT aggregate function now returns INT64.
3635

3736
### Deprecated
37+
- Deprecates constructor and properties `variableName` and `caseSensitive` of `org.partiql.planner.PlanningProblemDetails.UndefinedVariable`
38+
in favor of newly added constructor and properties `name` and `inScopeVariables`.
3839

3940
### Fixed
4041
- Fixes aggregations of attribute references to values of union types. This fix also allows for proper error handling by passing the UnknownAggregateFunction problem to the ProblemCallback. Please note that, with this change, the planner will no longer immediately throw an IllegalStateException for this exact scenario.

partiql-planner/src/main/kotlin/org/partiql/planner/Errors.kt

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.partiql.planner
22

33
import org.partiql.errors.ProblemDetails
44
import org.partiql.errors.ProblemSeverity
5+
import org.partiql.plan.Identifier
56
import org.partiql.types.StaticType
67

78
/**
@@ -24,15 +25,69 @@ public sealed class PlanningProblemDetails(
2425
public data class CompileError(val errorMessage: String) :
2526
PlanningProblemDetails(ProblemSeverity.ERROR, { errorMessage })
2627

27-
public data class UndefinedVariable(val variableName: String, val caseSensitive: Boolean) :
28-
PlanningProblemDetails(
29-
ProblemSeverity.ERROR,
30-
{
31-
"Undefined variable '$variableName'." +
32-
quotationHint(caseSensitive)
28+
public data class UndefinedVariable(
29+
val name: Identifier,
30+
val inScopeVariables: Set<String>
31+
) : PlanningProblemDetails(
32+
ProblemSeverity.ERROR,
33+
{
34+
"Variable ${pretty(name)} does not exist in the database environment and is not an attribute of the following in-scope variables $inScopeVariables." +
35+
quotationHint(isSymbolAndCaseSensitive(name))
36+
}
37+
) {
38+
39+
@Deprecated("This will be removed in a future major version release.", replaceWith = ReplaceWith("name"))
40+
val variableName: String = when (name) {
41+
is Identifier.Symbol -> name.symbol
42+
is Identifier.Qualified -> when (name.steps.size) {
43+
0 -> name.root.symbol
44+
else -> name.steps.last().symbol
45+
}
46+
}
47+
48+
@Deprecated("This will be removed in a future major version release.", replaceWith = ReplaceWith("name"))
49+
val caseSensitive: Boolean = when (name) {
50+
is Identifier.Symbol -> name.caseSensitivity == Identifier.CaseSensitivity.SENSITIVE
51+
is Identifier.Qualified -> when (name.steps.size) {
52+
0 -> name.root.caseSensitivity == Identifier.CaseSensitivity.SENSITIVE
53+
else -> name.steps.last().caseSensitivity == Identifier.CaseSensitivity.SENSITIVE
3354
}
55+
}
56+
57+
@Deprecated("This will be removed in a future major version release.", replaceWith = ReplaceWith("UndefinedVariable(Identifier, Set<String>)"))
58+
public constructor(variableName: String, caseSensitive: Boolean) : this(
59+
Identifier.Symbol(
60+
variableName,
61+
when (caseSensitive) {
62+
true -> Identifier.CaseSensitivity.SENSITIVE
63+
false -> Identifier.CaseSensitivity.INSENSITIVE
64+
}
65+
),
66+
emptySet()
3467
)
3568

69+
private companion object {
70+
/**
71+
* Used to check whether the [id] is an [Identifier.Symbol] and whether it is case-sensitive. This is helpful
72+
* for giving the [quotationHint] to the user.
73+
*/
74+
private fun isSymbolAndCaseSensitive(id: Identifier): Boolean = when (id) {
75+
is Identifier.Symbol -> id.caseSensitivity == Identifier.CaseSensitivity.SENSITIVE
76+
is Identifier.Qualified -> false
77+
}
78+
79+
private fun pretty(id: Identifier): String = when (id) {
80+
is Identifier.Symbol -> pretty(id)
81+
is Identifier.Qualified -> (listOf(id.root) + id.steps).joinToString(".") { pretty(it) }
82+
}
83+
84+
private fun pretty(id: Identifier.Symbol): String = when (id.caseSensitivity) {
85+
Identifier.CaseSensitivity.INSENSITIVE -> id.symbol
86+
Identifier.CaseSensitivity.SENSITIVE -> "\"${id.symbol}\""
87+
}
88+
}
89+
}
90+
3691
public data class UndefinedDmlTarget(val variableName: String, val caseSensitive: Boolean) :
3792
PlanningProblemDetails(
3893
ProblemSeverity.ERROR,

partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -449,8 +449,8 @@ internal class PlanTyper(
449449
}
450450
val resolvedVar = env.resolve(path, locals, strategy)
451451
if (resolvedVar == null) {
452-
handleUndefinedVariable(path.steps.last())
453-
return rex(ANY, rexOpErr("Undefined variable ${node.identifier}"))
452+
val details = handleUndefinedVariable(node.identifier, locals.schema.map { it.name }.toSet())
453+
return rex(ANY, rexOpErr(details.message))
454454
}
455455
return visitRex(resolvedVar, null)
456456
}
@@ -1349,15 +1349,42 @@ internal class PlanTyper(
13491349

13501350
// ERRORS
13511351

1352-
private fun handleUndefinedVariable(name: BindingName) {
1352+
/**
1353+
* Invokes [onProblem] with a newly created [PlanningProblemDetails.UndefinedVariable] and returns the
1354+
* [PlanningProblemDetails.UndefinedVariable].
1355+
*/
1356+
private fun handleUndefinedVariable(name: Identifier, locals: Set<String>): PlanningProblemDetails.UndefinedVariable {
1357+
val planName = name.toPlan()
1358+
val details = PlanningProblemDetails.UndefinedVariable(planName, locals)
13531359
onProblem(
13541360
Problem(
13551361
sourceLocation = UNKNOWN_PROBLEM_LOCATION,
1356-
details = PlanningProblemDetails.UndefinedVariable(name.name, name.bindingCase == BindingCase.SENSITIVE)
1362+
details = details
13571363
)
13581364
)
1365+
return details
1366+
}
1367+
1368+
private fun Identifier.CaseSensitivity.toPlan(): org.partiql.plan.Identifier.CaseSensitivity = when (this) {
1369+
Identifier.CaseSensitivity.SENSITIVE -> org.partiql.plan.Identifier.CaseSensitivity.SENSITIVE
1370+
Identifier.CaseSensitivity.INSENSITIVE -> org.partiql.plan.Identifier.CaseSensitivity.INSENSITIVE
1371+
}
1372+
1373+
private fun Identifier.toPlan(): org.partiql.plan.Identifier = when (this) {
1374+
is Identifier.Symbol -> this.toPlan()
1375+
is Identifier.Qualified -> this.toPlan()
13591376
}
13601377

1378+
private fun Identifier.Symbol.toPlan(): org.partiql.plan.Identifier.Symbol = org.partiql.plan.Identifier.Symbol(
1379+
this.symbol,
1380+
this.caseSensitivity.toPlan()
1381+
)
1382+
1383+
private fun Identifier.Qualified.toPlan(): org.partiql.plan.Identifier.Qualified = org.partiql.plan.Identifier.Qualified(
1384+
this.root.toPlan(),
1385+
this.steps.map { it.toPlan() }
1386+
)
1387+
13611388
private fun handleUnexpectedType(actual: StaticType, expected: Set<StaticType>) {
13621389
onProblem(
13631390
Problem(

partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTestsPorted.kt

Lines changed: 42 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import org.junit.jupiter.params.provider.MethodSource
1515
import org.partiql.errors.Problem
1616
import org.partiql.errors.UNKNOWN_PROBLEM_LOCATION
1717
import org.partiql.parser.PartiQLParser
18+
import org.partiql.plan.Identifier
1819
import org.partiql.plan.PartiQLPlan
1920
import org.partiql.plan.Statement
2021
import org.partiql.plan.debug.PlanPrinter
@@ -104,6 +105,18 @@ class PlanTyperTestsPorted {
104105
}
105106
}
106107

108+
private fun id(vararg parts: Identifier.Symbol): Identifier {
109+
return when (parts.size) {
110+
0 -> error("Identifier requires more than one part.")
111+
1 -> parts.first()
112+
else -> Identifier.Qualified(parts.first(), parts.drop(1))
113+
}
114+
}
115+
116+
private fun sensitive(part: String): Identifier.Symbol = Identifier.Symbol(part, Identifier.CaseSensitivity.SENSITIVE)
117+
118+
private fun insensitive(part: String): Identifier.Symbol = Identifier.Symbol(part, Identifier.CaseSensitivity.INSENSITIVE)
119+
107120
/**
108121
* MemoryConnector.Factory from reading the resources in /resource_path.txt for Github CI/CD.
109122
*/
@@ -131,7 +144,6 @@ class PlanTyperTestsPorted {
131144
}
132145
}
133146
map.entries.map {
134-
println("Map Entry: ${it.key} to ${it.value}")
135147
it.key to MemoryConnector.Metadata.of(*it.value.toTypedArray())
136148
}
137149
}
@@ -805,7 +817,7 @@ class PlanTyperTestsPorted {
805817
problemHandler = assertProblemExists {
806818
Problem(
807819
UNKNOWN_PROBLEM_LOCATION,
808-
PlanningProblemDetails.UndefinedVariable("a", false)
820+
PlanningProblemDetails.UndefinedVariable(insensitive("a"), setOf("t1", "t2"))
809821
)
810822
}
811823
),
@@ -2022,7 +2034,7 @@ class PlanTyperTestsPorted {
20222034
problemHandler = assertProblemExists {
20232035
Problem(
20242036
UNKNOWN_PROBLEM_LOCATION,
2025-
PlanningProblemDetails.UndefinedVariable("unknown_col", false)
2037+
PlanningProblemDetails.UndefinedVariable(insensitive("unknown_col"), setOf("pets"))
20262038
)
20272039
}
20282040
),
@@ -2707,7 +2719,7 @@ class PlanTyperTestsPorted {
27072719
problemHandler = assertProblemExists {
27082720
Problem(
27092721
UNKNOWN_PROBLEM_LOCATION,
2710-
PlanningProblemDetails.UndefinedVariable("main", true)
2722+
PlanningProblemDetails.UndefinedVariable(id(sensitive("pql"), sensitive("main")), setOf())
27112723
)
27122724
}
27132725
),
@@ -2720,7 +2732,7 @@ class PlanTyperTestsPorted {
27202732
problemHandler = assertProblemExists {
27212733
Problem(
27222734
UNKNOWN_PROBLEM_LOCATION,
2723-
PlanningProblemDetails.UndefinedVariable("pql", true)
2735+
PlanningProblemDetails.UndefinedVariable(sensitive("pql"), setOf())
27242736
)
27252737
}
27262738
),
@@ -2964,27 +2976,28 @@ class PlanTyperTestsPorted {
29642976
SuccessTestCase(
29652977
name = "AGGREGATE over nullable integers",
29662978
query = """
2967-
SELECT
2968-
COUNT(a) AS count_a
2969-
FROM <<
2970-
{ 'a': 1, 'b': 2 }
2971-
>> t1 INNER JOIN <<
2972-
{ 'c': 1, 'd': 3 }
2973-
>> t2
2974-
ON t1.a = t1.c
2975-
AND (
2976-
1 = (
2977-
SELECT COUNT(e) AS count_e
2978-
FROM <<
2979-
{ 'e': 10 }
2980-
>> t3
2979+
SELECT T1.a
2980+
FROM T1
2981+
LEFT JOIN T2 AS T2_1
2982+
ON T2_1.d =
2983+
(
2984+
SELECT
2985+
CASE WHEN COUNT(f) = 1 THEN MAX(f) ELSE 0 END AS e
2986+
FROM T3 AS T3_mapping
29812987
)
2982-
);
2988+
LEFT JOIN T2 AS T2_2
2989+
ON T2_2.d =
2990+
(
2991+
SELECT
2992+
CASE WHEN COUNT(f) = 1 THEN MAX(f) ELSE 0 END AS e
2993+
FROM T3 AS T3_mapping
2994+
)
2995+
;
29832996
""".trimIndent(),
29842997
expected = BagType(
29852998
StructType(
29862999
fields = mapOf(
2987-
"count_a" to StaticType.INT8
3000+
"a" to StaticType.BOOL
29883001
),
29893002
contentClosed = true,
29903003
constraints = setOf(
@@ -2993,8 +3006,9 @@ class PlanTyperTestsPorted {
29933006
TupleConstraint.Ordered
29943007
)
29953008
)
2996-
)
2997-
),
3009+
),
3010+
catalog = "aggregations"
3011+
)
29983012
)
29993013

30003014
@JvmStatic
@@ -3246,47 +3260,6 @@ class PlanTyperTestsPorted {
32463260
// Parameterized Tests
32473261
//
32483262

3249-
@Test
3250-
fun failingAggTest() {
3251-
val tc = SuccessTestCase(
3252-
name = "AGGREGATE over nullable integers",
3253-
query = """
3254-
SELECT T1.a
3255-
FROM T1
3256-
LEFT JOIN T2 AS T2_1
3257-
ON T2_1.d =
3258-
(
3259-
SELECT
3260-
CASE WHEN COUNT(f) = 1 THEN MAX(f) ELSE 0 END AS e
3261-
FROM T3 AS T3_mapping
3262-
)
3263-
LEFT JOIN T2 AS T2_2
3264-
ON T2_2.d =
3265-
(
3266-
SELECT
3267-
CASE WHEN COUNT(f) = 1 THEN MAX(f) ELSE 0 END AS e
3268-
FROM T3 AS T3_mapping
3269-
)
3270-
;
3271-
""".trimIndent(),
3272-
expected = BagType(
3273-
StructType(
3274-
fields = mapOf(
3275-
"a" to StaticType.BOOL
3276-
),
3277-
contentClosed = true,
3278-
constraints = setOf(
3279-
TupleConstraint.Open(false),
3280-
TupleConstraint.UniqueAttrs(true),
3281-
TupleConstraint.Ordered
3282-
)
3283-
)
3284-
),
3285-
catalog = "aggregations"
3286-
)
3287-
runTest(tc)
3288-
}
3289-
32903263
@Test
32913264
@Disabled("The planner doesn't support heterogeneous input to aggregation functions (yet?).")
32923265
fun failingTest() {
@@ -3601,7 +3574,7 @@ class PlanTyperTestsPorted {
36013574
problemHandler = assertProblemExists {
36023575
Problem(
36033576
UNKNOWN_PROBLEM_LOCATION,
3604-
PlanningProblemDetails.UndefinedVariable("pets", false)
3577+
PlanningProblemDetails.UndefinedVariable(insensitive("pets"), emptySet())
36053578
)
36063579
}
36073580
),
@@ -3634,7 +3607,7 @@ class PlanTyperTestsPorted {
36343607
problemHandler = assertProblemExists {
36353608
Problem(
36363609
UNKNOWN_PROBLEM_LOCATION,
3637-
PlanningProblemDetails.UndefinedVariable("pets", false)
3610+
PlanningProblemDetails.UndefinedVariable(insensitive("pets"), emptySet())
36383611
)
36393612
}
36403613
),
@@ -3701,7 +3674,7 @@ class PlanTyperTestsPorted {
37013674
problemHandler = assertProblemExists {
37023675
Problem(
37033676
UNKNOWN_PROBLEM_LOCATION,
3704-
PlanningProblemDetails.UndefinedVariable("pets", false)
3677+
PlanningProblemDetails.UndefinedVariable(id(insensitive("ddb"), insensitive("pets")), emptySet())
37053678
)
37063679
}
37073680
),
@@ -3971,7 +3944,7 @@ class PlanTyperTestsPorted {
39713944
problemHandler = assertProblemExists {
39723945
Problem(
39733946
UNKNOWN_PROBLEM_LOCATION,
3974-
PlanningProblemDetails.UndefinedVariable("non_existing_column", false)
3947+
PlanningProblemDetails.UndefinedVariable(insensitive("non_existing_column"), emptySet())
39753948
)
39763949
}
39773950
),
@@ -4026,7 +3999,7 @@ class PlanTyperTestsPorted {
40263999
problemHandler = assertProblemExists {
40274000
Problem(
40284001
UNKNOWN_PROBLEM_LOCATION,
4029-
PlanningProblemDetails.UndefinedVariable("unknown_col", false)
4002+
PlanningProblemDetails.UndefinedVariable(insensitive("unknown_col"), setOf("orders"))
40304003
)
40314004
}
40324005
),

0 commit comments

Comments
 (0)