Skip to content

Commit 234518c

Browse files
committed
Updates UndefinedVariable class, deprecates methods, and updates tests
1 parent ffa537c commit 234518c

File tree

4 files changed

+136
-79
lines changed

4 files changed

+136
-79
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ Thank you to all who have contributed!
3838

3939
### Deprecated
4040
- The current SqlBlock, SqlDialect, and SqlLayout are marked as deprecated and will be slightly changed in the next release.
41+
- Deprecates constructor and properties `variableName` and `caseSensitive` of `org.partiql.planner.PlanningProblemDetails.UndefinedVariable`
42+
in favor of newly added constructor and properties `name` and `inScopeVariables`.
4143

4244
### Fixed
4345
- `StaticType.flatten()` on an `AnyOfType` with `AnyType` will return `AnyType`

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

+61-6
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

+31-4
Original file line numberDiff line numberDiff line change
@@ -451,8 +451,8 @@ internal class PlanTyper(
451451
}
452452
val resolvedVar = env.resolve(path, locals, strategy)
453453
if (resolvedVar == null) {
454-
handleUndefinedVariable(path.steps.last())
455-
return rex(ANY, rexOpErr("Undefined variable ${node.identifier}"))
454+
val details = handleUndefinedVariable(node.identifier, locals.schema.map { it.name }.toSet())
455+
return rex(ANY, rexOpErr(details.message))
456456
}
457457
return visitRex(resolvedVar, null)
458458
}
@@ -1399,15 +1399,42 @@ internal class PlanTyper(
13991399

14001400
// ERRORS
14011401

1402-
private fun handleUndefinedVariable(name: BindingName) {
1402+
/**
1403+
* Invokes [onProblem] with a newly created [PlanningProblemDetails.UndefinedVariable] and returns the
1404+
* [PlanningProblemDetails.UndefinedVariable].
1405+
*/
1406+
private fun handleUndefinedVariable(name: Identifier, locals: Set<String>): PlanningProblemDetails.UndefinedVariable {
1407+
val planName = name.toPlan()
1408+
val details = PlanningProblemDetails.UndefinedVariable(planName, locals)
14031409
onProblem(
14041410
Problem(
14051411
sourceLocation = UNKNOWN_PROBLEM_LOCATION,
1406-
details = PlanningProblemDetails.UndefinedVariable(name.name, name.bindingCase == BindingCase.SENSITIVE)
1412+
details = details
14071413
)
14081414
)
1415+
return details
1416+
}
1417+
1418+
private fun Identifier.CaseSensitivity.toPlan(): org.partiql.plan.Identifier.CaseSensitivity = when (this) {
1419+
Identifier.CaseSensitivity.SENSITIVE -> org.partiql.plan.Identifier.CaseSensitivity.SENSITIVE
1420+
Identifier.CaseSensitivity.INSENSITIVE -> org.partiql.plan.Identifier.CaseSensitivity.INSENSITIVE
1421+
}
1422+
1423+
private fun Identifier.toPlan(): org.partiql.plan.Identifier = when (this) {
1424+
is Identifier.Symbol -> this.toPlan()
1425+
is Identifier.Qualified -> this.toPlan()
14091426
}
14101427

1428+
private fun Identifier.Symbol.toPlan(): org.partiql.plan.Identifier.Symbol = org.partiql.plan.Identifier.Symbol(
1429+
this.symbol,
1430+
this.caseSensitivity.toPlan()
1431+
)
1432+
1433+
private fun Identifier.Qualified.toPlan(): org.partiql.plan.Identifier.Qualified = org.partiql.plan.Identifier.Qualified(
1434+
this.root.toPlan(),
1435+
this.steps.map { it.toPlan() }
1436+
)
1437+
14111438
private fun handleUnexpectedType(actual: StaticType, expected: Set<StaticType>) {
14121439
onProblem(
14131440
Problem(

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

+42-69
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
),
@@ -2920,7 +2932,7 @@ class PlanTyperTestsPorted {
29202932
problemHandler = assertProblemExists {
29212933
Problem(
29222934
UNKNOWN_PROBLEM_LOCATION,
2923-
PlanningProblemDetails.UndefinedVariable("main", true)
2935+
PlanningProblemDetails.UndefinedVariable(id(sensitive("pql"), sensitive("main")), setOf())
29242936
)
29252937
}
29262938
),
@@ -2933,7 +2945,7 @@ class PlanTyperTestsPorted {
29332945
problemHandler = assertProblemExists {
29342946
Problem(
29352947
UNKNOWN_PROBLEM_LOCATION,
2936-
PlanningProblemDetails.UndefinedVariable("pql", true)
2948+
PlanningProblemDetails.UndefinedVariable(sensitive("pql"), setOf())
29372949
)
29382950
}
29392951
),
@@ -3177,27 +3189,28 @@ class PlanTyperTestsPorted {
31773189
SuccessTestCase(
31783190
name = "AGGREGATE over nullable integers",
31793191
query = """
3180-
SELECT
3181-
COUNT(a) AS count_a
3182-
FROM <<
3183-
{ 'a': 1, 'b': 2 }
3184-
>> t1 INNER JOIN <<
3185-
{ 'c': 1, 'd': 3 }
3186-
>> t2
3187-
ON t1.a = t1.c
3188-
AND (
3189-
1 = (
3190-
SELECT COUNT(e) AS count_e
3191-
FROM <<
3192-
{ 'e': 10 }
3193-
>> t3
3192+
SELECT T1.a
3193+
FROM T1
3194+
LEFT JOIN T2 AS T2_1
3195+
ON T2_1.d =
3196+
(
3197+
SELECT
3198+
CASE WHEN COUNT(f) = 1 THEN MAX(f) ELSE 0 END AS e
3199+
FROM T3 AS T3_mapping
31943200
)
3195-
);
3201+
LEFT JOIN T2 AS T2_2
3202+
ON T2_2.d =
3203+
(
3204+
SELECT
3205+
CASE WHEN COUNT(f) = 1 THEN MAX(f) ELSE 0 END AS e
3206+
FROM T3 AS T3_mapping
3207+
)
3208+
;
31963209
""".trimIndent(),
31973210
expected = BagType(
31983211
StructType(
31993212
fields = mapOf(
3200-
"count_a" to StaticType.INT8
3213+
"a" to StaticType.BOOL
32013214
),
32023215
contentClosed = true,
32033216
constraints = setOf(
@@ -3206,8 +3219,9 @@ class PlanTyperTestsPorted {
32063219
TupleConstraint.Ordered
32073220
)
32083221
)
3209-
)
3210-
),
3222+
),
3223+
catalog = "aggregations"
3224+
)
32113225
)
32123226

32133227
@JvmStatic
@@ -3459,47 +3473,6 @@ class PlanTyperTestsPorted {
34593473
// Parameterized Tests
34603474
//
34613475

3462-
@Test
3463-
fun failingAggTest() {
3464-
val tc = SuccessTestCase(
3465-
name = "AGGREGATE over nullable integers",
3466-
query = """
3467-
SELECT T1.a
3468-
FROM T1
3469-
LEFT JOIN T2 AS T2_1
3470-
ON T2_1.d =
3471-
(
3472-
SELECT
3473-
CASE WHEN COUNT(f) = 1 THEN MAX(f) ELSE 0 END AS e
3474-
FROM T3 AS T3_mapping
3475-
)
3476-
LEFT JOIN T2 AS T2_2
3477-
ON T2_2.d =
3478-
(
3479-
SELECT
3480-
CASE WHEN COUNT(f) = 1 THEN MAX(f) ELSE 0 END AS e
3481-
FROM T3 AS T3_mapping
3482-
)
3483-
;
3484-
""".trimIndent(),
3485-
expected = BagType(
3486-
StructType(
3487-
fields = mapOf(
3488-
"a" to StaticType.BOOL
3489-
),
3490-
contentClosed = true,
3491-
constraints = setOf(
3492-
TupleConstraint.Open(false),
3493-
TupleConstraint.UniqueAttrs(true),
3494-
TupleConstraint.Ordered
3495-
)
3496-
)
3497-
),
3498-
catalog = "aggregations"
3499-
)
3500-
runTest(tc)
3501-
}
3502-
35033476
@Test
35043477
@Disabled("The planner doesn't support heterogeneous input to aggregation functions (yet?).")
35053478
fun failingTest() {
@@ -3824,7 +3797,7 @@ class PlanTyperTestsPorted {
38243797
problemHandler = assertProblemExists {
38253798
Problem(
38263799
UNKNOWN_PROBLEM_LOCATION,
3827-
PlanningProblemDetails.UndefinedVariable("pets", false)
3800+
PlanningProblemDetails.UndefinedVariable(insensitive("pets"), emptySet())
38283801
)
38293802
}
38303803
),
@@ -3857,7 +3830,7 @@ class PlanTyperTestsPorted {
38573830
problemHandler = assertProblemExists {
38583831
Problem(
38593832
UNKNOWN_PROBLEM_LOCATION,
3860-
PlanningProblemDetails.UndefinedVariable("pets", false)
3833+
PlanningProblemDetails.UndefinedVariable(insensitive("pets"), emptySet())
38613834
)
38623835
}
38633836
),
@@ -3924,7 +3897,7 @@ class PlanTyperTestsPorted {
39243897
problemHandler = assertProblemExists {
39253898
Problem(
39263899
UNKNOWN_PROBLEM_LOCATION,
3927-
PlanningProblemDetails.UndefinedVariable("pets", false)
3900+
PlanningProblemDetails.UndefinedVariable(id(insensitive("ddb"), insensitive("pets")), emptySet())
39283901
)
39293902
}
39303903
),
@@ -4194,7 +4167,7 @@ class PlanTyperTestsPorted {
41944167
problemHandler = assertProblemExists {
41954168
Problem(
41964169
UNKNOWN_PROBLEM_LOCATION,
4197-
PlanningProblemDetails.UndefinedVariable("non_existing_column", false)
4170+
PlanningProblemDetails.UndefinedVariable(insensitive("non_existing_column"), emptySet())
41984171
)
41994172
}
42004173
),
@@ -4249,7 +4222,7 @@ class PlanTyperTestsPorted {
42494222
problemHandler = assertProblemExists {
42504223
Problem(
42514224
UNKNOWN_PROBLEM_LOCATION,
4252-
PlanningProblemDetails.UndefinedVariable("unknown_col", false)
4225+
PlanningProblemDetails.UndefinedVariable(insensitive("unknown_col"), setOf("orders"))
42534226
)
42544227
}
42554228
),

0 commit comments

Comments
 (0)