From 1dabd4e07e2d01681707c31db481e004de4ede51 Mon Sep 17 00:00:00 2001 From: yliuuuu <107505258+yliuuuu@users.noreply.github.com> Date: Fri, 12 Jan 2024 15:21:37 -0800 Subject: [PATCH 1/2] Implements date_add builtin in partiql-eval (#1334) --- .../org/partiql/plugin/PartiQLPlugin.kt | 24 ---- .../plugin/internal/fn/scalar/FnDateAddDay.kt | 128 +++++++++--------- .../internal/fn/scalar/FnDateAddHour.kt | 128 +++++++++--------- .../internal/fn/scalar/FnDateAddMinute.kt | 128 +++++++++--------- .../internal/fn/scalar/FnDateAddMonth.kt | 127 +++++++++-------- .../internal/fn/scalar/FnDateAddSecond.kt | 128 +++++++++--------- .../internal/fn/scalar/FnDateAddYear.kt | 128 +++++++++--------- .../internal/fn/scalar/FnDateDiffDay.kt | 20 --- .../internal/fn/scalar/FnDateDiffHour.kt | 20 --- .../internal/fn/scalar/FnDateDiffMinute.kt | 20 --- .../internal/fn/scalar/FnDateDiffMonth.kt | 20 --- .../internal/fn/scalar/FnDateDiffSecond.kt | 20 --- .../internal/fn/scalar/FnDateDiffYear.kt | 20 --- .../org/partiql/plugin/PartiQLHeader.kt | 19 ++- 14 files changed, 398 insertions(+), 532 deletions(-) diff --git a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/PartiQLPlugin.kt b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/PartiQLPlugin.kt index 0e09df84df..52bff975b9 100644 --- a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/PartiQLPlugin.kt +++ b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/PartiQLPlugin.kt @@ -369,51 +369,33 @@ object PartiQLPlugin : Plugin { Fn_DATE_ADD_YEAR__INT32_DATE__DATE, Fn_DATE_ADD_YEAR__INT64_DATE__DATE, Fn_DATE_ADD_YEAR__INT_DATE__DATE, - Fn_DATE_ADD_YEAR__INT32_TIME__TIME, - Fn_DATE_ADD_YEAR__INT64_TIME__TIME, - Fn_DATE_ADD_YEAR__INT_TIME__TIME, Fn_DATE_ADD_YEAR__INT32_TIMESTAMP__TIMESTAMP, Fn_DATE_ADD_YEAR__INT64_TIMESTAMP__TIMESTAMP, Fn_DATE_ADD_YEAR__INT_TIMESTAMP__TIMESTAMP, Fn_DATE_ADD_MONTH__INT32_DATE__DATE, Fn_DATE_ADD_MONTH__INT64_DATE__DATE, Fn_DATE_ADD_MONTH__INT_DATE__DATE, - Fn_DATE_ADD_MONTH__INT32_TIME__TIME, - Fn_DATE_ADD_MONTH__INT64_TIME__TIME, - Fn_DATE_ADD_MONTH__INT_TIME__TIME, Fn_DATE_ADD_MONTH__INT32_TIMESTAMP__TIMESTAMP, Fn_DATE_ADD_MONTH__INT64_TIMESTAMP__TIMESTAMP, Fn_DATE_ADD_MONTH__INT_TIMESTAMP__TIMESTAMP, Fn_DATE_ADD_DAY__INT32_DATE__DATE, Fn_DATE_ADD_DAY__INT64_DATE__DATE, Fn_DATE_ADD_DAY__INT_DATE__DATE, - Fn_DATE_ADD_DAY__INT32_TIME__TIME, - Fn_DATE_ADD_DAY__INT64_TIME__TIME, - Fn_DATE_ADD_DAY__INT_TIME__TIME, Fn_DATE_ADD_DAY__INT32_TIMESTAMP__TIMESTAMP, Fn_DATE_ADD_DAY__INT64_TIMESTAMP__TIMESTAMP, Fn_DATE_ADD_DAY__INT_TIMESTAMP__TIMESTAMP, - Fn_DATE_ADD_HOUR__INT32_DATE__DATE, - Fn_DATE_ADD_HOUR__INT64_DATE__DATE, - Fn_DATE_ADD_HOUR__INT_DATE__DATE, Fn_DATE_ADD_HOUR__INT32_TIME__TIME, Fn_DATE_ADD_HOUR__INT64_TIME__TIME, Fn_DATE_ADD_HOUR__INT_TIME__TIME, Fn_DATE_ADD_HOUR__INT32_TIMESTAMP__TIMESTAMP, Fn_DATE_ADD_HOUR__INT64_TIMESTAMP__TIMESTAMP, Fn_DATE_ADD_HOUR__INT_TIMESTAMP__TIMESTAMP, - Fn_DATE_ADD_MINUTE__INT32_DATE__DATE, - Fn_DATE_ADD_MINUTE__INT64_DATE__DATE, - Fn_DATE_ADD_MINUTE__INT_DATE__DATE, Fn_DATE_ADD_MINUTE__INT32_TIME__TIME, Fn_DATE_ADD_MINUTE__INT64_TIME__TIME, Fn_DATE_ADD_MINUTE__INT_TIME__TIME, Fn_DATE_ADD_MINUTE__INT32_TIMESTAMP__TIMESTAMP, Fn_DATE_ADD_MINUTE__INT64_TIMESTAMP__TIMESTAMP, Fn_DATE_ADD_MINUTE__INT_TIMESTAMP__TIMESTAMP, - Fn_DATE_ADD_SECOND__INT32_DATE__DATE, - Fn_DATE_ADD_SECOND__INT64_DATE__DATE, - Fn_DATE_ADD_SECOND__INT_DATE__DATE, Fn_DATE_ADD_SECOND__INT32_TIME__TIME, Fn_DATE_ADD_SECOND__INT64_TIME__TIME, Fn_DATE_ADD_SECOND__INT_TIME__TIME, @@ -421,21 +403,15 @@ object PartiQLPlugin : Plugin { Fn_DATE_ADD_SECOND__INT64_TIMESTAMP__TIMESTAMP, Fn_DATE_ADD_SECOND__INT_TIMESTAMP__TIMESTAMP, Fn_DATE_DIFF_YEAR__DATE_DATE__INT64, - Fn_DATE_DIFF_YEAR__TIME_TIME__INT64, Fn_DATE_DIFF_YEAR__TIMESTAMP_TIMESTAMP__INT64, Fn_DATE_DIFF_MONTH__DATE_DATE__INT64, - Fn_DATE_DIFF_MONTH__TIME_TIME__INT64, Fn_DATE_DIFF_MONTH__TIMESTAMP_TIMESTAMP__INT64, Fn_DATE_DIFF_DAY__DATE_DATE__INT64, - Fn_DATE_DIFF_DAY__TIME_TIME__INT64, Fn_DATE_DIFF_DAY__TIMESTAMP_TIMESTAMP__INT64, - Fn_DATE_DIFF_HOUR__DATE_DATE__INT64, Fn_DATE_DIFF_HOUR__TIME_TIME__INT64, Fn_DATE_DIFF_HOUR__TIMESTAMP_TIMESTAMP__INT64, - Fn_DATE_DIFF_MINUTE__DATE_DATE__INT64, Fn_DATE_DIFF_MINUTE__TIME_TIME__INT64, Fn_DATE_DIFF_MINUTE__TIMESTAMP_TIMESTAMP__INT64, - Fn_DATE_DIFF_SECOND__DATE_DATE__INT64, Fn_DATE_DIFF_SECOND__TIME_TIME__INT64, Fn_DATE_DIFF_SECOND__TIMESTAMP_TIMESTAMP__INT64, Fn_CURRENT_USER____STRING, diff --git a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddDay.kt b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddDay.kt index eb496c3c6b..ceb74eb033 100644 --- a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddDay.kt +++ b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddDay.kt @@ -7,14 +7,21 @@ import org.partiql.spi.function.PartiQLFunction import org.partiql.spi.function.PartiQLFunctionExperimental import org.partiql.types.function.FunctionParameter import org.partiql.types.function.FunctionSignature +import org.partiql.value.DateValue +import org.partiql.value.Int32Value +import org.partiql.value.Int64Value +import org.partiql.value.IntValue import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental import org.partiql.value.PartiQLValueType.DATE import org.partiql.value.PartiQLValueType.INT import org.partiql.value.PartiQLValueType.INT32 import org.partiql.value.PartiQLValueType.INT64 -import org.partiql.value.PartiQLValueType.TIME import org.partiql.value.PartiQLValueType.TIMESTAMP +import org.partiql.value.TimestampValue +import org.partiql.value.check +import org.partiql.value.dateValue +import org.partiql.value.timestampValue @OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) internal object Fn_DATE_ADD_DAY__INT32_DATE__DATE : PartiQLFunction.Scalar { @@ -31,7 +38,15 @@ internal object Fn_DATE_ADD_DAY__INT32_DATE__DATE : PartiQLFunction.Scalar { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_day not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + dateValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + dateValue(datetimeValue.plusDays(intervalValue)) + } } } @@ -50,7 +65,15 @@ internal object Fn_DATE_ADD_DAY__INT64_DATE__DATE : PartiQLFunction.Scalar { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_day not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + dateValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + dateValue(datetimeValue.plusDays(intervalValue)) + } } } @@ -69,64 +92,16 @@ internal object Fn_DATE_ADD_DAY__INT_DATE__DATE : PartiQLFunction.Scalar { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_day not implemented") - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_DAY__INT32_TIME__TIME : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_day", - returns = TIME, - parameters = listOf( - FunctionParameter("interval", INT32), - FunctionParameter("datetime", TIME), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_day not implemented") - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_DAY__INT64_TIME__TIME : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_day", - returns = TIME, - parameters = listOf( - FunctionParameter("interval", INT64), - FunctionParameter("datetime", TIME), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_day not implemented") - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_DAY__INT_TIME__TIME : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_day", - returns = TIME, - parameters = listOf( - FunctionParameter("interval", INT), - FunctionParameter("datetime", TIME), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_day not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + dateValue(null) + } else { + val datetimeValue = datetime.value!! + // TODO: We need to consider overflow here + val intervalValue = interval.long!! + dateValue(datetimeValue.plusDays(intervalValue)) + } } } @@ -145,7 +120,15 @@ internal object Fn_DATE_ADD_DAY__INT32_TIMESTAMP__TIMESTAMP : PartiQLFunction.Sc ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_day not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timestampValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + timestampValue(datetimeValue.plusDays(intervalValue)) + } } } @@ -164,7 +147,15 @@ internal object Fn_DATE_ADD_DAY__INT64_TIMESTAMP__TIMESTAMP : PartiQLFunction.Sc ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_day not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timestampValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + timestampValue(datetimeValue.plusDays(intervalValue)) + } } } @@ -183,6 +174,15 @@ internal object Fn_DATE_ADD_DAY__INT_TIMESTAMP__TIMESTAMP : PartiQLFunction.Scal ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_day not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timestampValue(null) + } else { + val datetimeValue = datetime.value!! + // TODO: We need to consider overflow here + val intervalValue = interval.long!! + timestampValue(datetimeValue.plusDays(intervalValue)) + } } } diff --git a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddHour.kt b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddHour.kt index 1171813821..29aac9e057 100644 --- a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddHour.kt +++ b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddHour.kt @@ -7,71 +7,21 @@ import org.partiql.spi.function.PartiQLFunction import org.partiql.spi.function.PartiQLFunctionExperimental import org.partiql.types.function.FunctionParameter import org.partiql.types.function.FunctionSignature +import org.partiql.value.Int32Value +import org.partiql.value.Int64Value +import org.partiql.value.IntValue import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.PartiQLValueType.DATE import org.partiql.value.PartiQLValueType.INT import org.partiql.value.PartiQLValueType.INT32 import org.partiql.value.PartiQLValueType.INT64 import org.partiql.value.PartiQLValueType.TIME import org.partiql.value.PartiQLValueType.TIMESTAMP - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_HOUR__INT32_DATE__DATE : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_hour", - returns = DATE, - parameters = listOf( - FunctionParameter("interval", INT32), - FunctionParameter("datetime", DATE), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_hour not implemented") - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_HOUR__INT64_DATE__DATE : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_hour", - returns = DATE, - parameters = listOf( - FunctionParameter("interval", INT64), - FunctionParameter("datetime", DATE), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_hour not implemented") - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_HOUR__INT_DATE__DATE : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_hour", - returns = DATE, - parameters = listOf( - FunctionParameter("interval", INT), - FunctionParameter("datetime", DATE), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_hour not implemented") - } -} +import org.partiql.value.TimeValue +import org.partiql.value.TimestampValue +import org.partiql.value.check +import org.partiql.value.timeValue +import org.partiql.value.timestampValue @OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) internal object Fn_DATE_ADD_HOUR__INT32_TIME__TIME : PartiQLFunction.Scalar { @@ -88,7 +38,15 @@ internal object Fn_DATE_ADD_HOUR__INT32_TIME__TIME : PartiQLFunction.Scalar { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_hour not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timeValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + timeValue(datetimeValue.plusHours(intervalValue)) + } } } @@ -107,7 +65,15 @@ internal object Fn_DATE_ADD_HOUR__INT64_TIME__TIME : PartiQLFunction.Scalar { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_hour not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timeValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + timeValue(datetimeValue.plusHours(intervalValue)) + } } } @@ -126,7 +92,16 @@ internal object Fn_DATE_ADD_HOUR__INT_TIME__TIME : PartiQLFunction.Scalar { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_hour not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timeValue(null) + } else { + val datetimeValue = datetime.value!! + // TODO: We need to consider overflow here + val intervalValue = interval.long!! + timeValue(datetimeValue.plusHours(intervalValue)) + } } } @@ -145,7 +120,15 @@ internal object Fn_DATE_ADD_HOUR__INT32_TIMESTAMP__TIMESTAMP : PartiQLFunction.S ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_hour not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timestampValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + timestampValue(datetimeValue.plusHours(intervalValue)) + } } } @@ -164,7 +147,15 @@ internal object Fn_DATE_ADD_HOUR__INT64_TIMESTAMP__TIMESTAMP : PartiQLFunction.S ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_hour not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timestampValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + timestampValue(datetimeValue.plusHours(intervalValue)) + } } } @@ -183,6 +174,15 @@ internal object Fn_DATE_ADD_HOUR__INT_TIMESTAMP__TIMESTAMP : PartiQLFunction.Sca ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_hour not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timestampValue(null) + } else { + val datetimeValue = datetime.value!! + // TODO: We need to consider overflow here + val intervalValue = interval.long!! + timestampValue(datetimeValue.plusHours(intervalValue)) + } } } diff --git a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddMinute.kt b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddMinute.kt index e8116bfb32..8edc5c5bb6 100644 --- a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddMinute.kt +++ b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddMinute.kt @@ -7,71 +7,21 @@ import org.partiql.spi.function.PartiQLFunction import org.partiql.spi.function.PartiQLFunctionExperimental import org.partiql.types.function.FunctionParameter import org.partiql.types.function.FunctionSignature +import org.partiql.value.Int32Value +import org.partiql.value.Int64Value +import org.partiql.value.IntValue import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.PartiQLValueType.DATE import org.partiql.value.PartiQLValueType.INT import org.partiql.value.PartiQLValueType.INT32 import org.partiql.value.PartiQLValueType.INT64 import org.partiql.value.PartiQLValueType.TIME import org.partiql.value.PartiQLValueType.TIMESTAMP - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_MINUTE__INT32_DATE__DATE : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_minute", - returns = DATE, - parameters = listOf( - FunctionParameter("interval", INT32), - FunctionParameter("datetime", DATE), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_minute not implemented") - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_MINUTE__INT64_DATE__DATE : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_minute", - returns = DATE, - parameters = listOf( - FunctionParameter("interval", INT64), - FunctionParameter("datetime", DATE), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_minute not implemented") - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_MINUTE__INT_DATE__DATE : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_minute", - returns = DATE, - parameters = listOf( - FunctionParameter("interval", INT), - FunctionParameter("datetime", DATE), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_minute not implemented") - } -} +import org.partiql.value.TimeValue +import org.partiql.value.TimestampValue +import org.partiql.value.check +import org.partiql.value.timeValue +import org.partiql.value.timestampValue @OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) internal object Fn_DATE_ADD_MINUTE__INT32_TIME__TIME : PartiQLFunction.Scalar { @@ -88,7 +38,15 @@ internal object Fn_DATE_ADD_MINUTE__INT32_TIME__TIME : PartiQLFunction.Scalar { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_minute not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timeValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + timeValue(datetimeValue.plusMinutes(intervalValue)) + } } } @@ -107,7 +65,15 @@ internal object Fn_DATE_ADD_MINUTE__INT64_TIME__TIME : PartiQLFunction.Scalar { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_minute not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timeValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + timeValue(datetimeValue.plusMinutes(intervalValue)) + } } } @@ -126,7 +92,16 @@ internal object Fn_DATE_ADD_MINUTE__INT_TIME__TIME : PartiQLFunction.Scalar { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_minute not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timeValue(null) + } else { + val datetimeValue = datetime.value!! + // TODO: We need to consider overflow here + val intervalValue = interval.long!! + timeValue(datetimeValue.plusMinutes(intervalValue)) + } } } @@ -145,7 +120,15 @@ internal object Fn_DATE_ADD_MINUTE__INT32_TIMESTAMP__TIMESTAMP : PartiQLFunction ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_minute not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timestampValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + timestampValue(datetimeValue.plusMinutes(intervalValue)) + } } } @@ -164,7 +147,15 @@ internal object Fn_DATE_ADD_MINUTE__INT64_TIMESTAMP__TIMESTAMP : PartiQLFunction ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_minute not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timestampValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + timestampValue(datetimeValue.plusMinutes(intervalValue)) + } } } @@ -183,6 +174,15 @@ internal object Fn_DATE_ADD_MINUTE__INT_TIMESTAMP__TIMESTAMP : PartiQLFunction.S ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_minute not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timestampValue(null) + } else { + val datetimeValue = datetime.value!! + // TODO: We need to consider overflow here + val intervalValue = interval.long!! + timestampValue(datetimeValue.plusMinutes(intervalValue)) + } } } diff --git a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddMonth.kt b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddMonth.kt index e259577507..32dcaba560 100644 --- a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddMonth.kt +++ b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddMonth.kt @@ -7,14 +7,21 @@ import org.partiql.spi.function.PartiQLFunction import org.partiql.spi.function.PartiQLFunctionExperimental import org.partiql.types.function.FunctionParameter import org.partiql.types.function.FunctionSignature +import org.partiql.value.DateValue +import org.partiql.value.Int32Value +import org.partiql.value.Int64Value +import org.partiql.value.IntValue import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental import org.partiql.value.PartiQLValueType.DATE import org.partiql.value.PartiQLValueType.INT import org.partiql.value.PartiQLValueType.INT32 import org.partiql.value.PartiQLValueType.INT64 -import org.partiql.value.PartiQLValueType.TIME import org.partiql.value.PartiQLValueType.TIMESTAMP +import org.partiql.value.TimestampValue +import org.partiql.value.check +import org.partiql.value.dateValue +import org.partiql.value.timestampValue @OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) internal object Fn_DATE_ADD_MONTH__INT32_DATE__DATE : PartiQLFunction.Scalar { @@ -31,7 +38,15 @@ internal object Fn_DATE_ADD_MONTH__INT32_DATE__DATE : PartiQLFunction.Scalar { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_month not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + dateValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + dateValue(datetimeValue.plusMonths(intervalValue)) + } } } @@ -50,7 +65,15 @@ internal object Fn_DATE_ADD_MONTH__INT64_DATE__DATE : PartiQLFunction.Scalar { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_month not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + dateValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + dateValue(datetimeValue.plusMonths(intervalValue)) + } } } @@ -69,64 +92,15 @@ internal object Fn_DATE_ADD_MONTH__INT_DATE__DATE : PartiQLFunction.Scalar { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_month not implemented") - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_MONTH__INT32_TIME__TIME : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_month", - returns = TIME, - parameters = listOf( - FunctionParameter("interval", INT32), - FunctionParameter("datetime", TIME), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_month not implemented") - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_MONTH__INT64_TIME__TIME : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_month", - returns = TIME, - parameters = listOf( - FunctionParameter("interval", INT64), - FunctionParameter("datetime", TIME), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_month not implemented") - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_MONTH__INT_TIME__TIME : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_month", - returns = TIME, - parameters = listOf( - FunctionParameter("interval", INT), - FunctionParameter("datetime", TIME), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_month not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + dateValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + dateValue(datetimeValue.plusMonths(intervalValue)) + } } } @@ -145,7 +119,15 @@ internal object Fn_DATE_ADD_MONTH__INT32_TIMESTAMP__TIMESTAMP : PartiQLFunction. ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_month not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timestampValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + timestampValue(datetimeValue.plusMonths(intervalValue)) + } } } @@ -164,7 +146,15 @@ internal object Fn_DATE_ADD_MONTH__INT64_TIMESTAMP__TIMESTAMP : PartiQLFunction. ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_month not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timestampValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + timestampValue(datetimeValue.plusMonths(intervalValue)) + } } } @@ -183,6 +173,15 @@ internal object Fn_DATE_ADD_MONTH__INT_TIMESTAMP__TIMESTAMP : PartiQLFunction.Sc ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_month not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timestampValue(null) + } else { + val datetimeValue = datetime.value!! + // TODO: We need to consider overflow here + val intervalValue = interval.long!! + timestampValue(datetimeValue.plusMonths(intervalValue)) + } } } diff --git a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddSecond.kt b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddSecond.kt index 65601ca89c..6e7a99291c 100644 --- a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddSecond.kt +++ b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddSecond.kt @@ -7,71 +7,21 @@ import org.partiql.spi.function.PartiQLFunction import org.partiql.spi.function.PartiQLFunctionExperimental import org.partiql.types.function.FunctionParameter import org.partiql.types.function.FunctionSignature +import org.partiql.value.Int32Value +import org.partiql.value.Int64Value +import org.partiql.value.IntValue import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.PartiQLValueType.DATE import org.partiql.value.PartiQLValueType.INT import org.partiql.value.PartiQLValueType.INT32 import org.partiql.value.PartiQLValueType.INT64 import org.partiql.value.PartiQLValueType.TIME import org.partiql.value.PartiQLValueType.TIMESTAMP - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_SECOND__INT32_DATE__DATE : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_second", - returns = DATE, - parameters = listOf( - FunctionParameter("interval", INT32), - FunctionParameter("datetime", DATE), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_second not implemented") - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_SECOND__INT64_DATE__DATE : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_second", - returns = DATE, - parameters = listOf( - FunctionParameter("interval", INT64), - FunctionParameter("datetime", DATE), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_second not implemented") - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_SECOND__INT_DATE__DATE : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_second", - returns = DATE, - parameters = listOf( - FunctionParameter("interval", INT), - FunctionParameter("datetime", DATE), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_second not implemented") - } -} +import org.partiql.value.TimeValue +import org.partiql.value.TimestampValue +import org.partiql.value.check +import org.partiql.value.timeValue +import org.partiql.value.timestampValue @OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) internal object Fn_DATE_ADD_SECOND__INT32_TIME__TIME : PartiQLFunction.Scalar { @@ -88,7 +38,15 @@ internal object Fn_DATE_ADD_SECOND__INT32_TIME__TIME : PartiQLFunction.Scalar { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_second not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timeValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + timeValue(datetimeValue.plusSeconds(intervalValue)) + } } } @@ -107,7 +65,15 @@ internal object Fn_DATE_ADD_SECOND__INT64_TIME__TIME : PartiQLFunction.Scalar { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_second not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timeValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + timeValue(datetimeValue.plusSeconds(intervalValue)) + } } } @@ -126,7 +92,16 @@ internal object Fn_DATE_ADD_SECOND__INT_TIME__TIME : PartiQLFunction.Scalar { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_second not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timeValue(null) + } else { + val datetimeValue = datetime.value!! + // TODO: We need to consider overflow here + val intervalValue = interval.long!! + timeValue(datetimeValue.plusSeconds(intervalValue)) + } } } @@ -145,7 +120,15 @@ internal object Fn_DATE_ADD_SECOND__INT32_TIMESTAMP__TIMESTAMP : PartiQLFunction ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_second not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timestampValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + timestampValue(datetimeValue.plusSeconds(intervalValue)) + } } } @@ -164,7 +147,15 @@ internal object Fn_DATE_ADD_SECOND__INT64_TIMESTAMP__TIMESTAMP : PartiQLFunction ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_second not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timestampValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + timestampValue(datetimeValue.plusSeconds(intervalValue)) + } } } @@ -183,6 +174,15 @@ internal object Fn_DATE_ADD_SECOND__INT_TIMESTAMP__TIMESTAMP : PartiQLFunction.S ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_second not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timestampValue(null) + } else { + val datetimeValue = datetime.value!! + // TODO: We need to consider overflow here + val intervalValue = interval.long!! + timestampValue(datetimeValue.plusSeconds(intervalValue)) + } } } diff --git a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddYear.kt b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddYear.kt index 54aca72711..40bd1b2606 100644 --- a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddYear.kt +++ b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddYear.kt @@ -7,14 +7,21 @@ import org.partiql.spi.function.PartiQLFunction import org.partiql.spi.function.PartiQLFunctionExperimental import org.partiql.types.function.FunctionParameter import org.partiql.types.function.FunctionSignature +import org.partiql.value.DateValue +import org.partiql.value.Int32Value +import org.partiql.value.Int64Value +import org.partiql.value.IntValue import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental import org.partiql.value.PartiQLValueType.DATE import org.partiql.value.PartiQLValueType.INT import org.partiql.value.PartiQLValueType.INT32 import org.partiql.value.PartiQLValueType.INT64 -import org.partiql.value.PartiQLValueType.TIME import org.partiql.value.PartiQLValueType.TIMESTAMP +import org.partiql.value.TimestampValue +import org.partiql.value.check +import org.partiql.value.dateValue +import org.partiql.value.timestampValue @OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) internal object Fn_DATE_ADD_YEAR__INT32_DATE__DATE : PartiQLFunction.Scalar { @@ -31,7 +38,15 @@ internal object Fn_DATE_ADD_YEAR__INT32_DATE__DATE : PartiQLFunction.Scalar { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_year not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + dateValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + dateValue(datetimeValue.plusYears(intervalValue)) + } } } @@ -50,7 +65,15 @@ internal object Fn_DATE_ADD_YEAR__INT64_DATE__DATE : PartiQLFunction.Scalar { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_year not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + dateValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + dateValue(datetimeValue.plusYears(intervalValue)) + } } } @@ -69,64 +92,16 @@ internal object Fn_DATE_ADD_YEAR__INT_DATE__DATE : PartiQLFunction.Scalar { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_year not implemented") - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_YEAR__INT32_TIME__TIME : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_year", - returns = TIME, - parameters = listOf( - FunctionParameter("interval", INT32), - FunctionParameter("datetime", TIME), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_year not implemented") - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_YEAR__INT64_TIME__TIME : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_year", - returns = TIME, - parameters = listOf( - FunctionParameter("interval", INT64), - FunctionParameter("datetime", TIME), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_year not implemented") - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_YEAR__INT_TIME__TIME : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_year", - returns = TIME, - parameters = listOf( - FunctionParameter("interval", INT), - FunctionParameter("datetime", TIME), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_year not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + dateValue(null) + } else { + val datetimeValue = datetime.value!! + // TODO: We need to consider overflow here + val intervalValue = interval.long!! + dateValue(datetimeValue.plusYears(intervalValue)) + } } } @@ -145,7 +120,15 @@ internal object Fn_DATE_ADD_YEAR__INT32_TIMESTAMP__TIMESTAMP : PartiQLFunction.S ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_year not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timestampValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + timestampValue(datetimeValue.plusYears(intervalValue)) + } } } @@ -164,7 +147,15 @@ internal object Fn_DATE_ADD_YEAR__INT64_TIMESTAMP__TIMESTAMP : PartiQLFunction.S ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_year not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timestampValue(null) + } else { + val datetimeValue = datetime.value!! + val intervalValue = interval.long!! + timestampValue(datetimeValue.plusYears(intervalValue)) + } } } @@ -183,6 +174,15 @@ internal object Fn_DATE_ADD_YEAR__INT_TIMESTAMP__TIMESTAMP : PartiQLFunction.Sca ) override fun invoke(args: Array): PartiQLValue { - TODO("Function date_add_year not implemented") + val interval = args[0].check() + val datetime = args[1].check() + return if (datetime.value == null || interval.value == null) { + timestampValue(null) + } else { + val datetimeValue = datetime.value!! + // TODO: We need to consider overflow here + val intervalValue = interval.long!! + timestampValue(datetimeValue.plusYears(intervalValue)) + } } } diff --git a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffDay.kt b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffDay.kt index 8c60b69255..3ccc8ecc4a 100644 --- a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffDay.kt +++ b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffDay.kt @@ -11,7 +11,6 @@ import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental import org.partiql.value.PartiQLValueType.DATE import org.partiql.value.PartiQLValueType.INT64 -import org.partiql.value.PartiQLValueType.TIME import org.partiql.value.PartiQLValueType.TIMESTAMP @OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) @@ -33,25 +32,6 @@ internal object Fn_DATE_DIFF_DAY__DATE_DATE__INT64 : PartiQLFunction.Scalar { } } -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_DIFF_DAY__TIME_TIME__INT64 : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_diff_day", - returns = INT64, - parameters = listOf( - FunctionParameter("datetime1", TIME), - FunctionParameter("datetime2", TIME), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_diff_day not implemented") - } -} - @OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) internal object Fn_DATE_DIFF_DAY__TIMESTAMP_TIMESTAMP__INT64 : PartiQLFunction.Scalar { diff --git a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffHour.kt b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffHour.kt index c08632a289..7727af6f91 100644 --- a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffHour.kt +++ b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffHour.kt @@ -9,30 +9,10 @@ import org.partiql.types.function.FunctionParameter import org.partiql.types.function.FunctionSignature import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.PartiQLValueType.DATE import org.partiql.value.PartiQLValueType.INT64 import org.partiql.value.PartiQLValueType.TIME import org.partiql.value.PartiQLValueType.TIMESTAMP -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_DIFF_HOUR__DATE_DATE__INT64 : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_diff_hour", - returns = INT64, - parameters = listOf( - FunctionParameter("datetime1", DATE), - FunctionParameter("datetime2", DATE), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_diff_hour not implemented") - } -} - @OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) internal object Fn_DATE_DIFF_HOUR__TIME_TIME__INT64 : PartiQLFunction.Scalar { diff --git a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffMinute.kt b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffMinute.kt index c9122dc102..49c1c5e425 100644 --- a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffMinute.kt +++ b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffMinute.kt @@ -9,30 +9,10 @@ import org.partiql.types.function.FunctionParameter import org.partiql.types.function.FunctionSignature import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.PartiQLValueType.DATE import org.partiql.value.PartiQLValueType.INT64 import org.partiql.value.PartiQLValueType.TIME import org.partiql.value.PartiQLValueType.TIMESTAMP -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_DIFF_MINUTE__DATE_DATE__INT64 : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_diff_minute", - returns = INT64, - parameters = listOf( - FunctionParameter("datetime1", DATE), - FunctionParameter("datetime2", DATE), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_diff_minute not implemented") - } -} - @OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) internal object Fn_DATE_DIFF_MINUTE__TIME_TIME__INT64 : PartiQLFunction.Scalar { diff --git a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffMonth.kt b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffMonth.kt index 1cd006df2d..45f8877e1c 100644 --- a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffMonth.kt +++ b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffMonth.kt @@ -11,7 +11,6 @@ import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental import org.partiql.value.PartiQLValueType.DATE import org.partiql.value.PartiQLValueType.INT64 -import org.partiql.value.PartiQLValueType.TIME import org.partiql.value.PartiQLValueType.TIMESTAMP @OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) @@ -33,25 +32,6 @@ internal object Fn_DATE_DIFF_MONTH__DATE_DATE__INT64 : PartiQLFunction.Scalar { } } -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_DIFF_MONTH__TIME_TIME__INT64 : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_diff_month", - returns = INT64, - parameters = listOf( - FunctionParameter("datetime1", TIME), - FunctionParameter("datetime2", TIME), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_diff_month not implemented") - } -} - @OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) internal object Fn_DATE_DIFF_MONTH__TIMESTAMP_TIMESTAMP__INT64 : PartiQLFunction.Scalar { diff --git a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffSecond.kt b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffSecond.kt index 6adee6358f..7d26cba5ab 100644 --- a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffSecond.kt +++ b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffSecond.kt @@ -9,30 +9,10 @@ import org.partiql.types.function.FunctionParameter import org.partiql.types.function.FunctionSignature import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.PartiQLValueType.DATE import org.partiql.value.PartiQLValueType.INT64 import org.partiql.value.PartiQLValueType.TIME import org.partiql.value.PartiQLValueType.TIMESTAMP -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_DIFF_SECOND__DATE_DATE__INT64 : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_diff_second", - returns = INT64, - parameters = listOf( - FunctionParameter("datetime1", DATE), - FunctionParameter("datetime2", DATE), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_diff_second not implemented") - } -} - @OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) internal object Fn_DATE_DIFF_SECOND__TIME_TIME__INT64 : PartiQLFunction.Scalar { diff --git a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffYear.kt b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffYear.kt index 7e0bdba66c..edf8b04e60 100644 --- a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffYear.kt +++ b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateDiffYear.kt @@ -11,7 +11,6 @@ import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental import org.partiql.value.PartiQLValueType.DATE import org.partiql.value.PartiQLValueType.INT64 -import org.partiql.value.PartiQLValueType.TIME import org.partiql.value.PartiQLValueType.TIMESTAMP @OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) @@ -33,25 +32,6 @@ internal object Fn_DATE_DIFF_YEAR__DATE_DATE__INT64 : PartiQLFunction.Scalar { } } -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_DIFF_YEAR__TIME_TIME__INT64 : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_diff_year", - returns = INT64, - parameters = listOf( - FunctionParameter("datetime1", TIME), - FunctionParameter("datetime2", TIME), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - TODO("Function date_diff_year not implemented") - } -} - @OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) internal object Fn_DATE_DIFF_YEAR__TIMESTAMP_TIMESTAMP__INT64 : PartiQLFunction.Scalar { diff --git a/plugins/partiql-plugin/src/test/kotlin/org/partiql/plugin/PartiQLHeader.kt b/plugins/partiql-plugin/src/test/kotlin/org/partiql/plugin/PartiQLHeader.kt index dfcc5c998f..53c3704b5f 100644 --- a/plugins/partiql-plugin/src/test/kotlin/org/partiql/plugin/PartiQLHeader.kt +++ b/plugins/partiql-plugin/src/test/kotlin/org/partiql/plugin/PartiQLHeader.kt @@ -642,9 +642,15 @@ internal object PartiQLHeader : Header() { val operators = mutableListOf() for (field in DatetimeField.values()) { for (type in datetime) { - if (field == DatetimeField.TIMEZONE_HOUR || field == DatetimeField.TIMEZONE_MINUTE) { - continue + when (field) { + DatetimeField.YEAR, DatetimeField.MONTH, DatetimeField.DAY -> + if (type == TIME) continue + DatetimeField.HOUR, DatetimeField.MINUTE, DatetimeField.SECOND -> + if (type == DATE) continue + DatetimeField.TIMEZONE_HOUR -> continue + DatetimeField.TIMEZONE_MINUTE -> continue } + for (interval in intervals) { val signature = FunctionSignature.Scalar( name = "date_add_${field.name.lowercase()}", @@ -667,8 +673,13 @@ internal object PartiQLHeader : Header() { val operators = mutableListOf() for (field in DatetimeField.values()) { for (type in datetime) { - if (field == DatetimeField.TIMEZONE_HOUR || field == DatetimeField.TIMEZONE_MINUTE) { - continue + when (field) { + DatetimeField.YEAR, DatetimeField.MONTH, DatetimeField.DAY -> + if (type == TIME) continue + DatetimeField.HOUR, DatetimeField.MINUTE, DatetimeField.SECOND -> + if (type == DATE) continue + DatetimeField.TIMEZONE_HOUR -> continue + DatetimeField.TIMEZONE_MINUTE -> continue } val signature = FunctionSignature.Scalar( name = "date_diff_${field.name.lowercase()}", From 57496a941d96fb0b28fa63bbff54c6771c34f094 Mon Sep 17 00:00:00 2001 From: yliuuuu <107505258+yliuuuu@users.noreply.github.com> Date: Wed, 24 Jan 2024 15:46:34 -0800 Subject: [PATCH 2/2] Partiql plugin cherrypick (#1349) * Isolates local variable resolution logic (#1344) --------- Co-authored-by: R. C. Howell --- .../org/partiql/planner/internal/Env.kt | 253 +++--------------- .../planner/internal/typer/PlanTyper.kt | 131 ++++----- .../partiql/planner/internal/typer/TypeEnv.kt | 155 +++++++++++ .../org/partiql/planner/internal/EnvTest.kt | 12 +- .../planner/internal/typer/TypeEnvTest.kt | 114 ++++++++ 5 files changed, 364 insertions(+), 301 deletions(-) create mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeEnv.kt create mode 100644 partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/TypeEnvTest.kt diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt index 888cb4f238..ca4676a8ec 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt @@ -4,9 +4,15 @@ import org.partiql.planner.PartiQLPlanner import org.partiql.planner.internal.ir.Agg import org.partiql.planner.internal.ir.Catalog import org.partiql.planner.internal.ir.Fn -import org.partiql.planner.internal.ir.Rel import org.partiql.planner.internal.ir.Rex +import org.partiql.planner.internal.ir.catalogSymbolRef +import org.partiql.planner.internal.ir.rex +import org.partiql.planner.internal.ir.rexOpGlobal +import org.partiql.planner.internal.ir.rexOpLit +import org.partiql.planner.internal.ir.rexOpPathKey +import org.partiql.planner.internal.ir.rexOpPathSymbol import org.partiql.planner.internal.typer.FnResolver +import org.partiql.planner.internal.typer.TypeEnv import org.partiql.spi.BindingCase import org.partiql.spi.BindingName import org.partiql.spi.BindingPath @@ -15,8 +21,8 @@ import org.partiql.spi.connector.ConnectorObjectHandle import org.partiql.spi.connector.ConnectorObjectPath import org.partiql.spi.connector.ConnectorSession import org.partiql.types.StaticType -import org.partiql.types.StructType -import org.partiql.types.TupleConstraint +import org.partiql.value.PartiQLValueExperimental +import org.partiql.value.stringValue /** * Handle for associating a catalog name with catalog related metadata objects. @@ -24,84 +30,19 @@ import org.partiql.types.TupleConstraint internal typealias Handle = Pair /** - * TypeEnv represents the environment in which we type expressions and resolve variables while planning. + * Metadata for a resolved global variable * - * TODO TypeEnv should be a stack of locals; also the strategy has been kept here because it's easier to - * pass through the traversal like this, but is conceptually odd to associate with the TypeEnv. - * @property schema - * @property strategy - */ -internal class TypeEnv( - val schema: List, - val strategy: ResolutionStrategy, -) { - - /** - * Return a copy with GLOBAL lookup strategy - */ - fun global() = TypeEnv(schema, ResolutionStrategy.GLOBAL) - - /** - * Return a copy with LOCAL lookup strategy - */ - fun local() = TypeEnv(schema, ResolutionStrategy.LOCAL) - - /** - * Debug string - */ - override fun toString() = buildString { - append("(") - append("strategy=$strategy") - append(", ") - val bindings = "< " + schema.joinToString { "${it.name}: ${it.type}" } + " >" - append("bindings=$bindings") - append(")") - } -} - -/** - * Metadata regarding a resolved variable. + * @property type Resolved StaticType + * @property ordinal The relevant catalog's index offset in the [Env.catalogs] list * @property depth The depth/level of the path match. + * @property position The relevant value's index offset in the [Catalog.values] list */ -internal sealed interface ResolvedVar { - - public val type: StaticType - public val ordinal: Int - public val depth: Int - - /** - * Metadata for a resolved local variable. - * - * @property type Resolved StaticType - * @property ordinal Index offset in [TypeEnv] - * @property resolvedSteps The fully resolved path steps.s - */ - class Local( - override val type: StaticType, - override val ordinal: Int, - val rootType: StaticType, - val resolvedSteps: List, - ) : ResolvedVar { - // the depth are always going to be 1 because this is local variable. - // the global path, however the path length maybe, going to be replaced by a binding name. - override val depth: Int = 1 - } - - /** - * Metadata for a resolved global variable - * - * @property type Resolved StaticType - * @property ordinal The relevant catalog's index offset in the [Env.catalogs] list - * @property depth The depth/level of the path match. - * @property position The relevant value's index offset in the [Catalog.values] list - */ - class Global( - override val type: StaticType, - override val ordinal: Int, - override val depth: Int, - val position: Int, - ) : ResolvedVar -} +internal class ResolvedVar( + val type: StaticType, + val ordinal: Int, + val depth: Int, + val position: Int, +) /** * Variable resolution strategies — https://partiql.org/assets/PartiQL-Specification.pdf#page=35 @@ -220,7 +161,7 @@ internal class Env( type ) // Return resolution metadata - ResolvedVar.Global(type, catalogIndex, depth, valueIndex) + ResolvedVar(type, catalogIndex, depth, valueIndex) } } } @@ -267,31 +208,13 @@ internal class Env( } } - private fun BindingPath.toCaseSensitive(): BindingPath { - return this.copy(steps = this.steps.map { it.copy(bindingCase = BindingCase.SENSITIVE) }) - } - /** * Attempt to resolve a [BindingPath] in the global + local type environments. */ - fun resolve(path: BindingPath, locals: TypeEnv, scope: Rex.Op.Var.Scope): ResolvedVar? { - val strategy = when (scope) { - Rex.Op.Var.Scope.DEFAULT -> locals.strategy - Rex.Op.Var.Scope.LOCAL -> ResolutionStrategy.LOCAL - } + fun resolve(path: BindingPath, locals: TypeEnv, strategy: ResolutionStrategy): Rex? { return when (strategy) { - ResolutionStrategy.LOCAL -> { - var type: ResolvedVar? = null - type = type ?: resolveLocalBind(path, locals.schema) - type = type ?: resolveGlobalBind(path) - type - } - ResolutionStrategy.GLOBAL -> { - var type: ResolvedVar? = null - type = type ?: resolveGlobalBind(path) - type = type ?: resolveLocalBind(path, locals.schema) - type - } + ResolutionStrategy.LOCAL -> locals.resolve(path) ?: resolveGlobalBind(path) + ResolutionStrategy.GLOBAL -> resolveGlobalBind(path) ?: locals.resolve(path) } } @@ -305,7 +228,7 @@ internal class Env( * TODO: Add global bindings * TODO: Replace paths with global variable references if found */ - private fun resolveGlobalBind(path: BindingPath): ResolvedVar? { + private fun resolveGlobalBind(path: BindingPath): Rex? { val currentCatalog = session.currentCatalog?.let { BindingName(it, BindingCase.SENSITIVE) } val currentCatalogPath = BindingPath(session.currentDirectory.map { BindingName(it, BindingCase.SENSITIVE) }) val absoluteCatalogPath = BindingPath(currentCatalogPath.steps + path.steps) @@ -320,122 +243,13 @@ internal class Env( ?: getGlobalType(currentCatalog, path, path) ?: getGlobalType(currentCatalog, path, absoluteCatalogPath) } - } - return resolvedVar - } - - /** - * Check locals, else search structs. - */ - internal fun resolveLocalBind(path: BindingPath, locals: List): ResolvedVar? { - if (path.steps.isEmpty()) { - return null - } - - // 1. Check locals for root - locals.forEachIndexed { ordinal, binding -> - val root = path.steps[0] - if (root.isEquivalentTo(binding.name)) { - return ResolvedVar.Local(binding.type, ordinal, binding.type, path.steps) - } - } - - // 2. Check if this variable is referencing a struct field, carrying ordinals - val matches = mutableListOf() - for (ordinal in locals.indices) { - val rootType = locals[ordinal].type - val pathPrefix = BindingName(locals[ordinal].name, BindingCase.SENSITIVE) - if (rootType is StructType) { - val varType = inferStructLookup(rootType, path) - if (varType != null) { - // we found this path within a struct! - val match = ResolvedVar.Local( - varType.resolvedType, - ordinal, - rootType, - listOf(pathPrefix) + varType.replacementPath.steps, - ) - matches.add(match) - } - } - } - - // 0 -> no match - // 1 -> resolved - // N -> ambiguous - return when (matches.size) { - 0 -> null - 1 -> matches.single() - else -> null // TODO emit ambiguous error - } + } ?: return null + // rewrite as path expression for any remaining steps. + val root = rex(resolvedVar.type, rexOpGlobal(catalogSymbolRef(resolvedVar.ordinal, resolvedVar.position))) + val tail = path.steps.drop(resolvedVar.depth) + return if (tail.isEmpty()) root else root.toPath(tail) } - /** - * Searches for the path within the given struct, returning null if not found. - * - * @return a [ResolvedPath] that contains the disambiguated [ResolvedPath.replacementPath] and the path's - * [StaticType]. Returns NULL if unable to find the [path] given the [struct]. - */ - private fun inferStructLookup(struct: StructType, path: BindingPath): ResolvedPath? { - var curr: StaticType = struct - val replacementSteps = path.steps.map { step -> - // Assume ORDERED for now - val currentStruct = curr as? StructType ?: return null - val (replacement, stepType) = inferStructLookup(currentStruct, step) ?: return null - curr = stepType - replacement - } - // Lookup final field - return ResolvedPath( - BindingPath(replacementSteps), - curr - ) - } - - /** - * Represents a disambiguated [BindingPath] and its inferred [StaticType]. - */ - private class ResolvedPath( - val replacementPath: BindingPath, - val resolvedType: StaticType, - ) - - /** - * @return a disambiguated [key] and the resulting [StaticType]. - */ - private fun inferStructLookup(struct: StructType, key: BindingName): Pair? { - val isClosed = struct.constraints.contains(TupleConstraint.Open(false)) - val isOrdered = struct.constraints.contains(TupleConstraint.Ordered) - return when { - // 1. Struct is closed and ordered - isClosed && isOrdered -> { - struct.fields.firstOrNull { entry -> key.isEquivalentTo(entry.key) }?.let { - (sensitive(it.key) to it.value) - } - } - // 2. Struct is closed - isClosed -> { - val matches = struct.fields.filter { entry -> key.isEquivalentTo(entry.key) } - when (matches.size) { - 0 -> null - 1 -> matches.first().let { (sensitive(it.key) to it.value) } - else -> { - val firstKey = matches.first().key - val sharedKey = when (matches.all { it.key == firstKey }) { - true -> sensitive(firstKey) - false -> key - } - sharedKey to StaticType.unionOf(matches.map { it.value }.toSet()).flatten() - } - } - } - // 3. Struct is open - else -> key to StaticType.ANY - } - } - - private fun sensitive(str: String): BindingName = BindingName(str, BindingCase.SENSITIVE) - /** * Logic for determining how many BindingNames were “matched” by the ConnectorMetadata * 1. Matched = RelativePath - Not Found @@ -450,4 +264,13 @@ internal class Env( ): Int { return originalPath.steps.size + outputCatalogPath.steps.size - inputCatalogPath.steps.size } + + @OptIn(PartiQLValueExperimental::class) + private fun Rex.toPath(steps: List): Rex = steps.fold(this) { curr, step -> + val op = when (step.bindingCase) { + BindingCase.SENSITIVE -> rexOpPathKey(curr, rex(StaticType.STRING, rexOpLit(stringValue(step.name)))) + BindingCase.INSENSITIVE -> rexOpPathSymbol(curr, step.name) + } + rex(StaticType.ANY, op) + } } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt index f2d9ca5662..1c15078553 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt @@ -22,8 +22,6 @@ import org.partiql.errors.UNKNOWN_PROBLEM_LOCATION import org.partiql.planner.PlanningProblemDetails import org.partiql.planner.internal.Env import org.partiql.planner.internal.ResolutionStrategy -import org.partiql.planner.internal.ResolvedVar -import org.partiql.planner.internal.TypeEnv import org.partiql.planner.internal.ir.Agg import org.partiql.planner.internal.ir.Fn import org.partiql.planner.internal.ir.Identifier @@ -32,7 +30,6 @@ import org.partiql.planner.internal.ir.Rel import org.partiql.planner.internal.ir.Rex import org.partiql.planner.internal.ir.Statement import org.partiql.planner.internal.ir.aggResolved -import org.partiql.planner.internal.ir.catalogSymbolRef import org.partiql.planner.internal.ir.fnResolved import org.partiql.planner.internal.ir.identifierSymbol import org.partiql.planner.internal.ir.rel @@ -60,7 +57,6 @@ import org.partiql.planner.internal.ir.rexOpCallStatic import org.partiql.planner.internal.ir.rexOpCaseBranch import org.partiql.planner.internal.ir.rexOpCollection import org.partiql.planner.internal.ir.rexOpErr -import org.partiql.planner.internal.ir.rexOpGlobal import org.partiql.planner.internal.ir.rexOpLit import org.partiql.planner.internal.ir.rexOpPathIndex import org.partiql.planner.internal.ir.rexOpPathKey @@ -70,7 +66,6 @@ import org.partiql.planner.internal.ir.rexOpSelect import org.partiql.planner.internal.ir.rexOpStruct import org.partiql.planner.internal.ir.rexOpStructField import org.partiql.planner.internal.ir.rexOpTupleUnion -import org.partiql.planner.internal.ir.rexOpVarResolved import org.partiql.planner.internal.ir.statementQuery import org.partiql.planner.internal.ir.util.PlanRewriter import org.partiql.spi.BindingCase @@ -124,10 +119,7 @@ internal class PlanTyper( throw IllegalArgumentException("PartiQLPlanner only supports Query statements") } // root TypeEnv has no bindings - val typeEnv = TypeEnv( - schema = emptyList(), - strategy = ResolutionStrategy.GLOBAL, - ) + val typeEnv = TypeEnv(schema = emptyList()) val root = statement.root.type(typeEnv) return statementQuery(root) } @@ -136,8 +128,12 @@ internal class PlanTyper( * Types the relational operators of a query expression. * * @property outer represents the outer TypeEnv of a query expression — only used by scan variable resolution. + * @property strategy */ - private inner class RelTyper(private val outer: TypeEnv) : PlanRewriter() { + private inner class RelTyper( + private val outer: TypeEnv, + private val strategy: ResolutionStrategy, + ) : PlanRewriter() { override fun visitRel(node: Rel, ctx: Rel.Type?) = visitRelOp(node.op, node.type) as Rel @@ -146,7 +142,7 @@ internal class PlanTyper( */ override fun visitRelOpScan(node: Rel.Op.Scan, ctx: Rel.Type?): Rel { // descend, with GLOBAL resolution strategy - val rex = node.rex.type(outer.global()) + val rex = node.rex.type(outer, ResolutionStrategy.GLOBAL) // compute rel type val valueT = getElementTypeForFromSource(rex.type) val type = ctx!!.copyWithSchema(listOf(valueT)) @@ -165,7 +161,7 @@ internal class PlanTyper( */ override fun visitRelOpScanIndexed(node: Rel.Op.ScanIndexed, ctx: Rel.Type?): Rel { // descend, with GLOBAL resolution strategy - val rex = node.rex.type(outer.global()) + val rex = node.rex.type(outer, ResolutionStrategy.GLOBAL) // compute rel type val valueT = getElementTypeForFromSource(rex.type) val indexT = StaticType.INT8 @@ -180,7 +176,7 @@ internal class PlanTyper( */ override fun visitRelOpUnpivot(node: Rel.Op.Unpivot, ctx: Rel.Type?): Rel { // descend, with GLOBAL resolution strategy - val rex = node.rex.type(outer.global()) + val rex = node.rex.type(outer, ResolutionStrategy.GLOBAL) // only UNPIVOT a struct if (rex.type !is StructType) { @@ -215,7 +211,7 @@ internal class PlanTyper( // compute input schema val input = visitRel(node.input, ctx) // type sub-nodes - val typeEnv = TypeEnv(input.type.schema, ResolutionStrategy.LOCAL) + val typeEnv = TypeEnv(input.type.schema) val predicate = node.predicate.type(typeEnv) // compute output schema val type = input.type @@ -228,10 +224,10 @@ internal class PlanTyper( // compute input schema val input = visitRel(node.input, ctx) // type sub-nodes - val typeEnv = TypeEnv(input.type.schema, ResolutionStrategy.LOCAL) + val typeEnv = TypeEnv(input.type.schema) val specs = node.specs.map { val rex = it.rex.type(typeEnv) - it.copy(rex) + it.copy(rex = rex) } // output schema of a sort is the same as the input val type = input.type.copy(props = setOf(Rel.Prop.ORDERED)) @@ -256,8 +252,7 @@ internal class PlanTyper( // compute input schema val input = visitRel(node.input, ctx) // type limit expression using outer scope with global resolution - val typeEnv = outer.global() - val limit = node.limit.type(typeEnv) + val limit = node.limit.type(outer, ResolutionStrategy.GLOBAL) // check types assertAsInt(limit.type) // compute output schema @@ -271,8 +266,7 @@ internal class PlanTyper( // compute input schema val input = visitRel(node.input, ctx) // type offset expression using outer scope with global resolution - val typeEnv = outer.global() - val offset = node.offset.type(typeEnv) + val offset = node.offset.type(outer, ResolutionStrategy.GLOBAL) // check types assertAsInt(offset.type) // compute output schema @@ -286,7 +280,7 @@ internal class PlanTyper( // compute input schema val input = visitRel(node.input, ctx) // type sub-nodes - val typeEnv = TypeEnv(input.type.schema, ResolutionStrategy.LOCAL) + val typeEnv = TypeEnv(input.type.schema) val projections = node.projections.map { it.type(typeEnv) } @@ -315,7 +309,7 @@ internal class PlanTyper( val type = relType(schema, ctx!!.props) // Type the condition on the output schema - val condition = node.rex.type(TypeEnv(type.schema, ResolutionStrategy.LOCAL)) + val condition = node.rex.type(TypeEnv(type.schema)) val op = relOpJoin(lhs, rhs, condition, node.type) return rel(type, op) @@ -359,20 +353,22 @@ internal class PlanTyper( val schema = node.items.fold((init)) { bindings, item -> excludeBindings(bindings, item) } // rewrite - val type = ctx!!.copy(schema) + val type = ctx!!.copy(schema = schema) // resolve exclude path roots val newItems = node.items.map { item -> val resolvedRoot = when (val root = item.root) { is Rex.Op.Var.Unresolved -> { // resolve `root` to local binding - val bindingPath = root.identifier.toBindingPath() - when (val resolved = env.resolveLocalBind(bindingPath, init)) { - null -> { - handleUnresolvedExcludeRoot(root.identifier) - root - } - else -> rexOpVarResolved(resolved.ordinal) + val locals = TypeEnv(input.type.schema) + val path = root.identifier.toBindingPath() + val resolved = locals.resolve(path) + if (resolved == null) { + handleUnresolvedExcludeRoot(root.identifier) + root + } else { + // root of exclude is always a symbol + resolved.op as Rex.Op.Var } } is Rex.Op.Var.Resolved -> root @@ -390,7 +386,7 @@ internal class PlanTyper( val input = visitRel(node.input, ctx) // type the calls and groups - val typer = RexTyper(locals = TypeEnv(input.type.schema, ResolutionStrategy.LOCAL)) + val typer = RexTyper(TypeEnv(input.type.schema), ResolutionStrategy.LOCAL) // typing of aggregate calls is slightly more complicated because they are not expressions. val calls = node.calls.mapIndexed { i, call -> @@ -427,7 +423,10 @@ internal class PlanTyper( * @property locals TypeEnv in which this rex tree is evaluated. */ @OptIn(PartiQLValueExperimental::class) - private inner class RexTyper(private val locals: TypeEnv) : PlanRewriter() { + private inner class RexTyper( + private val locals: TypeEnv, + private val strategy: ResolutionStrategy, + ) : PlanRewriter() { override fun visitRex(node: Rex, ctx: StaticType?): Rex = visitRexOp(node.op, node.type) as Rex @@ -444,52 +443,16 @@ internal class PlanTyper( override fun visitRexOpVarUnresolved(node: Rex.Op.Var.Unresolved, ctx: StaticType?): Rex { val path = node.identifier.toBindingPath() - val resolvedVar = env.resolve(path, locals, node.scope) - + val strategy = when (node.scope) { + Rex.Op.Var.Scope.DEFAULT -> strategy + Rex.Op.Var.Scope.LOCAL -> ResolutionStrategy.LOCAL + } + val resolvedVar = env.resolve(path, locals, strategy) if (resolvedVar == null) { handleUndefinedVariable(path.steps.last()) return rex(ANY, rexOpErr("Undefined variable ${node.identifier}")) } - val type = resolvedVar.type - return when (resolvedVar) { - is ResolvedVar.Global -> { - val variable = rex(type, rexOpGlobal(catalogSymbolRef(resolvedVar.ordinal, resolvedVar.position))) - when (resolvedVar.depth) { - path.steps.size -> variable - else -> { - val foldedPath = foldPath(path.steps, resolvedVar.depth, path.steps.size, variable) - visitRex(foldedPath, ctx) - } - } - } - is ResolvedVar.Local -> { - val variable = rex(type, rexOpVarResolved(resolvedVar.ordinal)) - when { - path.isEquivalentTo(resolvedVar.resolvedSteps) && path.steps.size == resolvedVar.depth -> variable - else -> { - val foldedPath = foldPath(resolvedVar.resolvedSteps, resolvedVar.depth, resolvedVar.resolvedSteps.size, variable) - visitRex(foldedPath, ctx) - } - } - } - } - } - - private fun foldPath(path: List, start: Int, end: Int, global: Rex) = - path.subList(start, end).fold(global) { current, step -> - when (step.bindingCase) { - BindingCase.SENSITIVE -> rex(ANY, rexOpPathKey(current, rex(STRING, rexOpLit(stringValue(step.name))))) - BindingCase.INSENSITIVE -> rex(ANY, rexOpPathSymbol(current, step.name)) - } - } - - private fun BindingPath.isEquivalentTo(other: List): Boolean { - this.steps.forEachIndexed { index, bindingName -> - if (bindingName != other[index]) { - return false - } - } - return true + return visitRex(resolvedVar, null) } override fun visitRexOpGlobal(node: Rex.Op.Global, ctx: StaticType?): Rex { @@ -559,10 +522,16 @@ internal class PlanTyper( val paths = root.type.allTypes.map { type -> val struct = type as? StructType ?: return@map rex(MISSING, rexOpLit(missingValue())) - val (pathType, replacementId) = inferStructLookup(struct, identifierSymbol(node.key, Identifier.CaseSensitivity.INSENSITIVE)) + val (pathType, replacementId) = inferStructLookup( + struct, + identifierSymbol(node.key, Identifier.CaseSensitivity.INSENSITIVE) + ) when (replacementId.caseSensitivity) { Identifier.CaseSensitivity.INSENSITIVE -> rex(pathType, rexOpPathSymbol(root, replacementId.symbol)) - Identifier.CaseSensitivity.SENSITIVE -> rex(pathType, rexOpPathKey(root, rexString(replacementId.symbol))) + Identifier.CaseSensitivity.SENSITIVE -> rex( + pathType, + rexOpPathKey(root, rexString(replacementId.symbol)) + ) } } val type = unionOf(paths.map { it.type }.toSet()).flatten() @@ -901,7 +870,7 @@ internal class PlanTyper( override fun visitRexOpPivot(node: Rex.Op.Pivot, ctx: StaticType?): Rex { val rel = node.rel.type(locals) - val typeEnv = TypeEnv(rel.type.schema, ResolutionStrategy.LOCAL) + val typeEnv = TypeEnv(rel.type.schema) val key = node.key.type(typeEnv) val value = node.value.type(typeEnv) val type = StructType( @@ -960,7 +929,7 @@ internal class PlanTyper( override fun visitRexOpSelect(node: Rex.Op.Select, ctx: StaticType?): Rex { val rel = node.rel.type(locals) - val typeEnv = TypeEnv(rel.type.schema, ResolutionStrategy.LOCAL) + val typeEnv = TypeEnv(rel.type.schema) var constructor = node.constructor.type(typeEnv) var constructorType = constructor.type // add the ordered property to the constructor @@ -1243,9 +1212,11 @@ internal class PlanTyper( // HELPERS - private fun Rel.type(typeEnv: TypeEnv): Rel = RelTyper(typeEnv).visitRel(this, null) + private fun Rel.type(locals: TypeEnv, strategy: ResolutionStrategy = ResolutionStrategy.LOCAL): Rel = + RelTyper(locals, strategy).visitRel(this, null) - private fun Rex.type(typeEnv: TypeEnv) = RexTyper(typeEnv).visitRex(this, this.type) + private fun Rex.type(locals: TypeEnv, strategy: ResolutionStrategy = ResolutionStrategy.LOCAL) = + RexTyper(locals, strategy).visitRex(this, this.type) private fun rexErr(message: String) = rex(MISSING, rexOpErr(message)) diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeEnv.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeEnv.kt new file mode 100644 index 0000000000..d413abde04 --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeEnv.kt @@ -0,0 +1,155 @@ +package org.partiql.planner.internal.typer + +import org.partiql.planner.internal.ir.Rel +import org.partiql.planner.internal.ir.Rex +import org.partiql.planner.internal.ir.rex +import org.partiql.planner.internal.ir.rexOpLit +import org.partiql.planner.internal.ir.rexOpPathKey +import org.partiql.planner.internal.ir.rexOpPathSymbol +import org.partiql.planner.internal.ir.rexOpVarResolved +import org.partiql.spi.BindingCase +import org.partiql.spi.BindingName +import org.partiql.spi.BindingPath +import org.partiql.types.StaticType +import org.partiql.types.StructType +import org.partiql.types.TupleConstraint +import org.partiql.value.PartiQLValueExperimental +import org.partiql.value.stringValue + +/** + * TypeEnv represents a variables type environment. + */ +internal class TypeEnv(public val schema: List) { + + /** + * We resolve a local with the following rules. See, PartiQL Specification p.35. + * + * 1) Check if the path root unambiguously matches a local binding name, set as root. + * 2) Check if the path root unambiguously matches a local binding struct value field. + * + * Convert any remaining binding names (tail) to a path expression. + * + * @param path + * @return + */ + fun resolve(path: BindingPath): Rex? { + val head: BindingName = path.steps[0] + var tail: List = path.steps.drop(1) + var r = matchRoot(head) + if (r == null) { + r = matchStruct(head) ?: return null + tail = path.steps + } + // Convert any remaining binding names (tail) to an untyped path expression. + return if (tail.isEmpty()) r else r.toPath(tail) + } + + /** + * Debugging string, ex: < x: int, y: string > + * + * @return + */ + override fun toString(): String = "< " + schema.joinToString { "${it.name}: ${it.type}" } + " >" + + /** + * Check if `name` unambiguously matches a local binding name and return its reference; otherwise return null. + * + * @param name + * @return + */ + private fun matchRoot(name: BindingName): Rex? { + var r: Rex? = null + for (i in schema.indices) { + val local = schema[i] + val type = local.type + if (name.isEquivalentTo(local.name)) { + if (r != null) { + // TODO root was already matched, emit ambiguous error. + return null + } + r = rex(type, rexOpVarResolved(i)) + } + } + return r + } + + /** + * Check if `name` unambiguously matches a field within a struct and return its reference; otherwise return null. + * + * @param name + * @return + */ + private fun matchStruct(name: BindingName): Rex? { + var c: Rex? = null + var known = false + for (i in schema.indices) { + val local = schema[i] + val type = local.type + if (type is StructType) { + when (type.containsKey(name)) { + true -> { + if (c != null && known) { + // TODO root was already definitively matched, emit ambiguous error. + return null + } + c = rex(type, rexOpVarResolved(i)) + known = true + } + null -> { + if (c != null) { + if (known) { + continue + } else { + // TODO we have more than one possible match, emit ambiguous error. + return null + } + } + c = rex(type, rexOpVarResolved(i)) + known = false + } + false -> continue + } + } + } + return c + } + + /** + * Converts a list of [BindingName] to a path expression. + * + * 1) Case SENSITIVE identifiers become string literal key lookups. + * 2) Case INSENSITIVE identifiers become symbol lookups. + * + * @param steps + * @return + */ + @OptIn(PartiQLValueExperimental::class) + private fun Rex.toPath(steps: List): Rex = steps.fold(this) { curr, step -> + val op = when (step.bindingCase) { + BindingCase.SENSITIVE -> rexOpPathKey(curr, rex(StaticType.STRING, rexOpLit(stringValue(step.name)))) + BindingCase.INSENSITIVE -> rexOpPathSymbol(curr, step.name) + } + rex(StaticType.ANY, op) + } + + /** + * Searches for the [BindingName] within the given [StructType]. + * + * Returns + * - true iff known to contain key + * - false iff known to NOT contain key + * - null iff NOT known to contain key + * + * @param name + * @return + */ + private fun StructType.containsKey(name: BindingName): Boolean? { + for (f in fields) { + if (name.isEquivalentTo(f.key)) { + return true + } + } + val closed = constraints.contains(TupleConstraint.Open(false)) + return if (closed) false else null + } +} diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/EnvTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/EnvTest.kt index cbdb3892e0..3f83b5b309 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/EnvTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/EnvTest.kt @@ -5,7 +5,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.partiql.planner.PartiQLPlanner import org.partiql.planner.internal.ir.Catalog -import org.partiql.planner.internal.ir.Rex +import org.partiql.planner.internal.typer.TypeEnv import org.partiql.plugins.local.LocalConnector import org.partiql.spi.BindingCase import org.partiql.spi.BindingName @@ -22,7 +22,7 @@ class EnvTest { private val root = this::class.java.getResource("/catalogs/default/pql")!!.toURI().toPath() - private val EMPTY_TYPE_ENV = TypeEnv(schema = emptyList(), ResolutionStrategy.GLOBAL) + private val EMPTY_TYPE_ENV = TypeEnv(schema = emptyList()) private val GLOBAL_OS = Catalog( name = "pql", @@ -52,7 +52,7 @@ class EnvTest { @Test fun testGlobalMatchingSensitiveName() { val path = BindingPath(listOf(BindingName("os", BindingCase.SENSITIVE))) - assertNotNull(env.resolve(path, EMPTY_TYPE_ENV, Rex.Op.Var.Scope.DEFAULT)) + assertNotNull(env.resolve(path, EMPTY_TYPE_ENV, ResolutionStrategy.GLOBAL)) assertEquals(1, env.catalogs.size) assert(env.catalogs.contains(GLOBAL_OS)) } @@ -60,7 +60,7 @@ class EnvTest { @Test fun testGlobalMatchingInsensitiveName() { val path = BindingPath(listOf(BindingName("oS", BindingCase.INSENSITIVE))) - assertNotNull(env.resolve(path, EMPTY_TYPE_ENV, Rex.Op.Var.Scope.DEFAULT)) + assertNotNull(env.resolve(path, EMPTY_TYPE_ENV, ResolutionStrategy.GLOBAL)) assertEquals(1, env.catalogs.size) assert(env.catalogs.contains(GLOBAL_OS)) } @@ -68,14 +68,14 @@ class EnvTest { @Test fun testGlobalNotMatchingSensitiveName() { val path = BindingPath(listOf(BindingName("oS", BindingCase.SENSITIVE))) - assertNull(env.resolve(path, EMPTY_TYPE_ENV, Rex.Op.Var.Scope.DEFAULT)) + assertNull(env.resolve(path, EMPTY_TYPE_ENV, ResolutionStrategy.GLOBAL)) assert(env.catalogs.isEmpty()) } @Test fun testGlobalNotMatchingInsensitiveName() { val path = BindingPath(listOf(BindingName("nonexistent", BindingCase.INSENSITIVE))) - assertNull(env.resolve(path, EMPTY_TYPE_ENV, Rex.Op.Var.Scope.DEFAULT)) + assertNull(env.resolve(path, EMPTY_TYPE_ENV, ResolutionStrategy.GLOBAL)) assert(env.catalogs.isEmpty()) } } diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/TypeEnvTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/TypeEnvTest.kt new file mode 100644 index 0000000000..67504b0f89 --- /dev/null +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/TypeEnvTest.kt @@ -0,0 +1,114 @@ +package org.partiql.planner.internal.typer + +import org.junit.jupiter.api.parallel.Execution +import org.junit.jupiter.api.parallel.ExecutionMode +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource +import org.partiql.planner.internal.ir.Rex +import org.partiql.planner.internal.ir.relBinding +import org.partiql.spi.BindingCase +import org.partiql.spi.BindingName +import org.partiql.spi.BindingPath +import org.partiql.types.BoolType +import org.partiql.types.StaticType +import org.partiql.types.StructType +import org.partiql.types.TupleConstraint +import kotlin.test.assertEquals +import kotlin.test.fail + +internal class TypeEnvTest { + + companion object { + + /** + * < + * A : { B: } }, + * a : { b: } }, + * X : { ... } }, + * x : { y: , ... } }, + * Y : { ... } }, + * T : { x: , x: } }, + * > + */ + @JvmStatic + val locals = TypeEnv( + listOf( + relBinding("A", struct("B" to BoolType())), + relBinding("a", struct("b" to BoolType())), + relBinding("X", struct(open = true)), + relBinding("x", struct("Y" to BoolType(), open = true)), + relBinding("y", struct(open = true)), + relBinding("T", struct("x" to BoolType(), "x" to BoolType())), + ) + ) + + private fun struct(vararg fields: Pair, open: Boolean = false): StructType { + return StructType( + fields = fields.map { StructType.Field(it.first, it.second) }, + constraints = setOf(TupleConstraint.Open(open)), + ) + } + + @JvmStatic + public fun cases() = listOf>( + // root matching + """ A.B """ to null, + """ A."B" """ to null, + """ "A".B """ to 0, + """ "A"."B" """ to 0, + """ "a".B """ to 1, + """ "a"."B" """ to 1, + """ x """ to null, + // """ x.y """ to 3, + """ y """ to 4, + + // struct searching + """ b """ to null, + """ "B" """ to 0, + """ "b" """ to 1, + """ "Y" """ to 3, + + // other + """ T.x """ to 5 + ) + } + + @ParameterizedTest + @MethodSource("cases") + @Execution(ExecutionMode.CONCURRENT) + fun resolve(case: Pair) { + val path = case.first.path() + val expected = case.second + val rex = locals.resolve(path) + if (rex == null) { + if (expected == null) { + return // pass + } else { + fail("could not resolve variable") + } + } + // For now, just traverse to the root + var root = rex.op + while (root !is Rex.Op.Var.Resolved) { + root = when (root) { + is Rex.Op.Path.Symbol -> root.root.op + is Rex.Op.Path.Key -> root.root.op + else -> { + fail("Expected path step of symbol or key, but found $root") + } + } + } + // + assertEquals(expected, root.ref) + } + + private fun String.path(): BindingPath { + val steps = trim().split(".").map { + when (it.startsWith("\"")) { + true -> BindingName(it.drop(1).dropLast(1), BindingCase.SENSITIVE) + else -> BindingName(it, BindingCase.INSENSITIVE) + } + } + return BindingPath(steps) + } +}