Skip to content

Commit

Permalink
Adds grammar for boolean test and absent predicates
Browse files Browse the repository at this point in the history
  • Loading branch information
RCHowell committed Jul 25, 2024
1 parent 73db367 commit 084c49f
Show file tree
Hide file tree
Showing 20 changed files with 734 additions and 124 deletions.
225 changes: 196 additions & 29 deletions partiql-ast/api/partiql-ast.api

Large diffs are not rendered by default.

28 changes: 25 additions & 3 deletions partiql-ast/src/main/kotlin/org/partiql/ast/helpers/ToLegacyAst.kt
Original file line number Diff line number Diff line change
Expand Up @@ -520,16 +520,38 @@ private class AstTranslator(val metas: Map<String, MetaContainer>) : AstBaseVisi
}
}

override fun visitExprIsType(node: Expr.IsType, ctx: Ctx) = translate(node) { metas ->
override fun visitExprIsNull(node: Expr.IsNull, ctx: Ctx) = translate(node) { metas ->
val value = visitExpr(node.value, ctx)
val type = visitType(node.type, ctx)
if (node.not != null && node.not!!) {
val type = nullType()
if (node.not != null && node.not) {
not(isType(value, type), metas)
} else {
isType(value, type, metas)
}
}

override fun visitExprIsMissing(node: Expr.IsMissing, ctx: Ctx) = translate(node) { metas ->
val value = visitExpr(node.value, ctx)
val type = missingType()
if (node.not != null && node.not) {
not(isType(value, type), metas)
} else {
isType(value, type, metas)
}
}

override fun visitExprIsTrue(node: Expr.IsTrue, ctx: Ctx): PartiqlAst.PartiqlAstNode {
error("IS [ NOT ] TRUE is not supported in the legacy AST")
}

override fun visitExprIsFalse(node: Expr.IsFalse, ctx: Ctx): PartiqlAst.PartiqlAstNode {
error("IS [ NOT ] FALSE is not supported in the legacy AST")
}

override fun visitExprIsUnknown(node: Expr.IsUnknown, ctx: Ctx): PartiqlAst.PartiqlAstNode {
error("IS [ NOT ] UNKNOWN is not supported in the legacy AST")
}

override fun visitExprCase(node: Expr.Case, ctx: Ctx) = translate(node) { metas ->
val cases = exprPairList(node.branches.translate<PartiqlAst.ExprPair>(ctx))
val condition = visitOrNull<PartiqlAst.Expr>(node.expr, ctx)
Expand Down
32 changes: 26 additions & 6 deletions partiql-ast/src/main/kotlin/org/partiql/ast/sql/SqlDialect.kt
Original file line number Diff line number Diff line change
Expand Up @@ -376,12 +376,32 @@ public abstract class SqlDialect : AstBaseVisitor<SqlBlock, SqlBlock>() {
return h
}

override fun visitExprIsType(node: Expr.IsType, head: SqlBlock): SqlBlock {
var h = head
h = visitExprWrapped(node.value, h)
h = h concat if (node.not == true) r(" IS NOT ") else r(" IS ")
h = visitType(node.type, h)
return h
override fun visitExprIsNull(node: Expr.IsNull, head: SqlBlock): SqlBlock {
return visitExprIsType(node.value, node.not, "NULL", head)
}

override fun visitExprIsMissing(node: Expr.IsMissing, head: SqlBlock): SqlBlock {
return visitExprIsType(node.value, node.not, "MISSING", head)
}

override fun visitExprIsTrue(node: Expr.IsTrue, head: SqlBlock): SqlBlock {
return visitExprIsType(node.value, node.not, "TRUE", head)
}

override fun visitExprIsFalse(node: Expr.IsFalse, head: SqlBlock): SqlBlock {
return visitExprIsType(node.value, node.not, "FALSE", head)
}

override fun visitExprIsUnknown(node: Expr.IsUnknown, head: SqlBlock): SqlBlock {
return visitExprIsType(node.value, node.not, "UNKNOWN", head)
}

private fun visitExprIsType(value: Expr, not: Boolean?, rhs: String, head: SqlBlock): SqlBlock {
var t = head
t = visitExprWrapped(value, t)
t = t concat if (not == true) " IS NOT " else " IS "
t = t concat rhs
return t
}

override fun visitExprCase(node: Expr.Case, head: SqlBlock): SqlBlock {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -404,11 +404,31 @@ internal abstract class InternalSqlDialect : AstBaseVisitor<InternalSqlBlock, In
return t
}

override fun visitExprIsType(node: Expr.IsType, tail: InternalSqlBlock): InternalSqlBlock {
override fun visitExprIsNull(node: Expr.IsNull, tail: InternalSqlBlock): InternalSqlBlock {
return visitExprIsType(node.value, node.not, "NULL", tail)
}

override fun visitExprIsMissing(node: Expr.IsMissing, tail: InternalSqlBlock): InternalSqlBlock {
return visitExprIsType(node.value, node.not, "MISSING", tail)
}

override fun visitExprIsTrue(node: Expr.IsTrue, tail: InternalSqlBlock): InternalSqlBlock {
return visitExprIsType(node.value, node.not, "TRUE", tail)
}

override fun visitExprIsFalse(node: Expr.IsFalse, tail: InternalSqlBlock): InternalSqlBlock {
return visitExprIsType(node.value, node.not, "FALSE", tail)
}

override fun visitExprIsUnknown(node: Expr.IsUnknown, tail: InternalSqlBlock): InternalSqlBlock {
return visitExprIsType(node.value, node.not, "UNKNOWN", tail)
}

private fun visitExprIsType(value: Expr, not: Boolean?, rhs: String, tail: InternalSqlBlock): InternalSqlBlock {
var t = tail
t = visitExprWrapped(node.value, t)
t = t concat if (node.not == true) " IS NOT " else " IS "
t = visitType(node.type, t)
t = visitExprWrapped(value, t)
t = t concat if (not == true) " IS NOT " else " IS "
t = t concat rhs
return t
}

Expand Down
29 changes: 26 additions & 3 deletions partiql-ast/src/main/resources/partiql_ast.ion
Original file line number Diff line number Diff line change
Expand Up @@ -434,10 +434,33 @@ expr::[
not: optional::bool,
},

// PartiQL special form `IS [NOT]`
is_type::{
// SQL special form `IS [NOT] NULL`
is_null::{
value: expr,
not: optional::bool,
},

// PartiQL special form `IS [NOT] MISSING`
is_missing::{
value: expr,
not: optional::bool,
},

// SQL special form `IS [NOT] TRUE`
is_true::{
value: expr,
not: optional::bool,
},

// SQL special form `IS [NOT] FALSE`
is_false::{
value: expr,
not: optional::bool,
},

// SQL special form `IS [NOT] UNKNOWN`
is_unknown::{
value: expr,
type: '.type',
not: optional::bool,
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -485,16 +485,25 @@ class ToLegacyAstTest {
not = true
}
},
expect("(is_type (lit 'a') (any_type))") {
exprIsType {
expect("(is_type (lit 'a') (null_type))") {
exprIsNull {
value = exprLit(symbolValue(("a")))
type = typeAny()
}
},
expect("(not (is_type (lit 'a') (any_type)))") {
exprIsType {
expect("(not (is_type (lit 'a') (null_type)))") {
exprIsNull {
value = exprLit(symbolValue(("a")))
not = true
}
},
expect("(is_type (lit 'a') (missing_type))") {
exprIsMissing {
value = exprLit(symbolValue(("a")))
}
},
expect("(not (is_type (lit 'a') (missing_type)))") {
exprIsMissing {
value = exprLit(symbolValue(("a")))
type = typeAny()
not = true
}
},
Expand Down
21 changes: 15 additions & 6 deletions partiql-ast/src/test/kotlin/org/partiql/ast/sql/SqlDialectTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -791,16 +791,25 @@ class SqlDialectTest {
not = true
}
},
expect("x IS BOOL") {
exprIsType {
expect("x IS TRUE") {
exprIsTrue {
value = v("x")
type = typeBool()
}
},
expect("x IS NOT BOOL") {
exprIsType {
expect("x IS NOT TRUE") {
exprIsTrue {
value = v("x")
not = true
}
},
expect("x IS UNKNOWN") {
exprIsUnknown {
value = v("x")
}
},
expect("x IS NOT UNKNOWN") {
exprIsUnknown {
value = v("x")
type = typeBool()
not = true
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import org.partiql.eval.internal.Record
import org.partiql.eval.value.Datum
import org.partiql.spi.fn.Agg
import org.partiql.spi.fn.FnExperimental
import org.partiql.value.PartiQLValueExperimental

internal sealed interface Operator {

Expand All @@ -14,7 +13,6 @@ internal sealed interface Operator {
*/
interface Expr : Operator {

@OptIn(PartiQLValueExperimental::class)
fun eval(env: Environment): Datum
}

Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.partiql.eval.internal.operator.rex

import org.partiql.eval.internal.Environment
import org.partiql.eval.internal.operator.Operator
import org.partiql.eval.value.Datum

/**
* Returns true IFF value is the MISSING value.
*/
internal class ExprIsMissing(value: Operator.Expr) : Operator.Expr {

/**
* Left-hand-side of IS MISSING catches typing errors.
*/
private val inner = ExprPermissive(value)

override fun eval(env: Environment): Datum {
val v = inner.eval(env)
return Datum.boolValue(v.isMissing)
}
}
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -1172,9 +1172,13 @@ internal class PartiQLPigVisitor(
not(inCollection, ctx.NOT().getSourceMetaContainer() + metaContainerOf(LegacyLogicalNotMeta.instance))
}

override fun visitPredicateIs(ctx: PartiQLParser.PredicateIsContext) = PartiqlAst.build {
override fun visitPredicateAbsent(ctx: PartiQLParser.PredicateAbsentContext) = PartiqlAst.build {
val lhs = visit(ctx.lhs) as PartiqlAst.Expr
val rhs = visit(ctx.type()) as PartiqlAst.Type
val rhs = when (ctx.absent.type) {
PartiQLParser.NULL -> nullType()
PartiQLParser.MISSING -> missingType()
else -> error("Unexpected value for absent predicate IS [NULL|MISSING]")
}
val isType = isType(lhs, rhs, ctx.IS().getSourceMetaContainer())
if (ctx.NOT() == null) return@build isType
not(isType, ctx.NOT().getSourceMetaContainer() + metaContainerOf(LegacyLogicalNotMeta.instance))
Expand Down
12 changes: 9 additions & 3 deletions partiql-parser/src/main/antlr/PartiQLParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ joinType
* 4. Addition, Subtraction (ex: a + b)
* 5. Other operators (ex: a || b, a & b)
* 6. Predicates (ex: a LIKE b, a < b, a IN b, a = b)
* 7. IS true/false. Not yet implemented in PartiQL, but defined in SQL-92. (ex: a IS TRUE)
* 7. IS TRUE|FALSE|UNKNOWN
* 8. NOT (ex: NOT a)
* 8. AND (ex: a AND b)
* 9. OR (ex: a OR b)
Expand Down Expand Up @@ -596,12 +596,18 @@ exprAnd

exprNot
: <assoc=right> op=NOT rhs=exprNot # Not
| parent=exprPredicate # ExprNotBase
| parent=exprTest # ExprNotBase
;

exprTest
: exprTest IS NOT? value=(TRUE|FALSE|UNKNOWN) # Test
| parent=exprPredicate # ExprTestBase
;

exprPredicate
: lhs=exprPredicate op=comparisonOp rhs=mathOp00 # PredicateComparison
| lhs=exprPredicate IS NOT? type # PredicateIs
| lhs=exprPredicate IS NOT? absent=(NULL|MISSING) # PredicateAbsent
| lhs=exprPredicate IS NOT? type # PredicateType
| lhs=exprPredicate NOT? IN PAREN_LEFT expr PAREN_RIGHT # PredicateIn
| lhs=exprPredicate NOT? IN rhs=mathOp00 # PredicateIn
| lhs=exprPredicate NOT? LIKE rhs=mathOp00 ( ESCAPE escape=expr )? # PredicateLike
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,11 @@ import org.partiql.ast.exprDateDiff
import org.partiql.ast.exprExtract
import org.partiql.ast.exprInCollection
import org.partiql.ast.exprIon
import org.partiql.ast.exprIsType
import org.partiql.ast.exprIsFalse
import org.partiql.ast.exprIsMissing
import org.partiql.ast.exprIsNull
import org.partiql.ast.exprIsTrue
import org.partiql.ast.exprIsUnknown
import org.partiql.ast.exprLike
import org.partiql.ast.exprLit
import org.partiql.ast.exprMatch
Expand Down Expand Up @@ -1583,6 +1587,17 @@ internal class PartiQLParserDefault : PartiQLParser {
exprNot(expr)
}

override fun visitTest(ctx: GeneratedParser.TestContext) = translate(ctx) {
val expr = visit(ctx.exprTest()) as Expr
val not: Boolean = ctx.NOT() != null
when (ctx.value.type) {
GeneratedParser.TRUE -> exprIsTrue(expr, not)
GeneratedParser.FALSE -> exprIsFalse(expr, not)
GeneratedParser.UNKNOWN -> exprIsUnknown(expr, not)
else -> throw error(ctx, "Unexpected value for boolean test IS [TRUE|FALSE|UNKNOWN]")
}
}

private fun checkForInvalidTokens(op: ParserRuleContext) {
val start = op.start.tokenIndex
val stop = op.stop.tokenIndex
Expand Down Expand Up @@ -1668,11 +1683,22 @@ internal class PartiQLParserDefault : PartiQLParser {
exprInCollection(lhs, rhs, not)
}

override fun visitPredicateIs(ctx: GeneratedParser.PredicateIsContext) = translate(ctx) {
override fun visitPredicateAbsent(ctx: GeneratedParser.PredicateAbsentContext) = translate(ctx) {
val value = visitAs<Expr>(ctx.lhs)
val not = ctx.NOT() != null
when (ctx.absent.type) {
GeneratedParser.NULL -> exprIsNull(value, not)
GeneratedParser.MISSING -> exprIsMissing(value, not)
else -> throw error(ctx, "Unexpected value for absent predicate IS [NULL|MISSING]")
}
}

override fun visitPredicateType(ctx: GeneratedParser.PredicateTypeContext) = translate(ctx) {
val value = visitAs<Expr>(ctx.lhs)
val type = visitAs<Type>(ctx.type()).also { isValidTypeParameterOrThrow(it, ctx.type()) }
val not = ctx.NOT() != null
exprIsType(value, type, not)
TODO()
// exprIsType(value, type, not)
}

override fun visitPredicateBetween(ctx: GeneratedParser.PredicateBetweenContext) = translate(ctx) {
Expand Down
Loading

0 comments on commit 084c49f

Please sign in to comment.