From 86e17b188657d2d2b9b34a13583e833272ec7872 Mon Sep 17 00:00:00 2001 From: Yingtao Liu Date: Thu, 22 Jun 2023 12:47:49 -0700 Subject: [PATCH 01/15] timestamp data model --- .../kotlin/org/partiql/value/datetime/Date.kt | 27 +++ .../kotlin/org/partiql/value/datetime/Time.kt | 187 ++++++++++++++++++ .../org/partiql/value/datetime/TimeZone.kt | 36 ++++ .../org/partiql/value/datetime/Timestamp.kt | 42 ++++ .../kotlin/org/partiql/value/datetime/Util.kt | 29 +++ 5 files changed, 321 insertions(+) create mode 100644 partiql-types/src/main/kotlin/org/partiql/value/datetime/Date.kt create mode 100644 partiql-types/src/main/kotlin/org/partiql/value/datetime/Time.kt create mode 100644 partiql-types/src/main/kotlin/org/partiql/value/datetime/TimeZone.kt create mode 100644 partiql-types/src/main/kotlin/org/partiql/value/datetime/Timestamp.kt create mode 100644 partiql-types/src/main/kotlin/org/partiql/value/datetime/Util.kt diff --git a/partiql-types/src/main/kotlin/org/partiql/value/datetime/Date.kt b/partiql-types/src/main/kotlin/org/partiql/value/datetime/Date.kt new file mode 100644 index 0000000000..9081b144bd --- /dev/null +++ b/partiql-types/src/main/kotlin/org/partiql/value/datetime/Date.kt @@ -0,0 +1,27 @@ +package org.partiql.value.datetime + +import java.time.LocalDate + +/** + * A date is modeled by [year], [month], and [day]. + * The valid range are from 0001-01-01 to 9999-12-31 + * The [day] must be valid for the year and month, otherwise an exception will be thrown. + */ +public data class Date private constructor( + val year: Int, + val month: Int, + val day: Int +) { + + public companion object { + public fun of(year: Int, month: Int, day: Int): Date { + if (year < 1 || year > 9999) throw DateTimeException("DATE", "Expect Year Field to be between 1 to 9999, but received $year") + try { + LocalDate.of(year, month, day) + } catch (e: java.time.DateTimeException) { + throw DateTimeException("Date", e.localizedMessage) + } + return Date(year, month, day) + } + } +} diff --git a/partiql-types/src/main/kotlin/org/partiql/value/datetime/Time.kt b/partiql-types/src/main/kotlin/org/partiql/value/datetime/Time.kt new file mode 100644 index 0000000000..b26bbab04f --- /dev/null +++ b/partiql-types/src/main/kotlin/org/partiql/value/datetime/Time.kt @@ -0,0 +1,187 @@ +package org.partiql.value.datetime + +import java.math.BigDecimal +import java.math.RoundingMode +import java.time.temporal.ChronoField + +/** + * This class is used to model both Time Without Time Zone type and Time With Time Zone Type. + * + * Informally, a data value of Time Without Time Zone represents a particular orientation of a clock + * which will represent different instances of "time" based on the timezone. + * a data value of Time With Time Zone represents an orientation of a clock attached with timezone offset. + * + */ +public data class Time private constructor( + val hour: Int, + val minute: Int, + val second: BigDecimal, + val timeZone: TimeZone?, + val precision: Int? +) { + public companion object { + public fun of( + hour: Int, + minute: Int, + second: BigDecimal, + timeZone: TimeZone?, + precision: Int? + ): Time { + try { + val hour = ChronoField.HOUR_OF_DAY.checkValidValue(hour.toLong()).toInt() + val minute = ChronoField.MINUTE_OF_HOUR.checkValidValue(minute.toLong()).toInt() + // round down the second to check + val wholeSecond = ChronoField.SECOND_OF_MINUTE.checkValidValue(second.setScale(0, RoundingMode.DOWN).toLong()) + val arbitraryTime = Time(hour, minute, second, timeZone, null) + if (precision == null) { + return arbitraryTime + } + return arbitraryTime.toPrecision(precision) + } catch (e: java.time.DateTimeException) { + throw DateTimeException("TIME", e.localizedMessage) + } catch (e: IllegalStateException) { + throw DateTimeException("TIME", e.localizedMessage) + } catch (e: IllegalArgumentException) { + throw DateTimeException("TIME", e.localizedMessage) + } + } + } + + private fun toPrecision(precision: Int) = + when { + second.scale() == precision -> this + second.scale() < precision -> paddingToPrecision(precision) + else -> roundToPrecision(precision) + } + + private fun paddingToPrecision(precision: Int) = + Time( + this.hour, + this.minute, + this.second.setScale(precision), + this.timeZone, + precision + ) + + private fun roundToPrecision(precision: Int): Time { + val elapsedSeconds: BigDecimal = + BigDecimal.valueOf(hour * SECONDS_IN_HOUR).add(BigDecimal.valueOf(minute * SECONDS_IN_MINUTE)).add(second) + var rounded = elapsedSeconds.setScale(precision, RoundingMode.HALF_UP) + var newHours = 0 + var newMinutes = 0 + val secondsInHour = BigDecimal.valueOf(SECONDS_IN_HOUR) + val secondsInMin = BigDecimal.valueOf(SECONDS_IN_MINUTE) + + if (rounded >= secondsInHour) { + val totalHours = rounded.divide(secondsInHour, 0, RoundingMode.DOWN) + rounded = rounded.subtract(totalHours.multiply(secondsInHour)) + newHours = totalHours.intValueExact() % 24 + } + if (rounded >= secondsInMin) { + val totalMinutes = rounded.divide(secondsInMin, 0, RoundingMode.DOWN) + rounded = rounded.subtract(totalMinutes.multiply(secondsInMin)) + newMinutes = totalMinutes.intValueExact() % 60 + } + + return Time(newHours, newMinutes, rounded, this.timeZone, precision) + } +} +// public data class Time( +// val hour: Int, +// val minute: Int, +// val second: BigDecimal, +// val tzSign: Char?, +// val tzHour: Int?, +// val tzMinute: Int?, +// val precision: Int? = null +// ) { +// val hasUnknownTimeZone :Boolean = tzSign == '-' && tzHour == 0 && tzMinute == 0 +// +// val hasTimeZone : Boolean= tzSign != null +// +// val elapsedSeconds: BigDecimal = +// BigDecimal.valueOf(hour * SECONDS_IN_HOUR).add(BigDecimal.valueOf(minute * SECONDS_IN_MINUTE)).add(second) +// +// public companion object { +// +// // May throw exception, need to catch +// // should move to parser +// @Throws(DateTimeException::class) +// public fun parseTimeLiteral(timeString: String): Time { +// val matcher: Matcher = TIME_PATTERN.matcher(timeString) +// if (!matcher.matches()) throw DateTimeException( +// "TIME", +// "TIME Format should be in HH-mm-ss.ddd+[+|-]hh:mm, received $timeString" +// ) +// try { +// val hour = ChronoField.HOUR_OF_DAY.checkValidValue(matcher.group("hour").toLong()).toInt() +// val minute = ChronoField.MINUTE_OF_HOUR.checkValidValue(matcher.group("minute").toLong()).toInt() +// val wholeSecond = ChronoField.SECOND_OF_MINUTE.checkValidValue(matcher.group("second").toLong()) +// val fractionPart = BigDecimal(".${matcher.group("fraction")}") +// val second = BigDecimal.valueOf(wholeSecond).add(fractionPart) +// val timezone = matcher.group("timezone") ?: null +// if (timezone != null) { +// val (tzSign, tzHour, tzMinute) = getTimeZoneComponent(timezone) +// check(tzHour <= 23) { +// throw DateTimeException("TIME", "TIME ZONE HOUR should be less than 24") +// } +// check(tzMinute <= 59) { +// throw DateTimeException("TIME", "TIME ZONE MINUTE should be less than 60") +// } +// return Time(hour, minute, second, tzSign, tzHour, tzMinute, null) +// } +// return Time(hour, minute, second, null, null, null, null) +// } catch (e: java.time.DateTimeException) { +// throw DateTimeException("TIME", e.localizedMessage) +// } catch (e: IllegalStateException) { +// throw DateTimeException("TIME", e.localizedMessage) +// } catch (e: IllegalArgumentException) { +// throw DateTimeException("TIME", e.localizedMessage) +// } +// } +// +// private fun getTimeZoneComponent(timezone: String) = +// Triple(timezone.substring(0, 1).first(), timezone.substring(1, 3).toInt(), timezone.substring(4, 6).toInt()) +// } +// +// private fun toPrecision(precision: Int) = +// when { +// second.scale() == precision -> this +// second.scale() < precision -> paddingToPrecision(precision) +// else -> roundToPrecision(precision) +// } +// +// private fun paddingToPrecision(precision: Int) = +// Time( +// this.hour, +// this.minute, +// this.second.setScale(precision), +// this.tzSign, +// this.tzHour, +// this.tzMinute, +// precision +// ) +// +// private fun roundToPrecision(precision: Int): Time { +// var rounded = this.elapsedSeconds.setScale(precision, RoundingMode.HALF_UP) +// var newHours: Int = 0 +// var newMinutes: Int = 0 +// var newSeconds: BigDecimal = BigDecimal.ZERO +// val secondsInHour = BigDecimal.valueOf(SECONDS_IN_HOUR) +// val secondsInMin = BigDecimal.valueOf(SECONDS_IN_MINUTE) +// +// if (rounded >= secondsInHour) { +// val totalHours = rounded.divide(secondsInHour, 0, RoundingMode.DOWN) +// rounded = rounded.subtract(totalHours.multiply(secondsInHour)) +// newHours = totalHours.intValueExact() % 24 +// } +// if (rounded >= secondsInMin) { +// val totalMinutes = rounded.divide(secondsInMin, 0, RoundingMode.DOWN) +// rounded = rounded.subtract(totalMinutes.multiply(secondsInMin)) +// newMinutes = totalMinutes.intValueExact() % 60 +// } +// newSeconds = rounded +// +// return Time(newHours, newMinutes, newSeconds, this.tzSign, this.tzHour, this.tzMinute, precision) +// } +// } diff --git a/partiql-types/src/main/kotlin/org/partiql/value/datetime/TimeZone.kt b/partiql-types/src/main/kotlin/org/partiql/value/datetime/TimeZone.kt new file mode 100644 index 0000000000..102332386b --- /dev/null +++ b/partiql-types/src/main/kotlin/org/partiql/value/datetime/TimeZone.kt @@ -0,0 +1,36 @@ +package org.partiql.value.datetime + +import kotlin.math.abs + +public sealed class TimeZone { + /** + * [RFC 3339](https://www.ietf.org/rfc/rfc3339.html): Unknown offset + */ + public object UnknownTimeZone : TimeZone() + + /** + * Utc offset is modeled by the difference in **total minutes** + * between Coordinated Universal Time (UTC) and local time, at a particular place. + */ + public data class UtcOffset private constructor(val totalOffsetMinutes: Int) : TimeZone() { + public companion object { + + /** + * Construct a UtcOffset with both timezone hour and time zone minute. + * Notice if the time zone is a negative offset, then both [tzHour] and [tzMinute] needs to be negative. + */ + public fun of(tzHour: Int, tzMinute: Int): UtcOffset { + if (abs(tzHour) > MAX_TIME_ZONE_HOURS) throw DateTimeException("TimeZone hour field", "should be less than 24") + if (abs(tzHour) > MAX_TIME_ZONE_MINUTES) throw DateTimeException("Timezone minute fields", "Should be less than 60") + return UtcOffset(tzHour * 60 + tzMinute) + } + + public fun of(totalOffsetMinutes: Int): UtcOffset { + if (abs(totalOffsetMinutes) > MAX_TOTAL_OFFSET_MINUTES) throw DateTimeException("TimeZone hour field", "should be less than 24") + return UtcOffset(totalOffsetMinutes) + } + } + } + + // TODO: add support for named Time zone (EST, PST, etc) +} diff --git a/partiql-types/src/main/kotlin/org/partiql/value/datetime/Timestamp.kt b/partiql-types/src/main/kotlin/org/partiql/value/datetime/Timestamp.kt new file mode 100644 index 0000000000..6f5bc63d9f --- /dev/null +++ b/partiql-types/src/main/kotlin/org/partiql/value/datetime/Timestamp.kt @@ -0,0 +1,42 @@ +package org.partiql.value.datetime + +import java.math.BigDecimal +import com.amazon.ion.Timestamp as TimestampIon + +public data class Timestamp( + val year: Int, + val month: Int, + val day: Int, + val hour: Int, + val minute: Int, + val second: BigDecimal, + val timeZone: TimeZone?, + val precision: Int? +) { + public companion object { + public fun of(date: Date, time: Time): Timestamp = + Timestamp( + date.year, date.month, date.day, + time.hour, time.minute, time.second, + time.timeZone, time.precision + ) + + public fun of(ionTs: TimestampIon) { + if (ionTs.localOffset == null) { + Timestamp( + ionTs.year, ionTs.month, ionTs.day, + ionTs.hour, ionTs.minute, ionTs.decimalSecond, + TimeZone.UnknownTimeZone, + null + ) + } else { + Timestamp( + ionTs.year, ionTs.month, ionTs.day, + ionTs.hour, ionTs.minute, ionTs.decimalSecond, + TimeZone.UtcOffset.of(ionTs.localOffset), + null + ) + } + } + } +} diff --git a/partiql-types/src/main/kotlin/org/partiql/value/datetime/Util.kt b/partiql-types/src/main/kotlin/org/partiql/value/datetime/Util.kt new file mode 100644 index 0000000000..85b25432f4 --- /dev/null +++ b/partiql-types/src/main/kotlin/org/partiql/value/datetime/Util.kt @@ -0,0 +1,29 @@ +package org.partiql.value.datetime + +import java.util.regex.Pattern + +internal val DATETIME_PATTERN = Pattern.compile( + "(?[-+]?\\d{4,})-(?\\d{1,2})-(?\\d{1,2})" + + "(?: (?\\d{1,2}):(?\\d{1,2})(?::(?\\d{1,2})(?:\\.(?\\d+))?)?)?" + + "\\s*(?[+-]\\d\\d:\\d\\d)?" +) + +internal val DATE_PATTERN = Pattern.compile("(?\\d{4,})-(?\\d{2,})-(?\\d{2,})") + +internal val TIME_PATTERN = + Pattern.compile("(?\\d{2,}):(?\\d{2,}):(?\\d{2,})(?:\\.(?\\d+))?\\s*(?[+-]\\d\\d:\\d\\d)?") + +internal const val MILLIS_IN_SECOND: Long = 1000 +internal const val MILLIS_IN_MINUTE = 60 * MILLIS_IN_SECOND +internal const val MILLIS_IN_HOUR = 60 * MILLIS_IN_MINUTE +internal const val MILLIS_IN_DAY = 24 * MILLIS_IN_HOUR +internal const val SECONDS_IN_MINUTE = 60L +internal const val SECONDS_IN_HOUR = 60 * SECONDS_IN_MINUTE +internal const val MAX_TIME_ZONE_HOURS: Int = 23 +internal const val MAX_TIME_ZONE_MINUTES: Int = 59 +internal const val MAX_TOTAL_OFFSET_MINUTES: Int = MAX_TIME_ZONE_HOURS * 60 + MAX_TIME_ZONE_MINUTES + +public class DateTimeException( + public val type: String, + public val error: String +) : Throwable() From 7d9f5edd44b07626246d83a2a7ce9b703b006db9 Mon Sep 17 00:00:00 2001 From: Yingtao Liu Date: Thu, 22 Jun 2023 13:42:47 -0700 Subject: [PATCH 02/15] parser tests --- partiql-ast/src/main/pig/partiql.ion | 5 + .../partiql/lang/eval/EvaluatingCompiler.kt | 1 + .../eval/physical/PhysicalPlanCompilerImpl.kt | 1 + .../SubqueryCoercionVisitorTransform.kt | 1 + .../lang/prettyprint/ASTPrettyPrinter.kt | 1 + .../lang/prettyprint/QueryPrettyPrinter.kt | 1 + .../lang/syntax/impl/PartiQLPigVisitor.kt | 535 ++++-- .../partiql/lang/syntax/util/DateTimeUtils.kt | 88 + .../lang/syntax/PartiQLParserDateTimeTests.kt | 1428 +++++++++-------- partiql-parser/src/main/antlr/PartiQL.g4 | 5 +- test/partiql-tests | 2 +- 11 files changed, 1300 insertions(+), 768 deletions(-) create mode 100644 partiql-lang/src/main/kotlin/org/partiql/lang/syntax/util/DateTimeUtils.kt diff --git a/partiql-ast/src/main/pig/partiql.ion b/partiql-ast/src/main/pig/partiql.ion index 10a3803a61..67144c0d41 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,10 @@ 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 + // TODO : Check if we have a better way to model this + (product timestamp_value year::int month::int day::int hour::int minute::int second::ion tz_sign::(? symbol) tz_hour::(? int) tz_minutes::(? int) precision::(? int) ) + // 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']`. 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 a0e6cf6c09..ee63bb512f 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 1ffc6d2cec..ed7c649996 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,12 +65,14 @@ 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.TimeZone import java.math.BigInteger import java.time.LocalDate import java.time.LocalTime import java.time.OffsetTime import java.time.format.DateTimeFormatter import java.time.format.DateTimeParseException +import kotlin.math.abs import kotlin.reflect.KClass import kotlin.reflect.cast @@ -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.toLowerCase(), 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(), ctx.TIMESTAMP()) + else -> getTimestampWithTimezone(timestamp, precision, ctx.LITERAL_STRING(), ctx.TIMESTAMP()) + } + } + override fun visitTuple(ctx: PartiQLParser.TupleContext) = PartiqlAst.build { val pairs = visitOrEmpty(ctx.pair(), PartiqlAst.ExprPair::class) val metas = ctx.BRACE_LEFT().getSourceMetaContainer() @@ -1451,17 +1568,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 +1603,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 +1645,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 +1673,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 +1730,13 @@ internal class PartiQLPigVisitor(val customTypes: List = listOf(), p is SymbolElement -> this.value.symbolValue.toLowerCase() is StringElement -> this.value.stringValue.toLowerCase() 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 +1768,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 +1781,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 +1826,86 @@ 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_TIME) + } + return timestampString to precision + } + + /** + * Parse Timestamp based on the existence of Time zone + */ + private fun getTimestampDynamic( + timestampString: String, + precision: Long?, + stringNode: TerminalNode, + timestampNode: TerminalNode + ) = PartiqlAst.build { + val timestamp = DateTimeUtils.parseTimestamp(timestampString) + val (tzSign, tzHour, tzMinute) = getTimeZoneField(timestamp.timeZone) + timestamp( + timestampValue( + timestamp.year.toLong(), timestamp.month.toLong(), timestamp.day.toLong(), + timestamp.hour.toLong(), timestamp.minute.toLong(), ionDecimal(Decimal.valueOf(timestamp.second)), + tzSign, tzHour, tzMinute, precision + ) + ) + } + + private fun getTimestampWithTimezone( + timestampString: String, + precision: Long?, + stringNode: TerminalNode, + timestampNode: TerminalNode + ) = PartiqlAst.build { + val timestamp = DateTimeUtils.parseTimestamp(timestampString) + if (timestamp.timeZone == null) throw Error("no time zone") + val (tzSign, tzHour, tzMinute) = getTimeZoneField(timestamp.timeZone) + timestamp( + timestampValue( + timestamp.year.toLong(), timestamp.month.toLong(), timestamp.day.toLong(), + timestamp.hour.toLong(), timestamp.minute.toLong(), ionDecimal(Decimal.valueOf(timestamp.second)), + tzSign, tzHour, tzMinute, precision + ) + ) + } + + private fun getTimeZoneField(timeZone: TimeZone?) = + when (timeZone) { + TimeZone.UnknownTimeZone -> Triple("-", 0L, 0L) + is TimeZone.UtcOffset -> { + val positiveOffsetMinutes = abs(timeZone.totalOffsetMinutes) + val tzHour = positiveOffsetMinutes / 60 + val tzMinutes = positiveOffsetMinutes - tzHour * 60 + if (timeZone.totalOffsetMinutes >= 0) { + Triple("+", tzHour.toLong(), tzMinutes.toLong()) + } else { + Triple("-", tzHour.toLong(), tzMinutes.toLong()) + } + } + + null -> Triple(null, null, null) + } + 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 +1969,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 +1988,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 +2030,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 +2063,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 +2075,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..55b53c3ecf --- /dev/null +++ b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/util/DateTimeUtils.kt @@ -0,0 +1,88 @@ +package org.partiql.lang.syntax.util + +import org.partiql.value.datetime.Date +import org.partiql.value.datetime.DateTimeException +import org.partiql.value.datetime.Time +import org.partiql.value.datetime.TimeZone +import org.partiql.value.datetime.Timestamp +import java.math.BigDecimal +import java.time.LocalDate +import java.time.temporal.ChronoField +import java.util.regex.Matcher +import java.util.regex.Pattern + +internal object DateTimeUtils { + val DATE_PATTERN = Pattern.compile("(?\\d{4,})-(?\\d{2,})-(?\\d{2,})") + val TIME_PATTERN = Pattern.compile("(?\\d{2,}):(?\\d{2,}):(?\\d{2,})(?:\\.(?\\d+))?\\s*(?([+-]\\d\\d:\\d\\d)|(?[Zz]))?") + val SQL_TIMESTAMP_DATE_TIME_DELIMITER = "\\s+".toRegex() + val RFC8889_TIMESTAMP_DATE_TIME_DELIMITER = "[Tt]".toRegex() + val TIMESTAMP_PATTERN = "(?$DATE_PATTERN)($SQL_TIMESTAMP_DATE_TIME_DELIMITER|$RFC8889_TIMESTAMP_DATE_TIME_DELIMITER)(?