diff --git a/CHANGELOG.md b/CHANGELOG.md index 1172bba0a1..bed6f1c485 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,8 @@ Thank you to all who have contributed! ### Added - Adds `org.partiql.value` (experimental) package for reading/writing PartiQL values +- Adds PartiQL's Timestamp Data Model. +- Adds support for Timestamp constructor call in Parser. ### Changed @@ -45,6 +47,7 @@ Thank you to all who have contributed! ### Contributors Thank you to all who have contributed! - @howero +- @yliuuuu - @ ## [0.12.0] - 2023-06-14 diff --git a/partiql-ast/src/main/pig/partiql.ion b/partiql-ast/src/main/pig/partiql.ion index 10a3803a61..8734632406 100644 --- a/partiql-ast/src/main/pig/partiql.ion +++ b/partiql-ast/src/main/pig/partiql.ion @@ -140,6 +140,7 @@ may then be further optimized by selecting better implementations of each operat // Constructors for DateTime types (date year::int month::int day::int) (lit_time value::time_value) + (timestamp value::timestamp_value) // Bag operators (bag_op op::bag_op_type quantifier::set_quantifier operands::(* expr 2)) @@ -178,6 +179,19 @@ may then be further optimized by selecting better implementations of each operat // Time (product time_value hour::int minute::int second::int nano::int precision::int with_time_zone::bool tz_minutes::(? int)) + // Timestamp + (product timestamp_value year::int month::int day::int hour::int minute::int second::ion timezone::(? timezone) precision::(? int) ) + + (sum timezone + // Unknown time zone -00:00 + (unknown_timezone) + // UTC offset ex: -01:30 -> tz_hour = -1, tz_minutes = -30, total offset minutes = -90 + // tz_hour is in [-23, 23], timezone minutes is in [-59,59] + // we only need total offset minutes to model this property + (utc_offset offset_minutes::int) + // TODO: Timezone ID not support yet. Ex: US/Pacific + ) + // A "step" within a path expression; that is the components of the expression following the root. (sum path_step // `someRoot[]`, or `someRoot.someField` which is equivalent to `someRoot['someField']`. @@ -535,9 +549,6 @@ may then be further optimized by selecting better implementations of each operat // `NUMERIC[( [, int])]`. Elements are precision then scale. (numeric_type precision::(? int) scale::(? int)) - // `TIMESTAMP` - (timestamp_type) - // `CHAR()` (character_type length::(? int)) @@ -557,6 +568,8 @@ may then be further optimized by selecting better implementations of each operat // Note: This logic is implemented in SqlParser. (time_type precision::(? int)) (time_with_time_zone_type precision::(? int)) + (timestamp_type precision::(? int)) + (timestamp_with_time_zone_type precision::(? int)) (struct_type) (tuple_type) diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/errors/ErrorCode.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/errors/ErrorCode.kt index 3d043897ce..91a1e6720e 100644 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/errors/ErrorCode.kt +++ b/partiql-lang/src/main/kotlin/org/partiql/lang/errors/ErrorCode.kt @@ -143,6 +143,13 @@ enum class ErrorCode( "invalid precision used for TIME type" ), + // TODO: We should combine this with the above + PARSE_INVALID_PRECISION_FOR_TIMESTAMP( + ErrorCategory.PARSER, + LOC_TOKEN, + "invalid precision used for TIMESTAMP type" + ), + PARSE_INVALID_DATE_STRING( ErrorCategory.PARSER, LOC_TOKEN, @@ -155,6 +162,12 @@ enum class ErrorCode( "expected time string to be of the format HH:MM:SS[.dddd...][+|-HH:MM]" ), + PARSE_INVALID_DATETIME_STRING( + ErrorCategory.PARSER, + LOC_TOKEN, + "Invalid timestamp string" + ), + PARSE_INVALID_TRIM_SPEC( ErrorCategory.PARSER, LOC_TOKEN, diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/EvaluatingCompiler.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/EvaluatingCompiler.kt index dd495f1e71..ca336d85ad 100644 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/EvaluatingCompiler.kt +++ b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/EvaluatingCompiler.kt @@ -464,6 +464,7 @@ internal class EvaluatingCompiler( is PartiqlAst.Expr.GraphMatch -> compileGraphMatch(expr, metas) is PartiqlAst.Expr.CallWindow -> TODO("Evaluating Compiler doesn't support window function") + is PartiqlAst.Expr.Timestamp -> TODO() } } diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompilerImpl.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompilerImpl.kt index 0db694dd14..32eedd435e 100644 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompilerImpl.kt +++ b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompilerImpl.kt @@ -279,6 +279,7 @@ internal class PhysicalPlanCompilerImpl( is PartiqlPhysical.Expr.BindingsToValues -> compileBindingsToValues(expr) is PartiqlPhysical.Expr.Pivot -> compilePivot(expr, metas) is PartiqlPhysical.Expr.GraphMatch -> TODO("Physical compilation of GraphMatch expression") + is PartiqlPhysical.Expr.Timestamp -> TODO() } } diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/SubqueryCoercionVisitorTransform.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/SubqueryCoercionVisitorTransform.kt index beb8fa1d14..969f5da980 100644 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/SubqueryCoercionVisitorTransform.kt +++ b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/SubqueryCoercionVisitorTransform.kt @@ -101,6 +101,7 @@ class SubqueryCoercionVisitorTransform : VisitorTransformBase() { is PartiqlAst.Expr.CanLosslessCast -> n is PartiqlAst.Expr.NullIf -> n is PartiqlAst.Expr.Coalesce -> n + is PartiqlAst.Expr.Timestamp -> TODO() } } diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/ASTPrettyPrinter.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/ASTPrettyPrinter.kt index c2276105b5..87657084aa 100644 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/ASTPrettyPrinter.kt +++ b/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/ASTPrettyPrinter.kt @@ -577,6 +577,7 @@ class ASTPrettyPrinter { ) is PartiqlAst.Expr.GraphMatch -> TODO("Unsupported GraphMatch AST node") is PartiqlAst.Expr.CallWindow -> TODO("PrettyPrinter doesn't support Window Function yet.") + is PartiqlAst.Expr.Timestamp -> TODO() } private fun toRecursionTreeList(nodes: List, attrOfParent: String? = null): List = diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/QueryPrettyPrinter.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/QueryPrettyPrinter.kt index e66024154e..788954e78a 100644 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/QueryPrettyPrinter.kt +++ b/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/QueryPrettyPrinter.kt @@ -328,6 +328,7 @@ class QueryPrettyPrinter { is PartiqlAst.Expr.CallWindow -> TODO() is PartiqlAst.Expr.GraphMatch -> TODO() is PartiqlAst.Expr.SessionAttribute -> writeSessionAttribute(node, sb) + is PartiqlAst.Expr.Timestamp -> TODO() } } diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt index a505a8bc4a..ac74588c37 100644 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt +++ b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt @@ -54,6 +54,7 @@ import org.partiql.lang.errors.PropertyValueMap import org.partiql.lang.eval.EvaluationException import org.partiql.lang.eval.time.MAX_PRECISION_FOR_TIME import org.partiql.lang.syntax.ParserException +import org.partiql.lang.syntax.util.DateTimeUtils import org.partiql.lang.types.CustomType import org.partiql.lang.util.DATE_PATTERN_REGEX import org.partiql.lang.util.bigDecimalOf @@ -64,6 +65,8 @@ import org.partiql.lang.util.unaryMinus import org.partiql.parser.antlr.PartiQLBaseVisitor import org.partiql.parser.antlr.PartiQLParser import org.partiql.pig.runtime.SymbolPrimitive +import org.partiql.value.datetime.DateTimeException +import org.partiql.value.datetime.TimeZone import java.math.BigInteger import java.time.LocalDate import java.time.LocalTime @@ -77,7 +80,10 @@ import kotlin.reflect.cast * Extends ANTLR's generated [PartiQLBaseVisitor] to visit an ANTLR ParseTree and convert it into a PartiQL AST. This * class uses the [PartiqlAst.PartiqlAstNode] to represent all nodes within the new AST. */ -internal class PartiQLPigVisitor(val customTypes: List = listOf(), private val parameterIndexes: Map = mapOf()) : +internal class PartiQLPigVisitor( + val customTypes: List = listOf(), + private val parameterIndexes: Map = mapOf() +) : PartiQLBaseVisitor() { companion object { @@ -124,8 +130,13 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p throw option.param.error("Unknown EXPLAIN parameter.", ErrorCode.PARSE_UNEXPECTED_TOKEN, cause = ex) } when (parameter) { - ExplainParameters.TYPE -> { type = parameter.getCompliantString(type, option.value) } - ExplainParameters.FORMAT -> { format = parameter.getCompliantString(format, option.value) } + ExplainParameters.TYPE -> { + type = parameter.getCompliantString(type, option.value) + } + + ExplainParameters.FORMAT -> { + format = parameter.getCompliantString(format, option.value) + } } } explain( @@ -155,7 +166,13 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p override fun visitSymbolPrimitive(ctx: PartiQLParser.SymbolPrimitiveContext) = PartiqlAst.build { val metas = ctx.ident.getSourceMetaContainer() when (ctx.ident.type) { - PartiQLParser.IDENTIFIER_QUOTED -> id(ctx.IDENTIFIER_QUOTED().getStringValue(), caseSensitive(), unqualified(), metas) + PartiQLParser.IDENTIFIER_QUOTED -> id( + ctx.IDENTIFIER_QUOTED().getStringValue(), + caseSensitive(), + unqualified(), + metas + ) + PartiQLParser.IDENTIFIER -> id(ctx.IDENTIFIER().getStringValue(), caseInsensitive(), unqualified(), metas) else -> throw ParserException("Invalid symbol reference.", ErrorCode.PARSE_INVALID_QUERY) } @@ -280,7 +297,13 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p val from = visit(ctx.fromClauseSimple(), PartiqlAst.FromSource::class) val where = visitOrNull(ctx.whereClause(), PartiqlAst.Expr::class) val returning = visitOrNull(ctx.returningClause(), PartiqlAst.ReturningExpr::class) - dml(dmlOpList(delete(ctx.DELETE().getSourceMetaContainer()), metas = ctx.DELETE().getSourceMetaContainer()), from, where, returning, ctx.DELETE().getSourceMetaContainer()) + dml( + dmlOpList(delete(ctx.DELETE().getSourceMetaContainer()), metas = ctx.DELETE().getSourceMetaContainer()), + from, + where, + returning, + ctx.DELETE().getSourceMetaContainer() + ) } override fun visitInsertStatementLegacy(ctx: PartiQLParser.InsertStatementLegacyContext) = PartiqlAst.build { @@ -331,7 +354,13 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p val returning = visitOrNull(ctx.returningClause(), PartiqlAst.ReturningExpr::class) dml( dmlOpList( - insertValue(target, visit(ctx.value, PartiqlAst.Expr::class), index = index, onConflict = onConflictLegacy, ctx.INSERT().getSourceMetaContainer()), + insertValue( + target, + visit(ctx.value, PartiqlAst.Expr::class), + index = index, + onConflict = onConflictLegacy, + ctx.INSERT().getSourceMetaContainer() + ), metas = metas ), returning = returning, @@ -367,7 +396,11 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p } override fun visitOnConflictLegacy(ctx: PartiQLParser.OnConflictLegacyContext) = PartiqlAst.build { - onConflict(expr = visitExpr(ctx.expr()), conflictAction = doNothing(), metas = ctx.ON().getSourceMetaContainer()) + onConflict( + expr = visitExpr(ctx.expr()), + conflictAction = doNothing(), + metas = ctx.ON().getSourceMetaContainer() + ) } override fun visitConflictAction(ctx: PartiQLParser.ConflictActionContext) = PartiqlAst.build { @@ -412,7 +445,8 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p pathExpr(visitSymbolPrimitive(ctx.symbolPrimitive()), caseSensitive()) } - override fun visitPathSimpleDotSymbol(ctx: PartiQLParser.PathSimpleDotSymbolContext) = getSymbolPathExpr(ctx.symbolPrimitive()) + override fun visitPathSimpleDotSymbol(ctx: PartiQLParser.PathSimpleDotSymbolContext) = + getSymbolPathExpr(ctx.symbolPrimitive()) override fun visitSetCommand(ctx: PartiQLParser.SetCommandContext) = PartiqlAst.build { val assignments = visitOrEmpty(ctx.setAssignment(), PartiqlAst.DmlOp.Set::class) @@ -424,7 +458,8 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p set(assignment(visitPathSimple(ctx.pathSimple()), visitExpr(ctx.expr()))) } - override fun visitUpdateClause(ctx: PartiQLParser.UpdateClauseContext) = visit(ctx.tableBaseReference(), PartiqlAst.FromSource::class) + override fun visitUpdateClause(ctx: PartiQLParser.UpdateClauseContext) = + visit(ctx.tableBaseReference(), PartiqlAst.FromSource::class) /** * @@ -472,18 +507,20 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p * */ - override fun visitSetQuantifierStrategy(ctx: PartiQLParser.SetQuantifierStrategyContext?): PartiqlAst.SetQuantifier? = when { - ctx == null -> null - ctx.DISTINCT() != null -> PartiqlAst.SetQuantifier.Distinct() - ctx.ALL() != null -> PartiqlAst.SetQuantifier.All() - else -> null - } + override fun visitSetQuantifierStrategy(ctx: PartiQLParser.SetQuantifierStrategyContext?): PartiqlAst.SetQuantifier? = + when { + ctx == null -> null + ctx.DISTINCT() != null -> PartiqlAst.SetQuantifier.Distinct() + ctx.ALL() != null -> PartiqlAst.SetQuantifier.All() + else -> null + } override fun visitSelectAll(ctx: PartiQLParser.SelectAllContext) = PartiqlAst.build { projectStar(ctx.ASTERISK().getSourceMetaContainer()) } - override fun visitSelectItems(ctx: PartiQLParser.SelectItemsContext) = convertProjectionItems(ctx.projectionItems(), ctx.SELECT().getSourceMetaContainer()) + override fun visitSelectItems(ctx: PartiQLParser.SelectItemsContext) = + convertProjectionItems(ctx.projectionItems(), ctx.SELECT().getSourceMetaContainer()) override fun visitSelectPivot(ctx: PartiQLParser.SelectPivotContext) = PartiqlAst.build { projectPivot(visitExpr(ctx.at), visitExpr(ctx.pivot)) @@ -506,7 +543,8 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p * */ - override fun visitLimitClause(ctx: PartiQLParser.LimitClauseContext): PartiqlAst.Expr = visit(ctx.arg, PartiqlAst.Expr::class) + override fun visitLimitClause(ctx: PartiQLParser.LimitClauseContext): PartiqlAst.Expr = + visit(ctx.arg, PartiqlAst.Expr::class) override fun visitExpr(ctx: PartiQLParser.ExprContext): PartiqlAst.Expr { checkThreadInterrupted() @@ -517,7 +555,8 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p override fun visitWhereClause(ctx: PartiQLParser.WhereClauseContext) = visitExpr(ctx.arg) - override fun visitWhereClauseSelect(ctx: PartiQLParser.WhereClauseSelectContext) = visit(ctx.arg, PartiqlAst.Expr::class) + override fun visitWhereClauseSelect(ctx: PartiQLParser.WhereClauseSelectContext) = + visit(ctx.arg, PartiqlAst.Expr::class) override fun visitHavingClause(ctx: PartiQLParser.HavingClauseContext) = visit(ctx.arg, PartiqlAst.Expr::class) @@ -672,7 +711,8 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p graphMatchPattern_(parts = parts, restrictor = restrictor, variable = variable) } - override fun visitPatternPathVariable(ctx: PartiQLParser.PatternPathVariableContext) = visitSymbolPrimitive(ctx.symbolPrimitive()) + override fun visitPatternPathVariable(ctx: PartiQLParser.PatternPathVariableContext) = + visitSymbolPrimitive(ctx.symbolPrimitive()) override fun visitSelectorBasic(ctx: PartiQLParser.SelectorBasicContext) = PartiqlAst.build { val metas = ctx.mod.getSourceMetaContainer() @@ -700,7 +740,8 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p } } - override fun visitPatternPartLabel(ctx: PartiQLParser.PatternPartLabelContext) = visitSymbolPrimitive(ctx.symbolPrimitive()) + override fun visitPatternPartLabel(ctx: PartiQLParser.PatternPartLabelContext) = + visitSymbolPrimitive(ctx.symbolPrimitive()) override fun visitPattern(ctx: PartiQLParser.PatternContext) = PartiqlAst.build { val restrictor = visitOrNull(ctx.restrictor, PartiqlAst.GraphMatchRestrictor::class) @@ -709,7 +750,13 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p val quantifier = visitOrNull(ctx.quantifier, PartiqlAst.GraphMatchQuantifier::class) val parts = visitOrEmpty(ctx.graphPart(), PartiqlAst.GraphMatchPatternPart::class) pattern( - graphMatchPattern_(parts = parts, variable = variable, restrictor = restrictor, quantifier = quantifier, prefilter = prefilter) + graphMatchPattern_( + parts = parts, + variable = variable, + restrictor = restrictor, + quantifier = quantifier, + prefilter = prefilter + ) ) } @@ -730,7 +777,12 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p val variable = visitOrNull(ctx.symbolPrimitive(), PartiqlAst.Expr.Id::class)?.name val prefilter = visitOrNull(ctx.whereClause(), PartiqlAst.Expr::class) val label = visitOrNull(ctx.patternPartLabel(), PartiqlAst.Expr.Id::class)?.name - edge_(direction = placeholderDirection, variable = variable, prefilter = prefilter, label = listOfNotNull(label)) + edge_( + direction = placeholderDirection, + variable = variable, + prefilter = prefilter, + label = listOfNotNull(label) + ) } override fun visitEdgeSpecLeft(ctx: PartiQLParser.EdgeSpecLeftContext) = PartiqlAst.build { @@ -748,10 +800,11 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p edge.copy(direction = edgeLeftOrRight()) } - override fun visitEdgeSpecUndirectedBidirectional(ctx: PartiQLParser.EdgeSpecUndirectedBidirectionalContext) = PartiqlAst.build { - val edge = visitEdgeSpec(ctx.edgeSpec()) - edge.copy(direction = edgeLeftOrUndirectedOrRight()) - } + override fun visitEdgeSpecUndirectedBidirectional(ctx: PartiQLParser.EdgeSpecUndirectedBidirectionalContext) = + PartiqlAst.build { + val edge = visitEdgeSpec(ctx.edgeSpec()) + edge.copy(direction = edgeLeftOrUndirectedOrRight()) + } override fun visitEdgeSpecUndirected(ctx: PartiQLParser.EdgeSpecUndirectedContext) = PartiqlAst.build { val edge = visitEdgeSpec(ctx.edgeSpec()) @@ -813,31 +866,68 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p * */ - override fun visitFromClause(ctx: PartiQLParser.FromClauseContext) = visit(ctx.tableReference(), PartiqlAst.FromSource::class) + override fun visitFromClause(ctx: PartiQLParser.FromClauseContext) = + visit(ctx.tableReference(), PartiqlAst.FromSource::class) override fun visitTableBaseRefClauses(ctx: PartiQLParser.TableBaseRefClausesContext) = PartiqlAst.build { val expr = visit(ctx.source, PartiqlAst.Expr::class) - val (asAlias, atAlias, byAlias) = visitNullableItems(listOf(ctx.asIdent(), ctx.atIdent(), ctx.byIdent()), PartiqlAst.Expr.Id::class) - scan_(expr, asAlias = asAlias.toPigSymbolPrimitive(), byAlias = byAlias.toPigSymbolPrimitive(), atAlias = atAlias.toPigSymbolPrimitive(), metas = expr.metas) + val (asAlias, atAlias, byAlias) = visitNullableItems( + listOf(ctx.asIdent(), ctx.atIdent(), ctx.byIdent()), + PartiqlAst.Expr.Id::class + ) + scan_( + expr, + asAlias = asAlias.toPigSymbolPrimitive(), + byAlias = byAlias.toPigSymbolPrimitive(), + atAlias = atAlias.toPigSymbolPrimitive(), + metas = expr.metas + ) } override fun visitTableBaseRefMatch(ctx: PartiQLParser.TableBaseRefMatchContext) = PartiqlAst.build { val expr = visit(ctx.source, PartiqlAst.Expr::class) - val (asAlias, atAlias, byAlias) = visitNullableItems(listOf(ctx.asIdent(), ctx.atIdent(), ctx.byIdent()), PartiqlAst.Expr.Id::class) - scan_(expr, asAlias = asAlias.toPigSymbolPrimitive(), byAlias = byAlias.toPigSymbolPrimitive(), atAlias = atAlias.toPigSymbolPrimitive(), metas = expr.metas) + val (asAlias, atAlias, byAlias) = visitNullableItems( + listOf(ctx.asIdent(), ctx.atIdent(), ctx.byIdent()), + PartiqlAst.Expr.Id::class + ) + scan_( + expr, + asAlias = asAlias.toPigSymbolPrimitive(), + byAlias = byAlias.toPigSymbolPrimitive(), + atAlias = atAlias.toPigSymbolPrimitive(), + metas = expr.metas + ) } override fun visitFromClauseSimpleExplicit(ctx: PartiQLParser.FromClauseSimpleExplicitContext) = PartiqlAst.build { val expr = visitPathSimple(ctx.pathSimple()) - val (asAlias, atAlias, byAlias) = visitNullableItems(listOf(ctx.asIdent(), ctx.atIdent(), ctx.byIdent()), PartiqlAst.Expr.Id::class) - scan_(expr, asAlias = asAlias.toPigSymbolPrimitive(), byAlias = byAlias.toPigSymbolPrimitive(), atAlias = atAlias.toPigSymbolPrimitive(), metas = expr.metas) + val (asAlias, atAlias, byAlias) = visitNullableItems( + listOf(ctx.asIdent(), ctx.atIdent(), ctx.byIdent()), + PartiqlAst.Expr.Id::class + ) + scan_( + expr, + asAlias = asAlias.toPigSymbolPrimitive(), + byAlias = byAlias.toPigSymbolPrimitive(), + atAlias = atAlias.toPigSymbolPrimitive(), + metas = expr.metas + ) } override fun visitTableUnpivot(ctx: PartiQLParser.TableUnpivotContext) = PartiqlAst.build { val expr = visitExpr(ctx.expr()) val metas = ctx.UNPIVOT().getSourceMetaContainer() - val (asAlias, atAlias, byAlias) = visitNullableItems(listOf(ctx.asIdent(), ctx.atIdent(), ctx.byIdent()), PartiqlAst.Expr.Id::class) - unpivot_(expr, asAlias = asAlias.toPigSymbolPrimitive(), atAlias = atAlias.toPigSymbolPrimitive(), byAlias = byAlias.toPigSymbolPrimitive(), metas) + val (asAlias, atAlias, byAlias) = visitNullableItems( + listOf(ctx.asIdent(), ctx.atIdent(), ctx.byIdent()), + PartiqlAst.Expr.Id::class + ) + unpivot_( + expr, + asAlias = asAlias.toPigSymbolPrimitive(), + atAlias = atAlias.toPigSymbolPrimitive(), + byAlias = byAlias.toPigSymbolPrimitive(), + metas + ) } override fun visitTableCrossJoin(ctx: PartiQLParser.TableCrossJoinContext) = PartiqlAst.build { @@ -868,7 +958,8 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p scan_(path, name, metas = path.metas) } - override fun visitTableWrapped(ctx: PartiQLParser.TableWrappedContext): PartiqlAst.PartiqlAstNode = visit(ctx.tableReference()) + override fun visitTableWrapped(ctx: PartiQLParser.TableWrappedContext): PartiqlAst.PartiqlAstNode = + visit(ctx.tableReference()) override fun visitJoinSpec(ctx: PartiQLParser.JoinSpecContext) = visitExpr(ctx.expr()) @@ -885,7 +976,8 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p } } - override fun visitJoinRhsTableJoined(ctx: PartiQLParser.JoinRhsTableJoinedContext) = visit(ctx.tableReference(), PartiqlAst.FromSource::class) + override fun visitJoinRhsTableJoined(ctx: PartiQLParser.JoinRhsTableJoinedContext) = + visit(ctx.tableReference(), PartiqlAst.FromSource::class) /** * SIMPLE EXPRESSIONS @@ -897,13 +989,17 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p override fun visitNot(ctx: PartiQLParser.NotContext) = visitUnaryOperation(ctx.rhs, ctx.op, null) - override fun visitMathOp00(ctx: PartiQLParser.MathOp00Context): PartiqlAst.PartiqlAstNode = visitBinaryOperation(ctx.lhs, ctx.rhs, ctx.op, ctx.parent) + override fun visitMathOp00(ctx: PartiQLParser.MathOp00Context): PartiqlAst.PartiqlAstNode = + visitBinaryOperation(ctx.lhs, ctx.rhs, ctx.op, ctx.parent) - override fun visitMathOp01(ctx: PartiQLParser.MathOp01Context): PartiqlAst.PartiqlAstNode = visitBinaryOperation(ctx.lhs, ctx.rhs, ctx.op, ctx.parent) + override fun visitMathOp01(ctx: PartiQLParser.MathOp01Context): PartiqlAst.PartiqlAstNode = + visitBinaryOperation(ctx.lhs, ctx.rhs, ctx.op, ctx.parent) - override fun visitMathOp02(ctx: PartiQLParser.MathOp02Context): PartiqlAst.PartiqlAstNode = visitBinaryOperation(ctx.lhs, ctx.rhs, ctx.op, ctx.parent) + override fun visitMathOp02(ctx: PartiQLParser.MathOp02Context): PartiqlAst.PartiqlAstNode = + visitBinaryOperation(ctx.lhs, ctx.rhs, ctx.op, ctx.parent) - override fun visitValueExpr(ctx: PartiQLParser.ValueExprContext) = visitUnaryOperation(ctx.rhs, ctx.sign, ctx.parent) + override fun visitValueExpr(ctx: PartiQLParser.ValueExprContext) = + visitUnaryOperation(ctx.rhs, ctx.sign, ctx.parent) /** * @@ -911,7 +1007,8 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p * */ - override fun visitPredicateComparison(ctx: PartiQLParser.PredicateComparisonContext) = visitBinaryOperation(ctx.lhs, ctx.rhs, ctx.op) + override fun visitPredicateComparison(ctx: PartiQLParser.PredicateComparisonContext) = + visitBinaryOperation(ctx.lhs, ctx.rhs, ctx.op) /** * Note: This predicate can take a wrapped expression on the RHS, and it will wrap it in a LIST. However, if the @@ -963,21 +1060,24 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p * */ - override fun visitExprTermWrappedQuery(ctx: PartiQLParser.ExprTermWrappedQueryContext) = visit(ctx.expr(), PartiqlAst.Expr::class) + override fun visitExprTermWrappedQuery(ctx: PartiQLParser.ExprTermWrappedQueryContext) = + visit(ctx.expr(), PartiqlAst.Expr::class) - override fun visitVariableIdentifier(ctx: PartiQLParser.VariableIdentifierContext): PartiqlAst.PartiqlAstNode = PartiqlAst.build { - val metas = ctx.ident.getSourceMetaContainer() - val qualifier = if (ctx.qualifier == null) unqualified() else localsFirst() - val sensitivity = if (ctx.ident.type == PartiQLParser.IDENTIFIER) caseInsensitive() else caseSensitive() - id(ctx.ident.getStringValue(), sensitivity, qualifier, metas) - } + override fun visitVariableIdentifier(ctx: PartiQLParser.VariableIdentifierContext): PartiqlAst.PartiqlAstNode = + PartiqlAst.build { + val metas = ctx.ident.getSourceMetaContainer() + val qualifier = if (ctx.qualifier == null) unqualified() else localsFirst() + val sensitivity = if (ctx.ident.type == PartiQLParser.IDENTIFIER) caseInsensitive() else caseSensitive() + id(ctx.ident.getStringValue(), sensitivity, qualifier, metas) + } - override fun visitVariableKeyword(ctx: PartiQLParser.VariableKeywordContext): PartiqlAst.PartiqlAstNode = PartiqlAst.build { - val keyword = ctx.nonReservedKeywords().start.text - val metas = ctx.start.getSourceMetaContainer() - val qualifier = ctx.qualifier?.let { localsFirst() } ?: unqualified() - id(keyword, caseInsensitive(), qualifier, metas) - } + override fun visitVariableKeyword(ctx: PartiQLParser.VariableKeywordContext): PartiqlAst.PartiqlAstNode = + PartiqlAst.build { + val keyword = ctx.nonReservedKeywords().start.text + val metas = ctx.start.getSourceMetaContainer() + val qualifier = ctx.qualifier?.let { localsFirst() } ?: unqualified() + id(keyword, caseInsensitive(), qualifier, metas) + } override fun visitParameter(ctx: PartiQLParser.ParameterContext) = PartiqlAst.build { val parameterIndex = parameterIndexes[ctx.QUESTION_MARK().symbol.tokenIndex] @@ -1150,7 +1250,8 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p override fun visitExtract(ctx: PartiQLParser.ExtractContext) = PartiqlAst.build { if (DateTimePart.safeValueOf(ctx.IDENTIFIER().text) == null) { - throw ctx.IDENTIFIER().err("Expected one of: ${DateTimePart.values()}", ErrorCode.PARSE_EXPECTED_DATE_TIME_PART) + throw ctx.IDENTIFIER() + .err("Expected one of: ${DateTimePart.values()}", ErrorCode.PARSE_EXPECTED_DATE_TIME_PART) } val datetimePart = lit(ionSymbol(ctx.IDENTIFIER().text)) val timeExpr = visit(ctx.rhs, PartiqlAst.Expr::class) @@ -1176,9 +1277,11 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p if (isTrimSpec) ctx.mod.toSymbol() to null else null to id(possibleModText!!, caseInsensitive(), unqualified(), ctx.mod.getSourceMetaContainer()) } + ctx.mod == null && ctx.sub != null -> { null to visitExpr(ctx.sub) } + ctx.mod != null && ctx.sub != null -> { if (isTrimSpec) ctx.mod.toSymbol() to visitExpr(ctx.sub) // todo we need to decide if it should be an evaluator error or a parser error @@ -1192,6 +1295,7 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p ) } } + else -> null to null } @@ -1231,12 +1335,16 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p val metas = ctx.func.getSourceMetaContainer() callWindow(ctx.func.text.lowercase(), over, args, metas) } + override fun visitOver(ctx: PartiQLParser.OverContext) = PartiqlAst.build { - val windowPartitionList = if (ctx.windowPartitionList() != null) visitWindowPartitionList(ctx.windowPartitionList()) else null - val windowSortSpecList = if (ctx.windowSortSpecList() != null) visitWindowSortSpecList(ctx.windowSortSpecList()) else null + val windowPartitionList = + if (ctx.windowPartitionList() != null) visitWindowPartitionList(ctx.windowPartitionList()) else null + val windowSortSpecList = + if (ctx.windowSortSpecList() != null) visitWindowSortSpecList(ctx.windowSortSpecList()) else null val metas = ctx.OVER().getSourceMetaContainer() over(windowPartitionList, windowSortSpecList, metas) } + override fun visitWindowPartitionList(ctx: PartiQLParser.WindowPartitionListContext) = PartiqlAst.build { val args = visitOrEmpty(ctx.expr(), PartiqlAst.Expr::class) val metas = ctx.PARTITION().getSourceMetaContainer() @@ -1318,7 +1426,8 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p override fun visitLiteralDate(ctx: PartiQLParser.LiteralDateContext) = PartiqlAst.build { val dateString = ctx.LITERAL_STRING().getStringValue() if (DATE_PATTERN_REGEX.matches(dateString).not()) { - throw ctx.LITERAL_STRING().err("Expected DATE string to be of the format yyyy-MM-dd", ErrorCode.PARSE_INVALID_DATE_STRING) + throw ctx.LITERAL_STRING() + .err("Expected DATE string to be of the format yyyy-MM-dd", ErrorCode.PARSE_INVALID_DATE_STRING) } try { LocalDate.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE) @@ -1339,6 +1448,14 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p } } + override fun visitLiteralTimestamp(ctx: PartiQLParser.LiteralTimestampContext): PartiqlAst.PartiqlAstNode { + val (timestamp, precision) = getTimestampStringAndPrecision(ctx.LITERAL_STRING(), ctx.LITERAL_INTEGER()) + return when (ctx.WITH()) { + null -> getTimestampDynamic(timestamp, precision, ctx.LITERAL_STRING()) + else -> getTimestampWithTimezone(timestamp, precision, ctx.LITERAL_STRING()) + } + } + override fun visitTuple(ctx: PartiQLParser.TupleContext) = PartiqlAst.build { val pairs = visitOrEmpty(ctx.pair(), PartiqlAst.ExprPair::class) val metas = ctx.BRACE_LEFT().getSourceMetaContainer() @@ -1375,7 +1492,6 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p PartiQLParser.BIGINT -> integer8Type(metas) PartiQLParser.REAL -> realType(metas) PartiQLParser.DOUBLE -> doublePrecisionType(metas) - PartiQLParser.TIMESTAMP -> timestampType(metas) PartiQLParser.CHAR -> characterType(metas = metas) PartiQLParser.CHARACTER -> characterType(metas = metas) PartiQLParser.MISSING -> missingType(metas) @@ -1431,8 +1547,15 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p if (precision != null && (precision < 0 || precision > MAX_PRECISION_FOR_TIME)) { throw ctx.precision.err("Unsupported precision", ErrorCode.PARSE_INVALID_PRECISION_FOR_TIME) } - if (ctx.WITH() == null) return@build timeType(precision) - timeWithTimeZoneType(precision) + val hasTimeZone = ctx.WITH() != null + when (ctx.datatype.type) { + PartiQLParser.TIME -> if (hasTimeZone) timeWithTimeZoneType(precision) else timeType(precision) + PartiQLParser.TIMESTAMP -> if (hasTimeZone) timestampWithTimeZoneType(precision) else timestampType( + precision + ) + + else -> throw ParserException("Unknown datatype", ErrorCode.PARSE_UNEXPECTED_TOKEN, PropertyValueMap()) + } } override fun visitTypeCustom(ctx: PartiQLParser.TypeCustomContext) = PartiqlAst.build { @@ -1451,17 +1574,34 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p */ override fun visitTerminal(node: TerminalNode?): PartiqlAst.PartiqlAstNode = super.visitTerminal(node) - override fun shouldVisitNextChild(node: RuleNode?, currentResult: PartiqlAst.PartiqlAstNode?) = super.shouldVisitNextChild(node, currentResult) + override fun shouldVisitNextChild(node: RuleNode?, currentResult: PartiqlAst.PartiqlAstNode?) = + super.shouldVisitNextChild(node, currentResult) + override fun visitErrorNode(node: ErrorNode?): PartiqlAst.PartiqlAstNode = super.visitErrorNode(node) override fun visitChildren(node: RuleNode?): PartiqlAst.PartiqlAstNode = super.visitChildren(node) - override fun visitExprPrimaryBase(ctx: PartiQLParser.ExprPrimaryBaseContext?): PartiqlAst.PartiqlAstNode = super.visitExprPrimaryBase(ctx) - override fun visitExprTermBase(ctx: PartiQLParser.ExprTermBaseContext?): PartiqlAst.PartiqlAstNode = super.visitExprTermBase(ctx) - override fun visitCollection(ctx: PartiQLParser.CollectionContext?): PartiqlAst.PartiqlAstNode = super.visitCollection(ctx) - override fun visitPredicateBase(ctx: PartiQLParser.PredicateBaseContext?): PartiqlAst.PartiqlAstNode = super.visitPredicateBase(ctx) - override fun visitTableNonJoin(ctx: PartiQLParser.TableNonJoinContext?): PartiqlAst.PartiqlAstNode = super.visitTableNonJoin(ctx) - override fun visitTableRefBase(ctx: PartiQLParser.TableRefBaseContext?): PartiqlAst.PartiqlAstNode = super.visitTableRefBase(ctx) - override fun visitJoinRhsBase(ctx: PartiQLParser.JoinRhsBaseContext?): PartiqlAst.PartiqlAstNode = super.visitJoinRhsBase(ctx) - override fun visitConflictTarget(ctx: PartiQLParser.ConflictTargetContext?): PartiqlAst.PartiqlAstNode = super.visitConflictTarget(ctx) + override fun visitExprPrimaryBase(ctx: PartiQLParser.ExprPrimaryBaseContext?): PartiqlAst.PartiqlAstNode = + super.visitExprPrimaryBase(ctx) + + override fun visitExprTermBase(ctx: PartiQLParser.ExprTermBaseContext?): PartiqlAst.PartiqlAstNode = + super.visitExprTermBase(ctx) + + override fun visitCollection(ctx: PartiQLParser.CollectionContext?): PartiqlAst.PartiqlAstNode = + super.visitCollection(ctx) + + override fun visitPredicateBase(ctx: PartiQLParser.PredicateBaseContext?): PartiqlAst.PartiqlAstNode = + super.visitPredicateBase(ctx) + + override fun visitTableNonJoin(ctx: PartiQLParser.TableNonJoinContext?): PartiqlAst.PartiqlAstNode = + super.visitTableNonJoin(ctx) + + override fun visitTableRefBase(ctx: PartiQLParser.TableRefBaseContext?): PartiqlAst.PartiqlAstNode = + super.visitTableRefBase(ctx) + + override fun visitJoinRhsBase(ctx: PartiQLParser.JoinRhsBaseContext?): PartiqlAst.PartiqlAstNode = + super.visitJoinRhsBase(ctx) + + override fun visitConflictTarget(ctx: PartiQLParser.ConflictTargetContext?): PartiqlAst.PartiqlAstNode = + super.visitConflictTarget(ctx) /** * @@ -1469,22 +1609,28 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p * */ - private fun visitOrEmpty(ctx: List?, clazz: KClass): List = when { - ctx.isNullOrEmpty() -> emptyList() - else -> ctx.map { clazz.cast(visit(it)) } - } + private fun visitOrEmpty(ctx: List?, clazz: KClass): List = + when { + ctx.isNullOrEmpty() -> emptyList() + else -> ctx.map { clazz.cast(visit(it)) } + } - private fun visitNullableItems(ctx: List?, clazz: KClass): List = when { + private fun visitNullableItems( + ctx: List?, + clazz: KClass + ): List = when { ctx.isNullOrEmpty() -> emptyList() else -> ctx.map { visitOrNull(it, clazz) } } - private fun visitOrNull(ctx: ParserRuleContext?, clazz: KClass): T? = when (ctx) { - null -> null - else -> clazz.cast(visit(ctx)) - } + private fun visitOrNull(ctx: ParserRuleContext?, clazz: KClass): T? = + when (ctx) { + null -> null + else -> clazz.cast(visit(ctx)) + } - private fun visit(ctx: ParserRuleContext, clazz: KClass): T = clazz.cast(visit(ctx)) + private fun visit(ctx: ParserRuleContext, clazz: KClass): T = + clazz.cast(visit(ctx)) private fun TerminalNode?.getSourceMetaContainer(): MetaContainer { if (this == null) return emptyMetaContainer() @@ -1505,7 +1651,12 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p return SourceLocationMeta(this.line.toLong(), this.charPositionInLine.toLong() + 1, length.toLong()) } - private fun visitBinaryOperation(lhs: ParserRuleContext?, rhs: ParserRuleContext?, op: Token?, parent: ParserRuleContext? = null) = PartiqlAst.build { + private fun visitBinaryOperation( + lhs: ParserRuleContext?, + rhs: ParserRuleContext?, + op: Token?, + parent: ParserRuleContext? = null + ) = PartiqlAst.build { if (parent != null) return@build visit(parent, PartiqlAst.Expr::class) val args = visitOrEmpty(listOf(lhs!!, rhs!!), PartiqlAst.Expr::class) val metas = op.getSourceMetaContainer() @@ -1528,47 +1679,54 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p } } - private fun visitUnaryOperation(operand: ParserRuleContext?, op: Token?, parent: ParserRuleContext? = null) = PartiqlAst.build { - if (parent != null) return@build visit(parent, PartiqlAst.Expr::class) - val arg = visit(operand!!, PartiqlAst.Expr::class) - val metas = op.getSourceMetaContainer() - when (op!!.type) { - PartiQLParser.PLUS -> { - when { - arg !is PartiqlAst.Expr.Lit -> pos(arg, metas) - arg.value is IntElement -> arg - arg.value is FloatElement -> arg - arg.value is DecimalElement -> arg - else -> pos(arg, metas) + private fun visitUnaryOperation(operand: ParserRuleContext?, op: Token?, parent: ParserRuleContext? = null) = + PartiqlAst.build { + if (parent != null) return@build visit(parent, PartiqlAst.Expr::class) + val arg = visit(operand!!, PartiqlAst.Expr::class) + val metas = op.getSourceMetaContainer() + when (op!!.type) { + PartiQLParser.PLUS -> { + when { + arg !is PartiqlAst.Expr.Lit -> pos(arg, metas) + arg.value is IntElement -> arg + arg.value is FloatElement -> arg + arg.value is DecimalElement -> arg + else -> pos(arg, metas) + } } - } - PartiQLParser.MINUS -> { - when { - arg !is PartiqlAst.Expr.Lit -> neg(arg, metas) - arg.value is IntElement -> { - val intValue = when (arg.value.integerSize) { - IntElementSize.LONG -> ionInt(-arg.value.longValue) - IntElementSize.BIG_INTEGER -> when (arg.value.bigIntegerValue) { - Long.MAX_VALUE.toBigInteger() + (1L).toBigInteger() -> ionInt(Long.MIN_VALUE) - else -> ionInt(arg.value.bigIntegerValue * BigInteger.valueOf(-1L)) + + PartiQLParser.MINUS -> { + when { + arg !is PartiqlAst.Expr.Lit -> neg(arg, metas) + arg.value is IntElement -> { + val intValue = when (arg.value.integerSize) { + IntElementSize.LONG -> ionInt(-arg.value.longValue) + IntElementSize.BIG_INTEGER -> when (arg.value.bigIntegerValue) { + Long.MAX_VALUE.toBigInteger() + (1L).toBigInteger() -> ionInt(Long.MIN_VALUE) + else -> ionInt(arg.value.bigIntegerValue * BigInteger.valueOf(-1L)) + } } + arg.copy(value = intValue.asAnyElement()) } - arg.copy(value = intValue.asAnyElement()) + + arg.value is FloatElement -> arg.copy(value = ionFloat(-(arg.value.doubleValue)).asAnyElement()) + arg.value is DecimalElement -> arg.copy(value = ionDecimal(-(arg.value.decimalValue)).asAnyElement()) + else -> neg(arg, metas) } - arg.value is FloatElement -> arg.copy(value = ionFloat(-(arg.value.doubleValue)).asAnyElement()) - arg.value is DecimalElement -> arg.copy(value = ionDecimal(-(arg.value.decimalValue)).asAnyElement()) - else -> neg(arg, metas) } + + PartiQLParser.NOT -> not(arg, metas) + else -> throw ParserException("Unknown unary operator", ErrorCode.PARSE_INVALID_QUERY) } - PartiQLParser.NOT -> not(arg, metas) - else -> throw ParserException("Unknown unary operator", ErrorCode.PARSE_INVALID_QUERY) } - } private fun PartiQLParser.SymbolPrimitiveContext.getSourceMetaContainer() = when (this.ident.type) { PartiQLParser.IDENTIFIER -> this.IDENTIFIER().getSourceMetaContainer() PartiQLParser.IDENTIFIER_QUOTED -> this.IDENTIFIER_QUOTED().getSourceMetaContainer() - else -> throw ParserException("Unable to get identifier's source meta-container.", ErrorCode.PARSE_INVALID_QUERY) + else -> throw ParserException( + "Unable to get identifier's source meta-container.", + ErrorCode.PARSE_INVALID_QUERY + ) } private fun PartiqlAst.Expr.getStringValue(token: Token? = null): String = when (this) { @@ -1578,9 +1736,13 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p is SymbolElement -> this.value.symbolValue.lowercase() is StringElement -> this.value.stringValue.lowercase() else -> - this.value.stringValueOrNull ?: throw token.err("Unable to pass the string value", ErrorCode.PARSE_UNEXPECTED_TOKEN) + this.value.stringValueOrNull ?: throw token.err( + "Unable to pass the string value", + ErrorCode.PARSE_UNEXPECTED_TOKEN + ) } } + else -> throw token.err("Unable to get value", ErrorCode.PARSE_UNEXPECTED_TOKEN) } @@ -1612,6 +1774,7 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p cause = e ) } + else -> integerNode.text.toInteger().toLong() } if (precision < 0 || precision > MAX_PRECISION_FOR_TIME) { @@ -1624,24 +1787,31 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p * Parses a [timeString] using [OffsetTime] and converts to a [PartiqlAst.Expr.LitTime]. If unable to parse, parses * using [getLocalTime]. */ - private fun getOffsetTime(timeString: String, precision: Long, stringNode: TerminalNode, timeNode: TerminalNode) = PartiqlAst.build { - try { - val time: OffsetTime = OffsetTime.parse(timeString) - litTime( - timeValue( - time.hour.toLong(), time.minute.toLong(), time.second.toLong(), time.nano.toLong(), - precision, true, (time.offset.totalSeconds / 60).toLong() + private fun getOffsetTime(timeString: String, precision: Long, stringNode: TerminalNode, timeNode: TerminalNode) = + PartiqlAst.build { + try { + val time: OffsetTime = OffsetTime.parse(timeString) + litTime( + timeValue( + time.hour.toLong(), time.minute.toLong(), time.second.toLong(), time.nano.toLong(), + precision, true, (time.offset.totalSeconds / 60).toLong() + ) ) - ) - } catch (e: DateTimeParseException) { - getLocalTime(timeString, true, precision, stringNode, timeNode) + } catch (e: DateTimeParseException) { + getLocalTime(timeString, true, precision, stringNode, timeNode) + } } - } /** * Parses a [timeString] using [LocalTime] and converts to a [PartiqlAst.Expr.LitTime] */ - private fun getLocalTime(timeString: String, withTimeZone: Boolean, precision: Long, stringNode: TerminalNode, timeNode: TerminalNode) = PartiqlAst.build { + private fun getLocalTime( + timeString: String, + withTimeZone: Boolean, + precision: Long, + stringNode: TerminalNode, + timeNode: TerminalNode + ) = PartiqlAst.build { val time: LocalTime val formatter = when (withTimeZone) { false -> DateTimeFormatter.ISO_TIME @@ -1662,15 +1832,87 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p ) } + private fun getTimestampStringAndPrecision( + stringNode: TerminalNode, + integerNode: TerminalNode? + ): Pair { + val timestampString = stringNode.getStringValue() + val precision = when (integerNode) { + null -> return timestampString to null + else -> integerNode.text.toInteger().toLong() + } + if (precision < 0) { + throw integerNode.err("Precision out of bounds", ErrorCode.PARSE_INVALID_PRECISION_FOR_TIMESTAMP) + } + return timestampString to precision + } + + /** + * Parse Timestamp based on the existence of Time zone + */ + private fun getTimestampDynamic( + timestampString: String, + precision: Long?, + node: TerminalNode + ) = PartiqlAst.build { + val timestamp = + try { + DateTimeUtils.parseTimestamp(timestampString) + } catch (e: DateTimeException) { + throw node.err("Invalid Date Time Literal", ErrorCode.PARSE_INVALID_DATETIME_STRING, cause = e) + } + val timeZone = timestamp.timeZone?.let { getTimeZone(it) } + timestamp( + timestampValue( + timestamp.year.toLong(), timestamp.month.toLong(), timestamp.day.toLong(), + timestamp.hour.toLong(), timestamp.minute.toLong(), ionDecimal(Decimal.valueOf(timestamp.decimalSecond)), + timeZone, precision + ) + ) + } + + private fun getTimestampWithTimezone( + timestampString: String, + precision: Long?, + node: TerminalNode + ) = PartiqlAst.build { + val timestamp = try { + DateTimeUtils.parseTimestamp(timestampString) + } catch (e: DateTimeException) { + throw node.err("Invalid Date Time Literal", ErrorCode.PARSE_INVALID_DATETIME_STRING, cause = e) + } + if (timestamp.timeZone == null) + throw node.err( + "Invalid Date Time Literal, expect Time Zone for Type Timestamp With Time Zone", + ErrorCode.PARSE_INVALID_DATETIME_STRING + ) + val timeZone = timestamp.timeZone?.let { getTimeZone(it) } + timestamp( + timestampValue( + timestamp.year.toLong(), timestamp.month.toLong(), timestamp.day.toLong(), + timestamp.hour.toLong(), timestamp.minute.toLong(), ionDecimal(Decimal.valueOf(timestamp.decimalSecond)), + timeZone, precision + ) + ) + } + + private fun getTimeZone(timeZone: TimeZone) = PartiqlAst.build { + when (timeZone) { + TimeZone.UnknownTimeZone -> unknownTimezone() + is TimeZone.UtcOffset -> utcOffset(timeZone.totalOffsetMinutes.toLong()) + } + } + private fun convertSymbolPrimitive(sym: PartiQLParser.SymbolPrimitiveContext?): SymbolPrimitive? = when (sym) { null -> null else -> SymbolPrimitive(sym.getString(), sym.getSourceMetaContainer()) } - private fun convertProjectionItems(ctx: PartiQLParser.ProjectionItemsContext, metas: MetaContainer) = PartiqlAst.build { - val projections = visitOrEmpty(ctx.projectionItem(), PartiqlAst.ProjectItem::class) - projectList(projections, metas) - } + private fun convertProjectionItems(ctx: PartiQLParser.ProjectionItemsContext, metas: MetaContainer) = + PartiqlAst.build { + val projections = visitOrEmpty(ctx.projectionItem(), PartiqlAst.ProjectItem::class) + projectList(projections, metas) + } private fun PartiQLParser.SelectClauseContext.getMetas(): MetaContainer = when (this) { is PartiQLParser.SelectAllContext -> this.SELECT().getSourceMetaContainer() @@ -1734,7 +1976,11 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p when { path.steps.last() is PartiqlAst.PathStep.PathUnpivot && steps.isEmpty() -> projectAll(path.root, path.metas) - path.steps.last() is PartiqlAst.PathStep.PathUnpivot -> projectAll(path(path.root, steps, path.metas), path.metas) + path.steps.last() is PartiqlAst.PathStep.PathUnpivot -> projectAll( + path(path.root, steps, path.metas), + path.metas + ) + else -> projectExpr_(path, asAlias = alias, path.metas) } } @@ -1749,14 +1995,15 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p else -> throw this.err("Unsupported token for grabbing string value.", ErrorCode.PARSE_INVALID_QUERY) } - private fun getStrategy(strategy: PartiQLParser.SetQuantifierStrategyContext?, default: PartiqlAst.SetQuantifier) = PartiqlAst.build { - when { - strategy == null -> default - strategy.DISTINCT() != null -> distinct() - strategy.ALL() != null -> all() - else -> default + private fun getStrategy(strategy: PartiQLParser.SetQuantifierStrategyContext?, default: PartiqlAst.SetQuantifier) = + PartiqlAst.build { + when { + strategy == null -> default + strategy.DISTINCT() != null -> distinct() + strategy.ALL() != null -> all() + else -> default + } } - } private fun getStrategy(strategy: PartiQLParser.SetQuantifierStrategyContext?): PartiqlAst.SetQuantifier? { return when { @@ -1790,10 +2037,12 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p lit(ionString(ctx.IDENTIFIER_QUOTED().getStringValue())), caseSensitive(), metas = ctx.IDENTIFIER_QUOTED().getSourceMetaContainer() ) + ctx.IDENTIFIER() != null -> pathExpr( lit(ionString(ctx.IDENTIFIER().text)), caseInsensitive(), metas = ctx.IDENTIFIER().getSourceMetaContainer() ) + else -> throw ParserException("Unable to get symbol's text.", ErrorCode.PARSE_INVALID_QUERY) } } @@ -1821,7 +2070,10 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p if (value !is IntElement) throw token.err("Expected an integer value.", ErrorCode.PARSE_MALFORMED_PARSE_TREE) if (value.integerSize == IntElementSize.BIG_INTEGER || value.longValue > Int.MAX_VALUE || value.longValue < Int.MIN_VALUE) - throw token.err("Type parameter exceeded maximum value", ErrorCode.PARSE_TYPE_PARAMETER_EXCEEDED_MAXIMUM_VALUE) + throw token.err( + "Type parameter exceeded maximum value", + ErrorCode.PARSE_TYPE_PARAMETER_EXCEEDED_MAXIMUM_VALUE + ) } private enum class ExplainParameters { @@ -1830,10 +2082,24 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p fun getCompliantString(target: String?, input: Token): String = when (target) { null -> input.text!! - else -> throw input.error("Cannot set EXPLAIN parameter ${this.name} multiple times.", ErrorCode.PARSE_UNEXPECTED_TOKEN) + else -> throw input.error( + "Cannot set EXPLAIN parameter ${this.name} multiple times.", + ErrorCode.PARSE_UNEXPECTED_TOKEN + ) } } - private fun TerminalNode?.err(msg: String, code: ErrorCode, ctx: PropertyValueMap = PropertyValueMap(), cause: Throwable? = null) = this.error(msg, code, ctx, cause) - private fun Token?.err(msg: String, code: ErrorCode, ctx: PropertyValueMap = PropertyValueMap(), cause: Throwable? = null) = this.error(msg, code, ctx, cause) + private fun TerminalNode?.err( + msg: String, + code: ErrorCode, + ctx: PropertyValueMap = PropertyValueMap(), + cause: Throwable? = null + ) = this.error(msg, code, ctx, cause) + + private fun Token?.err( + msg: String, + code: ErrorCode, + ctx: PropertyValueMap = PropertyValueMap(), + cause: Throwable? = null + ) = this.error(msg, code, ctx, cause) } diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/util/DateTimeUtils.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/util/DateTimeUtils.kt new file mode 100644 index 0000000000..ec349d24c0 --- /dev/null +++ b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/util/DateTimeUtils.kt @@ -0,0 +1,76 @@ +package org.partiql.lang.syntax.util + +import org.partiql.value.datetime.Date +import org.partiql.value.datetime.DateTimeException +import org.partiql.value.datetime.DateTimeValue +import org.partiql.value.datetime.Time +import org.partiql.value.datetime.TimeZone +import org.partiql.value.datetime.Timestamp +import java.math.BigDecimal +import java.util.regex.Matcher +import java.util.regex.Pattern + +internal object DateTimeUtils { + private val DATE_PATTERN: Pattern = Pattern.compile("(?\\d{4,})-(?\\d{2,})-(?\\d{2,})") + private val TIME_PATTERN: Pattern = Pattern.compile("(?\\d{2,}):(?\\d{2,}):(?\\d{2,})(?:\\.(?\\d+))?\\s*(?([+-]\\d\\d:\\d\\d)|(?[Zz]))?") + private val SQL_TIMESTAMP_DATE_TIME_DELIMITER = "\\s+".toRegex() + private val RFC8889_TIMESTAMP_DATE_TIME_DELIMITER = "[Tt]".toRegex() + private val TIMESTAMP_PATTERN = "(?$DATE_PATTERN)($SQL_TIMESTAMP_DATE_TIME_DELIMITER|$RFC8889_TIMESTAMP_DATE_TIME_DELIMITER)(?