From fe7c204baa07c3801a56d153c5d5fcb6797d2450 Mon Sep 17 00:00:00 2001 From: Ziqi Li Date: Fri, 11 Feb 2022 12:06:46 -0800 Subject: [PATCH 1/5] Changed Path AST node to use its root node source location --- lang/src/Test.kt | 66 +++++++++++++++++++ lang/src/org/partiql/lang/syntax/SqlParser.kt | 2 +- 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 lang/src/Test.kt diff --git a/lang/src/Test.kt b/lang/src/Test.kt new file mode 100644 index 0000000000..cce62137c9 --- /dev/null +++ b/lang/src/Test.kt @@ -0,0 +1,66 @@ +import com.amazon.ion.system.IonSystemBuilder +import org.partiql.lang.CompilerPipeline +import org.partiql.lang.eval.Bindings +import org.partiql.lang.eval.EvaluationSession +import org.partiql.lang.eval.ExprValue +import org.partiql.lang.syntax.SqlParser + +fun main(){ + val ion = IonSystemBuilder.standard().build() + val pipeline = CompilerPipeline.standard(ion) + + val globalVariables = Bindings.buildLazyBindings { + addBinding("users") { + val values = ion.loader.load( + """ + { + "name": "mustafa", + "age": 30, + } + { + "name": "ilteris", + "age": 26, + } + { + "name": "onur", + "age": 24, + }""".trimMargin() + ) + pipeline.valueFactory.newFromIonValue(values) + } + addBinding("roles") { + val values = ion.loader.load( + """ + { + "name": "mustafa", + "admin": true, + } + { + "name": "ilteris", + "admin": false, + }""".trimMargin() + ) + pipeline.valueFactory.newFromIonValue(values) + } + } + + //val query = "SELECT * FROM users WHERE name IN (SELECT VALUE name FROM roles)" + //val query = "SELECT * FROM users WHERE EXISTS (SELECT * FROM roles WHERE roles.name = users.name)" + val query = "SELECT name FROM roles" + + val parser = SqlParser(ion) + val ast = parser.parseAstStatement(query) + + val e = pipeline.compile(query) + val session = EvaluationSession.build { + globals(globalVariables) + } + + val result = e.eval(session) + + println(result) + +// for (exprValue in result) { +// println(exprValue) +// } +} \ No newline at end of file diff --git a/lang/src/org/partiql/lang/syntax/SqlParser.kt b/lang/src/org/partiql/lang/syntax/SqlParser.kt index 3b23784f18..c044fb8942 100644 --- a/lang/src/org/partiql/lang/syntax/SqlParser.kt +++ b/lang/src/org/partiql/lang/syntax/SqlParser.kt @@ -441,7 +441,7 @@ class SqlParser( else -> errMalformedParseTree("Unsupported path component: ${it.type}") } } - path(rootExpr, pathComponents, metas) + path(rootExpr, pathComponents, rootExpr.metas) // Here we use its root source location, since itself has no source location (not a token). } ParseType.PARAMETER -> parameter(token!!.value!!.longValue(), metas) ParseType.CASE -> { From 2e00c05740d1114189439649746d65034a167261 Mon Sep 17 00:00:00 2001 From: Ziqi Li Date: Fri, 11 Feb 2022 12:14:34 -0800 Subject: [PATCH 2/5] removed unnecesary files --- lang/src/Test.kt | 66 ------------------------------------------------ 1 file changed, 66 deletions(-) delete mode 100644 lang/src/Test.kt diff --git a/lang/src/Test.kt b/lang/src/Test.kt deleted file mode 100644 index cce62137c9..0000000000 --- a/lang/src/Test.kt +++ /dev/null @@ -1,66 +0,0 @@ -import com.amazon.ion.system.IonSystemBuilder -import org.partiql.lang.CompilerPipeline -import org.partiql.lang.eval.Bindings -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.syntax.SqlParser - -fun main(){ - val ion = IonSystemBuilder.standard().build() - val pipeline = CompilerPipeline.standard(ion) - - val globalVariables = Bindings.buildLazyBindings { - addBinding("users") { - val values = ion.loader.load( - """ - { - "name": "mustafa", - "age": 30, - } - { - "name": "ilteris", - "age": 26, - } - { - "name": "onur", - "age": 24, - }""".trimMargin() - ) - pipeline.valueFactory.newFromIonValue(values) - } - addBinding("roles") { - val values = ion.loader.load( - """ - { - "name": "mustafa", - "admin": true, - } - { - "name": "ilteris", - "admin": false, - }""".trimMargin() - ) - pipeline.valueFactory.newFromIonValue(values) - } - } - - //val query = "SELECT * FROM users WHERE name IN (SELECT VALUE name FROM roles)" - //val query = "SELECT * FROM users WHERE EXISTS (SELECT * FROM roles WHERE roles.name = users.name)" - val query = "SELECT name FROM roles" - - val parser = SqlParser(ion) - val ast = parser.parseAstStatement(query) - - val e = pipeline.compile(query) - val session = EvaluationSession.build { - globals(globalVariables) - } - - val result = e.eval(session) - - println(result) - -// for (exprValue in result) { -// println(exprValue) -// } -} \ No newline at end of file From aef18ad920664a695425c40dd5f53d2e13619500 Mon Sep 17 00:00:00 2001 From: Ziqi Li Date: Mon, 14 Feb 2022 00:03:00 -0800 Subject: [PATCH 3/5] Added test case --- .../StaticTypeInferenceVisitorTransformTest.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lang/test/org/partiql/lang/eval/visitors/StaticTypeInferenceVisitorTransformTest.kt b/lang/test/org/partiql/lang/eval/visitors/StaticTypeInferenceVisitorTransformTest.kt index 54de40b4e4..28599e100a 100644 --- a/lang/test/org/partiql/lang/eval/visitors/StaticTypeInferenceVisitorTransformTest.kt +++ b/lang/test/org/partiql/lang/eval/visitors/StaticTypeInferenceVisitorTransformTest.kt @@ -5737,6 +5737,21 @@ class StaticTypeInferenceVisitorTransformTest : VisitorTransformTestBase() { ) ) ), + TestCase( + name = "function signature without optional/variadic arguments, null propagating given non matching type for a path", + originalSql = "UPPER(x.y)", + globals = mapOf("x" to StructType(mapOf("y" to INT))), + handler = expectSemanticProblems( + expectedProblems = listOf( + createInvalidArgumentTypeForFunctionError( + sourceLocation = SourceLocationMeta(1L, 7L, 1L), + functionName = "upper", + expectedArgType = unionOf(STRING, SYMBOL), + actualType = INT + ) + ) + ) + ), TestCase( name = "function signature without optional/variadic arguments, null propagating given non matching type with missing", originalSql = "UPPER(x)", From 9c6e0f9e56f15e158f74e5db71c9802940e85e88 Mon Sep 17 00:00:00 2001 From: Ziqi Li Date: Mon, 14 Feb 2022 14:36:36 -0800 Subject: [PATCH 4/5] Added another test case for list reference --- .../StaticTypeInferenceVisitorTransformTest.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lang/test/org/partiql/lang/eval/visitors/StaticTypeInferenceVisitorTransformTest.kt b/lang/test/org/partiql/lang/eval/visitors/StaticTypeInferenceVisitorTransformTest.kt index 28599e100a..1c7d7672a6 100644 --- a/lang/test/org/partiql/lang/eval/visitors/StaticTypeInferenceVisitorTransformTest.kt +++ b/lang/test/org/partiql/lang/eval/visitors/StaticTypeInferenceVisitorTransformTest.kt @@ -5752,6 +5752,21 @@ class StaticTypeInferenceVisitorTransformTest : VisitorTransformTestBase() { ) ) ), + TestCase( + name = "function signature without optional/variadic arguments, null propagating given non matching type for list reference", + originalSql = "UPPER(x[0])", + globals = mapOf("x" to ListType(INT)), + handler = expectSemanticProblems( + expectedProblems = listOf( + createInvalidArgumentTypeForFunctionError( + sourceLocation = SourceLocationMeta(1L, 7L, 1L), + functionName = "upper", + expectedArgType = unionOf(STRING, SYMBOL), + actualType = INT + ) + ) + ) + ), TestCase( name = "function signature without optional/variadic arguments, null propagating given non matching type with missing", originalSql = "UPPER(x)", From 3ca8278d0a78514a538cbac3e4216de2af2ba518 Mon Sep 17 00:00:00 2001 From: Ziqi Li Date: Thu, 17 Feb 2022 17:46:12 -0800 Subject: [PATCH 5/5] Clean up ExprFunction tests --- lang/src/org/partiql/lang/eval/Exceptions.kt | 19 +- .../builtins/BuiltInFunctionTestExtensions.kt | 49 +++ .../eval/builtins/CharLengthEvaluationTest.kt | 19 -- .../eval/builtins/ConcatEvaluationTest.kt | 52 ---- .../eval/builtins/DateAddEvaluationTest.kt | 83 ------ .../eval/builtins/DateAddExprFunctionTest.kt | 182 ----------- .../eval/builtins/DateDiffEvaluationTest.kt | 78 ----- .../eval/builtins/DateDiffExprFunctionTest.kt | 251 ---------------- .../eval/builtins/ExistsEvaluationTest.kt | 128 -------- .../eval/builtins/ExtractEvaluationTest.kt | 282 ------------------ .../eval/builtins/FromUnixTimeFunctionTest.kt | 71 ----- .../eval/builtins/InvalidArgTypeChecker.kt | 138 +++++++++ .../lang/eval/builtins/InvalidArityChecker.kt | 69 +++++ .../lang/eval/builtins/LowerEvaluationTest.kt | 15 - .../eval/builtins/MakeDateEvaluationTest.kt | 125 -------- .../eval/builtins/MakeTimeEvaluationTest.kt | 172 ----------- .../lang/eval/builtins/SizeEvaluationTest.kt | 87 ------ .../eval/builtins/SizeExprFunctionTest.kt | 58 ---- .../eval/builtins/SubstringEvaluationTest.kt | 229 -------------- .../lang/eval/builtins/TimestampParserTest.kt | 24 +- .../TimestampTemporalAccessorTests.kt | 3 +- .../eval/builtins/ToStringExprFunctionTest.kt | 70 ----- .../builtins/ToTimestampExprFunctionTest.kt | 107 ------- .../lang/eval/builtins/TrimEvaluationTest.kt | 182 ----------- .../eval/builtins/TrimExprFunctionTest.kt | 65 ---- .../builtins/UnixTimestampFunctionTest.kt | 95 ------ .../lang/eval/builtins/UpperEvaluationTest.kt | 15 - .../eval/builtins/UtcNowEvaluationTest.kt | 88 ------ .../functions/CharLengthEvaluationTest.kt | 55 ++++ .../CharacterLengthEvaluationTest.kt | 55 ++++ .../functions/ConcatEvaluationTest.kt | 146 +++++++++ .../functions/DateAddEvaluationTest.kt | 191 ++++++++++++ .../functions/DateDiffEvaluationTest.kt | 247 +++++++++++++++ .../functions/ExistsEvaluationTest.kt | 55 ++++ .../functions/ExtractEvaluationTest.kt | 224 ++++++++++++++ .../functions/FromUnixTimeFunctionTest.kt | 86 ++++++ .../builtins/functions/LowerEvaluationTest.kt | 52 ++++ .../functions/MakeDateEvaluationTest.kt | 84 ++++++ .../functions/MakeTimeEvaluationTest.kt | 88 ++++++ .../builtins/functions/SizeEvaluationTest.kt | 57 ++++ .../functions/SubstringEvaluationTest.kt | 120 ++++++++ .../functions/ToStringEvaluationTest.kt | 99 ++++++ .../functions/ToTimestampEvaluationTest.kt | 128 ++++++++ .../builtins/functions/TrimEvaluationTest.kt | 102 +++++++ .../functions/UnixTimestampFunctionTest.kt | 68 +++++ .../builtins/functions/UpperEvaluationTest.kt | 53 ++++ .../functions/UtcNowEvaluationTest.kt | 33 ++ .../TimestampFormatPatternLexerTest.kt | 2 +- .../TimestampFormatPatternParserTest.kt | 14 +- .../ToTimestampFormatPatternValidationTest.kt | 2 +- 50 files changed, 2234 insertions(+), 2483 deletions(-) create mode 100644 lang/test/org/partiql/lang/eval/builtins/BuiltInFunctionTestExtensions.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/CharLengthEvaluationTest.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/ConcatEvaluationTest.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/DateAddEvaluationTest.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/DateAddExprFunctionTest.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/DateDiffEvaluationTest.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/DateDiffExprFunctionTest.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/ExistsEvaluationTest.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/ExtractEvaluationTest.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/FromUnixTimeFunctionTest.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/InvalidArgTypeChecker.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/InvalidArityChecker.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/LowerEvaluationTest.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/MakeDateEvaluationTest.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/MakeTimeEvaluationTest.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/SizeEvaluationTest.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/SizeExprFunctionTest.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/SubstringEvaluationTest.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/ToStringExprFunctionTest.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/ToTimestampExprFunctionTest.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/TrimEvaluationTest.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/TrimExprFunctionTest.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/UnixTimestampFunctionTest.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/UpperEvaluationTest.kt delete mode 100644 lang/test/org/partiql/lang/eval/builtins/UtcNowEvaluationTest.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/functions/CharLengthEvaluationTest.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/functions/CharacterLengthEvaluationTest.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/functions/ConcatEvaluationTest.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/functions/DateAddEvaluationTest.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/functions/DateDiffEvaluationTest.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/functions/ExistsEvaluationTest.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/functions/ExtractEvaluationTest.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/functions/FromUnixTimeFunctionTest.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/functions/LowerEvaluationTest.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/functions/MakeDateEvaluationTest.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/functions/MakeTimeEvaluationTest.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/functions/SizeEvaluationTest.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/functions/SubstringEvaluationTest.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/functions/ToStringEvaluationTest.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/functions/ToTimestampEvaluationTest.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/functions/TrimEvaluationTest.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/functions/UnixTimestampFunctionTest.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/functions/UpperEvaluationTest.kt create mode 100644 lang/test/org/partiql/lang/eval/builtins/functions/UtcNowEvaluationTest.kt diff --git a/lang/src/org/partiql/lang/eval/Exceptions.kt b/lang/src/org/partiql/lang/eval/Exceptions.kt index 1a0ffdefe0..b85e9943f3 100644 --- a/lang/src/org/partiql/lang/eval/Exceptions.kt +++ b/lang/src/org/partiql/lang/eval/Exceptions.kt @@ -52,6 +52,16 @@ internal fun errNoContext(message: String, errorCode: ErrorCode, internal: Boole internal fun err(message: String, errorCode: ErrorCode, errorContext: PropertyValueMap?, internal: Boolean): Nothing = throw EvaluationException(message, errorCode, errorContext, internal = internal) +internal fun expectedArgTypeErrorMsg (types: List) : String = when (types.size) { + 0 -> throw IllegalStateException("Should have at least one expected argument type. ") + 1 -> types[0].toString() + else -> { + val window = types.size - 1 + val (most, last) = types.windowed(window, window, true) + most.joinToString(", ") + ", or ${last.first()}" + } +} + /** Throw an [ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL] error */ internal fun errInvalidArgumentType( signature: FunctionSignature, @@ -62,14 +72,7 @@ internal fun errInvalidArgumentType( ): Nothing { val arity = signature.arity - val expectedTypeMsg = when(expectedTypes.size) { - 1 -> expectedTypes[0] - else -> { - val window = expectedTypes.size - 1 - val (most, last) = expectedTypes.windowed(window, window, true) - most.joinToString(", ") + ", or ${last.first()}" - } - } + val expectedTypeMsg = expectedArgTypeErrorMsg(expectedTypes) val errorContext = propertyValueMapOf( Property.FUNCTION_NAME to signature.name, diff --git a/lang/test/org/partiql/lang/eval/builtins/BuiltInFunctionTestExtensions.kt b/lang/test/org/partiql/lang/eval/builtins/BuiltInFunctionTestExtensions.kt new file mode 100644 index 0000000000..f22cb51152 --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/BuiltInFunctionTestExtensions.kt @@ -0,0 +1,49 @@ +package org.partiql.lang.eval.builtins + +import com.amazon.ion.Timestamp +import org.partiql.lang.ION +import org.partiql.lang.eval.Bindings +import org.partiql.lang.eval.EvaluationSession +import org.partiql.lang.eval.ExprValue +import org.partiql.lang.eval.ExprValueFactory +import org.partiql.lang.util.newFromIonText + +/** + * Internal function used by ExprFunctionTest to test invalid argument type. + */ +internal val invalidArgTypeChecker = InvalidArgTypeChecker() +internal fun checkInvalidArgType(funcName: String, syntaxSuffix: String = "(", args: List) = + invalidArgTypeChecker.checkInvalidArgType(funcName, syntaxSuffix, args) + +/** + * Internal function used by ExprFunctionTest to test invalid arity. + */ +internal val invalidArityChecker = InvalidArityChecker() +internal fun checkInvalidArity(funcName: String, minArity: Int, maxArity: Int) = + invalidArityChecker.checkInvalidArity(funcName, minArity, maxArity) + +private val valueFactory = ExprValueFactory.standard(ION) + +private fun String.toExprValue(): ExprValue = valueFactory.newFromIonText(this) + +private fun Map.toBindings(): Bindings = Bindings.ofMap(mapValues { it.value.toExprValue() }) + +/** + * Internal function used by ExprFunctionTest to build EvaluationSession. + */ +internal fun Map.toSession() = EvaluationSession.build { globals(this@toSession.toBindings()) } + +/** + * Internal function used by ExprFunctionTest to build EvaluationSession with now. + */ +internal fun buildSessionWithNow(numMillis: Long, localOffset: Int) = + EvaluationSession.build { now(Timestamp.forMillis(numMillis, localOffset)) } + +/** + * Used by ExprFunctionTest to represent a test case. + */ +data class ExprFunctionTestCase( + val source: String, + val expected: String, + val session: EvaluationSession = EvaluationSession.standard() +) diff --git a/lang/test/org/partiql/lang/eval/builtins/CharLengthEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/CharLengthEvaluationTest.kt deleted file mode 100644 index 0e9a613d27..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/CharLengthEvaluationTest.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.partiql.lang.eval.builtins - -import org.junit.Test -import org.partiql.lang.eval.EvaluatorTestBase - -class CharLengthEvaluationTest : EvaluatorTestBase() { - //Note: character_length is same as char_length - @Test fun character_length_1() = assertEval("character_length('a')", "1") - @Test fun char_length_0() = assertEval("char_length('')", "0") - @Test fun char_length_1() = assertEval("char_length('a')", "1") - @Test fun char_length_2() = assertEval("char_length('ab')", "2") - @Test fun char_length_3() = assertEval("char_length('abcdefghijklmnopqrstuvwxyz')", "26") - @Test fun char_length_4() = assertEval("char_length(null)", "null") - @Test fun char_length_5() = assertEval("char_length(missing)", "null") - @Test fun char_length_6() = assertEval("char_length('ȴȵ💩💋')", "4") - @Test fun char_length_7() = assertEval("char_length('😁😞😸😸')", "4") - @Test fun char_length_8() = assertEval("char_length('話家身圧費谷料村能計税金')", "12") - @Test fun char_length_9() = assertEval("char_length('eࠫ')", "2") //This is a unicode "combining character" which is actually 2 codepoints -} diff --git a/lang/test/org/partiql/lang/eval/builtins/ConcatEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/ConcatEvaluationTest.kt deleted file mode 100644 index 8478242588..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/ConcatEvaluationTest.kt +++ /dev/null @@ -1,52 +0,0 @@ -package org.partiql.lang.eval.builtins - -import org.junit.Test -import org.partiql.lang.errors.ErrorCode.EVALUATOR_CONCAT_FAILED_DUE_TO_INCOMPATIBLE_TYPE -import org.partiql.lang.errors.Property.ACTUAL_ARGUMENT_TYPES -import org.partiql.lang.eval.EvaluatorTestBase -import org.partiql.lang.eval.ExprValueType.STRING -import org.partiql.lang.eval.ExprValueType.INT -import org.partiql.lang.eval.ExprValueType.STRUCT -import org.partiql.lang.eval.ExprValueType.TIMESTAMP -import org.partiql.lang.util.sourceLocationProperties - -class ConcatEvaluationTest : EvaluatorTestBase() { - @Test - fun concatWrongLeftType() = - checkInputThrowingEvaluationException("1 || 'a'", - EVALUATOR_CONCAT_FAILED_DUE_TO_INCOMPATIBLE_TYPE, - sourceLocationProperties(1, 3) + - mapOf(ACTUAL_ARGUMENT_TYPES to listOf(INT, STRING).toString()), - expectedPermissiveModeResult = "MISSING") - - @Test - fun concatWrongRightType() = - checkInputThrowingEvaluationException("'a' || 1", - EVALUATOR_CONCAT_FAILED_DUE_TO_INCOMPATIBLE_TYPE, - sourceLocationProperties(1, 5) + - mapOf(ACTUAL_ARGUMENT_TYPES to listOf(STRING, INT).toString()), - expectedPermissiveModeResult = "MISSING") - - @Test - fun concatWrongBothTypes() = - checkInputThrowingEvaluationException("{} || `2010T`", - EVALUATOR_CONCAT_FAILED_DUE_TO_INCOMPATIBLE_TYPE, - sourceLocationProperties(1, 4) + - mapOf(ACTUAL_ARGUMENT_TYPES to listOf(STRUCT, TIMESTAMP).toString()), - expectedPermissiveModeResult = "MISSING") - - @Test - fun strings() = assertEval("'a' || 'b'", "\"ab\"") - - @Test - fun symbols() = assertEval("`'a'` || `'b'`", "\"ab\"") - - @Test - fun stringAndSymbols() = assertEval("'a' || `'b'`", "\"ab\"") - - @Test - fun nullAndString() = assertEval("null || 'b'", "null") - - @Test - fun missingAndString() = assertEval("missing || 'b'", "null") -} diff --git a/lang/test/org/partiql/lang/eval/builtins/DateAddEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/DateAddEvaluationTest.kt deleted file mode 100644 index 0c310c5d20..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/DateAddEvaluationTest.kt +++ /dev/null @@ -1,83 +0,0 @@ -package org.partiql.lang.eval.builtins - -import org.junit.Test -import org.partiql.lang.errors.UNBOUND_QUOTED_IDENTIFIER_HINT -import org.partiql.lang.eval.EvaluatorTestBase -import org.partiql.lang.eval.NodeMetadata - -/** - * More detailed tests are in [DateAddExprFunctionTest] and parsing related tests in - * [org.partiql.lang.syntax.SqlParserTest] and [org.partiql.lang.errors.ParserErrorsTest]. - */ -class DateAddEvaluationTest : EvaluatorTestBase() { - - @Test - fun dateAddYear() = assertEval("date_add(year, 1, `2017-01-10T05:30:55Z`)", "2018-01-10T05:30:55Z") - - @Test - fun dateAddMonth() = assertEval("date_add(month, 1, `2017-01-10T05:30:55Z`)", "2017-02-10T05:30:55Z") - - @Test - fun dateAddDay() = assertEval("date_add(day, 1, `2017-01-10T05:30:55Z`)", "2017-01-11T05:30:55Z") - - @Test - fun dateAddHour() = assertEval("date_add(hour, 1, `2017-01-10T05:30:55Z`)", "2017-01-10T06:30:55Z") - - @Test - fun dateAddMinute() = assertEval("date_add(minute, 1, `2017-01-10T05:30:55Z`)", "2017-01-10T05:31:55Z") - - @Test - fun dateAddSecond() = assertEval("date_add(second, 1, `2017-01-10T05:30:55Z`)", "2017-01-10T05:30:56Z") - - @Test - fun dateAddNull02() = assertEval("date_add(second, null, `2017-01-10T05:30:55Z`)", "null") - - @Test - fun dateAddNull03() = assertEval("date_add(second, 1, null)", "null") - - @Test - fun dateAddMissing02() = assertEval("date_add(second, missing, `2017-01-10T05:30:55Z`)", "null") - - @Test - fun dateAddMissing03() = assertEval("date_add(second, 1, missing)", "null") - - @Test - fun dateAddWithBindings() = assertEval("date_add(second, a, b)", "2017-01-10T05:30:56Z", mapOf( - "a" to "1", - "b" to "2017-01-10T05:30:55Z").toSession()) - - @Test - fun wrongArgumentTypes() = assertThrows( - "date_add(year, \"foobar\", 1)", - "No such binding: foobar. $UNBOUND_QUOTED_IDENTIFIER_HINT", - NodeMetadata(1, 16), - "MISSING") - - @Test - fun addingYearOutsideOfTimestampBoundaries() = assertThrows( - "date_add(year, 10000, `2017-06-27T`)", - "Year 12017 must be between 1 and 9999 inclusive", - NodeMetadata(1, 1), - "MISSING") - - @Test - fun addingNegativeYearOutsideOfTimestampBoundaries() = assertThrows( - "date_add(year, -10000, `2000-06-27T`)", - "Year -8001 must be between 1 and 9999 inclusive", - NodeMetadata(1, 1), - "MISSING") - - @Test - fun addingMonthsOutsideOfTimestampBoundaries() = assertThrows( - "date_add(month, 10000*12, `2017-06-27T`)", - "Year 12017 must be between 1 and 9999 inclusive", - NodeMetadata(1, 1), - "MISSING") - - @Test - fun addingNegativeMonthsOutsideOfTimestampBoundaries() = assertThrows( - "date_add(month, -10000*12, `2000-06-27T`)", - "Year -8001 must be between 1 and 9999 inclusive", - NodeMetadata(1, 1), - "MISSING") -} diff --git a/lang/test/org/partiql/lang/eval/builtins/DateAddExprFunctionTest.kt b/lang/test/org/partiql/lang/eval/builtins/DateAddExprFunctionTest.kt deleted file mode 100644 index 90158b297f..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/DateAddExprFunctionTest.kt +++ /dev/null @@ -1,182 +0,0 @@ -package org.partiql.lang.eval.builtins - -import com.amazon.ion.Timestamp -import junitparams.Parameters -import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.Test -import org.partiql.lang.TestBase -import org.partiql.lang.eval.Environment -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.RequiredArgs -import org.partiql.lang.eval.call -import org.partiql.lang.eval.timestampValue -import org.partiql.lang.syntax.DateTimePart - -class DateAddExprFunctionTest : TestBase() { - - - private val env = Environment.standard() - - private val subject = DateAddExprFunction(valueFactory) - - private fun callDateAdd(vararg args: Any) = subject.call(env, RequiredArgs(args.map { anyToExprValue(it) })).timestampValue() - - @Test - fun wrongTypeOfFirstArgument() { - assertThatThrownBy { callDateAdd(1, 1, Timestamp.valueOf("2017T")) } - .hasMessage("Expected text: 1") - .isExactlyInstanceOf(EvaluationException::class.java) - } - - @Test - fun nonExistingDateTimePart() { - assertThatThrownBy { callDateAdd("foobar", 1, Timestamp.valueOf("2017T")) } - .hasMessage("invalid datetime part, valid values: [year, month, day, hour, minute, second, timezone_hour, timezone_minute]") - .isExactlyInstanceOf(EvaluationException::class.java) - } - - @Test - fun wrongTypeOfSecondArgument() { - assertThatThrownBy { callDateAdd("year", "a", Timestamp.valueOf("2017T")) } - .hasMessage("Expected number: \"a\"") - .isExactlyInstanceOf(EvaluationException::class.java) - } - - @Test - fun wrongTypeOfThirdArgument() { - assertThatThrownBy { callDateAdd("year", 1, "foo") } - .hasMessage("Expected timestamp: \"foo\"") - .isExactlyInstanceOf(EvaluationException::class.java) - } - - fun parametersForInvalidDateTimePart() = listOf(DateTimePart.TIMEZONE_HOUR, - DateTimePart.TIMEZONE_MINUTE).map { it.toString().toLowerCase() } - - @Test - @Parameters - fun invalidDateTimePart(dateTimePart: String) { - assertThatThrownBy { callDateAdd(dateTimePart, 1, Timestamp.valueOf("2017T")) } - .hasMessage("invalid datetime part for date_add: $dateTimePart") - .isExactlyInstanceOf(EvaluationException::class.java) - } - - @Test - @Parameters - fun dateAdd(params: Pair Timestamp>) { - val (expected, call) = params - assertEquals(Timestamp.valueOf(expected), call.invoke()) - } - - fun parametersForDateAdd(): List Timestamp>> = listOf( - // add 1 at different precision levels - "2018T" to { callDateAdd("year", 1, Timestamp.valueOf("2017T")) }, - "2017-02T" to { callDateAdd("month", 1, Timestamp.valueOf("2017T")) }, - "2017-01-02T" to { callDateAdd("day", 1, Timestamp.valueOf("2017T")) }, - "2017-01-01T01:00-00:00" to { callDateAdd("hour", 1, Timestamp.valueOf("2017T")) }, - "2017-01-01T00:01-00:00" to { callDateAdd("minute", 1, Timestamp.valueOf("2017T")) }, - "2017-01-01T00:00:01-00:00" to { callDateAdd("second", 1, Timestamp.valueOf("2017T")) }, - - "2018-01T" to { callDateAdd("year", 1, Timestamp.valueOf("2017-01T")) }, - "2017-02T" to { callDateAdd("month", 1, Timestamp.valueOf("2017-01T")) }, - "2017-01-02T" to { callDateAdd("day", 1, Timestamp.valueOf("2017-01T")) }, - "2017-01-01T01:00-00:00" to { callDateAdd("hour", 1, Timestamp.valueOf("2017-01T")) }, - "2017-01-01T00:01-00:00" to { callDateAdd("minute", 1, Timestamp.valueOf("2017-01T")) }, - "2017-01-01T00:00:01-00:00" to { callDateAdd("second", 1, Timestamp.valueOf("2017-01T")) }, - - "2018-01-02T" to { callDateAdd("year", 1, Timestamp.valueOf("2017-01-02T")) }, - "2017-02-02T" to { callDateAdd("month", 1, Timestamp.valueOf("2017-01-02T")) }, - "2017-01-03T" to { callDateAdd("day", 1, Timestamp.valueOf("2017-01-02T")) }, - "2017-01-02T01:00-00:00" to { callDateAdd("hour", 1, Timestamp.valueOf("2017-01-02T")) }, - "2017-01-02T00:01-00:00" to { callDateAdd("minute", 1, Timestamp.valueOf("2017-01-02T")) }, - "2017-01-02T00:00:01-00:00" to { callDateAdd("second", 1, Timestamp.valueOf("2017-01-02T")) }, - - "2018-01-02T03:04Z" to { callDateAdd("year", 1, Timestamp.valueOf("2017-01-02T03:04Z")) }, - "2017-02-02T03:04Z" to { callDateAdd("month", 1, Timestamp.valueOf("2017-01-02T03:04Z")) }, - "2017-01-03T03:04Z" to { callDateAdd("day", 1, Timestamp.valueOf("2017-01-02T03:04Z")) }, - "2017-01-02T04:04Z" to { callDateAdd("hour", 1, Timestamp.valueOf("2017-01-02T03:04Z")) }, - "2017-01-02T03:05Z" to { callDateAdd("minute", 1, Timestamp.valueOf("2017-01-02T03:04Z")) }, - "2017-01-02T03:04:01Z" to { callDateAdd("second", 1, Timestamp.valueOf("2017-01-02T03:04Z")) }, - - "2018-01-02T03:04:05Z" to { callDateAdd("year", 1, Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - "2017-02-02T03:04:05Z" to { callDateAdd("month", 1, Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - "2017-01-03T03:04:05Z" to { callDateAdd("day", 1, Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - "2017-01-02T04:04:05Z" to { callDateAdd("hour", 1, Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - "2017-01-02T03:05:05Z" to { callDateAdd("minute", 1, Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - "2017-01-02T03:04:06Z" to { callDateAdd("second", 1, Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - - "2018-01-02T03:04:05.006Z" to { callDateAdd("year", 1, Timestamp.valueOf("2017-01-02T03:04:05.006Z")) }, - "2017-02-02T03:04:05.006Z" to { callDateAdd("month", 1, Timestamp.valueOf("2017-01-02T03:04:05.006Z")) }, - "2017-01-03T03:04:05.006Z" to { callDateAdd("day", 1, Timestamp.valueOf("2017-01-02T03:04:05.006Z")) }, - "2017-01-02T04:04:05.006Z" to { callDateAdd("hour", 1, Timestamp.valueOf("2017-01-02T03:04:05.006Z")) }, - "2017-01-02T03:05:05.006Z" to { callDateAdd("minute", 1, Timestamp.valueOf("2017-01-02T03:04:05.006Z")) }, - "2017-01-02T03:04:06.006Z" to { callDateAdd("second", 1, Timestamp.valueOf("2017-01-02T03:04:05.006Z")) }, - - // add enough to flip a year. Skipping milliseconds as it overflows Long - "2018-01T" to { callDateAdd("month", 12, Timestamp.valueOf("2017T")) }, - "2018-01-01T" to { callDateAdd("day", 365, Timestamp.valueOf("2017T")) }, - "2018-01-01T00:00-00:00" to { callDateAdd("hour", 365 * 24, Timestamp.valueOf("2017T")) }, - "2018-01-01T00:00-00:00" to { callDateAdd("minute", 365 * 24 * 60, Timestamp.valueOf("2017T")) }, - "2018-01-01T00:00:00-00:00" to { callDateAdd("second", 365 * 24 * 60 * 60, Timestamp.valueOf("2017T")) }, - - // add enough to flip a month. Skipping milliseconds as it overflows Long - "2017-02-01T" to { callDateAdd("day", 31, Timestamp.valueOf("2017-01T")) }, - "2017-02-01T00:00-00:00" to { callDateAdd("hour", 31 * 24 , Timestamp.valueOf("2017-01T")) }, - "2017-02-01T00:00-00:00" to { callDateAdd("minute", 31 * 24 * 60 , Timestamp.valueOf("2017-01T")) }, - "2017-02-01T00:00:00-00:00" to { callDateAdd("second", 31 * 24 * 60 * 60, Timestamp.valueOf("2017-01T")) }, - - // add enough to flip a day - "2017-02-04T00:00-00:00" to { callDateAdd("hour", 24 , Timestamp.valueOf("2017-02-03T")) }, - "2017-02-04T00:00-00:00" to { callDateAdd("minute", 24 * 60 , Timestamp.valueOf("2017-02-03T")) }, - "2017-02-04T00:00:00-00:00" to { callDateAdd("second", 24 * 60 * 60, Timestamp.valueOf("2017-02-03T")) }, - - // add enough to flip the hour - "2017-02-04T06:06Z" to { callDateAdd("minute", 60 , Timestamp.valueOf("2017-02-04T05:06Z")) }, - "2017-02-04T06:06:00Z" to { callDateAdd("second", 60 * 60, Timestamp.valueOf("2017-02-04T05:06Z")) }, - - // add enough to flip the minute - "2017-02-04T05:07:00Z" to { callDateAdd("second", 60, Timestamp.valueOf("2017-02-04T05:06Z")) }, - - // subtract 1 at different precision levels - "2016T" to { callDateAdd("year", -1, Timestamp.valueOf("2017T")) }, - "2016-12T" to { callDateAdd("month", -1, Timestamp.valueOf("2017T")) }, - "2016-12-31T" to { callDateAdd("day", -1, Timestamp.valueOf("2017T")) }, - "2016-12-31T23:00-00:00" to { callDateAdd("hour", -1, Timestamp.valueOf("2017T")) }, - "2016-12-31T23:59-00:00" to { callDateAdd("minute", -1, Timestamp.valueOf("2017T")) }, - "2016-12-31T23:59:59-00:00" to { callDateAdd("second", -1, Timestamp.valueOf("2017T")) }, - - "2016-02T" to { callDateAdd("year", -1, Timestamp.valueOf("2017-02T")) }, - "2017-01T" to { callDateAdd("month", -1, Timestamp.valueOf("2017-02T")) }, - "2017-01-31T" to { callDateAdd("day", -1, Timestamp.valueOf("2017-02T")) }, - "2017-01-31T23:00-00:00" to { callDateAdd("hour", -1, Timestamp.valueOf("2017-02T")) }, - "2017-01-31T23:59-00:00" to { callDateAdd("minute", -1, Timestamp.valueOf("2017-02T")) }, - "2017-01-31T23:59:59-00:00" to { callDateAdd("second", -1, Timestamp.valueOf("2017-02T")) }, - - "2016-02-03T" to { callDateAdd("year", -1, Timestamp.valueOf("2017-02-03T")) }, - "2017-01-03T" to { callDateAdd("month", -1, Timestamp.valueOf("2017-02-03T")) }, - "2017-02-02T" to { callDateAdd("day", -1, Timestamp.valueOf("2017-02-03T")) }, - "2017-02-02T23:00-00:00" to { callDateAdd("hour", -1, Timestamp.valueOf("2017-02-03T")) }, - "2017-02-02T23:59-00:00" to { callDateAdd("minute", -1, Timestamp.valueOf("2017-02-03T")) }, - "2017-02-02T23:59:59-00:00" to { callDateAdd("second", -1, Timestamp.valueOf("2017-02-03T")) }, - - "2016-02-03T04:05Z" to { callDateAdd("year", -1, Timestamp.valueOf("2017-02-03T04:05Z")) }, - "2017-01-03T04:05Z" to { callDateAdd("month", -1, Timestamp.valueOf("2017-02-03T04:05Z")) }, - "2017-02-02T04:05Z" to { callDateAdd("day", -1, Timestamp.valueOf("2017-02-03T04:05Z")) }, - "2017-02-03T03:05Z" to { callDateAdd("hour", -1, Timestamp.valueOf("2017-02-03T04:05Z")) }, - "2017-02-03T04:04Z" to { callDateAdd("minute", -1, Timestamp.valueOf("2017-02-03T04:05Z")) }, - "2017-02-03T04:04:59Z" to { callDateAdd("second", -1, Timestamp.valueOf("2017-02-03T04:05Z")) }, - - "2016-02-03T04:05:06Z" to { callDateAdd("year", -1, Timestamp.valueOf("2017-02-03T04:05:06Z")) }, - "2017-01-03T04:05:06Z" to { callDateAdd("month", -1, Timestamp.valueOf("2017-02-03T04:05:06Z")) }, - "2017-02-02T04:05:06Z" to { callDateAdd("day", -1, Timestamp.valueOf("2017-02-03T04:05:06Z")) }, - "2017-02-03T03:05:06Z" to { callDateAdd("hour", -1, Timestamp.valueOf("2017-02-03T04:05:06Z")) }, - "2017-02-03T04:04:06Z" to { callDateAdd("minute", -1, Timestamp.valueOf("2017-02-03T04:05:06Z")) }, - "2017-02-03T04:05:05Z" to { callDateAdd("second", -1, Timestamp.valueOf("2017-02-03T04:05:06Z")) }, - - "2016-02-03T04:05:06.007Z" to { callDateAdd("year", -1, Timestamp.valueOf("2017-02-03T04:05:06.007Z")) }, - "2017-01-03T04:05:06.007Z" to { callDateAdd("month", -1, Timestamp.valueOf("2017-02-03T04:05:06.007Z")) }, - "2017-02-02T04:05:06.007Z" to { callDateAdd("day", -1, Timestamp.valueOf("2017-02-03T04:05:06.007Z")) }, - "2017-02-03T03:05:06.007Z" to { callDateAdd("hour", -1, Timestamp.valueOf("2017-02-03T04:05:06.007Z")) }, - "2017-02-03T04:04:06.007Z" to { callDateAdd("minute", -1, Timestamp.valueOf("2017-02-03T04:05:06.007Z")) }, - "2017-02-03T04:05:05.007Z" to { callDateAdd("second", -1, Timestamp.valueOf("2017-02-03T04:05:06.007Z")) } - ) -} \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/DateDiffEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/DateDiffEvaluationTest.kt deleted file mode 100644 index 0ec93661db..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/DateDiffEvaluationTest.kt +++ /dev/null @@ -1,78 +0,0 @@ -package org.partiql.lang.eval.builtins - -import org.junit.Test -import org.partiql.lang.errors.ErrorCode -import org.partiql.lang.errors.Property -import org.partiql.lang.eval.EvaluatorTestBase - -/** - * More detailed tests are in [DateDiffExprFunctionTest] and parsing related tests in - * [org.partiql.lang.syntax.SqlParserTest] and [org.partiql.lang.errors.ParserErrorsTest]. - */ -class DateDiffEvaluationTest : EvaluatorTestBase() { - - @Test - fun dateDiffYear() = assertEval("date_diff(year, `2016-01-10T05:30:55Z`, `2017-01-10T05:30:55Z`)", "1") - - @Test - fun dateDiffMonth() = assertEval("date_diff(month, `2016-01-10T05:30:55Z`, `2017-01-10T05:30:55Z`)", "12") - - @Test - fun dateDiffDay() = assertEval("date_diff(day, `2016-01-10T05:30:55Z`, `2017-01-10T05:30:55Z`)", "366") - - @Test - fun dateDiffHour() = assertEval("date_diff(hour, `2016-01-10T05:30:55Z`, `2017-01-10T05:30:55Z`)", "8784") - - @Test - fun dateDiffMinute() = assertEval("date_diff(minute, `2016-01-10T05:30:55Z`, `2017-01-10T05:30:55Z`)", "527040") - - @Test - fun dateDiffSecond() = assertEval("date_diff(second, `2016-01-10T05:30:55Z`, `2017-01-10T05:30:55Z`)", "31622400") - - @Test - fun dateDiffNull01() = assertEval("date_diff(second, null, `2017-01-10T05:30:55Z`)", "null") - - @Test - fun dateDiffNull02() = assertEval("date_diff(second, `2016-01-10T05:30:55Z`, null)", "null") - - @Test - fun dateDiffMissing01() = assertEval("date_diff(second, missing, `2017-01-10T05:30:55Z`)", "null") - - @Test - fun dateDiffMissing02() = assertEval("date_diff(second, `2016-01-10T05:30:55Z`, missing)", "null") - - @Test - fun dateDiffWithBindings() = assertEval("date_diff(year, a, b)", - "1", - mapOf("a" to "2016-01-10T05:30:55Z", - "b" to "2017-01-10T05:30:55Z").toSession()) - - @Test - fun wrongArgumentTypes2() = - checkInputThrowingEvaluationException( - input = "date_diff(second, 1, `2017-01-10T05:30:55Z`)", - errorCode = ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - expectErrorContextValues = mapOf( - Property.FUNCTION_NAME to "date_diff", - Property.ARGUMENT_POSITION to 2, - Property.EXPECTED_ARGUMENT_TYPES to "TIMESTAMP", - Property.ACTUAL_ARGUMENT_TYPES to "INT", - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L), - expectedPermissiveModeResult = "MISSING") - - @Test - fun wrongArgumentTypes3() = - checkInputThrowingEvaluationException( - "date_diff(second, `2017-01-10T05:30:55Z`, 1)", - errorCode = ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - expectErrorContextValues = mapOf( - Property.FUNCTION_NAME to "date_diff", - Property.ARGUMENT_POSITION to 3, - Property.EXPECTED_ARGUMENT_TYPES to "TIMESTAMP", - Property.ACTUAL_ARGUMENT_TYPES to "INT", - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L), - expectedPermissiveModeResult = "MISSING") - -} \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/DateDiffExprFunctionTest.kt b/lang/test/org/partiql/lang/eval/builtins/DateDiffExprFunctionTest.kt deleted file mode 100644 index 7473914a1e..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/DateDiffExprFunctionTest.kt +++ /dev/null @@ -1,251 +0,0 @@ -package org.partiql.lang.eval.builtins - -import com.amazon.ion.Timestamp -import junitparams.Parameters -import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.Test -import org.partiql.lang.TestBase -import org.partiql.lang.eval.Environment -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.RequiredArgs -import org.partiql.lang.eval.call -import org.partiql.lang.eval.numberValue - -class DateDiffExprFunctionTest : TestBase() { - private val env = Environment.standard() - - private val subject = DateDiffExprFunction(valueFactory) - - private fun callDateDiff(vararg args: Any) = subject.call(env, RequiredArgs(args.map { anyToExprValue(it) })).numberValue() - - @Test - fun wrongTypeOfFirstArgument() { - assertThatThrownBy { callDateDiff("foobar", Timestamp.valueOf("2017T"), Timestamp.valueOf("2017T")) } - .hasMessage("invalid datetime part, valid values: [year, month, day, hour, minute, second, timezone_hour, timezone_minute]") - .isExactlyInstanceOf(EvaluationException::class.java) - } - - @Test - fun wrongTypeOfSecondArgument() { - assertThatThrownBy { callDateDiff("year", 1, Timestamp.valueOf("2017T")) } - .hasMessage("Expected timestamp: 1") - .isExactlyInstanceOf(EvaluationException::class.java) - } - - @Test - fun wrongTypeOfThirdArgument() { - assertThatThrownBy { callDateDiff("year", Timestamp.valueOf("2017T"), 1) } - .hasMessage("Expected timestamp: 1") - .isExactlyInstanceOf(EvaluationException::class.java) - } - - fun parametersForDateDiff(): List Number>> = listOf( - // same dates - 0 to { callDateDiff("year", Timestamp.valueOf("2017T"), Timestamp.valueOf("2017T")) }, - 0 to { callDateDiff("month", Timestamp.valueOf("2017T"), Timestamp.valueOf("2017T")) }, - 0 to { callDateDiff("day", Timestamp.valueOf("2017T"), Timestamp.valueOf("2017T")) }, - 0 to { callDateDiff("hour", Timestamp.valueOf("2017T"), Timestamp.valueOf("2017T")) }, - 0 to { callDateDiff("minute", Timestamp.valueOf("2017T"), Timestamp.valueOf("2017T")) }, - 0 to { callDateDiff("second", Timestamp.valueOf("2017T"), Timestamp.valueOf("2017T")) }, - - 0 to { callDateDiff("year", Timestamp.valueOf("2017-01T"), Timestamp.valueOf("2017-01T")) }, - 0 to { callDateDiff("month", Timestamp.valueOf("2017-01T"), Timestamp.valueOf("2017-01T")) }, - 0 to { callDateDiff("day", Timestamp.valueOf("2017-01T"), Timestamp.valueOf("2017-01T")) }, - 0 to { callDateDiff("hour", Timestamp.valueOf("2017-01T"), Timestamp.valueOf("2017-01T")) }, - 0 to { callDateDiff("minute", Timestamp.valueOf("2017-01T"), Timestamp.valueOf("2017-01T")) }, - 0 to { callDateDiff("second", Timestamp.valueOf("2017-01T"), Timestamp.valueOf("2017-01T")) }, - - 0 to { callDateDiff("year", Timestamp.valueOf("2017-01-02T"), Timestamp.valueOf("2017-01-02T")) }, - 0 to { callDateDiff("month", Timestamp.valueOf("2017-01-02T"), Timestamp.valueOf("2017-01-02T")) }, - 0 to { callDateDiff("day", Timestamp.valueOf("2017-01-02T"), Timestamp.valueOf("2017-01-02T")) }, - 0 to { callDateDiff("hour", Timestamp.valueOf("2017-01-02T"), Timestamp.valueOf("2017-01-02T")) }, - 0 to { callDateDiff("minute", Timestamp.valueOf("2017-01-02T"), Timestamp.valueOf("2017-01-02T")) }, - 0 to { callDateDiff("second", Timestamp.valueOf("2017-01-02T"), Timestamp.valueOf("2017-01-02T")) }, - - 0 to { callDateDiff("year", Timestamp.valueOf("2017-01-02T03:04Z"), Timestamp.valueOf("2017-01-02T03:04Z")) }, - 0 to { callDateDiff("month", Timestamp.valueOf("2017-01-02T03:04Z"), Timestamp.valueOf("2017-01-02T03:04Z")) }, - 0 to { callDateDiff("day", Timestamp.valueOf("2017-01-02T03:04Z"), Timestamp.valueOf("2017-01-02T03:04Z")) }, - 0 to { callDateDiff("hour", Timestamp.valueOf("2017-01-02T03:04Z"), Timestamp.valueOf("2017-01-02T03:04Z")) }, - 0 to { callDateDiff("minute", Timestamp.valueOf("2017-01-02T03:04Z"), Timestamp.valueOf("2017-01-02T03:04Z")) }, - 0 to { callDateDiff("second", Timestamp.valueOf("2017-01-02T03:04Z"), Timestamp.valueOf("2017-01-02T03:04Z")) }, - - 0 to { callDateDiff("year", Timestamp.valueOf("2017-01-02T03:04:05Z"), Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - 0 to { callDateDiff("month", Timestamp.valueOf("2017-01-02T03:04:05Z"), Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - 0 to { callDateDiff("day", Timestamp.valueOf("2017-01-02T03:04:05Z"), Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - 0 to { callDateDiff("hour", Timestamp.valueOf("2017-01-02T03:04:05Z"), Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - 0 to { callDateDiff("minute", Timestamp.valueOf("2017-01-02T03:04:05Z"), Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - 0 to { callDateDiff("second", Timestamp.valueOf("2017-01-02T03:04:05Z"), Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - - 0 to { callDateDiff("year", Timestamp.valueOf("2017-01-02T03:04:05.007Z"), Timestamp.valueOf("2017-01-02T03:04:05.007Z")) }, - 0 to { callDateDiff("month", Timestamp.valueOf("2017-01-02T03:04:05.007Z"), Timestamp.valueOf("2017-01-02T03:04:05.007Z")) }, - 0 to { callDateDiff("day", Timestamp.valueOf("2017-01-02T03:04:05.007Z"), Timestamp.valueOf("2017-01-02T03:04:05.007Z")) }, - 0 to { callDateDiff("hour", Timestamp.valueOf("2017-01-02T03:04:05.007Z"), Timestamp.valueOf("2017-01-02T03:04:05.007Z")) }, - 0 to { callDateDiff("minute", Timestamp.valueOf("2017-01-02T03:04:05.007Z"), Timestamp.valueOf("2017-01-02T03:04:05.007Z")) }, - 0 to { callDateDiff("second", Timestamp.valueOf("2017-01-02T03:04:05.007Z"), Timestamp.valueOf("2017-01-02T03:04:05.007Z")) }, - - 0 to { callDateDiff("year", Timestamp.valueOf("2017-01-02T03:04:05.007+08:09"), Timestamp.valueOf("2017-01-02T03:04:05.007+08:09")) }, - 0 to { callDateDiff("month", Timestamp.valueOf("2017-01-02T03:04:05.007+08:09"), Timestamp.valueOf("2017-01-02T03:04:05.007+08:09")) }, - 0 to { callDateDiff("day", Timestamp.valueOf("2017-01-02T03:04:05.007+08:09"), Timestamp.valueOf("2017-01-02T03:04:05.007+08:09")) }, - 0 to { callDateDiff("hour", Timestamp.valueOf("2017-01-02T03:04:05.007+08:09"), Timestamp.valueOf("2017-01-02T03:04:05.007+08:09")) }, - 0 to { callDateDiff("minute", Timestamp.valueOf("2017-01-02T03:04:05.007+08:09"), Timestamp.valueOf("2017-01-02T03:04:05.007+08:09")) }, - 0 to { callDateDiff("second", Timestamp.valueOf("2017-01-02T03:04:05.007+08:09"), Timestamp.valueOf("2017-01-02T03:04:05.007+08:09")) }, - - // later - earlier - 1 to { callDateDiff("year", Timestamp.valueOf("2017T"), Timestamp.valueOf("2018T")) }, - 12 to { callDateDiff("month", Timestamp.valueOf("2017T"), Timestamp.valueOf("2018T")) }, - 365 to { callDateDiff("day", Timestamp.valueOf("2017T"), Timestamp.valueOf("2018T")) }, - 365 * 24 to { callDateDiff("hour", Timestamp.valueOf("2017T"), Timestamp.valueOf("2018T")) }, - 365 * 24 * 60 to { callDateDiff("minute", Timestamp.valueOf("2017T"), Timestamp.valueOf("2018T")) }, - 365 * 24 * 60 * 60 to { callDateDiff("second", Timestamp.valueOf("2017T"), Timestamp.valueOf("2018T")) }, - - 0 to { callDateDiff("year", Timestamp.valueOf("2017-01T"), Timestamp.valueOf("2017-02T")) }, - 1 to { callDateDiff("month", Timestamp.valueOf("2017-01T"), Timestamp.valueOf("2017-02T")) }, - 31 to { callDateDiff("day", Timestamp.valueOf("2017-01T"), Timestamp.valueOf("2017-02T")) }, - 31 * 24 to { callDateDiff("hour", Timestamp.valueOf("2017-01T"), Timestamp.valueOf("2017-02T")) }, - 31 * 24 * 60 to { callDateDiff("minute", Timestamp.valueOf("2017-01T"), Timestamp.valueOf("2017-02T")) }, - 31 * 24 * 60 * 60 to { callDateDiff("second", Timestamp.valueOf("2017-01T"), Timestamp.valueOf("2017-02T")) }, - - 0 to { callDateDiff("year", Timestamp.valueOf("2017-01-02T"), Timestamp.valueOf("2017-01-03T")) }, - 0 to { callDateDiff("month", Timestamp.valueOf("2017-01-02T"), Timestamp.valueOf("2017-01-03T")) }, - 1 to { callDateDiff("day", Timestamp.valueOf("2017-01-02T"), Timestamp.valueOf("2017-01-03T")) }, - 24 to { callDateDiff("hour", Timestamp.valueOf("2017-01-02T"), Timestamp.valueOf("2017-01-03T")) }, - 24 * 60 to { callDateDiff("minute", Timestamp.valueOf("2017-01-02T"), Timestamp.valueOf("2017-01-03T")) }, - 24 * 60 * 60 to { callDateDiff("second", Timestamp.valueOf("2017-01-02T"), Timestamp.valueOf("2017-01-03T")) }, - - 0 to { callDateDiff("year", Timestamp.valueOf("2017-01-02T03:04Z"), Timestamp.valueOf("2017-01-02T04:04Z")) }, - 0 to { callDateDiff("month", Timestamp.valueOf("2017-01-02T03:04Z"), Timestamp.valueOf("2017-01-02T04:04Z")) }, - 0 to { callDateDiff("day", Timestamp.valueOf("2017-01-02T03:04Z"), Timestamp.valueOf("2017-01-02T04:04Z")) }, - 1 to { callDateDiff("hour", Timestamp.valueOf("2017-01-02T03:04Z"), Timestamp.valueOf("2017-01-02T04:04Z")) }, - 60 to { callDateDiff("minute", Timestamp.valueOf("2017-01-02T03:04Z"), Timestamp.valueOf("2017-01-02T04:04Z")) }, - 60 * 60 to { callDateDiff("second", Timestamp.valueOf("2017-01-02T03:04Z"), Timestamp.valueOf("2017-01-02T04:04Z")) }, - - 0 to { callDateDiff("year", Timestamp.valueOf("2017-01-02T03:04Z"), Timestamp.valueOf("2017-01-02T03:05Z")) }, - 0 to { callDateDiff("month", Timestamp.valueOf("2017-01-02T03:04Z"), Timestamp.valueOf("2017-01-02T03:05Z")) }, - 0 to { callDateDiff("day", Timestamp.valueOf("2017-01-02T03:04Z"), Timestamp.valueOf("2017-01-02T03:05Z")) }, - 0 to { callDateDiff("hour", Timestamp.valueOf("2017-01-02T03:04Z"), Timestamp.valueOf("2017-01-02T03:05Z")) }, - 1 to { callDateDiff("minute", Timestamp.valueOf("2017-01-02T03:04Z"), Timestamp.valueOf("2017-01-02T03:05Z")) }, - 60 to { callDateDiff("second", Timestamp.valueOf("2017-01-02T03:04Z"), Timestamp.valueOf("2017-01-02T03:05Z")) }, - - 0 to { callDateDiff("year", Timestamp.valueOf("2017-01-02T03:04:05Z"), Timestamp.valueOf("2017-01-02T03:04:06Z")) }, - 0 to { callDateDiff("month", Timestamp.valueOf("2017-01-02T03:04:05Z"), Timestamp.valueOf("2017-01-02T03:04:06Z")) }, - 0 to { callDateDiff("day", Timestamp.valueOf("2017-01-02T03:04:05Z"), Timestamp.valueOf("2017-01-02T03:04:06Z")) }, - 0 to { callDateDiff("hour", Timestamp.valueOf("2017-01-02T03:04:05Z"), Timestamp.valueOf("2017-01-02T03:04:06Z")) }, - 0 to { callDateDiff("minute", Timestamp.valueOf("2017-01-02T03:04:05Z"), Timestamp.valueOf("2017-01-02T03:04:06Z")) }, - 1 to { callDateDiff("second", Timestamp.valueOf("2017-01-02T03:04:05Z"), Timestamp.valueOf("2017-01-02T03:04:06Z")) }, - - 0 to { callDateDiff("year", Timestamp.valueOf("2017-01-02T03:04:05.007Z"), Timestamp.valueOf("2017-01-02T03:04:05.008Z")) }, - 0 to { callDateDiff("month", Timestamp.valueOf("2017-01-02T03:04:05.007Z"), Timestamp.valueOf("2017-01-02T03:04:05.008Z")) }, - 0 to { callDateDiff("day", Timestamp.valueOf("2017-01-02T03:04:05.007Z"), Timestamp.valueOf("2017-01-02T03:04:05.008Z")) }, - 0 to { callDateDiff("hour", Timestamp.valueOf("2017-01-02T03:04:05.007Z"), Timestamp.valueOf("2017-01-02T03:04:05.008Z")) }, - 0 to { callDateDiff("minute", Timestamp.valueOf("2017-01-02T03:04:05.007Z"), Timestamp.valueOf("2017-01-02T03:04:05.008Z")) }, - 0 to { callDateDiff("second", Timestamp.valueOf("2017-01-02T03:04:05.007Z"), Timestamp.valueOf("2017-01-02T03:04:05.008Z")) }, - - // earlier - later - -1 to { callDateDiff("year", Timestamp.valueOf("2018T"), Timestamp.valueOf("2017T")) }, - -12 to { callDateDiff("month", Timestamp.valueOf("2018T"), Timestamp.valueOf("2017T")) }, - -365 to { callDateDiff("day", Timestamp.valueOf("2018T"), Timestamp.valueOf("2017T")) }, - -365 * 24 to { callDateDiff("hour", Timestamp.valueOf("2018T"), Timestamp.valueOf("2017T")) }, - -365 * 24 * 60 to { callDateDiff("minute", Timestamp.valueOf("2018T"), Timestamp.valueOf("2017T")) }, - -365 * 24 * 60 * 60 to { callDateDiff("second", Timestamp.valueOf("2018T"), Timestamp.valueOf("2017T")) }, - - 0 to { callDateDiff("year", Timestamp.valueOf("2017-02T"), Timestamp.valueOf("2017-01T")) }, - -1 to { callDateDiff("month", Timestamp.valueOf("2017-02T"), Timestamp.valueOf("2017-01T")) }, - -31 to { callDateDiff("day", Timestamp.valueOf("2017-02T"), Timestamp.valueOf("2017-01T")) }, - -31 * 24 to { callDateDiff("hour", Timestamp.valueOf("2017-02T"), Timestamp.valueOf("2017-01T")) }, - -31 * 24 * 60 to { callDateDiff("minute", Timestamp.valueOf("2017-02T"), Timestamp.valueOf("2017-01T")) }, - -31 * 24 * 60 * 60 to { callDateDiff("second", Timestamp.valueOf("2017-02T"), Timestamp.valueOf("2017-01T")) }, - - 0 to { callDateDiff("year", Timestamp.valueOf("2017-01-03T"), Timestamp.valueOf("2017-01-02T")) }, - 0 to { callDateDiff("month", Timestamp.valueOf("2017-01-03T"), Timestamp.valueOf("2017-01-02T")) }, - -1 to { callDateDiff("day", Timestamp.valueOf("2017-01-03T"), Timestamp.valueOf("2017-01-02T")) }, - -24 to { callDateDiff("hour", Timestamp.valueOf("2017-01-03T"), Timestamp.valueOf("2017-01-02T")) }, - -24 * 60 to { callDateDiff("minute", Timestamp.valueOf("2017-01-03T"), Timestamp.valueOf("2017-01-02T")) }, - -24 * 60 * 60 to { callDateDiff("second", Timestamp.valueOf("2017-01-03T"), Timestamp.valueOf("2017-01-02T")) }, - - 0 to { callDateDiff("year", Timestamp.valueOf("2017-01-02T04:04Z"), Timestamp.valueOf("2017-01-02T03:04Z")) }, - 0 to { callDateDiff("month", Timestamp.valueOf("2017-01-02T04:04Z"), Timestamp.valueOf("2017-01-02T03:04Z")) }, - 0 to { callDateDiff("day", Timestamp.valueOf("2017-01-02T04:04Z"), Timestamp.valueOf("2017-01-02T03:04Z")) }, - -1 to { callDateDiff("hour", Timestamp.valueOf("2017-01-02T04:04Z"), Timestamp.valueOf("2017-01-02T03:04Z")) }, - -60 to { callDateDiff("minute", Timestamp.valueOf("2017-01-02T04:04Z"), Timestamp.valueOf("2017-01-02T03:04Z")) }, - -60 * 60 to { callDateDiff("second", Timestamp.valueOf("2017-01-02T04:04Z"), Timestamp.valueOf("2017-01-02T03:04Z")) }, - - 0 to { callDateDiff("year", Timestamp.valueOf("2017-01-02T03:05Z"), Timestamp.valueOf("2017-01-02T03:04Z")) }, - 0 to { callDateDiff("month", Timestamp.valueOf("2017-01-02T03:05Z"), Timestamp.valueOf("2017-01-02T03:04Z")) }, - 0 to { callDateDiff("day", Timestamp.valueOf("2017-01-02T03:05Z"), Timestamp.valueOf("2017-01-02T03:04Z")) }, - 0 to { callDateDiff("hour", Timestamp.valueOf("2017-01-02T03:05Z"), Timestamp.valueOf("2017-01-02T03:04Z")) }, - -1 to { callDateDiff("minute", Timestamp.valueOf("2017-01-02T03:05Z"), Timestamp.valueOf("2017-01-02T03:04Z")) }, - -60 to { callDateDiff("second", Timestamp.valueOf("2017-01-02T03:05Z"), Timestamp.valueOf("2017-01-02T03:04Z")) }, - - 0 to { callDateDiff("year", Timestamp.valueOf("2017-01-02T03:04:06Z"), Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - 0 to { callDateDiff("month", Timestamp.valueOf("2017-01-02T03:04:06Z"), Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - 0 to { callDateDiff("day", Timestamp.valueOf("2017-01-02T03:04:06Z"), Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - 0 to { callDateDiff("hour", Timestamp.valueOf("2017-01-02T03:04:06Z"), Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - 0 to { callDateDiff("minute", Timestamp.valueOf("2017-01-02T03:04:06Z"), Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - -1 to { callDateDiff("second", Timestamp.valueOf("2017-01-02T03:04:06Z"), Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - - 0 to { callDateDiff("year", Timestamp.valueOf("2017-01-02T03:04:05.008Z"), Timestamp.valueOf("2017-01-02T03:04:05.007Z")) }, - 0 to { callDateDiff("month", Timestamp.valueOf("2017-01-02T03:04:05.008Z"), Timestamp.valueOf("2017-01-02T03:04:05.007Z")) }, - 0 to { callDateDiff("day", Timestamp.valueOf("2017-01-02T03:04:05.008Z"), Timestamp.valueOf("2017-01-02T03:04:05.007Z")) }, - 0 to { callDateDiff("hour", Timestamp.valueOf("2017-01-02T03:04:05.008Z"), Timestamp.valueOf("2017-01-02T03:04:05.007Z")) }, - 0 to { callDateDiff("minute", Timestamp.valueOf("2017-01-02T03:04:05.008Z"), Timestamp.valueOf("2017-01-02T03:04:05.007Z")) }, - 0 to { callDateDiff("second", Timestamp.valueOf("2017-01-02T03:04:05.008Z"), Timestamp.valueOf("2017-01-02T03:04:05.007Z")) }, - - // on different local offsets - 0 to { callDateDiff("year", Timestamp.valueOf("2017-01-02T03:04+01:02"), Timestamp.valueOf("2017-01-02T03:04+00:00")) }, - 0 to { callDateDiff("month", Timestamp.valueOf("2017-01-02T03:04+00:02"), Timestamp.valueOf("2017-01-02T03:04+00:00")) }, - 0 to { callDateDiff("day", Timestamp.valueOf("2017-01-02T01:00+10:00"), Timestamp.valueOf("2017-01-02T01:00+00:00")) }, - 1 to { callDateDiff("hour", Timestamp.valueOf("2017-01-02T03:04+01:02"), Timestamp.valueOf("2017-01-02T03:04+00:00")) }, - 2 to { callDateDiff("minute", Timestamp.valueOf("2017-01-02T03:04+00:02"), Timestamp.valueOf("2017-01-02T03:04+00:00")) }, - - // different precisions - // year - 1 to { callDateDiff("month", Timestamp.valueOf("2017T"), Timestamp.valueOf("2017-02T")) }, - 1 to { callDateDiff("day", Timestamp.valueOf("2017T"), Timestamp.valueOf("2017-01-02T")) }, - 1 to { callDateDiff("hour", Timestamp.valueOf("2017T"), Timestamp.valueOf("2017-01-01T01:00Z")) }, - 1 to { callDateDiff("minute", Timestamp.valueOf("2017T"), Timestamp.valueOf("2017-01-01T00:01Z")) }, - 1 to { callDateDiff("second", Timestamp.valueOf("2017T"), Timestamp.valueOf("2017-01-01T00:00:01Z")) }, - - // month - 1 to { callDateDiff("day", Timestamp.valueOf("2017-01T"), Timestamp.valueOf("2017-01-02T")) }, - 1 to { callDateDiff("hour", Timestamp.valueOf("2017-01T"), Timestamp.valueOf("2017-01-01T01:00Z")) }, - 1 to { callDateDiff("minute", Timestamp.valueOf("2017-01T"), Timestamp.valueOf("2017-01-01T00:01Z")) }, - 1 to { callDateDiff("second", Timestamp.valueOf("2017-01T"), Timestamp.valueOf("2017-01-01T00:00:01Z")) }, - - // day - 1 to { callDateDiff("hour", Timestamp.valueOf("2017-01-01T"), Timestamp.valueOf("2017-01-01T01:00Z")) }, - 1 to { callDateDiff("minute", Timestamp.valueOf("2017-01-01T"), Timestamp.valueOf("2017-01-01T00:01Z")) }, - 1 to { callDateDiff("second", Timestamp.valueOf("2017-01-01T"), Timestamp.valueOf("2017-01-01T00:00:01Z")) }, - - // minute - 1 to { callDateDiff("second", Timestamp.valueOf("2017-01-01T00:00Z"), Timestamp.valueOf("2017-01-01T00:00:01Z")) }, - - // leap year - 366 to { callDateDiff("day", Timestamp.valueOf("2016-01-01T"), Timestamp.valueOf("2017-01-01T")) }, - 366 * 24 to { callDateDiff("hour", Timestamp.valueOf("2016-01-01T"), Timestamp.valueOf("2017-01-01T")) }, - 366 * 24 * 60 to { callDateDiff("minute", Timestamp.valueOf("2016-01-01T"), Timestamp.valueOf("2017-01-01T")) }, - 366 * 24 * 60 * 60 to { callDateDiff("second", Timestamp.valueOf("2016-01-01T"), Timestamp.valueOf("2017-01-01T")) }, - - // Days in a month - 31 to { callDateDiff("day", Timestamp.valueOf("2017-01-01T"), Timestamp.valueOf("2017-02-01T")) }, // January - 28 to { callDateDiff("day", Timestamp.valueOf("2017-02-01T"), Timestamp.valueOf("2017-03-01T")) }, // February - 29 to { callDateDiff("day", Timestamp.valueOf("2016-02-01T"), Timestamp.valueOf("2016-03-01T")) }, // February leap year - 31 to { callDateDiff("day", Timestamp.valueOf("2017-03-01T"), Timestamp.valueOf("2017-04-01T")) }, // March - 30 to { callDateDiff("day", Timestamp.valueOf("2017-04-01T"), Timestamp.valueOf("2017-05-01T")) }, // April - 31 to { callDateDiff("day", Timestamp.valueOf("2017-05-01T"), Timestamp.valueOf("2017-06-01T")) }, // May - 30 to { callDateDiff("day", Timestamp.valueOf("2017-06-01T"), Timestamp.valueOf("2017-07-01T")) }, // June - 31 to { callDateDiff("day", Timestamp.valueOf("2017-07-01T"), Timestamp.valueOf("2017-08-01T")) }, // July - 31 to { callDateDiff("day", Timestamp.valueOf("2017-08-01T"), Timestamp.valueOf("2017-09-01T")) }, // August - 30 to { callDateDiff("day", Timestamp.valueOf("2017-09-01T"), Timestamp.valueOf("2017-10-01T")) }, // September - 31 to { callDateDiff("day", Timestamp.valueOf("2017-10-01T"), Timestamp.valueOf("2017-11-01T")) }, // October - 30 to { callDateDiff("day", Timestamp.valueOf("2017-11-01T"), Timestamp.valueOf("2017-12-01T")) }, // November - 31 to { callDateDiff("day", Timestamp.valueOf("2017-12-01T"), Timestamp.valueOf("2018-01-01T")) } // December - ) - - @Test - @Parameters - fun dateDiff(params: Pair Number>) { - val (expected, call) = params - - assertEquals(expected.toLong(), call.invoke().toLong()) - } -} diff --git a/lang/test/org/partiql/lang/eval/builtins/ExistsEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/ExistsEvaluationTest.kt deleted file mode 100644 index 2b10133f6d..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/ExistsEvaluationTest.kt +++ /dev/null @@ -1,128 +0,0 @@ -package org.partiql.lang.eval.builtins - -import org.junit.Test -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ArgumentsSource -import org.partiql.lang.errors.ErrorCode -import org.partiql.lang.errors.Property -import org.partiql.lang.eval.EvaluatorTestBase -import org.partiql.lang.util.ArgumentsProviderBase - -class ExistsEvaluationTest : EvaluatorTestBase() { - // Pass test cases - data class ExistsPassCase(val source: String, val expected: String) - - @ParameterizedTest - @ArgumentsSource(ExistsPassCases::class) - fun runPassTests(testCase: ExistsPassCase) = assertEval(testCase.source, testCase.expected) - - class ExistsPassCases : ArgumentsProviderBase() { - override fun getParameters(): List = listOf( - ExistsPassCase("EXISTS(<<1, 2, 3>>)", "true"), - ExistsPassCase("EXISTS(<<>>)", "false"), - ExistsPassCase("EXISTS(`(1 2 3)`)", "true"), - ExistsPassCase("EXISTS(`()`)", "false"), - ExistsPassCase("EXISTS(`[1, 2, 3]`)", "true"), - ExistsPassCase("EXISTS(`[]`)", "false"), - ExistsPassCase("EXISTS(`{ a: 1, b: 2, c: 3 }`)", "true"), - ExistsPassCase("EXISTS(`{}`)", "false") - ) - } - - // Error test cases - @Test - fun existsWithNumberThrowError() = - checkInputThrowingEvaluationException( - input = "EXISTS(1)", - errorCode = ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - expectErrorContextValues = mapOf( - Property.FUNCTION_NAME to "exists", - Property.ARGUMENT_POSITION to 1, - Property.EXPECTED_ARGUMENT_TYPES to "SEXP, LIST, BAG, or STRUCT", - Property.ACTUAL_ARGUMENT_TYPES to "INT", - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L - ), - expectedPermissiveModeResult = "MISSING" - ) - - @Test - fun existsWithSymbolThrowError() = - checkInputThrowingEvaluationException( - input = "EXISTS(`a`)", - errorCode = ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - expectErrorContextValues = mapOf( - Property.FUNCTION_NAME to "exists", - Property.ARGUMENT_POSITION to 1, - Property.EXPECTED_ARGUMENT_TYPES to "SEXP, LIST, BAG, or STRUCT", - Property.ACTUAL_ARGUMENT_TYPES to "SYMBOL", - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L - ), - expectedPermissiveModeResult = "MISSING" - ) - - @Test - fun existsWithStringThrowError() = - checkInputThrowingEvaluationException( - input = "EXISTS('a')", - errorCode = ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - expectErrorContextValues = mapOf( - Property.FUNCTION_NAME to "exists", - Property.ARGUMENT_POSITION to 1, - Property.EXPECTED_ARGUMENT_TYPES to "SEXP, LIST, BAG, or STRUCT", - Property.ACTUAL_ARGUMENT_TYPES to "STRING", - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L - ), - expectedPermissiveModeResult = "MISSING" - ) - - @Test - fun existsWithTimestampThrowError() = - checkInputThrowingEvaluationException( - input = "EXISTS(`2017T`)", - errorCode = ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - expectErrorContextValues = mapOf( - Property.FUNCTION_NAME to "exists", - Property.ARGUMENT_POSITION to 1, - Property.EXPECTED_ARGUMENT_TYPES to "SEXP, LIST, BAG, or STRUCT", - Property.ACTUAL_ARGUMENT_TYPES to "TIMESTAMP", - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L - ), - expectedPermissiveModeResult = "MISSING" - ) - - @Test - fun existsWithNullThrowError() = - checkInputThrowingEvaluationException( - input = "EXISTS(null)", - errorCode = ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - expectErrorContextValues = mapOf( - Property.FUNCTION_NAME to "exists", - Property.ARGUMENT_POSITION to 1, - Property.EXPECTED_ARGUMENT_TYPES to "SEXP, LIST, BAG, or STRUCT", - Property.ACTUAL_ARGUMENT_TYPES to "NULL", - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L - ), - expectedPermissiveModeResult = "MISSING" - ) - - @Test - fun existsWithMissingThrowError() = - checkInputThrowingEvaluationException( - input = "EXISTS(missing)", - errorCode = ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - expectErrorContextValues = mapOf( - Property.FUNCTION_NAME to "exists", - Property.ARGUMENT_POSITION to 1, - Property.EXPECTED_ARGUMENT_TYPES to "SEXP, LIST, BAG, or STRUCT", - Property.ACTUAL_ARGUMENT_TYPES to "MISSING", - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L - ), - expectedPermissiveModeResult = "MISSING" - ) -} diff --git a/lang/test/org/partiql/lang/eval/builtins/ExtractEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/ExtractEvaluationTest.kt deleted file mode 100644 index 5bb129491a..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/ExtractEvaluationTest.kt +++ /dev/null @@ -1,282 +0,0 @@ -package org.partiql.lang.eval.builtins - -import com.amazon.ion.Timestamp -import junitparams.Parameters -import org.assertj.core.api.Assertions -import org.junit.Test -import org.partiql.lang.errors.ErrorCode -import org.partiql.lang.errors.Property -import org.partiql.lang.eval.Environment -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.EvaluatorTestBase -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.RequiredArgs -import org.partiql.lang.eval.call -import org.partiql.lang.eval.numberValue -import org.partiql.lang.eval.time.Time -import org.partiql.lang.syntax.DateTimePart -import org.partiql.lang.util.to -import java.time.LocalDate - -/** - * Parsing related tests in [org.partiql.lang.syntax.SqlParserTest] and [org.partiql.lang.errors.ParserErrorsTest]. - */ -class ExtractEvaluationTest : EvaluatorTestBase() { - - private val env = Environment.standard() - - private val subject = ExtractExprFunction(valueFactory) - - @Test - fun extractYear() = assertEval("extract(year from `2017-01-10T05:30:55Z`)", "2017.") - - @Test - fun extractMonth() = assertEval("extract(month from `2017-01-10T05:30:55Z`)", "1.") - - @Test - fun extractDay() = assertEval("extract(day from `2017-01-10T05:30:55Z`)", "10.") - - @Test - fun extractHour() = assertEval("extract(hour from `2017-01-10T05:30:55Z`)", "5.") - - @Test - fun extractMinute() = assertEval("extract(minute from `2017-01-10T05:30:55Z`)", "30.") - - @Test - fun extractSecond() = assertEval("extract(second from `2017-01-10T05:30:55Z`)", "55.") - - @Test - fun extractTimezoneHour() = assertEval("extract(timezone_hour from `2017-01-10T05:30:55+11:30`)", "11.") - - @Test - fun extractTimezoneMinute() = assertEval("extract(timezone_minute from `2017-01-10T05:30:55+11:30`)", "30.") - - @Test - fun extractFromNull() = assertEval("extract(timezone_minute from null)", "null") - - @Test - fun extractFromMissing() = assertEval("extract(timezone_minute from missing)", "null") - - @Test - fun extractTimezoneHourNegativeOffset() = - assertEval("extract(timezone_hour from `2017-01-10T05:30:55-11:30`)", "-11.") - - @Test - fun extractTimezoneMinuteNegativeOffset() = - assertEval("extract(timezone_minute from `2017-01-10T05:30:55-11:30`)", "-30.") - - @Test - fun extractWithBindings() = assertEval("extract(second from a)", - "55.", - mapOf("a" to "2017-01-10T05:30:55Z").toSession()) - - @Test - fun wrongArgumentTypes() = - checkInputThrowingEvaluationException( - input = "extract(year from 1)", - errorCode = ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - expectErrorContextValues = mapOf( - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L, - Property.FUNCTION_NAME to "extract", - Property.EXPECTED_ARGUMENT_TYPES to "TIMESTAMP, TIME, or DATE", - Property.ARGUMENT_POSITION to 2, - Property.ACTUAL_ARGUMENT_TYPES to "INT"), - expectedPermissiveModeResult = "MISSING" - ) - - data class ExtractFromDateTC(val source: String, val expected: ExprValue?) - - private fun createDateTC(source: String, expected: LocalDate) = - DateTimePart.values() - .map { dateTimePart -> - ExtractFromDateTC( - source = "EXTRACT(${dateTimePart.name} FROM $source)", - expected = when (dateTimePart) { - DateTimePart.YEAR -> valueFactory.newDecimal(expected.year) - DateTimePart.MONTH -> valueFactory.newDecimal(expected.monthValue) - DateTimePart.DAY -> valueFactory.newDecimal(expected.dayOfMonth) - DateTimePart.HOUR -> valueFactory.newDecimal(0) - DateTimePart.MINUTE -> valueFactory.newDecimal(0) - DateTimePart.SECOND -> valueFactory.newDecimal(0) - DateTimePart.TIMEZONE_HOUR -> null - DateTimePart.TIMEZONE_MINUTE -> null - } - ) - } - - fun parametersForRunTests() = listOf( - createDateTC("DATE '2012-12-12'", LocalDate.of(2012, 12, 12)), - createDateTC("DATE '2020-02-29'", LocalDate.of(2020, 2, 29)), - createDateTC("DATE '2021-03-24'", LocalDate.of(2021, 3, 24)) - ).flatten() - - @Test - @Parameters - fun runTests(tc: ExtractFromDateTC) = when (tc.expected) { - null -> { - try { - voidEval(tc.source) - fail("Expected evaluation error") - } catch (e: EvaluationException) { - // Do nothing - } - } - else -> assertExprEquals(eval(tc.source), tc.expected, "Expected exprValues to be equal.") - } - - data class ExtractFromTimeTC(val source: String, val expected: ExprValue?) - - private fun createTimeTC(source: String, expected: Time) = - DateTimePart.values() - .map { dateTimePart -> - ExtractFromTimeTC( - source = "EXTRACT(${dateTimePart.name} FROM $source)", - expected = when (dateTimePart) { - DateTimePart.YEAR -> null - DateTimePart.MONTH -> null - DateTimePart.DAY -> null - DateTimePart.HOUR -> valueFactory.newDecimal(expected.localTime.hour) - DateTimePart.MINUTE -> valueFactory.newDecimal(expected.localTime.minute) - DateTimePart.SECOND -> valueFactory.newDecimal(expected.secondsWithFractionalPart) - DateTimePart.TIMEZONE_HOUR -> expected.timezoneHour?.let { valueFactory.newDecimal(it) } - DateTimePart.TIMEZONE_MINUTE -> expected.timezoneMinute?.let { valueFactory.newDecimal(it) } - } - ) - } - - fun parametersForRunTimeTests() = listOf( - createTimeTC("TIME '23:12:59.128'", Time.of(23, 12, 59, 128000000, 3, null)), - createTimeTC("TIME WITH TIME ZONE '23:12:59.128-06:30'", Time.of(23, 12, 59, 128000000, 3, -390)), - createTimeTC("TIME WITH TIME ZONE '23:12:59.12800-00:00'", Time.of(23, 12, 59, 128000000, 5, 0)), - createTimeTC("TIME (2) '23:12:59.128'", Time.of(23, 12, 59, 130000000, 2, null)), - createTimeTC("TIME (2) WITH TIME ZONE '23:12:59.128-06:30'", Time.of(23, 12, 59, 130000000, 2, -390)), - createTimeTC("TIME (3) WITH TIME ZONE '23:12:59.128-06:30'", Time.of(23, 12, 59, 128000000, 3, -390)), - createTimeTC("TIME (3) WITH TIME ZONE '23:59:59.9998-18:00'", Time.of(0, 0, 0, 0, 3, -1080)) - ).flatten() - - @Test - @Parameters - fun runTimeTests(tc: ExtractFromTimeTC) = when (tc.expected) { - null -> { - try { - voidEval(tc.source) - fail("Expected evaluation error") - } catch (e: EvaluationException) { - // Do nothing - } - } - else -> assertExprEquals(eval(tc.source), tc.expected, "Expected exprValues to be equal.") - } - - private fun callExtract(vararg args: Any): Number? { - val value = subject.call(env, RequiredArgs(args.map { anyToExprValue(it) })) - return when(value.type) { - ExprValueType.NULL -> null - else -> value.numberValue() - } - } - - @Test - fun wrongTypeOfFirstArgument() { - Assertions.assertThatThrownBy { callExtract("foobar", 1) } - .hasMessage("invalid datetime part, valid values: [year, month, day, hour, minute, second, timezone_hour, timezone_minute]") - .isExactlyInstanceOf(EvaluationException::class.java) - } - - @Test - fun wrongTypeOfSecondArgument() { - Assertions.assertThatThrownBy { callExtract("year", "999") } - .hasMessage("Expected date, time or timestamp: '999'") - .isExactlyInstanceOf(EvaluationException::class.java) - } - - fun parametersForExtract(): List Number?>> = listOf( - // just year - 2017 to { callExtract("year", Timestamp.valueOf("2017T")) }, - 1 to { callExtract("month", Timestamp.valueOf("2017T")) }, - 1 to { callExtract("day", Timestamp.valueOf("2017T")) }, - 0 to { callExtract("hour", Timestamp.valueOf("2017T")) }, - 0 to { callExtract("minute", Timestamp.valueOf("2017T")) }, - 0 to { callExtract("second", Timestamp.valueOf("2017T")) }, - 0 to { callExtract("timezone_hour", Timestamp.valueOf("2017T")) }, - 0 to { callExtract("timezone_minute", Timestamp.valueOf("2017T")) }, - // year, month - 2017 to { callExtract("year", Timestamp.valueOf("2017-01T")) }, - 1 to { callExtract("month", Timestamp.valueOf("2017-01T")) }, - 1 to { callExtract("day", Timestamp.valueOf("2017-01T")) }, - 0 to { callExtract("hour", Timestamp.valueOf("2017-01T")) }, - 0 to { callExtract("minute", Timestamp.valueOf("2017-01T")) }, - 0 to { callExtract("second", Timestamp.valueOf("2017-01T")) }, - 0 to { callExtract("timezone_hour", Timestamp.valueOf("2017-01T")) }, - 0 to { callExtract("timezone_minute", Timestamp.valueOf("2017-01T")) }, - - // year, month, day - 2017 to { callExtract("year", Timestamp.valueOf("2017-01-02T")) }, - 1 to { callExtract("month", Timestamp.valueOf("2017-01-02T")) }, - 2 to { callExtract("day", Timestamp.valueOf("2017-01-02T")) }, - 0 to { callExtract("hour", Timestamp.valueOf("2017-01-02T")) }, - 0 to { callExtract("minute", Timestamp.valueOf("2017-01-02T")) }, - 0 to { callExtract("second", Timestamp.valueOf("2017-01-02T")) }, - 0 to { callExtract("timezone_hour", Timestamp.valueOf("2017-01-02T")) }, - 0 to { callExtract("timezone_minute", Timestamp.valueOf("2017-01-02T")) }, - - // year, month, day, hour, minute - 2017 to { callExtract("year", Timestamp.valueOf("2017-01-02T03:04Z")) }, - 1 to { callExtract("month", Timestamp.valueOf("2017-01-02T03:04Z")) }, - 2 to { callExtract("day", Timestamp.valueOf("2017-01-02T03:04Z")) }, - 3 to { callExtract("hour", Timestamp.valueOf("2017-01-02T03:04Z")) }, - 4 to { callExtract("minute", Timestamp.valueOf("2017-01-02T03:04Z")) }, - 0 to { callExtract("second", Timestamp.valueOf("2017-01-02T03:04Z")) }, - 0 to { callExtract("timezone_hour", Timestamp.valueOf("2017-01-02T03:04Z")) }, - 0 to { callExtract("timezone_minute", Timestamp.valueOf("2017-01-02T03:04Z")) }, - - // year, month, day, hour, minute, second - 2017 to { callExtract("year", Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - 1 to { callExtract("month", Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - 2 to { callExtract("day", Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - 3 to { callExtract("hour", Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - 4 to { callExtract("minute", Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - 5 to { callExtract("second", Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - 0 to { callExtract("timezone_hour", Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - 0 to { callExtract("timezone_minute", Timestamp.valueOf("2017-01-02T03:04:05Z")) }, - - // year, month, day, hour, minute, second, local offset - 2017 to { callExtract("year", Timestamp.valueOf("2017-01-02T03:04:05+07:08")) }, - 1 to { callExtract("month", Timestamp.valueOf("2017-01-02T03:04:05+07:08")) }, - 2 to { callExtract("day", Timestamp.valueOf("2017-01-02T03:04:05+07:08")) }, - 3 to { callExtract("hour", Timestamp.valueOf("2017-01-02T03:04:05+07:08")) }, - 4 to { callExtract("minute", Timestamp.valueOf("2017-01-02T03:04:05+07:08")) }, - 5 to { callExtract("second", Timestamp.valueOf("2017-01-02T03:04:05+07:08")) }, - 7 to { callExtract("timezone_hour", Timestamp.valueOf("2017-01-02T03:04:05+07:08")) }, - 8 to { callExtract("timezone_minute", Timestamp.valueOf("2017-01-02T03:04:05+07:08")) }, - - // negative offset - -7 to { callExtract("timezone_hour", Timestamp.valueOf("2017-01-02T03:04:05-07:08")) }, - -8 to { callExtract("timezone_minute", Timestamp.valueOf("2017-01-02T03:04:05-07:08")) }, - - // extract year, month, day, hour, minute, second from DATE literals - 2021 to { callExtract("year", LocalDate.of(2021, 3, 24)) }, - 3 to { callExtract("month", LocalDate.of(2021, 3, 24)) }, - 24 to { callExtract("day", LocalDate.of(2021, 3, 24)) }, - 0 to { callExtract("hour", LocalDate.of(2021, 3, 24)) }, - 0 to { callExtract("minute", LocalDate.of(2021, 3, 24)) }, - 0 to { callExtract("second", LocalDate.of(2021, 3, 24)) }, - - // extract hour, minute, second, timezone_hour, timezone_minute from TIME literals - 23 to { callExtract("hour", Time.of(23, 12, 59, 128000000, 2, -510))}, - 12 to { callExtract("minute", Time.of(23, 12, 59, 128000000, 2, -510))}, - 59.13 to { callExtract("second", Time.of(23, 12, 59, 128000000, 2, -510))}, - -8 to { callExtract("timezone_hour", Time.of(23, 12, 59, 128000000, 2, -510))}, - -30 to { callExtract("timezone_minute", Time.of(23, 12, 59, 128000000, 2, -510))} - ) - - @Test - @Parameters - fun extract(params: Pair Number?>) { - val (expected, call) = params - - assertEquals(expected?.toLong(), call.invoke()?.toLong()) - } -} \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/FromUnixTimeFunctionTest.kt b/lang/test/org/partiql/lang/eval/builtins/FromUnixTimeFunctionTest.kt deleted file mode 100644 index 6fc0362a31..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/FromUnixTimeFunctionTest.kt +++ /dev/null @@ -1,71 +0,0 @@ -package org.partiql.lang.eval.builtins - -import org.junit.Test -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ArgumentsSource -import org.partiql.lang.errors.ErrorCode -import org.partiql.lang.errors.Property -import org.partiql.lang.eval.EvaluatorTestBase -import org.partiql.lang.util.ArgumentsProviderBase -import org.partiql.lang.util.to - -data class FromUnixTimeTestCase(val unixTimestamp: String, val expected: String) - -class FromUnixTimeFunctionTest : EvaluatorTestBase() { - private val testUnixTime = 1234567890 - - @Test - fun `from_unixtime 0 args`() = - checkInputThrowingEvaluationException( - "from_unixtime()", - ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNC_CALL, - mapOf(Property.LINE_NUMBER to 1L, - Property.FUNCTION_NAME to "from_unixtime", - Property.COLUMN_NUMBER to 1L, - Property.ACTUAL_ARITY to 0, - Property.EXPECTED_ARITY_MIN to 1, - Property.EXPECTED_ARITY_MAX to 1)) - - @Test - fun `from_unixtime 2 args`() = - checkInputThrowingEvaluationException( - "from_unixtime($testUnixTime, $testUnixTime)", - ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNC_CALL, - mapOf(Property.LINE_NUMBER to 1L, - Property.FUNCTION_NAME to "from_unixtime", - Property.COLUMN_NUMBER to 1L, - Property.ACTUAL_ARITY to 2, - Property.EXPECTED_ARITY_MIN to 1, - Property.EXPECTED_ARITY_MAX to 1)) - - @Test - fun `from_unixtime 3 args`() = - checkInputThrowingEvaluationException( - "from_unixtime($testUnixTime, $testUnixTime, $testUnixTime)", - ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNC_CALL, - mapOf(Property.LINE_NUMBER to 1L, - Property.FUNCTION_NAME to "from_unixtime", - Property.COLUMN_NUMBER to 1L, - Property.ACTUAL_ARITY to 3, - Property.EXPECTED_ARITY_MIN to 1, - Property.EXPECTED_ARITY_MAX to 1)) - - - class FromUnixTimeCases : ArgumentsProviderBase() { - override fun getParameters(): List = listOf( - // negative unix epochs output timestamp before last epoch - FromUnixTimeTestCase("from_unixtime(-1)", "1969-12-31T23:59:59-00:00"), - FromUnixTimeTestCase("from_unixtime(-0.1)", "1969-12-31T23:59:59.9-00:00"), - // non-negative cases outputting a timestamp after last epoch - FromUnixTimeTestCase("from_unixtime(0)", "1970-01-01T00:00:00.000-00:00"), - FromUnixTimeTestCase("from_unixtime(0.001)", "1970-01-01T00:00:00.001-00:00"), - FromUnixTimeTestCase("from_unixtime(0.01)", "1970-01-01T00:00:00.01-00:00"), - FromUnixTimeTestCase("from_unixtime(0.1)", "1970-01-01T00:00:00.1-00:00"), - FromUnixTimeTestCase("from_unixtime(1)", "1970-01-01T00:00:01-00:00"), - FromUnixTimeTestCase("from_unixtime(1577836800)", "2020-01-01T00:00:00-00:00") - ) - } - @ParameterizedTest - @ArgumentsSource(FromUnixTimeCases::class) - fun runNoArgTests(tc: FromUnixTimeTestCase) = assertEval(tc.unixTimestamp, tc.expected) -} diff --git a/lang/test/org/partiql/lang/eval/builtins/InvalidArgTypeChecker.kt b/lang/test/org/partiql/lang/eval/builtins/InvalidArgTypeChecker.kt new file mode 100644 index 0000000000..521ced1997 --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/InvalidArgTypeChecker.kt @@ -0,0 +1,138 @@ +package org.partiql.lang.eval.builtins + +import org.partiql.lang.errors.ErrorCode +import org.partiql.lang.errors.Property +import org.partiql.lang.eval.EvaluatorTestBase +import org.partiql.lang.eval.expectedArgTypeErrorMsg +import org.partiql.lang.types.StaticType +import org.partiql.lang.types.SingleType +import org.partiql.lang.types.BoolType +import org.partiql.lang.types.IntType +import org.partiql.lang.types.FloatType +import org.partiql.lang.types.DecimalType +import org.partiql.lang.types.DateType +import org.partiql.lang.types.TimestampType +import org.partiql.lang.types.TimeType +import org.partiql.lang.types.SymbolType +import org.partiql.lang.types.StringType +import org.partiql.lang.types.ClobType +import org.partiql.lang.types.BlobType +import org.partiql.lang.types.ListType +import org.partiql.lang.types.SexpType +import org.partiql.lang.types.StructType +import org.partiql.lang.types.BagType +import org.partiql.lang.types.MissingType +import org.partiql.lang.types.NullType + + +import java.lang.StringBuilder + + +/** + * This class is used to represent each argument in ExprFunctions for argument type checking. + * @param argPosition is the position of argument in an ExprFunction + * @param expectedTypes is expected StaticType of the argument. As we exclude the cases of NULL and MISSING for type checking, this variable should not contain any of them. + * @param delimiter is the substring between this argument and the next argument. If this argument is the last one, it represents the substring after this argument. + */ +data class Argument( + val argPosition: Int, + val expectedTypes: StaticType, + val delimiter: String +) + +private fun SingleType.getExample() = when (this) { + is BoolType -> "TRUE" + is IntType -> "0" + is FloatType -> "`0e0`" + is DecimalType -> "0." + is DateType -> "DATE '2012-12-12'" + is TimestampType -> "`2017T`" + is TimeType -> "TIME '23:12:59.128'" + is SymbolType -> "`a`" + is StringType -> "'a'" + is ClobType -> "`{{ \"HelloWorld\" }}`" + is BlobType -> "`{{ aGVsbG8= }}`" + is ListType -> "[]" + is SexpType -> "sexp()" + is StructType -> "{}" + is BagType -> "<<>>" + is MissingType, + is NullType -> throw Exception("NULL or MISSING should be the problem of permissive mode, not type checking.") +} + +private fun StaticType.getOneExample() = (allTypes.first() as SingleType).getExample() + +/** + * This class is used to test invalid argument types for ExprFunctions, which has error code as EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL. + * The idea here is to automatically generate queries to test ExprFunctions with different invalid argument types. + * Also note that we exclude the cases of NULL and MISSING as argument types, as they should be the concern of permissive mode for ExprFunctions. + */ +// TODO: Get rid of `EvaluatorTestBase` +class InvalidArgTypeChecker : EvaluatorTestBase() { + /** + * Generates one test for each invalid type for each argument of the ExprFunction + * + * @param funcName is the name of the ExprFunction. + * @param syntaxSuffix is the substring after function name and before the first argument tested. + * @param args is the list of acceptable arguments for an ExprFunction. + */ + fun checkInvalidArgType(funcName: String, syntaxSuffix: String = "(", args: List) { + val size = args.size + val delimiters = args.map { it.delimiter } + val expectedTypes = args.map { it.expectedTypes } // expectedExprValueTypes[i] is expected StaticType of args[i] + val curArgTypeExamples = expectedTypes.map { it.getOneExample() }.toMutableList() // curArgTypeExamples[i] is one example of args[i] + // In each argument position, for each SingleType, we first check if it is an invalid argument type. + // If it is, we put the example of it in the current argument position and compose the query, then catch the error, + expectedTypes.forEachIndexed { index, expectedType -> + StaticType.ALL_TYPES.filter { it != StaticType.NULL && it != StaticType.MISSING }.forEach { singleType -> + if (!singleType.isSubTypeOf(expectedType)) { + curArgTypeExamples[index] = singleType.getExample() + val query = composeQuery("$funcName$syntaxSuffix", delimiters, curArgTypeExamples, size) + assertThrowsInvalidArgType( + query, + funcName, + args[index].argPosition, + expectedType, + singleType + ) + } + curArgTypeExamples[index] = expectedType.getOneExample() + } + } + } + + private fun composeQuery( + startStr: String, + delimiters: List, + curArgTypeExamples: List, + size: Int + ): String { + val sb = StringBuilder(startStr) + for (i in 0 until size) { + sb.append(curArgTypeExamples[i]) + sb.append(delimiters[i]) + } + return sb.toString() + } + + private fun assertThrowsInvalidArgType( + source: String, + funcName: String, + argPosition: Int, + expectedTypes: StaticType, + actualType: SingleType + ) = + checkInputThrowingEvaluationException( + input = source, + errorCode = ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, + expectErrorContextValues = mapOf( + Property.FUNCTION_NAME to funcName, + Property.ARGUMENT_POSITION to argPosition, + Property.EXPECTED_ARGUMENT_TYPES to expectedArgTypeErrorMsg(expectedTypes.typeDomain.toList()), + Property.ACTUAL_ARGUMENT_TYPES to actualType.runtimeType.toString(), + Property.LINE_NUMBER to 1L, + Property.COLUMN_NUMBER to 1L + ), + expectedPermissiveModeResult = "MISSING" + ) +} \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/InvalidArityChecker.kt b/lang/test/org/partiql/lang/eval/builtins/InvalidArityChecker.kt new file mode 100644 index 0000000000..d3cd5eaee6 --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/InvalidArityChecker.kt @@ -0,0 +1,69 @@ +package org.partiql.lang.eval.builtins + +import org.partiql.lang.errors.ErrorCode +import org.partiql.lang.errors.Property +import org.partiql.lang.eval.EvaluatorTestBase + +/** + * This class is used to check arity for ExprFunctions. + * + * We check arity with either providing more arguments or less arguments. + * For example, `char_length()` requires exactly 1 argument. + * 1) We test with less argument provided: `char_length()`, + * 2) we test with more argument provided `char_length('a', 'b')` + * + * Also, note that the arity check is performed before type check, so we can provide `null` as argument + * for any ExprFunctions without throwing invalid argument type error, if arity is incorrect. + */ +// TODO: Get rid of `EvaluatorTestBase` +class InvalidArityChecker : EvaluatorTestBase() { + + /** + * The number of additional arguments beyond the max arity + */ + private val maxArityOverflow = 5 + + /** + * Generates test cases for arity less than minimum arity and greater than maximum arity for the ExprFunction + * + * @param funcName is the name of an ExprFunction. + * @param maxArity is the maximum arity of an ExprFunction. + * @param minArity is the minimum arity of an ExprFunction. + */ + fun checkInvalidArity(funcName: String, minArity: Int, maxArity: Int) { + if (minArity < 0) throw IllegalStateException("Minimum arity has to be larger than 0.") + if (maxArity < minArity) throw IllegalStateException("Maximum arity has to be larger than or equal to minimum arity.") + + val sb = StringBuilder("$funcName(") + val maxInvalidArity = maxArity + maxArityOverflow + for (curArity in 0..maxInvalidArity) { + when (curArity) { + 0 -> {} // Don't need to do anything + 1 -> sb.append("null") + else -> sb.append(",null") + } + if (curArity < minArity || curArity > maxArity) { // If less or more argument provided, we catch invalid arity error + assertThrowsInvalidArity("$sb)", funcName, curArity, minArity, maxArity) + } + } + } + + private fun assertThrowsInvalidArity( + query: String, + funcName: String, + actualArity: Int, + minArity: Int, + maxArity: Int + ) = checkInputThrowingEvaluationException( + input = query, + errorCode = ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNC_CALL, + expectErrorContextValues = mapOf( + Property.FUNCTION_NAME to funcName, + Property.EXPECTED_ARITY_MIN to minArity, + Property.EXPECTED_ARITY_MAX to maxArity, + Property.ACTUAL_ARITY to actualArity, + Property.LINE_NUMBER to 1L, + Property.COLUMN_NUMBER to 1L + ) + ) +} diff --git a/lang/test/org/partiql/lang/eval/builtins/LowerEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/LowerEvaluationTest.kt deleted file mode 100644 index 9b538d5ee1..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/LowerEvaluationTest.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.partiql.lang.eval.builtins - -import org.junit.Test -import org.partiql.lang.eval.EvaluatorTestBase - -class LowerEvaluationTest : EvaluatorTestBase() { - @Test fun lower_0() = assertEval("lower('')", "\"\"") - @Test fun lower_1() = assertEval("lower('ABCDEF')", "\"abcdef\"") - @Test fun lower_2() = assertEval("lower('abcdef')", "\"abcdef\"") - @Test fun lower_3() = assertEval("lower(null)", "null") - @Test fun lower_4() = assertEval("lower(missing)", "null") - @Test fun lower_5() = assertEval("lower('123\$%(*&')", "\"123\$%(*&\"") - @Test fun lower_6() = assertEval("lower('ȴȵ💩Z💋')", "\"ȴȵ💩z💋\"") - @Test fun lower_7() = assertEval("lower('話家身圧費谷料村能計税金')", "\"話家身圧費谷料村能計税金\"") -} diff --git a/lang/test/org/partiql/lang/eval/builtins/MakeDateEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/MakeDateEvaluationTest.kt deleted file mode 100644 index 4db677049b..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/MakeDateEvaluationTest.kt +++ /dev/null @@ -1,125 +0,0 @@ -package org.partiql.lang.eval.builtins - -import com.amazon.ion.IonTimestamp -import junitparams.Parameters -import org.assertj.core.api.Assertions -import org.junit.Test -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ArgumentsSource -import org.partiql.lang.errors.ErrorCode -import org.partiql.lang.eval.Environment -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.EvaluatorTestBase -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.RequiredArgs -import org.partiql.lang.eval.call -import org.partiql.lang.eval.dateValue -import org.partiql.lang.util.ArgumentsProviderBase -import java.time.LocalDate - -class MakeDateEvaluationTest : EvaluatorTestBase() { - private val env = Environment.standard() - private val subject = MakeDateExprFunction(valueFactory) - - data class MakeDateTestCase(val query: String, val expected: String?, val errorCode: ErrorCode?) - - @ParameterizedTest - @ArgumentsSource(ArgumentsForDateLiterals::class) - fun testMakeDate(tc: MakeDateTestCase) { - when (tc.errorCode) { - null -> { - val originalExprValue = eval(tc.query) - assertEquals(originalExprValue.toString(), tc.expected) - if (originalExprValue.type == ExprValueType.DATE) { - val (year, month, day) = tc.expected!!.split("-") - val dateIonValue = originalExprValue.ionValue - dateIonValue as IonTimestamp - val timestamp = dateIonValue.timestampValue() - assertEquals("Expected year to be $year", year.toInt(), timestamp.year) - assertEquals("Expected month to be $month", month.toInt(), timestamp.month) - assertEquals("Expected day to be $day", day.toInt(), timestamp.day) - } - } - else -> { - try { - voidEval(tc.query) - fail("Expected evaluation error") - } catch (e: EvaluationException) { - assertEquals(tc.errorCode, e.errorCode) - } - } - } - } - - private class ArgumentsForDateLiterals : ArgumentsProviderBase() { - private fun case(query: String, expected: String) = MakeDateTestCase(query, expected, null) - private fun case(query: String, errorCode: ErrorCode) = MakeDateTestCase(query, null, errorCode) - - override fun getParameters() = listOf( - case("MAKE_DATE(2012,02,29)", "2012-02-29"), - case("MAKE_DATE(2021,02,28)", "2021-02-28"), - case("MAKE_DATE(2021,03,17) IS DATE", "true"), - case("MAKE_DATE(NULL,02,28)", "NULL"), - case("MAKE_DATE(2021,NULL,28)", "NULL"), - case("MAKE_DATE(2021,02,NULL)", "NULL"), - case("MAKE_DATE(MISSING,02,28)", "NULL"), - case("MAKE_DATE(2021,MISSING,28)", "NULL"), - case("MAKE_DATE(2021,02,MISSING)", "NULL"), - case("MAKE_DATE(NULL,MISSING,28)", "NULL"), - case("MAKE_DATE(MISSING,NULL,28)", "NULL"), - case("MAKE_DATE(MISSING,02,NULL)", "NULL"), - case("MAKE_DATE(NULL,NULL,28)", "NULL"), - case("MAKE_DATE(NULL,NULL,28)", "NULL"), - case("MAKE_DATE(MISSING,MISSING,MISSING)", "NULL"), - case("MAKE_DATE(2021,02,29)", ErrorCode.EVALUATOR_DATE_FIELD_OUT_OF_RANGE), - case("MAKE_DATE(2021,04,31)", ErrorCode.EVALUATOR_DATE_FIELD_OUT_OF_RANGE), - case("MAKE_DATE(2021,02,27.999)", ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL), - case("MAKE_DATE(2021,02,'27')", ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL), - case("MAKE_DATE(2021,02,'twenty-seven')", ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL), - case("MAKE_DATE('2021',02,27)", ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL) - ) - } - - private fun callMakeDate(vararg args: Any): LocalDate? { - val value = subject.call(env, RequiredArgs(args.map { anyToExprValue(it) })) - return when(value.type) { - ExprValueType.NULL -> null - else -> value.dateValue() - } - } - - @Test - fun wrongTypeOfArgumentForYear() { - Assertions.assertThatThrownBy { callMakeDate("2021", 2, 28) } - .hasMessage("Invalid argument type for make_date") - .isExactlyInstanceOf(EvaluationException::class.java) - } - - @Test - fun wrongTypeOfArgumentForMonth() { - Assertions.assertThatThrownBy { callMakeDate(2021, 2.0, 28) } - .hasMessage("Invalid argument type for make_date") - .isExactlyInstanceOf(EvaluationException::class.java) - } - - @Test - fun wrongTypeOfArgumentForDay() { - Assertions.assertThatThrownBy { callMakeDate(2021, 2, "twenty-eight") } - .hasMessage("Invalid argument type for make_date") - .isExactlyInstanceOf(EvaluationException::class.java) - } - - fun parametersForMakeDate(): List LocalDate?>> = listOf( - LocalDate.of(2021, 3,26) to {callMakeDate(2021, 3, 26)}, - LocalDate.of(2021, 2,28) to {callMakeDate(2021, 2, 28)}, - LocalDate.of(2020, 2,29) to {callMakeDate(2020, 2, 29)} - ) - - @Test - @Parameters - fun makeDate(params: Pair LocalDate?>) { - val (expected, call) = params - - assertEquals(expected, call.invoke()) - } -} \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/MakeTimeEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/MakeTimeEvaluationTest.kt deleted file mode 100644 index 0de9540d9d..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/MakeTimeEvaluationTest.kt +++ /dev/null @@ -1,172 +0,0 @@ -package org.partiql.lang.eval.builtins - -import junitparams.Parameters -import org.junit.Test -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ArgumentsSource -import org.partiql.lang.errors.ErrorCode -import org.partiql.lang.errors.Property -import org.partiql.lang.eval.Environment -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.EvaluatorTestBase -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.RequiredArgs -import org.partiql.lang.eval.RequiredWithOptional -import org.partiql.lang.eval.call -import org.partiql.lang.eval.time.Time -import org.partiql.lang.eval.timeValue -import org.partiql.lang.util.ArgumentsProviderBase -import org.partiql.lang.util.to - -class MakeTimeEvaluationTest : EvaluatorTestBase() { - private val env = Environment.standard() - private val subject = MakeTimeExprFunction(valueFactory) - - data class MakeTimeTestCase(val query: String, val expected: String?, val errorCode: ErrorCode?) - - @ParameterizedTest - @ArgumentsSource(ArgumentsForTimeLiterals::class) - fun testMakeTime(tc: MakeTimeTestCase) { - when (tc.errorCode) { - null -> { - val originalExprValue = eval(tc.query) - assertEquals(originalExprValue.toString(), tc.expected) - } - else -> { - try { - voidEval(tc.query) - fail("Expected evaluation error") - } catch (e: EvaluationException) { - assertEquals(tc.errorCode, e.errorCode) - } - } - } - } - - private class ArgumentsForTimeLiterals : ArgumentsProviderBase() { - private fun case(query: String, expected: String) = MakeTimeTestCase(query, expected, null) - private fun case(query: String, errorCode: ErrorCode) = MakeTimeTestCase(query, null, errorCode) - - override fun getParameters() = listOf( - case("MAKE_TIME(0,0,0.)", "00:00:00"), - case("MAKE_TIME(0,0,0.,0)", "00:00:00+00:00"), - case("MAKE_TIME(23,12,59.12345)", "23:12:59.12345"), - case("MAKE_TIME(23,12,59.12345,800)", "23:12:59.12345+13:20"), - case("MAKE_TIME(23,59,59.999999999)", "23:59:59.999999999"), - case("MAKE_TIME(23,12,59.12345,-800)", "23:12:59.12345-13:20"), - case("MAKE_TIME(23,59,59.999999999,-1080)", "23:59:59.999999999-18:00"), - case("MAKE_TIME(23,59,59.999999999,1080)", "23:59:59.999999999+18:00"), - case("MAKE_TIME(NULL,59,59.999999999)", "NULL"), - case("MAKE_TIME(23,NULL,59.999999999)", "NULL"), - case("MAKE_TIME(23,59,NULL)", "NULL"), - case("MAKE_TIME(NULL,59,59.999999999,1080)", "NULL"), - case("MAKE_TIME(23,NULL,59.999999999,1080)", "NULL"), - case("MAKE_TIME(23,59,NULL,1080)", "NULL"), - case("MAKE_TIME(23,59,59.999999999,NULL)", "NULL"), - case("MAKE_TIME(MISSING,59,59.999999999,1080)", "NULL"), - case("MAKE_TIME(23,MISSING,59.999999999,1080)", "NULL"), - case("MAKE_TIME(23,59,MISSING,1080)", "NULL"), - case("MAKE_TIME(23,59,MISSING,1080)", "NULL"), - case("MAKE_TIME(23,59,59.999999999,MISSING)", "NULL"), - case("MAKE_TIME(23,59,MISSING,NULL)", "NULL"), - case("MAKE_TIME(24,0,0.)", ErrorCode.EVALUATOR_TIME_FIELD_OUT_OF_RANGE), - case("MAKE_TIME(23,60,0.)", ErrorCode.EVALUATOR_TIME_FIELD_OUT_OF_RANGE), - case("MAKE_TIME(23,59,60.)", ErrorCode.EVALUATOR_TIME_FIELD_OUT_OF_RANGE), - case("MAKE_TIME(23,59,59.999999999,-1081)", ErrorCode.EVALUATOR_TIME_FIELD_OUT_OF_RANGE), - case("MAKE_TIME(23,59,59.999999999,1081)", ErrorCode.EVALUATOR_TIME_FIELD_OUT_OF_RANGE), - case("MAKE_TIME('23',59,59.999999999,1080)", ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL), - case("MAKE_TIME(23,'59',59.999999999,1080)", ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL), - case("MAKE_TIME(23,59,59,1080)", ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL), - case("MAKE_TIME(23,59,'59.999999999',1080)", ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL), - case("MAKE_TIME(23,59,59.999999999,'1080')", ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL), - case("MAKE_TIME(23.,59,59.999999999,1080)", ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL), - case("MAKE_TIME('twenty-three',59,59.999999999,1080)", ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL) - ) - } - - private fun callMakeTime(vararg args: Any): Time? { - val required = args.take(3).map { anyToExprValue(it) } - val opt = args.drop(3) - val args = when(opt.firstOrNull()) { - null -> RequiredArgs(required) - else -> RequiredWithOptional(required, anyToExprValue(opt.first())) - } - val value = subject.call(env, args) - return when(value.type) { - ExprValueType.NULL -> null - else -> value.timeValue() - } - } - - @Test - fun wrongTypeOfArgumentForHour() { - checkInputThrowingEvaluationException("MAKE_TIME('23', 2, 28.0)", - ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - mapOf(Property.EXPECTED_ARGUMENT_TYPES to "INT", - Property.ARGUMENT_POSITION to 1, - Property.ACTUAL_ARGUMENT_TYPES to "STRING", - Property.FUNCTION_NAME to "make_time", - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L), - expectedPermissiveModeResult = "MISSING") -/* - Assertions.assertThatThrownBy { callMakeTime("23", 2, 28.toBigDecimal()) } - .hasMessage("Invalid argument type for make_time") - .isExactlyInstanceOf(EvaluationException::class.java) - - */ - } - - @Test - fun wrongTypeOfArgumentForMinute() { - checkInputThrowingEvaluationException("MAKE_TIME(23, 2.0, 28.0)", - ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - mapOf(Property.EXPECTED_ARGUMENT_TYPES to "INT", - Property.ARGUMENT_POSITION to 2, - Property.ACTUAL_ARGUMENT_TYPES to "DECIMAL", - Property.FUNCTION_NAME to "make_time", - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L), - expectedPermissiveModeResult = "MISSING") - } - - @Test - fun wrongTypeOfArgumentForSecond() { - checkInputThrowingEvaluationException("MAKE_TIME(23, 2, 28)", - ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - mapOf(Property.EXPECTED_ARGUMENT_TYPES to "DECIMAL", - Property.ARGUMENT_POSITION to 3, - Property.ACTUAL_ARGUMENT_TYPES to "INT", - Property.FUNCTION_NAME to "make_time", - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L), - expectedPermissiveModeResult = "MISSING") - } - - @Test - fun wrongTypeOfArgumentForTzMinutes() { - checkInputThrowingEvaluationException("MAKE_TIME(23, 2, 28.0, 12.0)", - ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - mapOf(Property.EXPECTED_ARGUMENT_TYPES to "INT", - Property.ARGUMENT_POSITION to 4, - Property.ACTUAL_ARGUMENT_TYPES to "DECIMAL", - Property.FUNCTION_NAME to "make_time", - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L), - expectedPermissiveModeResult = "MISSING") - } - - fun parametersForMakeTime(): List Time?>> = listOf( - Time.of(23, 3,26,0,0) to {callMakeTime(23, 3, 26.toBigDecimal())}, - Time.of(23, 3,26,123450000,5) to {callMakeTime(23, 3, 26.12345.toBigDecimal())}, - Time.of(23, 3,26,123450000,5, 630) to {callMakeTime(23, 3, 26.12345.toBigDecimal(), 630)} - ) - - @Test - @Parameters - fun makeTime(params: Pair Time?>) { - val (expected, call) = params - - assertEquals(expected, call.invoke()) - } -} \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/SizeEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/SizeEvaluationTest.kt deleted file mode 100644 index ae4ea763bf..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/SizeEvaluationTest.kt +++ /dev/null @@ -1,87 +0,0 @@ -package org.partiql.lang.eval.builtins - -import org.junit.Test -import org.partiql.lang.errors.ErrorCode -import org.partiql.lang.errors.Property -import org.partiql.lang.eval.EvaluatorTestBase -import org.partiql.lang.util.to - -/** - * More detailed tests are in [SizeExprFunctionTest] - */ -class SizeEvaluationTest : EvaluatorTestBase() { - - @Test - fun emptyStruct() = assertEval("size({})", "0") - - @Test - fun emptyList() = assertEval("size([])", "0") - - @Test - fun emptySexp() = assertEval("size(sexp())", "0") - - @Test - fun emptyBag() = assertEval("size(<<>>)", "0") - - @Test - fun nonEmptyStruct() = assertEval("size(`{ a: 1 }`)", "1") - - @Test - fun nonEmptyList() = assertEval("size(['foo'])", "1") - - @Test - fun nonEmptySexp() = assertEval("size(sexp(1, 2, 3))", "3") - - @Test - fun nonEmptyBag() = assertEval("size(<<'foo'>>)", "1") - - @Test - fun nullArgument() = assertEval("size(null)", "null") - - @Test - fun missingArgument() = assertEval("size(missing)", "null") - - - @Test - fun lessArguments() { - checkInputThrowingEvaluationException( - "size()", - ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNC_CALL, - // Kotlin auto-casts the values of EXPECTED_ARITY_MIN and EXPECTED_ARITY_MAX to Long - // unless we explicitly specify [Any] as for the value generic argument of the map below - mapOf( - Property.FUNCTION_NAME to "size", - Property.EXPECTED_ARITY_MIN to 1, - Property.EXPECTED_ARITY_MAX to 1, - Property.ACTUAL_ARITY to 0, - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L)) - } - - @Test - fun moreArguments() = - checkInputThrowingEvaluationException("size(null, null)", - ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNC_CALL, - // Kotlin auto-casts the values of EXPECTED_ARITY_MIN and EXPECTED_ARITY_MAX to Long - // unless we explicitly specify [Any] as for the value generic argument of the map below - mapOf( - Property.FUNCTION_NAME to "size", - Property.EXPECTED_ARITY_MIN to 1, - Property.EXPECTED_ARITY_MAX to 1, - Property.ACTUAL_ARITY to 2, - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L)) - - - @Test - fun wrongTypeOfArgument() = - checkInputThrowingEvaluationException("size(1)", - ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - mapOf(Property.EXPECTED_ARGUMENT_TYPES to "LIST, BAG, STRUCT, or SEXP", - Property.ARGUMENT_POSITION to 1, - Property.ACTUAL_ARGUMENT_TYPES to "INT", - Property.FUNCTION_NAME to "size", - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L), - expectedPermissiveModeResult = "MISSING") -} \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/SizeExprFunctionTest.kt b/lang/test/org/partiql/lang/eval/builtins/SizeExprFunctionTest.kt deleted file mode 100644 index b2fe26f654..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/SizeExprFunctionTest.kt +++ /dev/null @@ -1,58 +0,0 @@ -package org.partiql.lang.eval.builtins - -import com.amazon.ion.IonValue -import org.junit.Test -import org.partiql.lang.TestBase -import org.partiql.lang.eval.Environment -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.RequiredArgs -import org.partiql.lang.eval.call - -class SizeExprFunctionTest : TestBase() { - private val subject = SizeExprFunction(valueFactory) - - private val env = Environment.standard() - - private fun callSize(vararg arg: ExprValue): Number? = subject.call(env, RequiredArgs(arg.toList())).scalar.numberValue() - - private fun listOf(vararg values: IonValue): ExprValue = valueFactory.newList( - values.asSequence().map { valueFactory.newFromIonValue(it) }) - - private fun structOf(vararg values: IonValue): ExprValue { - val struct = ion.newEmptyStruct() - values.forEachIndexed { index, value -> struct.put(index.toString(), value) } - - return valueFactory.newFromIonValue(struct) - } - - private fun bagOf(vararg values: IonValue): ExprValue { - return valueFactory.newBag(values.asSequence().map { valueFactory.newFromIonValue(it) }) - } - - @Test - fun emptyStruct() = assertEquals(0L, callSize(structOf())) - - @Test - fun emptyList() = assertEquals(0L, callSize(listOf())) - - @Test - fun emptyBag() = assertEquals(0L, callSize(bagOf())) - - @Test - fun singleElementStruct() = assertEquals(1L, callSize(structOf(ion.newString("foo")))) - - @Test - fun singleElementList() = assertEquals(1L, callSize(listOf(ion.newString("foo")))) - - @Test - fun singleElementBag() = assertEquals(1L, callSize(bagOf(ion.newString("foo")))) - - @Test - fun multiElementStruct() = assertEquals(2L, callSize(structOf(ion.newString("foo"), ion.newString("bar")))) - - @Test - fun multiElementList() = assertEquals(2L, callSize(listOf(ion.newString("foo"), ion.newString("bar")))) - - @Test - fun multiElementBag() = assertEquals(2L, callSize(bagOf(ion.newString("foo"), ion.newString("bar")))) -} \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/SubstringEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/SubstringEvaluationTest.kt deleted file mode 100644 index 37f2f0fe59..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/SubstringEvaluationTest.kt +++ /dev/null @@ -1,229 +0,0 @@ -package org.partiql.lang.eval.builtins - -import org.junit.Test -import org.partiql.lang.errors.ErrorCode -import org.partiql.lang.errors.Property -import org.partiql.lang.eval.EvaluatorTestBase -import org.partiql.lang.util.to - -class SubstringEvaluationTest : EvaluatorTestBase() { - private fun checkSubstringThrowingEvaluationException(input: String, expectErrorContextValues: Map = mapOf()) = - checkInputThrowingEvaluationException( - input = input, - errorCode = ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - expectErrorContextValues = mapOf( - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L, - Property.FUNCTION_NAME to "substring" - ) + expectErrorContextValues, - expectedPermissiveModeResult = "MISSING" - ) - - // Old syntax: SUBSTRING( FROM [FOR ]) - @Test fun substringOldSyntax_1() = assertEval("substring('abcdefghi' from 0)", "\"abcdefghi\"") - @Test fun substringOldSyntax_2() = assertEval("substring('abcdefghi' from 1)", "\"abcdefghi\"") - @Test fun substringOldSyntax_3() = assertEval("substring('abcdefghi' from -1)", "\"abcdefghi\"") - @Test fun substringOldSyntax_4() = assertEval("substring('abcdefghi' from 3)", "\"cdefghi\"") - @Test fun substringOldSyntax_5() = assertEval("substring('abcdefghi' from 3 for 20)", "\"cdefghi\"") - @Test fun substringOldSyntax_6() = assertEval("substring('1234567890' from 10)", "\"0\"") - @Test fun substringOldSyntax_7() = assertEval("substring('1234567890' from 11)", "\"\"") - @Test fun substringOldSyntax_8() = assertEval("substring('1234567890' from 10 for 10)", "\"0\"") - @Test fun substringOldSyntax_9() = assertEval("substring('1234567890' from 11 for 10)", "\"\"") - @Test fun substringOldSyntax_10() = assertEval("substring('abcdefghi' from 3 for 4)", "\"cdef\"") - @Test fun substringOldSyntax_11() = assertEval("substring('abcdefghi' from -1 for 4)", "\"ab\"") - @Test fun substringOldSyntax_12() = assertEval("substring('abcdefghi' from 1 for 1)", "\"a\"") - - @Test fun substringOldSyntaxUnicode_1() = assertEval("substring('😁😞😸😸' from 2 for 2)", "\"😞😸\"") - @Test fun substringOldSyntaxUnicode_2() = assertEval("substring('話家身圧費谷料村能計税金' from 3 for 5)", "\"身圧費谷料\"") - @Test fun substringOldSyntaxUnicode_3() = assertEval("substring('話家身圧費谷料村能計税金' from -3 for 6)", "\"話家\"") - - //Note: U+0832 is a "combining diacritical mark" https://en.wikipedia.org/wiki/Combining_character. - //Even though it is visually merged with the preceding letter when displayed, it still counts as a distinct codepoint. - @Test fun substringOldSyntaxUnicode_4() = assertEval("substring('abcde\u0832fgh' from 3 for 6)", "\"cde\u0832fg\"") - - @Test - fun substringOldSyntaxError_1() = - checkSubstringThrowingEvaluationException( - input = "substring(123456789 from 1 for 3)", - expectErrorContextValues = mapOf( - Property.EXPECTED_ARGUMENT_TYPES to "STRING", - Property.ARGUMENT_POSITION to 1, - Property.ACTUAL_ARGUMENT_TYPES to "INT" - ) - ) - @Test - fun substringOldSyntaxError_2() = - checkInputThrowingEvaluationException( - input = "substring('abcdefghi' from 1 for -1)", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_FUNC_CALL, - expectErrorContextValues = mapOf(), - expectedPermissiveModeResult = "MISSING") - @Test - fun substringOldSyntaxError_3() = - checkSubstringThrowingEvaluationException( - input = "substring('abcdefghi' from 1.0)", - expectErrorContextValues = mapOf( - Property.EXPECTED_ARGUMENT_TYPES to "INT", - Property.ARGUMENT_POSITION to 2, - Property.ACTUAL_ARGUMENT_TYPES to "DECIMAL" - ) - ) - - @Test - fun substringOldSyntaxError_4() = checkSubstringThrowingEvaluationException( - input = "substring('abcdefghi' from 1.0 for 1)", - expectErrorContextValues = mapOf( - Property.EXPECTED_ARGUMENT_TYPES to "INT", - Property.ARGUMENT_POSITION to 2, - Property.ACTUAL_ARGUMENT_TYPES to "DECIMAL" - ) - ) - @Test - fun substringOldSyntaxError_5() = checkSubstringThrowingEvaluationException( - input = "substring('abcdefghi' from 1 for 1.0)", - expectErrorContextValues = mapOf( - Property.EXPECTED_ARGUMENT_TYPES to "INT", - Property.ARGUMENT_POSITION to 3, - Property.ACTUAL_ARGUMENT_TYPES to "DECIMAL" - ) - ) - @Test - fun substringOldSyntaxError_6() = checkSubstringThrowingEvaluationException( - input = "substring('abcdefghi' from 1 for '1')", - expectErrorContextValues = mapOf( - Property.EXPECTED_ARGUMENT_TYPES to "INT", - Property.ARGUMENT_POSITION to 3, - Property.ACTUAL_ARGUMENT_TYPES to "STRING" - ) - ) - @Test - fun substringOldSyntaxError_7() = checkSubstringThrowingEvaluationException( - input = "substring('abcdefghi' from '1')", - expectErrorContextValues = mapOf( - Property.EXPECTED_ARGUMENT_TYPES to "INT", - Property.ARGUMENT_POSITION to 2, - Property.ACTUAL_ARGUMENT_TYPES to "STRING" - ) - ) - - @Test fun substringOldSyntaxNullArg_1() = assertEval("substring(null from 1)", "null") - @Test fun substringOldSyntaxNullArg_2() = assertEval("substring('abc' from null)", "null") - @Test fun substringOldSyntaxNullArg_3() = assertEval("substring(null from 1 for 1)", "null") - @Test fun substringOldSyntaxNullArg_4() = assertEval("substring('abc' from null for 1)", "null") - @Test fun substringOldSyntaxNullArg_5() = assertEval("substring('abc' from 1 for null)", "null") - - @Test fun substringOldSyntaxMissingArg_1() = assertEval("substring(missing from 1)", "null") - @Test fun substringOldSyntaxMissingArg_2() = assertEval("substring('abc' from missing)", "null") - @Test fun substringOldSyntaxMissingArg_3() = assertEval("substring(missing from 1 for 1)", "null") - @Test fun substringOldSyntaxMissingArg_4() = assertEval("substring('abc' from missing for 1)", "null") - @Test fun substringOldSyntaxMissingArg_5() = assertEval("substring('abc' from 1 for missing)", "null") - - @Test fun substringOldSyntaxEmpty_1() = assertEval("substring('' from -1)", "\"\"") - @Test fun substringOldSyntaxEmpty_2() = assertEval("substring('' from 0)", "\"\"") - @Test fun substringOldSyntaxEmpty_3() = assertEval("substring('' from 99)", "\"\"") - @Test fun substringOldSyntaxEmpty_4() = assertEval("substring('' from -1 for 999)", "\"\"") - @Test fun substringOldSyntaxEmpty_5() = assertEval("substring('' from 0 for 999)", "\"\"") - @Test fun substringOldSyntaxEmpty_6() = assertEval("substring('' from -4 for 1)", "\"\"") - @Test fun substringOldSyntaxBefore() = assertEval("substring('1' from -4 for 1)", "\"\"") - - //Same as above, with the new syntax: SUBSTRING(, [, ]) - @Test fun substringNewSyntax_1() = assertEval("substring('abcdefghi', 0)", "\"abcdefghi\"") - @Test fun substringNewSyntax_2() = assertEval("substring('abcdefghi', 1)", "\"abcdefghi\"") - @Test fun substringNewSyntax_3() = assertEval("substring('abcdefghi', -1)", "\"abcdefghi\"") - @Test fun substringNewSyntax_4() = assertEval("substring('abcdefghi', 3)", "\"cdefghi\"") - @Test fun substringNewSyntax_5() = assertEval("substring('abcdefghi', 3, 20)", "\"cdefghi\"") - @Test fun substringNewSyntax_6() = assertEval("substring('1234567890', 10)", "\"0\"") - @Test fun substringNewSyntax_7() = assertEval("substring('1234567890', 11)", "\"\"") - @Test fun substringNewSyntax_8() = assertEval("substring('1234567890', 10, 10)", "\"0\"") - @Test fun substringNewSyntax_9() = assertEval("substring('1234567890', 11, 10)", "\"\"") - @Test fun substringNewSyntax_10() = assertEval("substring('abcdefghi', 3, 4)", "\"cdef\"") - @Test fun substringNewSyntax_11() = assertEval("substring('abcdefghi', -1, 4)", "\"ab\"") - @Test fun substringNewSyntax_12() = assertEval("substring('abcdefghi', 1, 1)", "\"a\"") - - @Test fun substringNewSyntaxUnicode_1() = assertEval("substring('😁😞😸😸', 2, 2)", "\"😞😸\"") - @Test fun substringNewSyntaxUnicode_2() = assertEval("substring('話家身圧費谷料村能計税金', 3, 5)", "\"身圧費谷料\"") - @Test fun substringNewSyntaxUnicode_3() = assertEval("substring('話家身圧費谷料村能計税金', -3, 6)", "\"話家\"") - @Test fun substringNewSyntaxUnicode_4() = assertEval("substring('abcde\u0832fgh', 3, 6)", "\"cde\u0832fg\"") - - @Test - fun substringNewSyntaxError_1() = - checkSubstringThrowingEvaluationException( - input = "substring(123456789, 1, 3)", - expectErrorContextValues = mapOf( - Property.EXPECTED_ARGUMENT_TYPES to "STRING", - Property.ARGUMENT_POSITION to 1, - Property.ACTUAL_ARGUMENT_TYPES to "INT" - )) - @Test - fun substringNewSyntaxError_2() = - checkInputThrowingEvaluationException( - input = "substring('abcdefghi', 1, -1)", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_FUNC_CALL, - expectErrorContextValues = mapOf(), - expectedPermissiveModeResult = "MISSING") - @Test - fun substringNewSyntaxError_3() = - checkSubstringThrowingEvaluationException( - input = "substring('abcdefghi', 1.0)", - expectErrorContextValues = mapOf( - Property.EXPECTED_ARGUMENT_TYPES to "INT", - Property.ARGUMENT_POSITION to 2, - Property.ACTUAL_ARGUMENT_TYPES to "DECIMAL" - )) - @Test - fun substringNewSyntaxError_4() = - checkSubstringThrowingEvaluationException( - input = "substring('abcdefghi', 1.0, 1)", - expectErrorContextValues = mapOf( - Property.EXPECTED_ARGUMENT_TYPES to "INT", - Property.ARGUMENT_POSITION to 2, - Property.ACTUAL_ARGUMENT_TYPES to "DECIMAL" - )) - @Test - fun substringNewSyntaxError_5() = - checkSubstringThrowingEvaluationException( - input = "substring('abcdefghi', 1, 1.0)", - expectErrorContextValues = mapOf( - Property.EXPECTED_ARGUMENT_TYPES to "INT", - Property.ARGUMENT_POSITION to 3, - Property.ACTUAL_ARGUMENT_TYPES to "DECIMAL" - )) - @Test - fun substringNewSyntaxError_6() = checkSubstringThrowingEvaluationException( - input = "substring('abcdefghi',1,'1')", - expectErrorContextValues = mapOf( - Property.EXPECTED_ARGUMENT_TYPES to "INT", - Property.ARGUMENT_POSITION to 3, - Property.ACTUAL_ARGUMENT_TYPES to "STRING" - ) - ) - @Test - fun substringNewSyntaxError_7() = checkSubstringThrowingEvaluationException( - input = "substring('abcdefghi','1')", - expectErrorContextValues = mapOf( - Property.EXPECTED_ARGUMENT_TYPES to "INT", - Property.ARGUMENT_POSITION to 2, - Property.ACTUAL_ARGUMENT_TYPES to "STRING" - ) - ) - - @Test fun substringNewSyntaxNullArg_1() = assertEval("substring(null, 1)", "null") - @Test fun substringNewSyntaxNullArg_2() = assertEval("substring('abc', null)", "null") - @Test fun substringNewSyntaxNullArg_3() = assertEval("substring(null, 1, 1)", "null") - @Test fun substringNewSyntaxNullArg_4() = assertEval("substring('abc', null, 1)", "null") - @Test fun substringNewSyntaxNullArg_5() = assertEval("substring('abc', 1, null)", "null") - - @Test fun substringNewSyntaxMissingArg_1() = assertEval("substring(missing, 1)", "null") - @Test fun substringNewSyntaxMissingArg_2() = assertEval("substring('abc', missing)", "null") - @Test fun substringNewSyntaxMissingArg_3() = assertEval("substring(missing, 1, 1)", "null") - @Test fun substringNewSyntaxMissingArg_4() = assertEval("substring('abc', missing, 1)", "null") - @Test fun substringNewSyntaxMissingArg_5() = assertEval("substring('abc', 1, missing)", "null") - - @Test fun substringNewSyntaxEmpty_1() = assertEval("substring('' from -1)", "\"\"") - @Test fun substringNewSyntaxEmpty_2() = assertEval("substring('' from 0)", "\"\"") - @Test fun substringNewSyntaxEmpty_3() = assertEval("substring('' from 99)", "\"\"") - @Test fun substringNewSyntaxEmpty_4() = assertEval("substring('' from -1 for 999)", "\"\"") - @Test fun substringNewSyntaxEmpty_5() = assertEval("substring('' from 0 for 999)", "\"\"") - @Test fun substringNewSyntaxEmpty_6() = assertEval("substring('' from -4 for 1)", "\"\"") - @Test fun substringNewSyntaxBefore() = assertEval("substring('1' from -4 for 1)", "\"\"") -} diff --git a/lang/test/org/partiql/lang/eval/builtins/TimestampParserTest.kt b/lang/test/org/partiql/lang/eval/builtins/TimestampParserTest.kt index 21777aa9aa..aa9f34ba70 100644 --- a/lang/test/org/partiql/lang/eval/builtins/TimestampParserTest.kt +++ b/lang/test/org/partiql/lang/eval/builtins/TimestampParserTest.kt @@ -1,19 +1,17 @@ package org.partiql.lang.eval.builtins -import com.amazon.ion.Timestamp -import junitparams.JUnitParamsRunner -import junitparams.Parameters -import junitparams.naming.TestCaseName +import com.amazon.ion.* +import org.partiql.lang.errors.* +import org.partiql.lang.eval.* +import junitparams.* +import junitparams.naming.* +import org.junit.* import org.junit.Test -import org.junit.runner.RunWith -import org.partiql.lang.errors.ErrorCode -import org.partiql.lang.eval.EvaluationException -import java.lang.reflect.Type -import java.time.format.DateTimeParseException -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNull -import kotlin.test.fail +import org.junit.runner.* +import java.lang.reflect.* +import java.time.format.* +import java.time.temporal.* +import kotlin.test.* @RunWith(JUnitParamsRunner::class) class TimestampParserTest { diff --git a/lang/test/org/partiql/lang/eval/builtins/TimestampTemporalAccessorTests.kt b/lang/test/org/partiql/lang/eval/builtins/TimestampTemporalAccessorTests.kt index 806af3c624..35dab018af 100644 --- a/lang/test/org/partiql/lang/eval/builtins/TimestampTemporalAccessorTests.kt +++ b/lang/test/org/partiql/lang/eval/builtins/TimestampTemporalAccessorTests.kt @@ -1,5 +1,6 @@ package org.partiql.lang.eval.builtins + import com.amazon.ion.Timestamp import junitparams.JUnitParamsRunner import junitparams.Parameters @@ -10,7 +11,7 @@ import org.junit.runner.RunWith import java.time.DateTimeException import java.time.format.DateTimeFormatter import java.time.temporal.UnsupportedTemporalTypeException -import java.util.Random +import java.util.* import kotlin.test.assertEquals import kotlin.test.assertNull diff --git a/lang/test/org/partiql/lang/eval/builtins/ToStringExprFunctionTest.kt b/lang/test/org/partiql/lang/eval/builtins/ToStringExprFunctionTest.kt deleted file mode 100644 index 90fc77c4ef..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/ToStringExprFunctionTest.kt +++ /dev/null @@ -1,70 +0,0 @@ -package org.partiql.lang.eval.builtins - -import org.junit.Test -import org.partiql.lang.errors.ErrorCode -import org.partiql.lang.errors.Property -import org.partiql.lang.eval.EvaluatorTestBase - -class ToStringExprFunctionTest : EvaluatorTestBase() { - // Note that the amount of testing here is a bit on the light side because most of the testing for the formatting - // functionality behind `to_string` is in TimestampTemporalAccessorTests. - @Test fun to_string1() = assertEval("to_string(`0500-03-09`, 'MM/dd/yyyy')", "\"03/09/0500\"") - @Test fun to_string2() = assertEval("to_string(`0500-03-09`, 'M/d/y')", "\"3/9/500\"") - @Test fun to_string3() = assertEval("to_string(`0001-03-09`, 'MM/dd/yyyy')", "\"03/09/0001\"") - @Test fun to_string4() = assertEval("to_string(`0001-03-09`, 'M/d/y')", "\"3/9/1\"") - @Test fun to_string5() = assertEval("to_string(`9999-03-09`, 'MM/dd/yyyy')", "\"03/09/9999\"") - @Test fun to_string6() = assertEval("to_string(`9999-03-09`, 'M/d/y')", "\"3/9/9999\"") - @Test fun to_string7() = assertEval("to_string(`0001-03-09`, 'y')", "\"1\"") - @Test fun to_string8() = assertEval("to_string(`9999-03-09`, null)", "null") - @Test fun to_string9() = assertEval("to_string(null, 'M/d/y')", "null") - @Test fun to_string10() = assertEval("to_string(`9999-03-09`, missing)", "null") - @Test fun to_string11() = assertEval("to_string(missing, 'M/d/y')", "null") - - @Test fun to_string_common_1() = assertEval("to_string(`1969-07-20T20:18Z`, 'MMMM d, y')", "\"July 20, 1969\"") - @Test fun to_string_common_2() = assertEval("to_string(`1969-07-20T20:18Z`, 'MMM d, yyyy')", "\"Jul 20, 1969\"") - @Test fun to_string_common_3() = assertEval("to_string(`1969-07-20T20:18Z`, 'M-d-yy')", "\"7-20-69\"") - @Test fun to_string_common_4() = assertEval("to_string(`1969-07-20T20:18Z`, 'MM-d-y')", "\"07-20-1969\"") - @Test fun to_string_common_5() = assertEval("to_string(`1969-07-20T20:18Z`, 'MMMM d, y h:m a')", "\"July 20, 1969 8:18 PM\"") - @Test fun to_string_common_6() = assertEval("to_string(`1969-07-20T20:18Z`, 'y-MM-dd''T''H:m:ssX')", "\"1969-07-20T20:18:00Z\"") - @Test fun to_string_common_7() = assertEval("to_string(`1969-07-20T20:18+08:00`, 'y-MM-dd''T''H:m:ssX')", "\"1969-07-20T20:18:00+08\"") - @Test fun to_string_common_8() = assertEval("to_string(`1969-07-20T20:18+08:00`, 'y-MM-dd''T''H:m:ssXXXX')", "\"1969-07-20T20:18:00+0800\"") - @Test fun to_string_common_9() = assertEval("to_string(`1969-07-20T20:18+08:00`, 'y-MM-dd''T''H:m:ssXXXXX')", "\"1969-07-20T20:18:00+08:00\"") - - - @Test - fun to_string_invalid_symbol1() { - checkInputThrowingEvaluationException( - "to_string(`2017-01-01`, 'b')", //Symbol 'b' is unknown to Java's DateTimeFormatter - ErrorCode.EVALUATOR_INVALID_TIMESTAMP_FORMAT_PATTERN, - mapOf(Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L, - Property.TIMESTAMP_FORMAT_PATTERN to "b"), - expectedPermissiveModeResult = "MISSING") - } - - @Test - fun to_string_invalid_symbol2() { - checkInputThrowingEvaluationException( - //Symbol 'z' is known to Java's DateTimeFormatter but is not handled by TimestampTemporalAccessor - "to_string(`2017-01-01`, 'Y')", - ErrorCode.EVALUATOR_INVALID_TIMESTAMP_FORMAT_PATTERN, - mapOf(Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L, - Property.TIMESTAMP_FORMAT_PATTERN to "Y"), - expectedPermissiveModeResult = "MISSING") - } - - @Test - fun to_string_invalid_symbol3() { - checkInputThrowingEvaluationException( - //Symbol 'VV' is known to Java's DateTimeFormatter but is not handled by TimestampTemporalAccessor - //*and* causes a different exception to be thrown by DateTimeFormatter.format() than 'z' - "to_string(`2017-01-01`, 'VV')", - ErrorCode.EVALUATOR_INVALID_TIMESTAMP_FORMAT_PATTERN, - mapOf(Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L, - Property.TIMESTAMP_FORMAT_PATTERN to "VV"), - expectedPermissiveModeResult = "MISSING") - } - -} \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/ToTimestampExprFunctionTest.kt b/lang/test/org/partiql/lang/eval/builtins/ToTimestampExprFunctionTest.kt deleted file mode 100644 index 94170fd8e8..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/ToTimestampExprFunctionTest.kt +++ /dev/null @@ -1,107 +0,0 @@ -package org.partiql.lang.eval.builtins - -import org.junit.Test -import org.partiql.lang.errors.ErrorCode -import org.partiql.lang.errors.Property -import org.partiql.lang.eval.EvaluatorTestBase -import org.partiql.lang.util.to - -/** - * Note that tests here on on the light side because most of the testing is done in [TimestampParserTest]. - */ -class ToTimestampExprFunctionTest : EvaluatorTestBase() { - - @Test fun to_timestamp_common_1() = assertEval("to_timestamp('July 20, 1969', 'MMMM d, y')", "1969-07-20T") - @Test fun to_timestamp_common_2() = assertEval("to_timestamp('Jul 20, 1969', 'MMM d, yyyy')", "1969-07-20T") - @Test fun to_timestamp_common_4() = assertEval("to_timestamp('1969-07-20T20:18Z', 'yyyy-MM-dd''T''HH:mmX')", "1969-07-20T20:18Z") - @Test fun to_timestamp_common_5() = assertEval("to_timestamp('July 20, 1969 8:18 PM', 'MMMM d, y h:m a')", "1969-07-20T20:18-00:00") - @Test fun to_timestamp_common_6() = assertEval("to_timestamp('1969-07-20T20:18:00Z', 'yyyy-MM-dd''T''H:m:ssX')", "1969-07-20T20:18:00Z") - @Test fun to_timestamp_common_7() = assertEval("to_timestamp('1969-07-20T20:18:01+08', 'yyyy-MM-dd''T''H:m:ssX')", "1969-07-20T20:18:01+08:00") - @Test fun to_timestamp_common_8() = assertEval("to_timestamp('1969-07-20T20:18:02+0800', 'yyyy-MM-dd''T''H:m:ssXXXX')", "1969-07-20T20:18:02+08:00") - @Test fun to_timestamp_common_9() = assertEval("to_timestamp('1969-07-20T20:18:03+08:00', 'yyyy-MM-dd''T''H:m:ssXXXXX')", "1969-07-20T20:18:03+08:00") - - @Test fun to_timestamp_parse_timestamp_1() = assertEval("to_timestamp('1969-07-20T20:18:00Z')", "1969-07-20T20:18:00Z") - @Test fun to_timestamp_parse_timestamp_2() = assertEval("to_timestamp('1969-07-20T20:18:03+08:00')", "1969-07-20T20:18:03+08:00") - - @Test fun to_timestamp_null_arg_1() = assertEval("to_timestamp(null)", "null") - @Test fun to_timestamp_null_arg_2() = assertEval("to_timestamp(null, 'M-d-yyyy')", "null") - @Test fun to_timestamp_null_arg_3() = assertEval("to_timestamp('07-20-1969', null)", "null") - @Test fun to_timestamp_null_arg_4() = assertEval("to_timestamp(null, null)", "null") - - @Test fun to_timestamp_missing_arg_1() = assertEval("to_timestamp(missing)", "null") - @Test fun to_timestamp_missing_arg_2() = assertEval("to_timestamp(missing, 'M-d-yyyy')", "null") - @Test fun to_timestamp_missing_arg_3() = assertEval("to_timestamp('07-20-1969', missing)", "null") - @Test fun to_timestamp_missing_arg_4() = assertEval("to_timestamp(null, null)", "null") - - @Test - fun to_timestamp_too_few_args() { - checkInputThrowingEvaluationException( - "to_timestamp()", - ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNC_CALL, - mapOf( - Property.FUNCTION_NAME to "to_timestamp", - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L, - Property.ACTUAL_ARITY to 0, - Property.EXPECTED_ARITY_MIN to 1, - Property.EXPECTED_ARITY_MAX to 2)) - } - - @Test - fun to_timestamp_too_many_args() { - checkInputThrowingEvaluationException( - "to_timestamp('one', 'two', 'three')", - ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNC_CALL, - mapOf( - Property.FUNCTION_NAME to "to_timestamp", - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L, - Property.ACTUAL_ARITY to 3, - Property.EXPECTED_ARITY_MIN to 1, - Property.EXPECTED_ARITY_MAX to 2)) - } - - @Test - fun to_timestamp_invalid_ion_timestamp() { - checkInputThrowingEvaluationException( - "to_timestamp('not a valid timestamp')", - ErrorCode.EVALUATOR_ION_TIMESTAMP_PARSE_FAILURE, - mapOf(Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L), - expectedPermissiveModeResult = "MISSING") - } - - @Test - fun to_timestamp_empty_format_pattern() { - checkInputThrowingEvaluationException( - "to_timestamp('doesnt matter', '')", - ErrorCode.EVALUATOR_INCOMPLETE_TIMESTAMP_FORMAT_PATTERN, - mapOf(Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L, - Property.TIMESTAMP_FORMAT_PATTERN to "", - Property.TIMESTAMP_FORMAT_PATTERN_FIELDS to "YEAR"), - expectedPermissiveModeResult = "MISSING") - } - - @Test - fun to_timestamp_invalid_format_pattern() { - checkInputThrowingEvaluationException( - "to_timestamp('doesnt matter', 'asdfasdfasdf')", - ErrorCode.EVALUATOR_INVALID_TIMESTAMP_FORMAT_PATTERN_TOKEN, - mapOf(Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L, - Property.TIMESTAMP_FORMAT_PATTERN to "asdfasdfasdf"), - expectedPermissiveModeResult = "MISSING") - } - - @Test - fun to_timestamp_invalid_timestamp() { - checkInputThrowingEvaluationException( - "to_timestamp('asdf', 'yyyy')", - ErrorCode.EVALUATOR_CUSTOM_TIMESTAMP_PARSE_FAILURE, - mapOf(Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L, - Property.TIMESTAMP_FORMAT_PATTERN to "yyyy"), - expectedPermissiveModeResult = "MISSING") - } -} \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/TrimEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/TrimEvaluationTest.kt deleted file mode 100644 index 56d1e8bf69..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/TrimEvaluationTest.kt +++ /dev/null @@ -1,182 +0,0 @@ -package org.partiql.lang.eval.builtins - -import org.junit.Test -import org.partiql.lang.errors.ErrorCode -import org.partiql.lang.errors.Property -import org.partiql.lang.eval.EvaluatorTestBase - -class TrimEvaluationTest : EvaluatorTestBase() { - - @Test - fun trimDefault1() = assertEval("trim(' string ')", "\"string\"") - - @Test - fun trimDefault2() = assertEval("trim(' string')", "\"string\"") - - @Test - fun trimDefault3() = assertEval("trim('string ')", "\"string\"") - - @Test - fun trimOtherWhitespaces() = assertEval("trim('\tstring\t')", "\"\tstring\t\"") - - @Test - fun trimSimpleCaseWithFrom1() = assertEval("trim(from ' string ')", "\"string\"") - - @Test - fun trimSimpleCaseWithFrom2() = assertEval("trim(from ' string')", "\"string\"") - - @Test - fun trimSimpleCaseWithFrom3() = assertEval("trim(from 'string ')", "\"string\"") - - @Test - fun trimBoth1() = assertEval("trim(both from ' string ')", "\"string\"") - - @Test - fun trimBoth2() = assertEval("trim(both from ' string')", "\"string\"") - - @Test - fun trimBoth3() = assertEval("trim(both from 'string ')", "\"string\"") - - @Test - fun trimLeading1() = assertEval("trim(leading from ' string ')", "\"string \"") - - @Test - fun trimLeading2() = assertEval("trim(leading from ' string')", "\"string\"") - - @Test - fun trimLeading3() = assertEval("trim(leading from 'string ')", "\"string \"") - - @Test - fun trimTrailing1() = assertEval("trim(trailing from ' string ')", "\" string\"") - - @Test - fun trimTrailing2() = assertEval("trim(trailing from ' string')", "\" string\"") - - @Test - fun trimTrailing3() = assertEval("trim(trailing from 'string ')", "\"string\"") - - @Test - fun trimMultiple1() = assertEval("trim(both ' -=' from '- =string =- ')", "\"string\"") - - @Test - fun trimMultiple2() = assertEval("trim(both ' -=' from '--=== -= -= -= string')", "\"string\"") - - @Test - fun trimMultiple3() = assertEval("trim(both ' -=' from 'string ==- = -=- - ---------- ')", "\"string\"") - - @Test - fun trimMultiple4() = assertEval("trim(both ' ' from ' ')", "\"\"") - - @Test - fun trimMultiple5() = assertEval("trim(leading ' ' from ' ')", "\"\"") - - @Test - fun trimMultiple6() = assertEval("trim(trailing ' ' from ' ')", "\"\"") - - @Test - fun trimEmoji1() = assertEval("trim(both '💩' from '💩💩💩💩💩💩💩💩💩💩😁😞😸😸💩💩💩💩💩💩💩💩💩💩💩💩💩💩')", - "\"😁😞😸😸\"") - - @Test - fun trimEmoji2() = assertEval("trim(' 😁😞😸😸 ')", "\"😁😞😸😸\"") - - @Test - fun trimChinese() = assertEval("trim(both '話 ' from '話話 話話話話話話費谷料村能話話話話 話話話話 ')", "\"費谷料村能\"") - - @Test - fun trimExpression() = assertEval("trim(' a'||'b ')", "\"ab\"") - - @Test - fun trimToRemoveExpression() = assertEval(""" - SELECT - trim(both el from ' 1ab1 ') as trimmed - FROM - <<' 1'>> AS el - """, "[{trimmed:\"ab\"}]") - - @Test - fun trimWrongToRemoveType() = - checkInputThrowingEvaluationException( - input = "trim(trailing 1 from '')", - errorCode = ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - expectErrorContextValues = mapOf( - Property.FUNCTION_NAME to "trim", - Property.ARGUMENT_POSITION to 2, - Property.EXPECTED_ARGUMENT_TYPES to "STRING", - Property.ACTUAL_ARGUMENT_TYPES to "INT", - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L - ), - expectedPermissiveModeResult = "MISSING" - ) - - @Test - fun trimWrongStringType() = - checkInputThrowingEvaluationException( - input = "trim(true)", - errorCode = ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - expectErrorContextValues = mapOf( - Property.FUNCTION_NAME to "trim", - Property.ARGUMENT_POSITION to 1, - Property.EXPECTED_ARGUMENT_TYPES to "STRING, or SYMBOL", - Property.ACTUAL_ARGUMENT_TYPES to "BOOL", - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L), - expectedPermissiveModeResult = "MISSING") - - - @Test - fun trimWrongStringType2() = - checkInputThrowingEvaluationException( - input = "trim(trailing from true)", - errorCode = ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - expectErrorContextValues = mapOf( - Property.FUNCTION_NAME to "trim", - Property.ARGUMENT_POSITION to 2, - Property.EXPECTED_ARGUMENT_TYPES to "STRING", - Property.ACTUAL_ARGUMENT_TYPES to "BOOL", - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L - ), - expectedPermissiveModeResult = "MISSING" - ) - - @Test - fun trimDefaultSpecification1() = assertEval("trim('12' from '1212b1212')", "\"b\"") - - @Test - fun trimDefaultSpecification2() = assertEval("trim('12' from '1212b')", "\"b\"") - - @Test - fun trimDefaultSpecification3() = assertEval("trim('12' from 'b1212')", "\"b\"") - - @Test - fun trimNull01() = assertEval("trim(both null from '')", "null") - - @Test - fun trimNull02() = assertEval("trim(both '' from null)", "null") - - @Test - fun trimNull03() = assertEval("trim(null from '')", "null") - - @Test - fun trimNull04() = assertEval("trim('' from null)", "null") - - @Test - fun trimNull05() = assertEval("trim(null)", "null") - - @Test - fun trimMissing01() = assertEval("trim(both missing from '')", "null") - - @Test - fun trimMissing02() = assertEval("trim(both '' from missing)", "null") - - @Test - fun trimMissing03() = assertEval("trim(missing from '')", "null") - - @Test - fun trimMissing04() = assertEval("trim('' from missing)", "null") - - @Test - fun trimMissing05() = assertEval("trim(missing)", "null") -} \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/TrimExprFunctionTest.kt b/lang/test/org/partiql/lang/eval/builtins/TrimExprFunctionTest.kt deleted file mode 100644 index e61ca8d90c..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/TrimExprFunctionTest.kt +++ /dev/null @@ -1,65 +0,0 @@ -package org.partiql.lang.eval.builtins - -import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.Test -import org.partiql.lang.TestBase -import org.partiql.lang.eval.Environment -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.RequiredArgs -import org.partiql.lang.eval.RequiredWithVariadic -import org.partiql.lang.eval.call -import org.partiql.lang.eval.stringValue - -/** - * Tests for [TrimExprFunction], most tests are done e2e through the evaluator, see [TrimEvaluationTest] - */ -class TrimExprFunctionTest : TestBase() { - private val env = Environment.standard() - - private val subject = TrimExprFunction(valueFactory) - - private fun callTrim(vararg args: Any): String { - val args = args.map { anyToExprValue(it) }.toList() - val required = args.take(1) - val rest = args.drop(1) - return when (rest.size) { - 0 -> subject.call(env, RequiredArgs(required)) - else -> subject.call(env, RequiredWithVariadic(required, rest)) - }.stringValue() - } - - @Test - fun oneArgument() = assertEquals("string", callTrim(" string ")) - - @Test - fun twoArguments() = assertEquals("string ", callTrim("leading", " string ")) - - @Test - fun twoArguments2() = assertEquals("string", callTrim("12", "1212string1212")) - - @Test - fun twoArgumentsBoth() = assertEquals("", callTrim("both", " ")) - - @Test - fun twoArgumentsLeading() = assertEquals("", callTrim("leading", " ")) - - @Test - fun twoArgumentsTrailing() = assertEquals("", callTrim("trailing", " ")) - - @Test - fun threeArguments() = assertEquals("string", callTrim("both", "a", "aaaaaaaaaastringaaaaaaa")) - - @Test - fun wrongSpecificationType() { - assertThatThrownBy { assertEquals("string", callTrim(1, "string")) } - .isExactlyInstanceOf(EvaluationException::class.java) - .hasMessageContaining("with two arguments trim's first argument must be either the specification or a 'to remove' string") - } - - @Test - fun wrongToRemoveType() { - assertThatThrownBy { assertEquals("string", callTrim("both", 1, "string")) } - .isExactlyInstanceOf(EvaluationException::class.java) - .hasMessageContaining("Expected text: 1") - } -} \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/UnixTimestampFunctionTest.kt b/lang/test/org/partiql/lang/eval/builtins/UnixTimestampFunctionTest.kt deleted file mode 100644 index bf02829abe..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/UnixTimestampFunctionTest.kt +++ /dev/null @@ -1,95 +0,0 @@ -package org.partiql.lang.eval.builtins - -import com.amazon.ion.Timestamp -import org.junit.Test -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ArgumentsSource -import org.partiql.lang.errors.ErrorCode -import org.partiql.lang.errors.Property -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.EvaluatorTestBase -import org.partiql.lang.util.ArgumentsProviderBase -import org.partiql.lang.util.to - -data class UnixTimestampNoArgTestCase(val numMillis: Long, val expected: String) -data class UnixTimestampOneArgTestCase(val timestamp: String, val expected: String) - -class UnixTimestampFunctionTest : EvaluatorTestBase() { - private val testTimestamp = "`2007-02-23T12:14Z`" - - @Test - fun `unix_timestamp 2 args`() = - checkInputThrowingEvaluationException( - "unix_timestamp($testTimestamp, $testTimestamp)", - ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNC_CALL, - mapOf(Property.LINE_NUMBER to 1L, - Property.FUNCTION_NAME to "unix_timestamp", - Property.COLUMN_NUMBER to 1L, - Property.ACTUAL_ARITY to 2, - Property.EXPECTED_ARITY_MIN to 0, - Property.EXPECTED_ARITY_MAX to 1)) - - @Test - fun `unix_timestamp 3 args`() = - checkInputThrowingEvaluationException( - "unix_timestamp($testTimestamp, $testTimestamp, $testTimestamp)", - ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNC_CALL, - mapOf(Property.LINE_NUMBER to 1L, - Property.FUNCTION_NAME to "unix_timestamp", - Property.COLUMN_NUMBER to 1L, - Property.ACTUAL_ARITY to 3, - Property.EXPECTED_ARITY_MIN to 0, - Property.EXPECTED_ARITY_MAX to 1)) - - - class NoArgsCases : ArgumentsProviderBase() { - override fun getParameters(): List = listOf( - // unix_timestamp no args, now = 0 - UnixTimestampNoArgTestCase(numMillis = 0, expected = "0"), - // nix_timestamp no args, now = 1ms - UnixTimestampNoArgTestCase(numMillis = 1, expected = "0"), - // unix_timestamp no args, now = 999ms - UnixTimestampNoArgTestCase(numMillis = 999, expected = "0"), - // unix_timestamp no args, now = 1s - UnixTimestampNoArgTestCase(numMillis = 1000, expected = "1"), - // unix_timestamp no args, now = 1001ms - UnixTimestampNoArgTestCase(numMillis = 1001, expected = "1") - ) - } - @ParameterizedTest - @ArgumentsSource(NoArgsCases::class) - fun runNoArgTests(tc: UnixTimestampNoArgTestCase) = - assertEval( - "unix_timestamp()", - tc.expected, - session = EvaluationSession.build { now(Timestamp.forMillis(tc.numMillis, 0)) }) - - - class OneArgCases : ArgumentsProviderBase() { - private val epoch2020 = "1577836800" - private val epoch2020Decimal = "1577836800." - - override fun getParameters(): List = listOf( - // time before the last epoch - UnixTimestampOneArgTestCase("unix_timestamp(`1969T`)", "-31536000"), - UnixTimestampOneArgTestCase("unix_timestamp(`1969-12-31T23:59:59.999Z`)", "-0.001"), - // exactly the last epoch - UnixTimestampOneArgTestCase("unix_timestamp(`1970T`)", "0"), - UnixTimestampOneArgTestCase("unix_timestamp(`1970-01-01T00:00:00.000Z`)", "0."), - // whole number unix epoch - UnixTimestampOneArgTestCase("unix_timestamp(`2020T`)", epoch2020), - UnixTimestampOneArgTestCase("unix_timestamp(`2020-01T`)", epoch2020), - UnixTimestampOneArgTestCase("unix_timestamp(`2020-01-01T`)", epoch2020), - UnixTimestampOneArgTestCase("unix_timestamp(`2020-01-01T00:00Z`)", epoch2020), - UnixTimestampOneArgTestCase("unix_timestamp(`2020-01-01T00:00:00Z`)", epoch2020), - // decimal unix epoch - UnixTimestampOneArgTestCase("unix_timestamp(`2020-01-01T00:00:00.0Z`)", epoch2020Decimal), - UnixTimestampOneArgTestCase("unix_timestamp(`2020-01-01T00:00:00.00Z`)", epoch2020Decimal), - UnixTimestampOneArgTestCase("unix_timestamp(`2020-01-01T00:00:00.000Z`)", epoch2020Decimal), - UnixTimestampOneArgTestCase("unix_timestamp(`2020-01-01T00:00:00.100Z`)", "1577836800.1") - ) - } - @ParameterizedTest - @ArgumentsSource(OneArgCases::class) - fun runOneArgTests(tc: UnixTimestampOneArgTestCase) = assertEval(tc.timestamp, tc.expected) -} diff --git a/lang/test/org/partiql/lang/eval/builtins/UpperEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/UpperEvaluationTest.kt deleted file mode 100644 index ce56f49939..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/UpperEvaluationTest.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.partiql.lang.eval.builtins - -import org.junit.Test -import org.partiql.lang.eval.EvaluatorTestBase - -class UpperEvaluationTest : EvaluatorTestBase() { - @Test fun upper_0() = assertEval("upper('')", "\"\"") - @Test fun upper_1() = assertEval("upper('abcdef')", "\"ABCDEF\"") - @Test fun upper_2() = assertEval("upper('ABCDEF')", "\"ABCDEF\"") - @Test fun upper_3() = assertEval("upper(null)", "null") - @Test fun upper_4() = assertEval("upper(missing)", "null") - @Test fun upper_5() = assertEval("upper('123\$%(*&')", "\"123\$%(*&\"") - @Test fun upper_6() = assertEval("upper('ȴȵ💩z💋')", "\"ȴȵ💩Z💋\"") - @Test fun upper_7() = assertEval("upper('話家身圧費谷料村能計税金')", "\"話家身圧費谷料村能計税金\"") -} diff --git a/lang/test/org/partiql/lang/eval/builtins/UtcNowEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/UtcNowEvaluationTest.kt deleted file mode 100644 index 3a9eb3c302..0000000000 --- a/lang/test/org/partiql/lang/eval/builtins/UtcNowEvaluationTest.kt +++ /dev/null @@ -1,88 +0,0 @@ -package org.partiql.lang.eval.builtins - -import com.amazon.ion.Timestamp -import org.junit.Test -import org.partiql.lang.errors.ErrorCode -import org.partiql.lang.errors.Property -import org.partiql.lang.eval.Bindings -import org.partiql.lang.eval.Environment -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.EvaluatorTestBase -import org.partiql.lang.eval.RequiredArgs -import org.partiql.lang.eval.call -import org.partiql.lang.eval.timestampValue -import org.partiql.lang.util.timestampValue -import org.partiql.lang.util.to - -class UtcNowEvaluationTest : EvaluatorTestBase() { - private val env = Environment.standard() - - @Test fun utcnow1() = assertEval("utcnow()", - "1970-01-01T00:00:00.000Z", - session = EvaluationSession.build { now(Timestamp.forMillis(0, 0)) }) - - @Test fun utcnow2() = assertEval("utcnow()", - "1970-01-01T00:00:01.000Z", - session = EvaluationSession.build { now(Timestamp.forMillis(1_000, 0)) }) - - @Test fun utcnowWithDifferentOffset() { - val fiveMinutesInMillis = 5L * 60 * 1_000 - val now = Timestamp.forMillis(fiveMinutesInMillis, 1) // 1970-01-01T00:06:01.000+00:01 - val session = EvaluationSession.build { now(now) } - - assertEval("utcnow()", "1970-01-01T00:05:00.000Z", session = session) - } - - @Test - fun utcNowDefaultSession() { - val actual = createUtcNow(valueFactory).call(env, RequiredArgs(listOf())).ionValue.timestampValue() - - assertEquals("utcNow is not the session now", env.session.now, actual) - assertEquals("utcNow is not at the zero offset", 0, actual.localOffset) - } - - @Test - fun utcNowPassedInSession() { - val now = Timestamp.forMillis(10, 0) - - val env = Environment(locals = Bindings.empty(), - session = EvaluationSession.build { now(now) }) - - val actual = createUtcNow(valueFactory).call(env, RequiredArgs(listOf())).ionValue.timestampValue() - - assertEquals(now, actual) - assertEquals(10, actual.millis) - assertEquals("utcNow is not at the zero offset", 0, actual.localOffset) - } - - @Test - fun utcNowPassedInSessionWithNonUtcOffset() { - val utcMillis = 10L * 24 * 60 * 60 * 1_000 // 1970-01-10T00:00:00.000Z - val localOffset = 5 - - val now = Timestamp.forMillis(utcMillis, localOffset) // 1970-01-10T00:05:00.000+00:05 - - val env = Environment(locals = Bindings.empty(), - session = EvaluationSession.build { now(now) }) - - val actual = createUtcNow(valueFactory).call(env, RequiredArgs(listOf())).timestampValue() - - assertEquals(utcMillis, actual.millis) - assertEquals("utcNow is not at the zero offset", 0, actual.localOffset) - } - - @Test - fun utcNowPassedInArguments() = - checkInputThrowingEvaluationException( - input = "utcnow('')", - errorCode = ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNC_CALL, - expectErrorContextValues = mapOf( - Property.LINE_NUMBER to 1L, - Property.COLUMN_NUMBER to 1L, - Property.FUNCTION_NAME to "utcnow", - Property.EXPECTED_ARITY_MIN to 0, - Property.EXPECTED_ARITY_MAX to 0, - Property.ACTUAL_ARITY to 1 - ) - ) -} diff --git a/lang/test/org/partiql/lang/eval/builtins/functions/CharLengthEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/functions/CharLengthEvaluationTest.kt new file mode 100644 index 0000000000..ad5ddca77f --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/functions/CharLengthEvaluationTest.kt @@ -0,0 +1,55 @@ +package org.partiql.lang.eval.builtins.functions + +import org.junit.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource +import org.partiql.lang.eval.EvaluatorTestBase +import org.partiql.lang.eval.builtins.Argument +import org.partiql.lang.eval.builtins.ExprFunctionTestCase +import org.partiql.lang.eval.builtins.checkInvalidArgType +import org.partiql.lang.eval.builtins.checkInvalidArity + +import org.partiql.lang.types.StaticType +import org.partiql.lang.util.ArgumentsProviderBase + +class CharLengthEvaluationTest : EvaluatorTestBase() { + // Pass test cases + @ParameterizedTest + @ArgumentsSource(PassCases::class) + fun charLengthPassTests(testCase: ExprFunctionTestCase) = assertEval(testCase.source, testCase.expected) + + class PassCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + ExprFunctionTestCase("char_length('')", "0"), + ExprFunctionTestCase("char_length('a')", "1"), + ExprFunctionTestCase("char_length(`a`)", "1"), + ExprFunctionTestCase("char_length(`'a'`)", "1"), + ExprFunctionTestCase("char_length(`\"a\"`)", "1"), + ExprFunctionTestCase("char_length('ab')", "2"), + ExprFunctionTestCase("char_length('abcdefghijklmnopqrstuvwxyz')", "26"), + ExprFunctionTestCase("char_length(null)", "null"), + ExprFunctionTestCase("char_length(missing)", "null"), + ExprFunctionTestCase("char_length('ȴȵ💩💋')", "4"), + ExprFunctionTestCase("char_length('😁😞😸😸')", "4"), + ExprFunctionTestCase("char_length('話家身圧費谷料村能計税金')", "12"), + ExprFunctionTestCase("char_length('eࠫ')", "2"), //This is a unicode "combining character" which is actually 2 codepoints + ) + } + + // Error test cases: Invalid argument type + @Test + fun charLengthInvalidArgTypeTest() = checkInvalidArgType( + funcName = "char_length", + args = listOf( + Argument(1, StaticType.unionOf(StaticType.STRING, StaticType.SYMBOL), ")") + ) + ) + + // Error test cases: Invalid arity + @Test + fun charLengthInvalidArityTest() = checkInvalidArity( + funcName = "char_length", + maxArity = 1, + minArity = 1 + ) +} diff --git a/lang/test/org/partiql/lang/eval/builtins/functions/CharacterLengthEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/functions/CharacterLengthEvaluationTest.kt new file mode 100644 index 0000000000..d50b0aa1ec --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/functions/CharacterLengthEvaluationTest.kt @@ -0,0 +1,55 @@ +package org.partiql.lang.eval.builtins.functions + +import org.junit.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource +import org.partiql.lang.eval.EvaluatorTestBase +import org.partiql.lang.eval.builtins.Argument +import org.partiql.lang.eval.builtins.ExprFunctionTestCase +import org.partiql.lang.eval.builtins.checkInvalidArgType +import org.partiql.lang.eval.builtins.checkInvalidArity +import org.partiql.lang.types.StaticType +import org.partiql.lang.util.ArgumentsProviderBase + +// Everything here for CharacterLength() is the same as CharLength(), since they are the same functions. +class CharacterLengthEvaluationTest : EvaluatorTestBase() { + // Pass test cases + @ParameterizedTest + @ArgumentsSource(PassCases::class) + fun characterLengthPassTests(testCase: ExprFunctionTestCase) = assertEval(testCase.source, testCase.expected) + + class PassCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + ExprFunctionTestCase("character_length('')", "0"), + ExprFunctionTestCase("character_length('a')", "1"), + ExprFunctionTestCase("character_length(`a`)", "1"), + ExprFunctionTestCase("character_length(`'a'`)", "1"), + ExprFunctionTestCase("character_length(`\"a\"`)", "1"), + ExprFunctionTestCase("character_length('ab')", "2"), + ExprFunctionTestCase("character_length('abcdefghijklmnopqrstuvwxyz')", "26"), + ExprFunctionTestCase("character_length(null)", "null"), + ExprFunctionTestCase("character_length(missing)", "null"), + ExprFunctionTestCase("character_length('ȴȵ💩💋')", "4"), + ExprFunctionTestCase("character_length('😁😞😸😸')", "4"), + ExprFunctionTestCase("character_length('話家身圧費谷料村能計税金')", "12"), + ExprFunctionTestCase("character_length('eࠫ')", "2") //This is a unicode "combining character" which is actually 2 codepoints + ) + } + + // Error test cases: Invalid argument type + @Test + fun characterLengthInvalidArgTypeTest() = checkInvalidArgType( + funcName = "character_length", + args = listOf( + Argument(1, StaticType.unionOf(StaticType.STRING, StaticType.SYMBOL), ")") + ) + ) + + // Error test cases: Invalid arity + @Test + fun characterLengthInvalidArityTest() = checkInvalidArity( + funcName = "character_length", + maxArity = 1, + minArity = 1 + ) +} diff --git a/lang/test/org/partiql/lang/eval/builtins/functions/ConcatEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/functions/ConcatEvaluationTest.kt new file mode 100644 index 0000000000..6c110b9916 --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/functions/ConcatEvaluationTest.kt @@ -0,0 +1,146 @@ +package org.partiql.lang.eval.builtins.functions + +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource +import org.partiql.lang.errors.ErrorCode +import org.partiql.lang.errors.Property +import org.partiql.lang.eval.EvaluatorTestBase +import org.partiql.lang.eval.builtins.ExprFunctionTestCase +import org.partiql.lang.util.ArgumentsProviderBase + +class ConcatEvaluationTest : EvaluatorTestBase() { + // Pass test cases + @ParameterizedTest + @ArgumentsSource(ConcatPassCases::class) + fun runPassTests(testCase: ExprFunctionTestCase) = assertEval(testCase.source, testCase.expected) + + class ConcatPassCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + // Following are all the valid types: + // 1. String: 'a' + // 2. Ion string: `"a"` + // 3. Ion symbol: `a` or `'a'` + // 4. null or missing + + // 1st arg: String + ExprFunctionTestCase("'a' || 'b'", "\"ab\""), // 2nd arg: String + ExprFunctionTestCase("'a' || `\"b\"`", "\"ab\""), // 2nd arg: Ion String + ExprFunctionTestCase("'a' || `b`", "\"ab\""), // 2nd arg: Ion symbol `` + ExprFunctionTestCase("'a' || `'b'`", "\"ab\""), // 2nd arg: Ion symbol `''` + ExprFunctionTestCase("'a' || null", "null"), // 2nd arg: null + ExprFunctionTestCase("'a' || missing", "null"), // 2nd arg: missing + + // 1st arg: Ion String + ExprFunctionTestCase("`\"a\"` || 'b'", "\"ab\""), // 2nd arg: String + ExprFunctionTestCase("`\"a\"` || `\"b\"`", "\"ab\""), // 2nd arg: Ion String + ExprFunctionTestCase("`\"a\"` || `b`", "\"ab\""), // 2nd arg: Ion symbol `` + ExprFunctionTestCase("`\"a\"` || `'b'`", "\"ab\""), // 2nd arg: Ion symbol `''` + ExprFunctionTestCase("`\"a\"` || null", "null"), // 2nd arg: null + ExprFunctionTestCase("`\"a\"` || missing", "null"), // 2nd arg: missing + + // 1st arg: Ion symbol (``) + ExprFunctionTestCase("`a` || 'b'", "\"ab\""), // 2nd arg: String + ExprFunctionTestCase("`a` || `\"b\"`", "\"ab\""), // 2nd arg: Ion String + ExprFunctionTestCase("`a` || `b`", "\"ab\""), // 2nd arg: Ion symbol `` + ExprFunctionTestCase("`a` || `'b'`", "\"ab\""), // 2nd arg: Ion symbol `''` + ExprFunctionTestCase("`a` || null", "null"), // 2nd arg: null + ExprFunctionTestCase("`a` || missing", "null"), // 2nd arg: missing + + // 1st arg: Ion symbol (``) + ExprFunctionTestCase("`'a'` || 'b'", "\"ab\""), // 2nd arg: String + ExprFunctionTestCase("`'a'` || `\"b\"`", "\"ab\""), // 2nd arg: Ion String + ExprFunctionTestCase("`'a'` || `b`", "\"ab\""), // 2nd arg: Ion symbol `` + ExprFunctionTestCase("`'a'` || `'b'`", "\"ab\""), // 2nd arg: Ion symbol `''` + ExprFunctionTestCase("`'a'` || null", "null"), // 2nd arg: null + ExprFunctionTestCase("`'a'` || missing", "null"), // 2nd arg: missing + + // 1st arg: null + ExprFunctionTestCase("null || 'b'", "null"), // 2nd arg: String + ExprFunctionTestCase("null || `\"b\"`", "null"), // 2nd arg: Ion String + ExprFunctionTestCase("null || `b`", "null"), // 2nd arg: Ion symbol `` + ExprFunctionTestCase("null || `'b'`", "null"), // 2nd arg: Ion symbol `''` + ExprFunctionTestCase("null || null", "null"), // 2nd arg: null + ExprFunctionTestCase("null || missing", "null"), // 2nd arg: missing + + // 1st arg: missing + ExprFunctionTestCase("missing || 'b'", "null"), // 2nd arg: String + ExprFunctionTestCase("missing || `\"b\"`", "null"), // 2nd arg: Ion String + ExprFunctionTestCase("missing || `b`", "null"), // 2nd arg: Ion symbol `` + ExprFunctionTestCase("missing || `'b'`", "null"), // 2nd arg: Ion symbol `''` + ExprFunctionTestCase("missing || null", "null"), // 2nd arg: null + ExprFunctionTestCase("missing || missing", "null"), // 2nd arg: missing + + // Test for more characters in strings + ExprFunctionTestCase("'' || 'a'", "\"a\""), + ExprFunctionTestCase("`'ab'` || `'c'`", "\"abc\""), + ExprFunctionTestCase("'abcdefghijklmnopqrstuvwxy' || `'z'`", "\"abcdefghijklmnopqrstuvwxyz\""), + ExprFunctionTestCase("'ȴȵ💩💋' || 'abc'", "\"ȴȵ💩💋abc\""), + ExprFunctionTestCase("'😁😞😸😸' || 'abc'", "\"😁😞😸😸abc\""), + ExprFunctionTestCase("'話家身圧費谷料村能' || '計税金'", "\"話家身圧費谷料村能計税金\""), + ExprFunctionTestCase("'eࠫ' || 'abc'", "\"eࠫabc\""), + ) + } + + // Error test cases: Invalid argument type + data class InvalidArgTypeTestCase( + val source: String, + val actualArgType: String, + val line: Long, + val column: Long, + ) + + @ParameterizedTest + @ArgumentsSource(InvalidArgTypeCases::class) + fun concatInvalidArgumentTypeTests(testCase: InvalidArgTypeTestCase) = checkInputThrowingEvaluationException( + input = testCase.source, + errorCode = ErrorCode.EVALUATOR_CONCAT_FAILED_DUE_TO_INCOMPATIBLE_TYPE, + expectErrorContextValues = mapOf( + Property.ACTUAL_ARGUMENT_TYPES to testCase.actualArgType, + Property.LINE_NUMBER to testCase.line, + Property.COLUMN_NUMBER to testCase.column + ), + expectedPermissiveModeResult = "MISSING" + ) + + class InvalidArgTypeCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + // 1st argument wrong + InvalidArgTypeTestCase("1 || 'a'", listOf("INT", "STRING").toString(), 1L, 3L), + InvalidArgTypeTestCase("`1` || 'a'", listOf("INT", "STRING").toString(), 1L, 5L), + InvalidArgTypeTestCase("1.0 || 'a'", listOf("DECIMAL", "STRING").toString(), 1L, 5L), + InvalidArgTypeTestCase("`1.0` || 'a'", listOf("DECIMAL", "STRING").toString(), 1L, 7L), + InvalidArgTypeTestCase("`2021T` || 'a'", listOf("TIMESTAMP", "STRING").toString(), 1L, 9L), + InvalidArgTypeTestCase("<<>> || 'a'", listOf("BAG", "STRING").toString(), 1L, 6L), + InvalidArgTypeTestCase("sexp() || 'a'", listOf("SEXP", "STRING").toString(), 1L, 8L), + InvalidArgTypeTestCase("`()` || 'a'", listOf("SEXP", "STRING").toString(), 1L, 6L), + InvalidArgTypeTestCase("[] || 'a'", listOf("LIST", "STRING").toString(), 1L, 4L), + InvalidArgTypeTestCase("`[]` || 'a'", listOf("LIST", "STRING").toString(), 1L, 6L), + InvalidArgTypeTestCase("{} || 'a'", listOf("STRUCT", "STRING").toString(), 1L, 4L), + InvalidArgTypeTestCase("`{}` || 'a'", listOf("STRUCT", "STRING").toString(), 1L, 6L), + // 2nd argument wrong + InvalidArgTypeTestCase("'a' || 1", listOf("STRING", "INT").toString(), 1L, 5L), + InvalidArgTypeTestCase("'a' || `1`", listOf("STRING", "INT").toString(), 1L, 5L), + InvalidArgTypeTestCase("'a' || `2021T`", listOf("STRING", "TIMESTAMP").toString(), 1L, 5L), + InvalidArgTypeTestCase("'a' || <<>>", listOf("STRING", "BAG").toString(), 1L, 5L), + InvalidArgTypeTestCase("'a' || sexp()", listOf("STRING", "SEXP").toString(), 1L, 5L), + InvalidArgTypeTestCase("'a' || `()`", listOf("STRING", "SEXP").toString(), 1L, 5L), + InvalidArgTypeTestCase("'a' || []", listOf("STRING", "LIST").toString(), 1L, 5L), + InvalidArgTypeTestCase("'a' || `[]`", listOf("STRING", "LIST").toString(), 1L, 5L), + InvalidArgTypeTestCase("'a' || {}", listOf("STRING", "STRUCT").toString(), 1L, 5L), + InvalidArgTypeTestCase("'a' || `{}`", listOf("STRING", "STRUCT").toString(), 1L, 5L), + // both arguments wrong + InvalidArgTypeTestCase("1 || 1", listOf("INT", "INT").toString(), 1L, 3L), + InvalidArgTypeTestCase("`1` || `1`", listOf("INT", "INT").toString(), 1L, 5L), + InvalidArgTypeTestCase("`2021T` || `2021T`", listOf("TIMESTAMP", "TIMESTAMP").toString(), 1L, 9L), + InvalidArgTypeTestCase("<<>> || <<>>", listOf("BAG", "BAG").toString(), 1L, 6L), + InvalidArgTypeTestCase("sexp() || sexp()", listOf("SEXP", "SEXP").toString(), 1L, 8L), + InvalidArgTypeTestCase("`()` || `()`", listOf("SEXP", "SEXP").toString(), 1L, 6L), + InvalidArgTypeTestCase("[] || []", listOf("LIST", "LIST").toString(), 1L, 4L), + InvalidArgTypeTestCase("`[]` || `[]`", listOf("LIST", "LIST").toString(), 1L, 6L), + InvalidArgTypeTestCase("{} || {}", listOf("STRUCT", "STRUCT").toString(), 1L, 4L), + InvalidArgTypeTestCase("`{}` || `{}`", listOf("STRUCT", "STRUCT").toString(), 1L, 6L) + ) + } + + // For invalid arity error tests, if anything missing from left side or right side of `||`, it should be a syntax error. +} diff --git a/lang/test/org/partiql/lang/eval/builtins/functions/DateAddEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/functions/DateAddEvaluationTest.kt new file mode 100644 index 0000000000..e6adc6b562 --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/functions/DateAddEvaluationTest.kt @@ -0,0 +1,191 @@ +package org.partiql.lang.eval.builtins.functions + +import org.junit.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource +import org.partiql.lang.eval.EvaluatorTestBase +import org.partiql.lang.eval.NodeMetadata +import org.partiql.lang.eval.builtins.Argument +import org.partiql.lang.eval.builtins.ExprFunctionTestCase +import org.partiql.lang.eval.builtins.checkInvalidArgType +import org.partiql.lang.eval.builtins.toSession +import org.partiql.lang.types.StaticType +import org.partiql.lang.util.ArgumentsProviderBase + +class DateAddEvaluationTest : EvaluatorTestBase() { + // Pass test cases + @ParameterizedTest + @ArgumentsSource(DateAddPassCases::class) + fun runPassTests(testCase: ExprFunctionTestCase) = assertEval(testCase.source, testCase.expected, testCase.session) + + class DateAddPassCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + ExprFunctionTestCase("date_add(second, null, `2017-01-10T05:30:55Z`)", "null"), + ExprFunctionTestCase("date_add(second, 1, null)", "null"), + ExprFunctionTestCase("date_add(second, missing, `2017-01-10T05:30:55Z`)", "null"), + ExprFunctionTestCase("date_add(second, 1, missing)", "null"), + ExprFunctionTestCase("date_add(year, `1`, `2017T`)", "2018T"), + ExprFunctionTestCase( + "date_add(second, a, b)", + "2017-01-10T05:30:56Z", + mapOf("a" to "1", "b" to "2017-01-10T05:30:55Z").toSession() + ), + + // add 1 at different precision levels + ExprFunctionTestCase("date_add(year, 1, `2017T`)", "2018T"), + ExprFunctionTestCase("date_add(month, 1, `2017T`)", "2017-02T"), + ExprFunctionTestCase("date_add(day, 1, `2017T`)", "2017-01-02T"), + ExprFunctionTestCase("date_add(hour, 1, `2017T`)", "2017-01-01T01:00-00:00"), + ExprFunctionTestCase("date_add(minute, 1, `2017T`)", "2017-01-01T00:01-00:00"), + ExprFunctionTestCase("date_add(second, 1, `2017T`)", "2017-01-01T00:00:01-00:00"), + + ExprFunctionTestCase("date_add(year, 1, `2017-01T`)", "2018-01T"), + ExprFunctionTestCase("date_add(month, 1, `2017-01T`)", "2017-02T"), + ExprFunctionTestCase("date_add(day, 1, `2017-01T`)", "2017-01-02T"), + ExprFunctionTestCase("date_add(hour, 1, `2017-01T`)", "2017-01-01T01:00-00:00"), + ExprFunctionTestCase("date_add(minute, 1, `2017-01T`)", "2017-01-01T00:01-00:00"), + ExprFunctionTestCase("date_add(second, 1, `2017-01T`)", "2017-01-01T00:00:01-00:00"), + + ExprFunctionTestCase("date_add(year, 1, `2017-01-02T`)", "2018-01-02T"), + ExprFunctionTestCase("date_add(month, 1, `2017-01-02T`)", "2017-02-02T"), + ExprFunctionTestCase("date_add(day, 1, `2017-01-02T`)", "2017-01-03T"), + ExprFunctionTestCase("date_add(hour, 1, `2017-01-02T`)", "2017-01-02T01:00-00:00"), + ExprFunctionTestCase("date_add(minute, 1, `2017-01-02T`)", "2017-01-02T00:01-00:00"), + ExprFunctionTestCase("date_add(second, 1, `2017-01-02T`)", "2017-01-02T00:00:01-00:00"), + + ExprFunctionTestCase("date_add(year, 1, `2017-01-02T03:04Z`)", "2018-01-02T03:04Z"), + ExprFunctionTestCase("date_add(month, 1, `2017-01-02T03:04Z`)", "2017-02-02T03:04Z"), + ExprFunctionTestCase("date_add(day, 1, `2017-01-02T03:04Z`)", "2017-01-03T03:04Z"), + ExprFunctionTestCase("date_add(hour, 1, `2017-01-02T03:04Z`)", "2017-01-02T04:04Z"), + ExprFunctionTestCase("date_add(minute, 1, `2017-01-02T03:04Z`)", "2017-01-02T03:05Z"), + ExprFunctionTestCase("date_add(second, 1, `2017-01-02T03:04Z`)", "2017-01-02T03:04:01Z"), + + ExprFunctionTestCase("date_add(year, 1, `2017-01-02T03:04:05Z`)", "2018-01-02T03:04:05Z"), + ExprFunctionTestCase("date_add(month, 1, `2017-01-02T03:04:05Z`)", "2017-02-02T03:04:05Z"), + ExprFunctionTestCase("date_add(day, 1, `2017-01-02T03:04:05Z`)", "2017-01-03T03:04:05Z"), + ExprFunctionTestCase("date_add(hour, 1, `2017-01-02T03:04:05Z`)", "2017-01-02T04:04:05Z"), + ExprFunctionTestCase("date_add(minute, 1, `2017-01-02T03:04:05Z`)", "2017-01-02T03:05:05Z"), + ExprFunctionTestCase("date_add(second, 1, `2017-01-02T03:04:05Z`)", "2017-01-02T03:04:06Z"), + + ExprFunctionTestCase("date_add(year, 1, `2017-01-02T03:04:05.006Z`)", "2018-01-02T03:04:05.006Z"), + ExprFunctionTestCase("date_add(month, 1, `2017-01-02T03:04:05.006Z`)", "2017-02-02T03:04:05.006Z"), + ExprFunctionTestCase("date_add(day, 1, `2017-01-02T03:04:05.006Z`)", "2017-01-03T03:04:05.006Z"), + ExprFunctionTestCase("date_add(hour, 1, `2017-01-02T03:04:05.006Z`)", "2017-01-02T04:04:05.006Z"), + ExprFunctionTestCase("date_add(minute, 1, `2017-01-02T03:04:05.006Z`)", "2017-01-02T03:05:05.006Z"), + ExprFunctionTestCase("date_add(second, 1, `2017-01-02T03:04:05.006Z`)", "2017-01-02T03:04:06.006Z"), + + // add enough to flip a year. Skipping milliseconds as it overflows Long + ExprFunctionTestCase("date_add(month, 12, `2017T`)", "2018-01T"), + ExprFunctionTestCase("date_add(day, 365, `2017T`)", "2018-01-01T"), + ExprFunctionTestCase("date_add(hour, ${365 * 24}, `2017T`)", "2018-01-01T00:00-00:00"), + ExprFunctionTestCase("date_add(minute, ${365 * 24 * 60}, `2017T`)", "2018-01-01T00:00-00:00"), + ExprFunctionTestCase("date_add(minute, ${365 * 24 * 60 * 60}, `2017T`)", "2076-12-17T00:00-00:00"), + + // add enough to flip a month. Skipping milliseconds as it overflows Long + ExprFunctionTestCase("date_add(day, 31, `2017-01T`)", "2017-02-01T"), + ExprFunctionTestCase("date_add(hour, ${31 * 24}, `2017-01T`)", "2017-02-01T00:00-00:00"), + ExprFunctionTestCase("date_add(minute, ${31 * 24 * 60}, `2017-01T`)", "2017-02-01T00:00-00:00"), + ExprFunctionTestCase("date_add(second, ${31 * 24 * 60 * 60}, `2017-01T`)", "2017-02-01T00:00:00-00:00"), + + // add enough to flip a day + ExprFunctionTestCase("date_add(hour, 24, `2017-02-03T`)", "2017-02-04T00:00-00:00"), + ExprFunctionTestCase("date_add(minute, ${24 * 60}, `2017-02-03T`)", "2017-02-04T00:00-00:00"), + ExprFunctionTestCase("date_add(second, ${24 * 60 * 60}, `2017-02-03T`)", "2017-02-04T00:00:00-00:00"), + + // add enough to flip the hour + ExprFunctionTestCase("date_add(minute, 60, `2017-02-04T05:06Z`)", "2017-02-04T06:06Z"), + ExprFunctionTestCase("date_add(second, ${60 * 60}, `2017-02-04T05:06Z`)", "2017-02-04T06:06:00Z"), + + // add enough to flip the minute + ExprFunctionTestCase("date_add(second, 60, `2017-02-04T05:06Z`)", "2017-02-04T05:07:00Z"), + + // subtract 1 at different precision levels + ExprFunctionTestCase("date_add(year, -1, `2017T`)", "2016T"), + ExprFunctionTestCase("date_add(month, -1, `2017T`)", "2016-12T"), + ExprFunctionTestCase("date_add(day, -1, `2017T`)", "2016-12-31T"), + ExprFunctionTestCase("date_add(hour, -1, `2017T`)", "2016-12-31T23:00-00:00"), + ExprFunctionTestCase("date_add(minute, -1, `2017T`)", "2016-12-31T23:59-00:00"), + ExprFunctionTestCase("date_add(second, -1, `2017T`)", "2016-12-31T23:59:59-00:00"), + + ExprFunctionTestCase("date_add(year, -1, `2017-02T`)", "2016-02T"), + ExprFunctionTestCase("date_add(month, -1, `2017-02T`)", "2017-01T"), + ExprFunctionTestCase("date_add(day, -1, `2017-02T`)", "2017-01-31T"), + ExprFunctionTestCase("date_add(hour, -1, `2017-02T`)", "2017-01-31T23:00-00:00"), + ExprFunctionTestCase("date_add(minute, -1, `2017-02T`)", "2017-01-31T23:59-00:00"), + ExprFunctionTestCase("date_add(second, -1, `2017-02T`)", "2017-01-31T23:59:59-00:00"), + + ExprFunctionTestCase("date_add(year, -1, `2017-02-03T`)", "2016-02-03T"), + ExprFunctionTestCase("date_add(month, -1, `2017-02-03T`)", "2017-01-03T"), + ExprFunctionTestCase("date_add(day, -1, `2017-02-03T`)", "2017-02-02T"), + ExprFunctionTestCase("date_add(hour, -1, `2017-02-03T`)", "2017-02-02T23:00-00:00"), + ExprFunctionTestCase("date_add(minute, -1, `2017-02-03T`)", "2017-02-02T23:59-00:00"), + ExprFunctionTestCase("date_add(second, -1, `2017-02-03T`)", "2017-02-02T23:59:59-00:00"), + + ExprFunctionTestCase("date_add(year, -1, `2017-02-03T04:05Z`)", "2016-02-03T04:05Z"), + ExprFunctionTestCase("date_add(month, -1, `2017-02-03T04:05Z`)", "2017-01-03T04:05Z"), + ExprFunctionTestCase("date_add(day, -1, `2017-02-03T04:05Z`)", "2017-02-02T04:05Z"), + ExprFunctionTestCase("date_add(hour, -1, `2017-02-03T04:05Z`)", "2017-02-03T03:05Z"), + ExprFunctionTestCase("date_add(minute, -1, `2017-02-03T04:05Z`)", "2017-02-03T04:04Z"), + ExprFunctionTestCase("date_add(second, -1, `2017-02-03T04:05Z`)", "2017-02-03T04:04:59Z"), + + ExprFunctionTestCase("date_add(year, -1, `2017-02-03T04:05:06Z`)", "2016-02-03T04:05:06Z"), + ExprFunctionTestCase("date_add(month, -1, `2017-02-03T04:05:06Z`)", "2017-01-03T04:05:06Z"), + ExprFunctionTestCase("date_add(day, -1, `2017-02-03T04:05:06Z`)", "2017-02-02T04:05:06Z"), + ExprFunctionTestCase("date_add(hour, -1, `2017-02-03T04:05:06Z`)", "2017-02-03T03:05:06Z"), + ExprFunctionTestCase("date_add(minute, -1, `2017-02-03T04:05:06Z`)", "2017-02-03T04:04:06Z"), + ExprFunctionTestCase("date_add(second, -1, `2017-02-03T04:05:06Z`)", "2017-02-03T04:05:05Z"), + + ExprFunctionTestCase("date_add(year, -1, `2017-02-03T04:05:06.007Z`)", "2016-02-03T04:05:06.007Z"), + ExprFunctionTestCase("date_add(month, -1, `2017-02-03T04:05:06.007Z`)", "2017-01-03T04:05:06.007Z"), + ExprFunctionTestCase("date_add(day, -1, `2017-02-03T04:05:06.007Z`)", "2017-02-02T04:05:06.007Z"), + ExprFunctionTestCase("date_add(hour, -1, `2017-02-03T04:05:06.007Z`)", "2017-02-03T03:05:06.007Z"), + ExprFunctionTestCase("date_add(minute, -1, `2017-02-03T04:05:06.007Z`)", "2017-02-03T04:04:06.007Z"), + ExprFunctionTestCase("date_add(second, -1, `2017-02-03T04:05:06.007Z`)", "2017-02-03T04:05:05.007Z") + ) + } + + // Error test cases: Invalid arguments + data class InvalidArgTestCase( + val query: String, + val message: String + ) + + @ParameterizedTest + @ArgumentsSource(InvalidArgCases::class) + fun dateAddInvalidArgumentTests(testCase: InvalidArgTestCase) = + assertThrows(testCase.query, testCase.message, NodeMetadata(1, 1), "MISSING") + + class InvalidArgCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + InvalidArgTestCase( + "date_add(year, 10000, `2017-06-27T`)", + "Year 12017 must be between 1 and 9999 inclusive" + ), + InvalidArgTestCase( + "date_add(year, -10000, `2000-06-27T`)", + "Year -8001 must be between 1 and 9999 inclusive" + ), + InvalidArgTestCase( + "date_add(month, 10000*12, `2017-06-27T`)", + "Year 12017 must be between 1 and 9999 inclusive" + ), + InvalidArgTestCase( + "date_add(month, -10000*12, `2000-06-27T`)", + "Year -8001 must be between 1 and 9999 inclusive" + ) + ) + } + + // Error test cases: Invalid argument type + @Test + fun dateAddInvalidArgTypeTest() = checkInvalidArgType( + funcName = "date_add", + syntaxSuffix = "(year,", + args = listOf( + Argument(2, StaticType.INT, ","), + Argument(3, StaticType.TIMESTAMP, ")") + ) + ) + + // The invalid arity check is considered as syntax error and already done in the ParserErrorsTest.kt +} diff --git a/lang/test/org/partiql/lang/eval/builtins/functions/DateDiffEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/functions/DateDiffEvaluationTest.kt new file mode 100644 index 0000000000..33e7701b67 --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/functions/DateDiffEvaluationTest.kt @@ -0,0 +1,247 @@ +package org.partiql.lang.eval.builtins.functions + +import org.junit.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource +import org.partiql.lang.eval.EvaluatorTestBase + +import org.partiql.lang.eval.builtins.Argument +import org.partiql.lang.eval.builtins.ExprFunctionTestCase +import org.partiql.lang.eval.builtins.checkInvalidArgType +import org.partiql.lang.eval.builtins.toSession +import org.partiql.lang.types.StaticType +import org.partiql.lang.util.ArgumentsProviderBase + +class DateDiffEvaluationTest : EvaluatorTestBase() { + // Pass test cases + @ParameterizedTest + @ArgumentsSource(DateDiffPassCases::class) + fun runPassTests(testCase: ExprFunctionTestCase) = assertEval(testCase.source, testCase.expected, testCase.session) + + class DateDiffPassCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + ExprFunctionTestCase("date_diff(second, null, `2017-01-10T05:30:55Z`)", "null"), + ExprFunctionTestCase("date_diff(second, `2016-01-10T05:30:55Z`, null)", "null"), + ExprFunctionTestCase("date_diff(second, missing, `2017-01-10T05:30:55Z`)", "null"), + ExprFunctionTestCase("date_diff(second, `2016-01-10T05:30:55Z`, missing)", "null"), + ExprFunctionTestCase( + "date_diff(year, a, b)", + "1", + mapOf("a" to "2016-01-10T05:30:55Z", "b" to "2017-01-10T05:30:55Z").toSession() + ), + + // same dates + ExprFunctionTestCase("date_diff(year, `2017T`, `2017T`)", "0"), + ExprFunctionTestCase("date_diff(month, `2017T`, `2017T`)", "0"), + ExprFunctionTestCase("date_diff(day, `2017T`, `2017T`)", "0"), + ExprFunctionTestCase("date_diff(hour, `2017T`, `2017T`)", "0"), + ExprFunctionTestCase("date_diff(minute, `2017T`, `2017T`)", "0"), + ExprFunctionTestCase("date_diff(second, `2017T`, `2017T`)", "0"), + + ExprFunctionTestCase("date_diff(year, `2017-01T`, `2017-01T`)", "0"), + ExprFunctionTestCase("date_diff(month, `2017-01T`, `2017-01T`)", "0"), + ExprFunctionTestCase("date_diff(day, `2017-01T`, `2017-01T`)", "0"), + ExprFunctionTestCase("date_diff(hour, `2017-01T`, `2017-01T`)", "0"), + ExprFunctionTestCase("date_diff(minute, `2017-01T`, `2017-01T`)", "0"), + ExprFunctionTestCase("date_diff(second, `2017-01T`, `2017-01T`)", "0"), + + ExprFunctionTestCase("date_diff(year, `2017-01-02T`, `2017-01-02T`)", "0"), + ExprFunctionTestCase("date_diff(month, `2017-01-02T`, `2017-01-02T`)", "0"), + ExprFunctionTestCase("date_diff(day, `2017-01-02T`, `2017-01-02T`)", "0"), + ExprFunctionTestCase("date_diff(hour, `2017-01-02T`, `2017-01-02T`)", "0"), + ExprFunctionTestCase("date_diff(minute, `2017-01-02T`, `2017-01-02T`)", "0"), + ExprFunctionTestCase("date_diff(second, `2017-01-02T`, `2017-01-02T`)", "0"), + + ExprFunctionTestCase("date_diff(year, `2017-01-02T03:04Z`, `2017-01-02T03:04Z`)", "0"), + ExprFunctionTestCase("date_diff(month, `2017-01-02T03:04Z`, `2017-01-02T03:04Z`)", "0"), + ExprFunctionTestCase("date_diff(day, `2017-01-02T03:04Z`, `2017-01-02T03:04Z`)", "0"), + ExprFunctionTestCase("date_diff(hour, `2017-01-02T03:04Z`, `2017-01-02T03:04Z`)", "0"), + ExprFunctionTestCase("date_diff(minute, `2017-01-02T03:04Z`, `2017-01-02T03:04Z`)", "0"), + ExprFunctionTestCase("date_diff(second, `2017-01-02T03:04Z`, `2017-01-02T03:04Z`)", "0"), + + ExprFunctionTestCase("date_diff(year, `2017-01-02T03:04:05Z`, `2017-01-02T03:04:05Z`)", "0"), + ExprFunctionTestCase("date_diff(month, `2017-01-02T03:04:05Z`, `2017-01-02T03:04:05Z`)", "0"), + ExprFunctionTestCase("date_diff(day, `2017-01-02T03:04:05Z`, `2017-01-02T03:04:05Z`)", "0"), + ExprFunctionTestCase("date_diff(hour, `2017-01-02T03:04:05Z`, `2017-01-02T03:04:05Z`)", "0"), + ExprFunctionTestCase("date_diff(minute, `2017-01-02T03:04:05Z`, `2017-01-02T03:04:05Z`)", "0"), + ExprFunctionTestCase("date_diff(second, `2017-01-02T03:04:05Z`, `2017-01-02T03:04:05Z`)", "0"), + + ExprFunctionTestCase("date_diff(year, `2017-01-02T03:04:05.007Z`, `2017-01-02T03:04:05.007Z`)", "0"), + ExprFunctionTestCase("date_diff(month, `2017-01-02T03:04:05.007Z`, `2017-01-02T03:04:05.007Z`)", "0"), + ExprFunctionTestCase("date_diff(day, `2017-01-02T03:04:05.007Z`, `2017-01-02T03:04:05.007Z`)", "0"), + ExprFunctionTestCase("date_diff(hour, `2017-01-02T03:04:05.007Z`, `2017-01-02T03:04:05.007Z`)", "0"), + ExprFunctionTestCase("date_diff(minute, `2017-01-02T03:04:05.007Z`, `2017-01-02T03:04:05.007Z`)", "0"), + ExprFunctionTestCase("date_diff(second, `2017-01-02T03:04:05.007Z`, `2017-01-02T03:04:05.007Z`)", "0"), + + ExprFunctionTestCase("date_diff(year, `2017-01-02T03:04:05.007+08:09`, `2017-01-02T03:04:05.007+08:09`)", "0"), + ExprFunctionTestCase("date_diff(month, `2017-01-02T03:04:05.007+08:09`, `2017-01-02T03:04:05.007+08:09`)", "0"), + ExprFunctionTestCase("date_diff(day, `2017-01-02T03:04:05.007+08:09`, `2017-01-02T03:04:05.007+08:09`)", "0"), + ExprFunctionTestCase("date_diff(hour, `2017-01-02T03:04:05.007+08:09`, `2017-01-02T03:04:05.007+08:09`)", "0"), + ExprFunctionTestCase("date_diff(minute, `2017-01-02T03:04:05.007+08:09`, `2017-01-02T03:04:05.007+08:09`)", "0"), + ExprFunctionTestCase("date_diff(second, `2017-01-02T03:04:05.007+08:09`, `2017-01-02T03:04:05.007+08:09`)", "0"), + + // later - earlier + ExprFunctionTestCase("date_diff(year, `2017T`, `2018T`)", "1"), + ExprFunctionTestCase("date_diff(month, `2017T`, `2018T`)", "12"), + ExprFunctionTestCase("date_diff(day, `2017T`, `2018T`)", "365"), + ExprFunctionTestCase("date_diff(hour, `2017T`, `2018T`)", "${365 * 24}"), + ExprFunctionTestCase("date_diff(minute, `2017T`, `2018T`)", "${365 * 24 * 60}"), + ExprFunctionTestCase("date_diff(second, `2017T`, `2018T`)", "${365 * 24 * 60 * 60}"), + + ExprFunctionTestCase("date_diff(year, `2017-01T`, `2017-02T`)", "0"), + ExprFunctionTestCase("date_diff(month, `2017-01T`, `2017-02T`)", "1"), + ExprFunctionTestCase("date_diff(day, `2017-01T`, `2017-02T`)", "31"), + ExprFunctionTestCase("date_diff(hour, `2017-01T`, `2017-02T`)", "${31 * 24}"), + ExprFunctionTestCase("date_diff(minute, `2017-01T`, `2017-02T`)", "${31 * 24 * 60}"), + ExprFunctionTestCase("date_diff(second, `2017-01T`, `2017-02T`)", "${31 * 24 * 60 * 60}"), + + ExprFunctionTestCase("date_diff(year, `2017-01-02T`, `2017-01-03T`)", "0"), + ExprFunctionTestCase("date_diff(month, `2017-01-02T`, `2017-01-03T`)", "0"), + ExprFunctionTestCase("date_diff(day, `2017-01-02T`, `2017-01-03T`)", "1"), + ExprFunctionTestCase("date_diff(hour, `2017-01-02T`, `2017-01-03T`)", "24"), + ExprFunctionTestCase("date_diff(minute, `2017-01-02T`, `2017-01-03T`)", "${24 * 60}"), + ExprFunctionTestCase("date_diff(second, `2017-01-02T`, `2017-01-03T`)", "${24 * 60 * 60}"), + + ExprFunctionTestCase("date_diff(year, `2017-01-02T03:04Z`, `2017-01-02T04:04Z`)", "0"), + ExprFunctionTestCase("date_diff(month, `2017-01-02T03:04Z`, `2017-01-02T04:04Z`)", "0"), + ExprFunctionTestCase("date_diff(day, `2017-01-02T03:04Z`, `2017-01-02T04:04Z`)", "0"), + ExprFunctionTestCase("date_diff(hour, `2017-01-02T03:04Z`, `2017-01-02T04:04Z`)", "1"), + ExprFunctionTestCase("date_diff(minute, `2017-01-02T03:04Z`, `2017-01-02T04:04Z`)", "60"), + ExprFunctionTestCase("date_diff(second, `2017-01-02T03:04Z`, `2017-01-02T04:04Z`)", "${60 * 60}"), + + ExprFunctionTestCase("date_diff(year, `2017-01-02T03:04Z`, `2017-01-02T03:05Z`)", "0"), + ExprFunctionTestCase("date_diff(month, `2017-01-02T03:04Z`, `2017-01-02T03:05Z`)", "0"), + ExprFunctionTestCase("date_diff(day, `2017-01-02T03:04Z`, `2017-01-02T03:05Z`)", "0"), + ExprFunctionTestCase("date_diff(hour, `2017-01-02T03:04Z`, `2017-01-02T03:05Z`)", "0"), + ExprFunctionTestCase("date_diff(minute, `2017-01-02T03:04Z`, `2017-01-02T03:05Z`)", "1"), + ExprFunctionTestCase("date_diff(second, `2017-01-02T03:04Z`, `2017-01-02T03:05Z`)", "60"), + + ExprFunctionTestCase("date_diff(year, `2017-01-02T03:04:05Z`, `2017-01-02T03:04:06Z`)", "0"), + ExprFunctionTestCase("date_diff(month, `2017-01-02T03:04:05Z`, `2017-01-02T03:04:06Z`)", "0"), + ExprFunctionTestCase("date_diff(day, `2017-01-02T03:04:05Z`, `2017-01-02T03:04:06Z`)", "0"), + ExprFunctionTestCase("date_diff(hour, `2017-01-02T03:04:05Z`, `2017-01-02T03:04:06Z`)", "0"), + ExprFunctionTestCase("date_diff(minute, `2017-01-02T03:04:05Z`, `2017-01-02T03:04:06Z`)", "0"), + ExprFunctionTestCase("date_diff(second, `2017-01-02T03:04:05Z`, `2017-01-02T03:04:06Z`)", "1"), + + ExprFunctionTestCase("date_diff(year, `2017-01-02T03:04:05.007Z`, `2017-01-02T03:04:05.008Z`)", "0"), + ExprFunctionTestCase("date_diff(month, `2017-01-02T03:04:05.007Z`, `2017-01-02T03:04:05.008Z`)", "0"), + ExprFunctionTestCase("date_diff(day, `2017-01-02T03:04:05.007Z`, `2017-01-02T03:04:05.008Z`)", "0"), + ExprFunctionTestCase("date_diff(hour, `2017-01-02T03:04:05.007Z`, `2017-01-02T03:04:05.008Z`)", "0"), + ExprFunctionTestCase("date_diff(minute, `2017-01-02T03:04:05.007Z`, `2017-01-02T03:04:05.008Z`)", "0"), + ExprFunctionTestCase("date_diff(second, `2017-01-02T03:04:05.007Z`, `2017-01-02T03:04:05.008Z`)", "0"), + + // earlier - later + ExprFunctionTestCase("date_diff(year, `2018T`, `2017T`)", "-1"), + ExprFunctionTestCase("date_diff(month, `2018T`, `2017T`)", "-12"), + ExprFunctionTestCase("date_diff(day, `2018T`, `2017T`)", "-365"), + ExprFunctionTestCase("date_diff(hour, `2018T`, `2017T`)", "${-365 * 24}"), + ExprFunctionTestCase("date_diff(minute, `2018T`, `2017T`)", "${-365 * 24 * 60}"), + ExprFunctionTestCase("date_diff(second, `2018T`, `2017T`)", "${-365 * 24 * 60 * 60}"), + + ExprFunctionTestCase("date_diff(year, `2017-02T`, `2017-01T`)", "0"), + ExprFunctionTestCase("date_diff(month, `2017-02T`, `2017-01T`)", "-1"), + ExprFunctionTestCase("date_diff(day, `2017-02T`, `2017-01T`)", "-31"), + ExprFunctionTestCase("date_diff(hour, `2017-02T`, `2017-01T`)", "${-31 * 24}"), + ExprFunctionTestCase("date_diff(minute, `2017-02T`, `2017-01T`)", "${-31 * 24 * 60}"), + ExprFunctionTestCase("date_diff(second, `2017-02T`, `2017-01T`)", "${-31 * 24 * 60 * 60}"), + + ExprFunctionTestCase("date_diff(year, `2017-01-03T`, `2017-01-02T`)", "0"), + ExprFunctionTestCase("date_diff(month, `2017-01-03T`, `2017-01-02T`)", "0"), + ExprFunctionTestCase("date_diff(day, `2017-01-03T`, `2017-01-02T`)", "-1"), + ExprFunctionTestCase("date_diff(hour, `2017-01-03T`, `2017-01-02T`)", "-24"), + ExprFunctionTestCase("date_diff(minute, `2017-01-03T`, `2017-01-02T`)", "${-24 * 60}"), + ExprFunctionTestCase("date_diff(second, `2017-01-03T`, `2017-01-02T`)", "${-24 * 60 * 60}"), + + ExprFunctionTestCase("date_diff(year, `2017-01-02T04:04Z`, `2017-01-02T03:04Z`)", "0"), + ExprFunctionTestCase("date_diff(month, `2017-01-02T04:04Z`, `2017-01-02T03:04Z`)", "0"), + ExprFunctionTestCase("date_diff(day, `2017-01-02T04:04Z`, `2017-01-02T03:04Z`)", "0"), + ExprFunctionTestCase("date_diff(hour, `2017-01-02T04:04Z`, `2017-01-02T03:04Z`)", "-1"), + ExprFunctionTestCase("date_diff(minute, `2017-01-02T04:04Z`, `2017-01-02T03:04Z`)", "-60"), + ExprFunctionTestCase("date_diff(second, `2017-01-02T04:04Z`, `2017-01-02T03:04Z`)", "${-60 * 60}"), + + ExprFunctionTestCase("date_diff(year, `2017-01-02T03:05Z`, `2017-01-02T03:04Z`)", "0"), + ExprFunctionTestCase("date_diff(month, `2017-01-02T03:05Z`, `2017-01-02T03:04Z`)", "0"), + ExprFunctionTestCase("date_diff(day, `2017-01-02T03:05Z`, `2017-01-02T03:04Z`)", "0"), + ExprFunctionTestCase("date_diff(hour, `2017-01-02T03:05Z`, `2017-01-02T03:04Z`)", "0"), + ExprFunctionTestCase("date_diff(minute, `2017-01-02T03:05Z`, `2017-01-02T03:04Z`)", "-1"), + ExprFunctionTestCase("date_diff(second, `2017-01-02T03:05Z`, `2017-01-02T03:04Z`)", "-60"), + + ExprFunctionTestCase("date_diff(year, `2017-01-02T03:04:06Z`, `2017-01-02T03:04:05Z`)", "0"), + ExprFunctionTestCase("date_diff(month, `2017-01-02T03:04:06Z`, `2017-01-02T03:04:05Z`)", "0"), + ExprFunctionTestCase("date_diff(day, `2017-01-02T03:04:06Z`, `2017-01-02T03:04:05Z`)", "0"), + ExprFunctionTestCase("date_diff(hour, `2017-01-02T03:04:06Z`, `2017-01-02T03:04:05Z`)", "0"), + ExprFunctionTestCase("date_diff(minute, `2017-01-02T03:04:06Z`, `2017-01-02T03:04:05Z`)", "0"), + ExprFunctionTestCase("date_diff(second, `2017-01-02T03:04:06Z`, `2017-01-02T03:04:05Z`)", "-1"), + + ExprFunctionTestCase("date_diff(year, `2017-01-02T03:04:05.008Z`, `2017-01-02T03:04:05.007Z`)", "0"), + ExprFunctionTestCase("date_diff(month, `2017-01-02T03:04:05.008Z`, `2017-01-02T03:04:05.007Z`)", "0"), + ExprFunctionTestCase("date_diff(day, `2017-01-02T03:04:05.008Z`, `2017-01-02T03:04:05.007Z`)", "0"), + ExprFunctionTestCase("date_diff(hour, `2017-01-02T03:04:05.008Z`, `2017-01-02T03:04:05.007Z`)", "0"), + ExprFunctionTestCase("date_diff(minute, `2017-01-02T03:04:05.008Z`, `2017-01-02T03:04:05.007Z`)", "0"), + ExprFunctionTestCase("date_diff(second, `2017-01-02T03:04:05.008Z`, `2017-01-02T03:04:05.007Z`)", "0"), + + // on different local offsets + ExprFunctionTestCase("date_diff(year, `2017-01-02T03:04+01:02`, `2017-01-02T03:04+00:00`)", "0"), + ExprFunctionTestCase("date_diff(month, `2017-01-02T03:04+00:02`, `2017-01-02T03:04+00:00`)", "0"), + ExprFunctionTestCase("date_diff(day, `2017-01-02T01:00+10:00`, `2017-01-02T01:00+00:00`)", "0"), + ExprFunctionTestCase("date_diff(hour, `2017-01-02T03:04+01:02`, `2017-01-02T03:04+00:00`)", "1"), + ExprFunctionTestCase("date_diff(minute, `2017-01-02T03:04+00:02`, `2017-01-02T03:04+00:00`)", "2"), + + // different precisions + // year + ExprFunctionTestCase("date_diff(month, `2017T`, `2017-02T`)", "1"), + ExprFunctionTestCase("date_diff(day, `2017T`, `2017-01-02T`)", "1"), + ExprFunctionTestCase("date_diff(hour, `2017T`, `2017-01-01T01:00Z`)", "1"), + ExprFunctionTestCase("date_diff(minute, `2017T`, `2017-01-01T00:01Z`)", "1"), + ExprFunctionTestCase("date_diff(second, `2017T`, `2017-01-01T00:00:01Z`)", "1"), + + // month + ExprFunctionTestCase("date_diff(day, `2017-01T`, `2017-01-02T`)", "1"), + ExprFunctionTestCase("date_diff(hour, `2017-01T`, `2017-01-01T01:00Z`)", "1"), + ExprFunctionTestCase("date_diff(minute, `2017-01T`, `2017-01-01T00:01Z`)", "1"), + ExprFunctionTestCase("date_diff(second, `2017-01T`, `2017-01-01T00:00:01Z`)", "1"), + + // day + ExprFunctionTestCase("date_diff(hour, `2017-01-01T`, `2017-01-01T01:00Z`)", "1"), + ExprFunctionTestCase("date_diff(minute, `2017-01-01T`, `2017-01-01T00:01Z`)", "1"), + ExprFunctionTestCase("date_diff(second, `2017-01-01T`, `2017-01-01T00:00:01Z`)", "1"), + + // minute + ExprFunctionTestCase("date_diff(second, `2017-01-01T00:00Z`, `2017-01-01T00:00:01Z`)", "1"), + + // leap year + ExprFunctionTestCase("date_diff(day, `2016-01-01T`, `2017-01-01T`)", "366"), + ExprFunctionTestCase("date_diff(hour, `2016-01-01T`, `2017-01-01T`)", "${366 * 24}"), + ExprFunctionTestCase("date_diff(minute, `2016-01-01T`, `2017-01-01T`)", "${366 * 24 * 60}"), + ExprFunctionTestCase("date_diff(second, `2016-01-01T`, `2017-01-01T`)", "${366 * 24 * 60 * 60}"), + + // Days in a month + ExprFunctionTestCase("date_diff(day, `2017-01-01T`, `2017-02-01T`)", "31"), // January + ExprFunctionTestCase("date_diff(day, `2017-02-01T`, `2017-03-01T`)", "28"), // February + ExprFunctionTestCase("date_diff(day, `2016-02-01T`, `2016-03-01T`)", "29"), // February leap year + ExprFunctionTestCase("date_diff(day, `2017-03-01T`, `2017-04-01T`)", "31"), // March + ExprFunctionTestCase("date_diff(day, `2017-04-01T`, `2017-05-01T`)", "30"), // April + ExprFunctionTestCase("date_diff(day, `2017-05-01T`, `2017-06-01T`)", "31"), // May + ExprFunctionTestCase("date_diff(day, `2017-06-01T`, `2017-07-01T`)", "30"), // June + ExprFunctionTestCase("date_diff(day, `2017-07-01T`, `2017-08-01T`)", "31"), // July + ExprFunctionTestCase("date_diff(day, `2017-08-01T`, `2017-09-01T`)", "31"), // August + ExprFunctionTestCase("date_diff(day, `2017-09-01T`, `2017-10-01T`)", "30"), // September + ExprFunctionTestCase("date_diff(day, `2017-10-01T`, `2017-11-01T`)", "31"), // October + ExprFunctionTestCase("date_diff(day, `2017-11-01T`, `2017-12-01T`)", "30"), // November + ExprFunctionTestCase("date_diff(day, `2017-12-01T`, `2018-01-01T`)", "31") // December + ) + } + + // Error test cases: Invalid argument type + @Test + fun dateDiffInvalidArgTypeTest() = checkInvalidArgType( + funcName = "date_diff", + syntaxSuffix = "(day,", + args = listOf( + Argument(2, StaticType.TIMESTAMP, ","), + Argument(3, StaticType.TIMESTAMP, ")") + ) + ) + + // The invalid arity check is considered as syntax error and already done in the ParserErrorsTest.kt +} \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/functions/ExistsEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/functions/ExistsEvaluationTest.kt new file mode 100644 index 0000000000..66b0a8e0a6 --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/functions/ExistsEvaluationTest.kt @@ -0,0 +1,55 @@ +package org.partiql.lang.eval.builtins.functions + +import org.junit.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource +import org.partiql.lang.eval.EvaluatorTestBase +import org.partiql.lang.eval.builtins.Argument +import org.partiql.lang.eval.builtins.ExprFunctionTestCase +import org.partiql.lang.eval.builtins.checkInvalidArgType +import org.partiql.lang.eval.builtins.checkInvalidArity +import org.partiql.lang.types.StaticType +import org.partiql.lang.util.ArgumentsProviderBase + +class ExistsEvaluationTest : EvaluatorTestBase() { + // Pass test cases + @ParameterizedTest + @ArgumentsSource(ExistsPassCases::class) + fun runPassTests(testCase: ExprFunctionTestCase) = assertEval(testCase.source, testCase.expected) + + class ExistsPassCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + ExprFunctionTestCase("exists(<<1, 2, 3>>)", "true"), + ExprFunctionTestCase("exists(<<>>)", "false"), + ExprFunctionTestCase("exists(sexp(1,2,3))", "true"), + ExprFunctionTestCase("exists(`(1 2 3)`)", "true"), + ExprFunctionTestCase("exists(sexp())", "false"), + ExprFunctionTestCase("exists(`()`)", "false"), + ExprFunctionTestCase("exists([1, 2, 3])", "true"), + ExprFunctionTestCase("exists(`[1, 2, 3]`)", "true"), + ExprFunctionTestCase("exists([])", "false"), + ExprFunctionTestCase("exists(`[]`)", "false"), + ExprFunctionTestCase("exists({ `a`: 1, `b`: 2, `c`: 3 })", "true"), + ExprFunctionTestCase("exists(`{ a: 1, b: 2, c: 3 }`)", "true"), + ExprFunctionTestCase("exists({})", "false"), + ExprFunctionTestCase("exists(`{}`)", "false") + ) + } + + // Error test cases: Invalid argument type + @Test + fun existsInvalidArgTypeTest() = checkInvalidArgType( + funcName = "exists", + args = listOf( + Argument(1, StaticType.unionOf(StaticType.SEXP, StaticType.LIST, StaticType.BAG, StaticType.STRUCT), ")") + ) + ) + + // Error test cases: Invalid arity + @Test + fun existsInvalidArityTest() = checkInvalidArity( + funcName = "exists", + maxArity = 1, + minArity = 1 + ) +} diff --git a/lang/test/org/partiql/lang/eval/builtins/functions/ExtractEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/functions/ExtractEvaluationTest.kt new file mode 100644 index 0000000000..bde0b23058 --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/functions/ExtractEvaluationTest.kt @@ -0,0 +1,224 @@ +package org.partiql.lang.eval.builtins.functions + +import org.junit.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource +import org.partiql.lang.eval.EvaluatorTestBase +import org.partiql.lang.eval.NodeMetadata +import org.partiql.lang.eval.builtins.Argument +import org.partiql.lang.eval.builtins.ExprFunctionTestCase +import org.partiql.lang.eval.builtins.checkInvalidArgType +import org.partiql.lang.eval.builtins.toSession +import org.partiql.lang.types.StaticType +import org.partiql.lang.util.ArgumentsProviderBase + +/** + * Parsing related tests in [org.partiql.lang.syntax.SqlParserTest] and [org.partiql.lang.errors.ParserErrorsTest]. + */ +class ExtractEvaluationTest : EvaluatorTestBase() { + // Pass test cases + @ParameterizedTest + @ArgumentsSource(ExtractPassCases::class) + fun runPassTests(testCase: ExprFunctionTestCase) = assertEval(testCase.source, testCase.expected, testCase.session) + + class ExtractPassCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + ExprFunctionTestCase("extract(year FROM null)", "null"), + ExprFunctionTestCase("extract(month FROM null)", "null"), + ExprFunctionTestCase("extract(day FROM null)", "null"), + ExprFunctionTestCase("extract(hour FROM null)", "null"), + ExprFunctionTestCase("extract(minute FROM null)", "null"), + ExprFunctionTestCase("extract(second FROM null)", "null"), + ExprFunctionTestCase("extract(timezone_hour FROM null)", "null"), + ExprFunctionTestCase("extract(timezone_minute FROM null)", "null"), + ExprFunctionTestCase("extract(year FROM missing)", "null"), + ExprFunctionTestCase("extract(month FROM missing)", "null"), + ExprFunctionTestCase("extract(day FROM missing)", "null"), + ExprFunctionTestCase("extract(hour FROM missing)", "null"), + ExprFunctionTestCase("extract(minute FROM missing)", "null"), + ExprFunctionTestCase("extract(second FROM missing)", "null"), + ExprFunctionTestCase("extract(timezone_hour FROM missing)", "null"), + ExprFunctionTestCase("extract(timezone_minute FROM missing)", "null"), + ExprFunctionTestCase("extract(second FROM a)", "55.", mapOf("a" to "2017-01-10T05:30:55Z").toSession()), + // just year + ExprFunctionTestCase("extract(year FROM `2017T`)", "2017."), + ExprFunctionTestCase("extract(month FROM `2017T`)", "1."), + ExprFunctionTestCase("extract(day FROM `2017T`)", "1."), + ExprFunctionTestCase("extract(hour FROM `2017T`)", "0."), + ExprFunctionTestCase("extract(minute FROM `2017T`)", "0."), + ExprFunctionTestCase("extract(second FROM `2017T`)", "0."), + ExprFunctionTestCase("extract(timezone_hour FROM `2017T`)", "0."), + ExprFunctionTestCase("extract(timezone_minute FROM `2017T`)", "0."), + // year, month + ExprFunctionTestCase("extract(year FROM `2017-01T`)", "2017."), + ExprFunctionTestCase("extract(month FROM `2017-01T`)", "1."), + ExprFunctionTestCase("extract(day FROM `2017-01T`)", "1."), + ExprFunctionTestCase("extract(hour FROM `2017-01T`)", "0."), + ExprFunctionTestCase("extract(minute FROM `2017-01T`)", "0."), + ExprFunctionTestCase("extract(second FROM `2017-01T`)", "0."), + ExprFunctionTestCase("extract(timezone_hour FROM `2017-01T`)", "0."), + ExprFunctionTestCase("extract(timezone_minute FROM `2017-01T`)", "0."), + // year, month, day + ExprFunctionTestCase("extract(year FROM `2017-01-02T`)", "2017."), + ExprFunctionTestCase("extract(month FROM `2017-01-02T`)", "1."), + ExprFunctionTestCase("extract(day FROM `2017-01-02T`)", "2."), + ExprFunctionTestCase("extract(hour FROM `2017-01-02T`)", "0."), + ExprFunctionTestCase("extract(minute FROM `2017-01-02T`)", "0."), + ExprFunctionTestCase("extract(second FROM `2017-01-02T`)", "0."), + ExprFunctionTestCase("extract(timezone_hour FROM `2017-01-02T`)", "0."), + ExprFunctionTestCase("extract(timezone_minute FROM `2017-01-02T`)", "0."), + // year, month, day, hour, minute + ExprFunctionTestCase("extract(year FROM `2017-01-02T03:04Z`)", "2017."), + ExprFunctionTestCase("extract(month FROM `2017-01-02T03:04Z`)", "1."), + ExprFunctionTestCase("extract(day FROM `2017-01-02T03:04Z`)", "2."), + ExprFunctionTestCase("extract(hour FROM `2017-01-02T03:04Z`)", "3."), + ExprFunctionTestCase("extract(minute FROM `2017-01-02T03:04Z`)", "4."), + ExprFunctionTestCase("extract(second FROM `2017-01-02T03:04Z`)", "0."), + ExprFunctionTestCase("extract(timezone_hour FROM `2017-01-02T03:04Z`)", "0."), + ExprFunctionTestCase("extract(timezone_minute FROM `2017-01-02T03:04Z`)", "0."), + // year, month, day, hour, minute, second + ExprFunctionTestCase("extract(year FROM `2017-01-02T03:04:05Z`)", "2017."), + ExprFunctionTestCase("extract(month FROM `2017-01-02T03:04:05Z`)", "1."), + ExprFunctionTestCase("extract(day FROM `2017-01-02T03:04:05Z`)", "2."), + ExprFunctionTestCase("extract(hour FROM `2017-01-02T03:04:05Z`)", "3."), + ExprFunctionTestCase("extract(minute FROM `2017-01-02T03:04:05Z`)", "4."), + ExprFunctionTestCase("extract(second FROM `2017-01-02T03:04:05Z`)", "5."), + ExprFunctionTestCase("extract(timezone_hour FROM `2017-01-02T03:04:05Z`)", "0."), + ExprFunctionTestCase("extract(timezone_minute FROM `2017-01-02T03:04:05Z`)", "0."), + // year, month, day, hour, minute, second, local offset + ExprFunctionTestCase("extract(year FROM `2017-01-02T03:04:05+07:08`)", "2017."), + ExprFunctionTestCase("extract(month FROM `2017-01-02T03:04:05+07:08`)", "1."), + ExprFunctionTestCase("extract(day FROM `2017-01-02T03:04:05+07:08`)", "2."), + ExprFunctionTestCase("extract(hour FROM `2017-01-02T03:04:05+07:08`)", "3."), + ExprFunctionTestCase("extract(minute FROM `2017-01-02T03:04:05+07:08`)", "4."), + ExprFunctionTestCase("extract(second FROM `2017-01-02T03:04:05+07:08`)", "5."), + ExprFunctionTestCase("extract(timezone_hour FROM `2017-01-02T03:04:05+07:08`)", "7."), + ExprFunctionTestCase("extract(timezone_minute FROM `2017-01-02T03:04:05+07:08`)", "8."), + // negative offset + ExprFunctionTestCase("extract(timezone_hour FROM `2017-01-02T03:04:05-07:08`)", "-7."), + ExprFunctionTestCase("extract(timezone_minute FROM `2017-01-02T03:04:05-07:08`)", "-8."), + // DATE + ExprFunctionTestCase("extract(year FROM DATE '2012-12-12')", "2012."), + ExprFunctionTestCase("extract(month FROM DATE '2012-12-12')", "12."), + ExprFunctionTestCase("extract(day FROM DATE '2012-12-12')", "12."), + ExprFunctionTestCase("extract(hour FROM DATE '2012-12-12')", "0."), + ExprFunctionTestCase("extract(minute FROM DATE '2012-12-12')", "0."), + ExprFunctionTestCase("extract(second FROM DATE '2012-12-12')", "0."), + // TIME + ExprFunctionTestCase("extract(hour FROM TIME '23:12:59.128')", "23."), + ExprFunctionTestCase("extract(minute FROM TIME '23:12:59.128')", "12."), + ExprFunctionTestCase("extract(second FROM TIME '23:12:59.128')", "59.128"), + ExprFunctionTestCase("extract(hour FROM TIME (2) '23:12:59.128')", "23."), + ExprFunctionTestCase("extract(minute FROM TIME (2) '23:12:59.128')", "12."), + ExprFunctionTestCase("extract(second FROM TIME (2) '23:12:59.128')", "59.13"), + // TIME WITH TIME ZONE + ExprFunctionTestCase("extract(hour FROM TIME WITH TIME ZONE '23:12:59.128-06:30')", "23."), + ExprFunctionTestCase("extract(minute FROM TIME WITH TIME ZONE '23:12:59.128-06:30')", "12."), + ExprFunctionTestCase("extract(second FROM TIME WITH TIME ZONE '23:12:59.128-06:30')", "59.128"), + ExprFunctionTestCase("extract(timezone_hour FROM TIME WITH TIME ZONE '23:12:59.128-06:30')", "-6."), + ExprFunctionTestCase("extract(timezone_minute FROM TIME WITH TIME ZONE '23:12:59.128-06:30')", "-30."), + ExprFunctionTestCase("extract(hour FROM TIME (2) WITH TIME ZONE '23:12:59.128-06:30')", "23."), + ExprFunctionTestCase("extract(minute FROM TIME (2) WITH TIME ZONE '23:12:59.128-06:30')", "12."), + ExprFunctionTestCase("extract(second FROM TIME (2) WITH TIME ZONE '23:12:59.128-06:30')", "59.13"), + ExprFunctionTestCase("extract(timezone_hour FROM TIME (2) WITH TIME ZONE '23:12:59.128-06:30')", "-6."), + ExprFunctionTestCase("extract(timezone_minute FROM TIME (2) WITH TIME ZONE '23:12:59.128-06:30')", "-30.") + ) + } + + // Invalid arguments + data class InvalidArgTestCase( + val query: String, + val message: String + ) + + @ParameterizedTest + @ArgumentsSource(InvalidArgCases::class) + fun extractInvalidArgumentTests(testCase: InvalidArgTestCase) = + assertThrows(testCase.query, testCase.message, NodeMetadata(1, 1), "MISSING") + + class InvalidArgCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + // DATE + InvalidArgTestCase( + "EXTRACT(timezone_hour FROM DATE '2012-12-12')", + "Timestamp unit timezone_hour not supported for DATE type" + ), + InvalidArgTestCase( + "EXTRACT(timezone_minute FROM DATE '2012-12-12')", + "Timestamp unit timezone_minute not supported for DATE type" + ), + // TIME + InvalidArgTestCase("EXTRACT(year FROM TIME '23:12:59.128')", "Time unit year not supported for TIME type."), + InvalidArgTestCase( + "EXTRACT(month FROM TIME '23:12:59.128')", + "Time unit month not supported for TIME type." + ), + InvalidArgTestCase("EXTRACT(day FROM TIME '23:12:59.128')", "Time unit day not supported for TIME type."), + InvalidArgTestCase( + "EXTRACT(timezone_hour FROM TIME '23:12:59.128')", + "Time unit timezone_hour not supported for TIME type without TIME ZONE" + ), + InvalidArgTestCase( + "EXTRACT(timezone_minute FROM TIME '23:12:59.128')", + "Time unit timezone_minute not supported for TIME type without TIME ZONE" + ), + InvalidArgTestCase( + "EXTRACT(year FROM TIME (2) '23:12:59.128')", + "Time unit year not supported for TIME type." + ), + InvalidArgTestCase( + "EXTRACT(month FROM TIME (2) '23:12:59.128')", + "Time unit month not supported for TIME type." + ), + InvalidArgTestCase( + "EXTRACT(day FROM TIME (2) '23:12:59.128')", + "Time unit day not supported for TIME type." + ), + InvalidArgTestCase( + "EXTRACT(timezone_hour FROM TIME (2) '23:12:59.128')", + "Time unit timezone_hour not supported for TIME type without TIME ZONE" + ), + InvalidArgTestCase( + "EXTRACT(timezone_minute FROM TIME (2) '23:12:59.128')", + "Time unit timezone_minute not supported for TIME type without TIME ZONE" + ), + // TIME WITH TIME ZONE + InvalidArgTestCase( + "EXTRACT(year FROM TIME WITH TIME ZONE '23:12:59.128-06:30')", + "Time unit year not supported for TIME type." + ), + InvalidArgTestCase( + "EXTRACT(month FROM TIME WITH TIME ZONE '23:12:59.128-06:30')", + "Time unit month not supported for TIME type." + ), + InvalidArgTestCase( + "EXTRACT(day FROM TIME WITH TIME ZONE '23:12:59.128-06:30')", + "Time unit day not supported for TIME type." + ), + InvalidArgTestCase( + "EXTRACT(year FROM TIME (2) '23:12:59.128')", + "Time unit year not supported for TIME type." + ), + InvalidArgTestCase( + "EXTRACT(month FROM TIME (2) '23:12:59.128')", + "Time unit month not supported for TIME type." + ), + InvalidArgTestCase( + "EXTRACT(day FROM TIME (2) '23:12:59.128')", + "Time unit day not supported for TIME type." + ) + ) + } + + // Error test cases: Invalid argument type + @Test + fun extractInvalidArgTypeTest() = checkInvalidArgType( + funcName = "extract", + syntaxSuffix = "(year from ", + args = listOf( + Argument(2, StaticType.unionOf(StaticType.TIMESTAMP, StaticType.TIME, StaticType.DATE), ")") + ) + ) + + // The invalid arity check is considered as syntax error and already done in the ParserErrorsTest.kt +} \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/functions/FromUnixTimeFunctionTest.kt b/lang/test/org/partiql/lang/eval/builtins/functions/FromUnixTimeFunctionTest.kt new file mode 100644 index 0000000000..af74a36914 --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/functions/FromUnixTimeFunctionTest.kt @@ -0,0 +1,86 @@ +package org.partiql.lang.eval.builtins.functions + +import org.junit.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource +import org.partiql.lang.eval.EvaluatorTestBase +import org.partiql.lang.eval.NodeMetadata +import org.partiql.lang.eval.builtins.Argument +import org.partiql.lang.eval.builtins.ExprFunctionTestCase +import org.partiql.lang.eval.builtins.checkInvalidArgType +import org.partiql.lang.eval.builtins.checkInvalidArity +import org.partiql.lang.types.StaticType +import org.partiql.lang.util.ArgumentsProviderBase + +class FromUnixTimeFunctionTest : EvaluatorTestBase() { + @ParameterizedTest + @ArgumentsSource(FromUnixTimePassCases::class) + fun runPassTests(tc: ExprFunctionTestCase) = assertEval(tc.source, tc.expected) + + class FromUnixTimePassCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + // negative unix epochs output timestamp before last epoch + ExprFunctionTestCase("from_unixtime(-1)", "1969-12-31T23:59:59-00:00"), + ExprFunctionTestCase("from_unixtime(-0.1)", "1969-12-31T23:59:59.9-00:00"), + ExprFunctionTestCase("from_unixtime(`-1`)", "1969-12-31T23:59:59-00:00"), + ExprFunctionTestCase("from_unixtime(`-0.1`)", "1969-12-31T23:59:59.9-00:00"), + // non-negative cases outputting a timestamp after last epoch + ExprFunctionTestCase("from_unixtime(0)", "1970-01-01T00:00:00.000-00:00"), + ExprFunctionTestCase("from_unixtime(0.001)", "1970-01-01T00:00:00.001-00:00"), + ExprFunctionTestCase("from_unixtime(0.01)", "1970-01-01T00:00:00.01-00:00"), + ExprFunctionTestCase("from_unixtime(0.1)", "1970-01-01T00:00:00.1-00:00"), + ExprFunctionTestCase("from_unixtime(1)", "1970-01-01T00:00:01-00:00"), + ExprFunctionTestCase("from_unixtime(1577836800)", "2020-01-01T00:00:00-00:00"), + ExprFunctionTestCase("from_unixtime(`0`)", "1970-01-01T00:00:00.000-00:00"), + ExprFunctionTestCase("from_unixtime(`0.001`)", "1970-01-01T00:00:00.001-00:00"), + ExprFunctionTestCase("from_unixtime(`0.01`)", "1970-01-01T00:00:00.01-00:00"), + ExprFunctionTestCase("from_unixtime(`0.1`)", "1970-01-01T00:00:00.1-00:00"), + ExprFunctionTestCase("from_unixtime(`1`)", "1970-01-01T00:00:01-00:00"), + ExprFunctionTestCase("from_unixtime(`1577836800`)", "2020-01-01T00:00:00-00:00"), + // Null or missing + ExprFunctionTestCase("from_unixtime(null)", "null"), + ExprFunctionTestCase("from_unixtime(missing)", "null"), + ) + } + + // Invalid arguments + data class InvalidArgTestCase( + val query: String, + val message: String + ) + + @ParameterizedTest + @ArgumentsSource(InvalidArgCases::class) + fun fromUnixTimeInvalidArgumentTests(testCase: InvalidArgTestCase) = + assertThrows(testCase.query, testCase.message, NodeMetadata(1, 1), null, true) + + class InvalidArgCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + InvalidArgTestCase( + "from_unixtime(1577836800000)", + "Internal error, millis: 1.5778368E+15 is outside of valid the range: from -62135769600000 (0001T), inclusive, to 253402300800000 (10000T) , exclusive" + ), + InvalidArgTestCase( + "from_unixtime(-1577836800000)", + "Internal error, millis: -1.5778368E+15 is outside of valid the range: from -62135769600000 (0001T), inclusive, to 253402300800000 (10000T) , exclusive" + ) + ) + } + + // Error test cases: Invalid argument type + @Test + fun fromUnixTimeInvalidArgTypeTest() = checkInvalidArgType( + funcName = "from_unixtime", + args = listOf( + Argument(1, StaticType.unionOf(StaticType.DECIMAL, StaticType.INT), ")") + ) + ) + + // Error test cases: Invalid arity + @Test + fun fromUnixTimeInvalidArityTest() = checkInvalidArity( + funcName = "from_unixtime", + maxArity = 1, + minArity = 1 + ) +} diff --git a/lang/test/org/partiql/lang/eval/builtins/functions/LowerEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/functions/LowerEvaluationTest.kt new file mode 100644 index 0000000000..7559d28e9a --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/functions/LowerEvaluationTest.kt @@ -0,0 +1,52 @@ +package org.partiql.lang.eval.builtins.functions + +import org.junit.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource +import org.partiql.lang.eval.EvaluatorTestBase +import org.partiql.lang.eval.builtins.Argument +import org.partiql.lang.eval.builtins.ExprFunctionTestCase +import org.partiql.lang.eval.builtins.checkInvalidArgType +import org.partiql.lang.eval.builtins.checkInvalidArity +import org.partiql.lang.types.StaticType +import org.partiql.lang.util.ArgumentsProviderBase + +class LowerEvaluationTest : EvaluatorTestBase() { + @ParameterizedTest + @ArgumentsSource(LowerPassCases::class) + fun runPassTests(tc: ExprFunctionTestCase) = assertEval(tc.source, tc.expected) + + class LowerPassCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + ExprFunctionTestCase("lower('')", "\"\""), + ExprFunctionTestCase("lower(`A`)", "\"a\""), + ExprFunctionTestCase("lower(`'A'`)", "\"a\""), + ExprFunctionTestCase("lower('A')", "\"a\""), + ExprFunctionTestCase("lower(`\"A\"`)", "\"a\""), + ExprFunctionTestCase("lower('ABCDEF')", "\"abcdef\""), + ExprFunctionTestCase("lower('abcdef')", "\"abcdef\""), + ExprFunctionTestCase("lower(null)", "null"), + ExprFunctionTestCase("lower(missing)", "null"), + ExprFunctionTestCase("lower('123\$%(*&')", "\"123\$%(*&\""), + ExprFunctionTestCase("lower('ȴȵ💩Z💋')", "\"ȴȵ💩z💋\""), + ExprFunctionTestCase("lower('話家身圧費谷料村能計税金')", "\"話家身圧費谷料村能計税金\"") + ) + } + + // Error test cases: Invalid argument type + @Test + fun lowerInvalidArgTypeTest() = checkInvalidArgType( + funcName = "lower", + args = listOf( + Argument(1, StaticType.unionOf(StaticType.STRING, StaticType.SYMBOL), ")") + ) + ) + + // Error test cases: Invalid arity + @Test + fun lowerInvalidArityTest() = checkInvalidArity( + funcName = "lower", + maxArity = 1, + minArity = 1 + ) +} diff --git a/lang/test/org/partiql/lang/eval/builtins/functions/MakeDateEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/functions/MakeDateEvaluationTest.kt new file mode 100644 index 0000000000..42b309c20c --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/functions/MakeDateEvaluationTest.kt @@ -0,0 +1,84 @@ +package org.partiql.lang.eval.builtins.functions + +import org.junit.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource +import org.partiql.lang.errors.ErrorCode +import org.partiql.lang.errors.Property +import org.partiql.lang.eval.EvaluatorTestBase +import org.partiql.lang.eval.builtins.Argument +import org.partiql.lang.eval.builtins.ExprFunctionTestCase +import org.partiql.lang.eval.builtins.checkInvalidArgType +import org.partiql.lang.eval.builtins.checkInvalidArity +import org.partiql.lang.types.StaticType +import org.partiql.lang.util.ArgumentsProviderBase + +class MakeDateEvaluationTest : EvaluatorTestBase() { + @ParameterizedTest + @ArgumentsSource(MakeDatePassCases::class) + fun runPassTests(testCase: ExprFunctionTestCase) = assertEquals(eval(testCase.source).toString(), testCase.expected) + + class MakeDatePassCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + ExprFunctionTestCase("make_date(100, 1, 1)", "0100-01-01"), + ExprFunctionTestCase("make_date(1985, 1, 1)", "1985-01-01"), + ExprFunctionTestCase("make_date(2102, 02, 03)", "2102-02-03"), + ExprFunctionTestCase("make_date(3000, 02, 03)", "3000-02-03"), + ExprFunctionTestCase("make_date(2012, 02, 29)", "2012-02-29"), + ExprFunctionTestCase("make_date(2021, 02, 28)", "2021-02-28"), + ExprFunctionTestCase("make_date(`100`, `1`, `1`)", "0100-01-01"), + ExprFunctionTestCase("make_date(NULL, 02, 28)", "NULL"), + ExprFunctionTestCase("make_date(2021, NULL, 28)", "NULL"), + ExprFunctionTestCase("make_date(2021, 02, NULL)", "NULL"), + ExprFunctionTestCase("make_date(MISSING, 02, 28)", "NULL"), + ExprFunctionTestCase("make_date(MISSING, 02, 28)", "NULL"), + ExprFunctionTestCase("make_date(2021, MISSING, 28)", "NULL"), + ExprFunctionTestCase("make_date(2021, 02, MISSING)", "NULL"), + ExprFunctionTestCase("make_date(NULL, MISSING, 28)", "NULL"), + ExprFunctionTestCase("make_date(MISSING, NULL, 28)", "NULL"), + ExprFunctionTestCase("make_date(MISSING, 02, NULL)", "NULL"), + ExprFunctionTestCase("make_date(NULL, NULL, 28)", "NULL"), + ExprFunctionTestCase("make_date(NULL, NULL, 28)", "NULL"), + ExprFunctionTestCase("make_date(MISSING, MISSING, MISSING)", "NULL"), + ExprFunctionTestCase("make_date(2021, 03, 17) IS DATE", "true") + ) + } + + // Invalid arguments + @ParameterizedTest + @ArgumentsSource(InvalidArgCases::class) + fun makeDateInvalidArgumentTests(query: String) = checkInputThrowingEvaluationException( + input = query, + errorCode = ErrorCode.EVALUATOR_DATE_FIELD_OUT_OF_RANGE, + expectErrorContextValues = mapOf( + Property.LINE_NUMBER to 1L, + Property.COLUMN_NUMBER to 1L + ) + ) + + class InvalidArgCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + "make_date(2021, 2, 29)", + "make_date(2021, 4, 31)" + ) + } + + // Error test cases: Invalid argument type + @Test + fun makeDateInvalidArgTypeTest() = checkInvalidArgType( + funcName = "make_date", + args = listOf( + Argument(1, StaticType.INT, ","), + Argument(2, StaticType.INT, ","), + Argument(3, StaticType.INT, ")") + ) + ) + + // Error test cases: Invalid arity + @Test + fun makeDateInvalidArityTest() = checkInvalidArity( + funcName = "make_date", + maxArity = 3, + minArity = 3 + ) +} \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/functions/MakeTimeEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/functions/MakeTimeEvaluationTest.kt new file mode 100644 index 0000000000..e898dc9ed7 --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/functions/MakeTimeEvaluationTest.kt @@ -0,0 +1,88 @@ +package org.partiql.lang.eval.builtins.functions + +import org.junit.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource +import org.partiql.lang.errors.ErrorCode +import org.partiql.lang.errors.Property +import org.partiql.lang.eval.EvaluatorTestBase +import org.partiql.lang.eval.builtins.Argument +import org.partiql.lang.eval.builtins.ExprFunctionTestCase +import org.partiql.lang.eval.builtins.checkInvalidArgType +import org.partiql.lang.eval.builtins.checkInvalidArity +import org.partiql.lang.types.StaticType +import org.partiql.lang.util.ArgumentsProviderBase +import org.partiql.lang.util.to + +class MakeTimeEvaluationTest : EvaluatorTestBase() { + @ParameterizedTest + @ArgumentsSource(MakeTimePassCases::class) + fun runPassTests(testCase: ExprFunctionTestCase) = assertEquals(eval(testCase.source).toString(), testCase.expected) + + class MakeTimePassCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + ExprFunctionTestCase("make_time(0, 0, 0.)", "00:00:00"), + ExprFunctionTestCase("make_time(0, 0, 0., 0)", "00:00:00+00:00"), + ExprFunctionTestCase("make_time(23, 12, 59.12345)", "23:12:59.12345"), + ExprFunctionTestCase("make_time(23, 12, 59.12345, 800)", "23:12:59.12345+13:20"), + ExprFunctionTestCase("make_time(23, 59, 59.999999999)", "23:59:59.999999999"), + ExprFunctionTestCase("make_time(23, 12, 59.12345, -800)", "23:12:59.12345-13:20"), + ExprFunctionTestCase("make_time(23, 59, 59.999999999, -1080)", "23:59:59.999999999-18:00"), + ExprFunctionTestCase("make_time(23, 59, 59.999999999, 1080)", "23:59:59.999999999+18:00"), + ExprFunctionTestCase("make_time(`23`, `12`, `59.12345`, `800`)", "23:12:59.12345+13:20"), + ExprFunctionTestCase("make_time(null, 59, 59.999999999)", "NULL"), + ExprFunctionTestCase("make_time(23, null, 59.999999999)", "NULL"), + ExprFunctionTestCase("make_time(23, 59, null)", "NULL"), + ExprFunctionTestCase("make_time(null, 59, 59.999999999, 1080)", "NULL"), + ExprFunctionTestCase("make_time(23, null, 59.999999999, 1080)", "NULL"), + ExprFunctionTestCase("make_time(23, 59, null, 1080)", "NULL"), + ExprFunctionTestCase("make_time(23, 59, 59.999999999, null)", "NULL"), + ExprFunctionTestCase("make_time(missing, 59,59.999999999, 1080)", "NULL"), + ExprFunctionTestCase("make_time(23, 59, missing, 1080)", "NULL"), + ExprFunctionTestCase("make_time(23, 59, 59.999999999, missing)", "NULL"), + ExprFunctionTestCase("make_time(23, 59, missing, null)", "NULL") + ) + } + + // Error test cases: Invalid arguments + @ParameterizedTest + @ArgumentsSource(InvalidArgCases::class) + fun makeTimeInvalidArgumentTests(query: String) = checkInputThrowingEvaluationException( + input = query, + errorCode = ErrorCode.EVALUATOR_TIME_FIELD_OUT_OF_RANGE, + expectErrorContextValues = mapOf( + Property.LINE_NUMBER to 1L, + Property.COLUMN_NUMBER to 1L + ) + ) + + class InvalidArgCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + "make_time(24, 0, 0.)", + "make_time(23, 60, 0.)", + "make_time(23, 59, 60.)", + "make_time(23, 59, 59.999999999, -1081)", + "make_time(23, 59, 59.999999999, 1081)" + ) + } + + // Error test cases: Invalid argument type + @Test + fun makeTimeInvalidArgTypeTest() = checkInvalidArgType( + funcName = "make_time", + args = listOf( + Argument(1, StaticType.INT, ","), + Argument(2, StaticType.INT, ","), + Argument(3, StaticType.DECIMAL, ","), + Argument(4, StaticType.INT, ")"), + ) + ) + + // Error test cases: Invalid arity + @Test + fun makeTimeInvalidArityTest() = checkInvalidArity( + funcName = "make_time", + minArity = 3, + maxArity = 4 + ) +} \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/functions/SizeEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/functions/SizeEvaluationTest.kt new file mode 100644 index 0000000000..3506e8c36d --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/functions/SizeEvaluationTest.kt @@ -0,0 +1,57 @@ +package org.partiql.lang.eval.builtins.functions + +import org.junit.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource +import org.partiql.lang.eval.EvaluatorTestBase +import org.partiql.lang.eval.builtins.Argument +import org.partiql.lang.eval.builtins.ExprFunctionTestCase +import org.partiql.lang.eval.builtins.checkInvalidArgType +import org.partiql.lang.eval.builtins.checkInvalidArity +import org.partiql.lang.types.StaticType +import org.partiql.lang.util.ArgumentsProviderBase + +class SizeEvaluationTest : EvaluatorTestBase() { + // Pass test cases + @ParameterizedTest + @ArgumentsSource(SizePassCases::class) + fun runPassTests(testCase: ExprFunctionTestCase) = assertEval(testCase.source, testCase.expected) + + class SizePassCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + ExprFunctionTestCase("size(<<1, 2, 3>>)", "3"), + ExprFunctionTestCase("size(<<>>)", "0"), + ExprFunctionTestCase("size(sexp(1,2))", "2"), + ExprFunctionTestCase("size(`(1 2)`)", "2"), + ExprFunctionTestCase("size(sexp())", "0"), + ExprFunctionTestCase("size(`()`)", "0"), + ExprFunctionTestCase("size([1])", "1"), + ExprFunctionTestCase("size(`[1]`)", "1"), + ExprFunctionTestCase("size([])", "0"), + ExprFunctionTestCase("size(`[]`)", "0"), + ExprFunctionTestCase("size({ `a`: 1, `b`: 2, `c`: 3 })", "3"), + ExprFunctionTestCase("size(`{ a: 1, b: 2, c: 3 }`)", "3"), + ExprFunctionTestCase("size({})", "0"), + ExprFunctionTestCase("size(`{}`)", "0"), + ExprFunctionTestCase("size(null)", "null"), + ExprFunctionTestCase("size(missing)", "null") + ) + } + + // Error test cases: Invalid argument type + @Test + fun sizeInvalidArgTypeTest() = checkInvalidArgType( + funcName = "size", + args = listOf( + Argument(1, StaticType.unionOf(StaticType.LIST, StaticType.BAG, StaticType.STRUCT, StaticType.SEXP), ")") + ) + ) + + // Error test cases: Invalid arity + @Test + fun sizeInvalidArityTest() = checkInvalidArity( + funcName = "size", + maxArity = 1, + minArity = 1 + ) +} \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/functions/SubstringEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/functions/SubstringEvaluationTest.kt new file mode 100644 index 0000000000..5a662bac26 --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/functions/SubstringEvaluationTest.kt @@ -0,0 +1,120 @@ +package org.partiql.lang.eval.builtins.functions + +import org.junit.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource +import org.partiql.lang.eval.EvaluatorTestBase +import org.partiql.lang.eval.builtins.Argument +import org.partiql.lang.eval.builtins.ExprFunctionTestCase +import org.partiql.lang.eval.builtins.checkInvalidArgType +import org.partiql.lang.types.StaticType +import org.partiql.lang.util.ArgumentsProviderBase + +class SubstringEvaluationTest : EvaluatorTestBase() { + // Pass test cases + @ParameterizedTest + @ArgumentsSource(SubstringPassCases::class) + fun runPassTests(testCase: ExprFunctionTestCase) = assertEval(testCase.source, testCase.expected) + + class SubstringPassCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + // Old syntax: substring( FROM [FOR ]) + ExprFunctionTestCase("substring('abcdefghi' FROM 0)", "\"abcdefghi\""), + ExprFunctionTestCase("substring('abcdefghi' FROM 1)", "\"abcdefghi\""), + ExprFunctionTestCase("substring('abcdefghi' FROM -1)", "\"abcdefghi\""), + ExprFunctionTestCase("substring('abcdefghi' FROM 3)", "\"cdefghi\""), + ExprFunctionTestCase("substring('abcdefghi' FROM 3 FOR 20)", "\"cdefghi\""), + ExprFunctionTestCase("substring('1234567890' FROM 10)", "\"0\""), + ExprFunctionTestCase("substring('1234567890' FROM 11)", "\"\""), + ExprFunctionTestCase("substring('1234567890' FROM 10 FOR 10)", "\"0\""), + ExprFunctionTestCase("substring('1234567890' FROM 11 FOR 10)", "\"\""), + ExprFunctionTestCase("substring('abcdefghi' FROM 3 FOR 4)", "\"cdef\""), + ExprFunctionTestCase("substring('abcdefghi' FROM -1 FOR 4)", "\"ab\""), + ExprFunctionTestCase("substring('abcdefghi' FROM 1 FOR 1)", "\"a\""), + ExprFunctionTestCase("substring('😁😞😸😸' FROM 2 FOR 2)", "\"😞😸\""), + ExprFunctionTestCase("substring('話家身圧費谷料村能計税金' FROM 3 FOR 5)", "\"身圧費谷料\""), + ExprFunctionTestCase("substring('話家身圧費谷料村能計税金' FROM -3 FOR 6)", "\"話家\""), + ExprFunctionTestCase( + "substring('abcde\u0832fgh' FROM 3 FOR 6)", + "\"cde\u0832fg\"" + ), //Note: U+0832 is a "combining diacritical mark" https://en.wikipedia.org/wiki/Combining_character. + // Even though it is visually merged with the preceding letter when displayed, it still counts as a distinct codepoint. + ExprFunctionTestCase("substring(null FROM 1)", "null"), + ExprFunctionTestCase("substring('abc' FROM null)", "null"), + ExprFunctionTestCase("substring(null FROM 1 FOR 1)", "null"), + ExprFunctionTestCase("substring('abc' FROM null FOR 1)", "null"), + ExprFunctionTestCase("substring('abc' FROM 1 FOR null)", "null"), + ExprFunctionTestCase("substring(missing FROM 1)", "null"), + ExprFunctionTestCase("substring('abc' FROM missing)", "null"), + ExprFunctionTestCase("substring(missing FROM 1 FOR 1)", "null"), + ExprFunctionTestCase("substring('abc' FROM missing FOR 1)", "null"), + ExprFunctionTestCase("substring('' FROM -1)", "\"\""), + ExprFunctionTestCase("substring('' FROM 0)", "\"\""), + ExprFunctionTestCase("substring('' FROM 99)", "\"\""), + ExprFunctionTestCase("substring('' FROM -1 FOR 999)", "\"\""), + ExprFunctionTestCase("substring('' FROM 0 FOR 999)", "\"\""), + ExprFunctionTestCase("substring('' FROM -4 FOR 1)", "\"\""), + ExprFunctionTestCase("substring('1' FROM -4 FOR 1)", "\"\""), + // New syntax: substring(, [, ]) + ExprFunctionTestCase("substring('abcdefghi', 0)", "\"abcdefghi\""), + ExprFunctionTestCase("substring('abcdefghi', 1)", "\"abcdefghi\""), + ExprFunctionTestCase("substring('abcdefghi', 1)", "\"abcdefghi\""), + ExprFunctionTestCase("substring('abcdefghi', -1)", "\"abcdefghi\""), + ExprFunctionTestCase("substring('abcdefghi', 3)", "\"cdefghi\""), + ExprFunctionTestCase("substring('abcdefghi', 3, 20)", "\"cdefghi\""), + ExprFunctionTestCase("substring('1234567890', 10)", "\"0\""), + ExprFunctionTestCase("substring('1234567890', 11)", "\"\""), + ExprFunctionTestCase("substring('1234567890', 10, 10)", "\"0\""), + ExprFunctionTestCase("substring('1234567890', 11, 10)", "\"\""), + ExprFunctionTestCase("substring('abcdefghi', 3, 4)", "\"cdef\""), + ExprFunctionTestCase("substring('abcdefghi', -1, 4)", "\"ab\""), + ExprFunctionTestCase("substring('abcdefghi', 1, 1)", "\"a\""), + ExprFunctionTestCase("substring('😁😞😸😸', 2, 2)", "\"😞😸\""), + ExprFunctionTestCase("substring('話家身圧費谷料村能計税金', 3, 5)", "\"身圧費谷料\""), + ExprFunctionTestCase("substring('話家身圧費谷料村能計税金', -3, 6)", "\"話家\""), + ExprFunctionTestCase("substring('abcde\u0832fgh', 3, 6)", "\"cde\u0832fg\""), + ExprFunctionTestCase("substring(null, 1)", "null"), + ExprFunctionTestCase("substring('abc', null)", "null"), + ExprFunctionTestCase("substring(null, 1, 1)", "null"), + ExprFunctionTestCase("substring('abc', null, 1)", "null"), + ExprFunctionTestCase("substring('abc', 1, null)", "null"), + ExprFunctionTestCase("substring(missing, 1)", "null"), + ExprFunctionTestCase("substring('abc', missing)", "null"), + ExprFunctionTestCase("substring(missing, 1, 1)", "null"), + ExprFunctionTestCase("substring('abc', missing, 1)", "null"), + ExprFunctionTestCase("substring('abc', 1, missing)", "null"), + ExprFunctionTestCase("substring('', -1)", "\"\""), + ExprFunctionTestCase("substring('', 0)", "\"\""), + ExprFunctionTestCase("substring('', 99)", "\"\""), + ExprFunctionTestCase("substring('', -1, 999)", "\"\""), + ExprFunctionTestCase("substring('', 0, 999)", "\"\""), + ExprFunctionTestCase("substring('', -4, 1)", "\"\""), + ExprFunctionTestCase("substring('1', -4, 1)", "\"\"") + ) + } + + // Error test cases: Invalid argument type + // Old syntax + @Test + fun substringInvalidArgTypeTest1() = checkInvalidArgType( + funcName = "substring", + args = listOf( + Argument(1, StaticType.STRING, " from "), + Argument(2, StaticType.INT, " for "), + Argument(3, StaticType.INT, ")") + ) + ) + + // New syntax + @Test + fun substringInvalidArgTypeTest2() = checkInvalidArgType( + funcName = "substring", + args = listOf( + Argument(1, StaticType.STRING, ","), + Argument(2, StaticType.INT, ","), + Argument(3, StaticType.INT, ")") + ) + ) + + // The invalid arity check is considered as syntax error and already done in the ParserErrorsTest.kt +} diff --git a/lang/test/org/partiql/lang/eval/builtins/functions/ToStringEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/functions/ToStringEvaluationTest.kt new file mode 100644 index 0000000000..57e520d2e3 --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/functions/ToStringEvaluationTest.kt @@ -0,0 +1,99 @@ +package org.partiql.lang.eval.builtins.functions + +import org.junit.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource +import org.partiql.lang.errors.ErrorCode +import org.partiql.lang.errors.Property +import org.partiql.lang.eval.EvaluatorTestBase +import org.partiql.lang.eval.builtins.Argument +import org.partiql.lang.eval.builtins.ExprFunctionTestCase +import org.partiql.lang.eval.builtins.checkInvalidArgType +import org.partiql.lang.eval.builtins.checkInvalidArity +import org.partiql.lang.types.StaticType +import org.partiql.lang.util.ArgumentsProviderBase +import org.partiql.lang.util.to + +class ToStringEvaluationTest : EvaluatorTestBase() { + // Pass test cases + @ParameterizedTest + @ArgumentsSource(ToStringPassCases::class) + fun runPassTests(testCase: ExprFunctionTestCase) = assertEval(testCase.source, testCase.expected) + + class ToStringPassCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + // Note that the amount of testing here is a bit on the light side because most of the testing for the formatting + // functionality behind `to_string` is in TimestampTemporalAccessorTests. + ExprFunctionTestCase("to_string(`0500-03-09`, 'MM/dd/yyyy')", "\"03/09/0500\""), + ExprFunctionTestCase("to_string(`0500-03-09`, 'M/d/y')", "\"3/9/500\""), + ExprFunctionTestCase("to_string(`0001-03-09`, 'MM/dd/yyyy')", "\"03/09/0001\""), + ExprFunctionTestCase("to_string(`0001-03-09`, 'M/d/y')", "\"3/9/1\""), + ExprFunctionTestCase("to_string(`9999-03-09`, 'MM/dd/yyyy')", "\"03/09/9999\""), + ExprFunctionTestCase("to_string(`9999-03-09`, 'M/d/y')", "\"3/9/9999\""), + ExprFunctionTestCase("to_string(`0001-03-09`, 'y')", "\"1\""), + ExprFunctionTestCase("to_string(`9999-03-09`, null)", "null"), + ExprFunctionTestCase("to_string(null, 'M/d/y')", "null"), + ExprFunctionTestCase("to_string(`9999-03-09`, missing)", "null"), + ExprFunctionTestCase("to_string(missing, 'M/d/y')", "null"), + ExprFunctionTestCase("to_string(`1969-07-20T20:18Z`, 'MMMM d, y')", "\"July 20, 1969\""), + ExprFunctionTestCase("to_string(`1969-07-20T20:18Z`, 'MMM d, yyyy')", "\"Jul 20, 1969\""), + ExprFunctionTestCase("to_string(`1969-07-20T20:18Z`, 'M-d-yy')", "\"7-20-69\""), + ExprFunctionTestCase("to_string(`1969-07-20T20:18Z`, 'MM-d-y')", "\"07-20-1969\""), + ExprFunctionTestCase("to_string(`1969-07-20T20:18Z`, 'MMMM d, y h:m a')", "\"July 20, 1969 8:18 PM\""), + ExprFunctionTestCase("to_string(`1969-07-20T20:18Z`, 'y-MM-dd''T''H:m:ssX')", "\"1969-07-20T20:18:00Z\""), + ExprFunctionTestCase("to_string(`1969-07-20T20:18+08:00`, 'y-MM-dd''T''H:m:ssX')", "\"1969-07-20T20:18:00+08\""), + ExprFunctionTestCase("to_string(`1969-07-20T20:18+08:00`, 'y-MM-dd''T''H:m:ssXXXX')", "\"1969-07-20T20:18:00+0800\""), + ExprFunctionTestCase("to_string(`1969-07-20T20:18+08:00`, 'y-MM-dd''T''H:m:ssXXXXX')", "\"1969-07-20T20:18:00+08:00\"") + ) + } + + // Error test cases: Invalid arguments + data class InvalidArgTestCase( + val source: String, + val invalidTimeFormatPattern: String + ) + + @ParameterizedTest + @ArgumentsSource(InvalidArgCases::class) + fun toStringInvalidArgumentTests(testCase: InvalidArgTestCase) = checkInputThrowingEvaluationException( + testCase.source, + ErrorCode.EVALUATOR_INVALID_TIMESTAMP_FORMAT_PATTERN, + mapOf( + Property.LINE_NUMBER to 1L, + Property.COLUMN_NUMBER to 1L, + Property.TIMESTAMP_FORMAT_PATTERN to testCase.invalidTimeFormatPattern + ), + expectedPermissiveModeResult = "MISSING" + ) + + class InvalidArgCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + InvalidArgTestCase("to_string(`2017-01-01`, 'b')", "b"), + + //Symbol 'z' is known to Java's DateTimeFormatter but is not handled by TimestampTemporalAccessor + InvalidArgTestCase("to_string(`2017-01-01`, 'Y')", "Y"), + + //Symbol 'VV' is known to Java's DateTimeFormatter but is not handled by TimestampTemporalAccessor + //*and* causes a different exception to be thrown by DateTimeFormatter.format() than 'z' + InvalidArgTestCase("to_string(`2017-01-01`, 'VV')", "VV") + ) + } + + // Error test cases: Invalid argument type + @Test + fun toStringInvalidArgTypeTest() = checkInvalidArgType( + funcName = "to_string", + args = listOf( + Argument(1, StaticType.TIMESTAMP, ","), + Argument(2, StaticType.STRING, ")") + ) + ) + + // Error test cases: Invalid arity + @Test + fun toStringInvalidArityTest() = checkInvalidArity( + funcName = "to_string", + maxArity = 2, + minArity = 2 + ) +} \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/functions/ToTimestampEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/functions/ToTimestampEvaluationTest.kt new file mode 100644 index 0000000000..ffa93cb30e --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/functions/ToTimestampEvaluationTest.kt @@ -0,0 +1,128 @@ +package org.partiql.lang.eval.builtins.functions + +import org.junit.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource +import org.partiql.lang.errors.ErrorCode +import org.partiql.lang.errors.Property +import org.partiql.lang.eval.EvaluatorTestBase +import org.partiql.lang.eval.builtins.Argument +import org.partiql.lang.eval.builtins.ExprFunctionTestCase +import org.partiql.lang.eval.builtins.checkInvalidArgType +import org.partiql.lang.eval.builtins.checkInvalidArity +import org.partiql.lang.types.StaticType +import org.partiql.lang.util.ArgumentsProviderBase +import org.partiql.lang.util.to +import kotlin.math.min + +class ToTimestampEvaluationTest : EvaluatorTestBase() { + // Pass test cases + @ParameterizedTest + @ArgumentsSource(ToTimestampPassCases::class) + fun runPassTests(testCase: ExprFunctionTestCase) = assertEval(testCase.source, testCase.expected) + + class ToTimestampPassCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + ExprFunctionTestCase("to_timestamp('1969-07-20T20:18:00Z')", "1969-07-20T20:18:00Z"), + ExprFunctionTestCase("to_timestamp('July 20, 1969', 'MMMM d, y')", "1969-07-20T"), + ExprFunctionTestCase("to_timestamp('Jul 20, 1969', 'MMM d, yyyy')", "1969-07-20T"), + ExprFunctionTestCase("to_timestamp('1969-07-20T20:18Z', 'yyyy-MM-dd''T''HH:mmX')", "1969-07-20T20:18Z"), + ExprFunctionTestCase("to_timestamp('July 20, 1969 8:18 PM', 'MMMM d, y h:m a')", "1969-07-20T20:18-00:00"), + ExprFunctionTestCase("to_timestamp('1969-07-20T20:18:00Z', 'yyyy-MM-dd''T''H:m:ssX')", "1969-07-20T20:18:00Z"), + ExprFunctionTestCase("to_timestamp('1969-07-20T20:18:01+08', 'yyyy-MM-dd''T''H:m:ssX')", "1969-07-20T20:18:01+08:00"), + ExprFunctionTestCase( + "to_timestamp('1969-07-20T20:18:02+0800', 'yyyy-MM-dd''T''H:m:ssXXXX')", + "1969-07-20T20:18:02+08:00" + ), + ExprFunctionTestCase( + "to_timestamp('1969-07-20T20:18:03+08:00', 'yyyy-MM-dd''T''H:m:ssXXXXX')", + "1969-07-20T20:18:03+08:00" + ), + ExprFunctionTestCase("to_timestamp('1969-07-20T20:18:00Z')", "1969-07-20T20:18:00Z"), + ExprFunctionTestCase("to_timestamp('1969-07-20T20:18:03+08:00')", "1969-07-20T20:18:03+08:00"), + ExprFunctionTestCase("to_timestamp(null)", "null"), + ExprFunctionTestCase("to_timestamp(null, 'M-d-yyyy')", "null"), + ExprFunctionTestCase("to_timestamp('07-20-1969', null)", "null"), + ExprFunctionTestCase("to_timestamp(null, null)", "null"), + ExprFunctionTestCase("to_timestamp(missing)", "null"), + ExprFunctionTestCase("to_timestamp(missing, 'M-d-yyyy')", "null"), + ExprFunctionTestCase("to_timestamp('07-20-1969', missing)", "null"), + ExprFunctionTestCase("to_timestamp(null, null)", "null") + ) + } + + // Invalid arguments + @Test + fun to_timestamp_invalid_ion_timestamp() { + checkInputThrowingEvaluationException( + "to_timestamp('not a valid timestamp')", + ErrorCode.EVALUATOR_ION_TIMESTAMP_PARSE_FAILURE, + mapOf( + Property.LINE_NUMBER to 1L, + Property.COLUMN_NUMBER to 1L + ), + expectedPermissiveModeResult = "MISSING" + ) + } + + @Test + fun to_timestamp_empty_format_pattern() { + checkInputThrowingEvaluationException( + "to_timestamp('doesnt matter', '')", + ErrorCode.EVALUATOR_INCOMPLETE_TIMESTAMP_FORMAT_PATTERN, + mapOf( + Property.LINE_NUMBER to 1L, + Property.COLUMN_NUMBER to 1L, + Property.TIMESTAMP_FORMAT_PATTERN to "", + Property.TIMESTAMP_FORMAT_PATTERN_FIELDS to "YEAR" + ), + expectedPermissiveModeResult = "MISSING" + ) + } + + @Test + fun to_timestamp_invalid_format_pattern() { + checkInputThrowingEvaluationException( + "to_timestamp('doesnt matter', 'asdfasdfasdf')", + ErrorCode.EVALUATOR_INVALID_TIMESTAMP_FORMAT_PATTERN_TOKEN, + mapOf( + Property.LINE_NUMBER to 1L, + Property.COLUMN_NUMBER to 1L, + Property.TIMESTAMP_FORMAT_PATTERN to "asdfasdfasdf" + ), + expectedPermissiveModeResult = "MISSING" + ) + } + + @Test + fun to_timestamp_invalid_timestamp() { + checkInputThrowingEvaluationException( + "to_timestamp('asdf', 'yyyy')", + ErrorCode.EVALUATOR_CUSTOM_TIMESTAMP_PARSE_FAILURE, + mapOf( + Property.LINE_NUMBER to 1L, + Property.COLUMN_NUMBER to 1L, + Property.TIMESTAMP_FORMAT_PATTERN to "yyyy" + ), + expectedPermissiveModeResult = "MISSING" + ) + } + + // Error test cases: Invalid argument type + @Test + fun toTimestampInvalidArgTypeTest() = checkInvalidArgType( + funcName = "to_timestamp", + args = listOf( + Argument(1, StaticType.STRING, ","), + Argument(2, StaticType.STRING, ")") + ) + ) + + // Error test cases: Invalid arity + @Test + fun toTimestampInvalidArityTest() = checkInvalidArity( + funcName = "to_timestamp", + minArity = 1, + maxArity = 2 + ) +} \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/functions/TrimEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/functions/TrimEvaluationTest.kt new file mode 100644 index 0000000000..d39e6407f2 --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/functions/TrimEvaluationTest.kt @@ -0,0 +1,102 @@ +package org.partiql.lang.eval.builtins.functions + +import org.junit.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource +import org.partiql.lang.eval.EvaluatorTestBase +import org.partiql.lang.eval.builtins.Argument +import org.partiql.lang.eval.builtins.ExprFunctionTestCase +import org.partiql.lang.eval.builtins.checkInvalidArgType +import org.partiql.lang.types.StaticType +import org.partiql.lang.util.ArgumentsProviderBase + +class TrimEvaluationTest : EvaluatorTestBase() { + // Pass test cases + @ParameterizedTest + @ArgumentsSource(CharLengthPassCases::class) + fun runPassTests(testCase: ExprFunctionTestCase) = assertEval(testCase.source, testCase.expected) + + class CharLengthPassCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + ExprFunctionTestCase("trim(' string ')", "\"string\""), + ExprFunctionTestCase("trim(' string')", "\"string\""), + ExprFunctionTestCase("trim('string ')", "\"string\""), + ExprFunctionTestCase("trim('\tstring\t')", "\"\tstring\t\""), + ExprFunctionTestCase("trim(from ' string ')", "\"string\""), + ExprFunctionTestCase("trim(from ' string')", "\"string\""), + ExprFunctionTestCase("trim(from 'string ')", "\"string\""), + ExprFunctionTestCase("trim(both from ' string ')", "\"string\""), + ExprFunctionTestCase("trim(both from ' string')", "\"string\""), + ExprFunctionTestCase("trim(both from 'string ')", "\"string\""), + ExprFunctionTestCase("trim(leading from ' string ')", "\"string \""), + ExprFunctionTestCase("trim(leading from ' string')", "\"string\""), + ExprFunctionTestCase("trim(leading from 'string ')", "\"string \""), + ExprFunctionTestCase("trim(trailing from ' string ')", "\" string\""), + ExprFunctionTestCase("trim(trailing from ' string')", "\" string\""), + ExprFunctionTestCase("trim(trailing from 'string ')", "\"string\""), + ExprFunctionTestCase("trim(both ' -=' from '- =string =- ')", "\"string\""), + ExprFunctionTestCase("trim(both ' -=' from '--=== -= -= -= string')", "\"string\""), + ExprFunctionTestCase("trim(both ' -=' from 'string ==- = -=- - ---------- ')", "\"string\""), + ExprFunctionTestCase("trim(both ' ' from ' ')", "\"\""), + ExprFunctionTestCase("trim(leading ' ' from ' ')", "\"\""), + ExprFunctionTestCase("trim(trailing ' ' from ' ')", "\"\""), + ExprFunctionTestCase( + "trim(both '💩' from '💩💩💩💩💩💩💩💩💩💩😁😞😸😸💩💩💩💩💩💩💩💩💩💩💩💩💩💩')", + "\"😁😞😸😸\"" + ), + ExprFunctionTestCase("trim(' 😁😞😸😸 ')", "\"😁😞😸😸\""), + ExprFunctionTestCase("trim(both '話 ' from '話話 話話話話話話費谷料村能話話話話 話話話話 ')", "\"費谷料村能\""), + ExprFunctionTestCase("trim(' a'||'b ')", "\"ab\""), + ExprFunctionTestCase( + """ + SELECT trim(both el from ' 1ab1 ') AS trimmed + FROM <<' 1'>> AS el + """.trimIndent(), + "[{trimmed:\"ab\"}]" + ), + ExprFunctionTestCase("trim('12' from '1212b1212')", "\"b\""), + ExprFunctionTestCase("trim('12' from '1212b')", "\"b\""), + ExprFunctionTestCase("trim('12' from 'b1212')", "\"b\""), + ExprFunctionTestCase("trim(both null from '')", "null"), + ExprFunctionTestCase("trim(both '' from null)", "null"), + ExprFunctionTestCase("trim(null from '')", "null"), + ExprFunctionTestCase("trim('' from null)", "null"), + ExprFunctionTestCase("trim(null)", "null"), + ExprFunctionTestCase("trim(both missing from '')", "null"), + ExprFunctionTestCase("trim(both '' from missing)", "null"), + ExprFunctionTestCase("trim(missing from '')", "null"), + ExprFunctionTestCase("trim('' from missing)", "null"), + ExprFunctionTestCase("trim(missing)", "null") + ) + } + + // Error test cases: Invalid argument type + @Test + fun trimInvalidArgTypeTest1() = checkInvalidArgType( + funcName = "trim", + args = listOf( + Argument(1, StaticType.unionOf(StaticType.STRING, StaticType.SYMBOL), ")") + ) + ) + + @Test + fun trimInvalidArgTypeTest2() = checkInvalidArgType( + funcName = "trim", + syntaxSuffix = "(trailing ", + args = listOf( + Argument(2, StaticType.STRING, " from "), + Argument(3, StaticType.STRING, ")") + ) + ) + + @Test + fun trimInvalidArgTypeTest3() = checkInvalidArgType( + funcName = "trim", + syntaxSuffix = "(from ", + args = listOf( + Argument(1, StaticType.unionOf(StaticType.STRING, StaticType.SYMBOL), ")") + ) + ) + + // The invalid arity check is considered as syntax error and already done in the ParserErrorsTest.kt +} \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/functions/UnixTimestampFunctionTest.kt b/lang/test/org/partiql/lang/eval/builtins/functions/UnixTimestampFunctionTest.kt new file mode 100644 index 0000000000..9a7d3cbdf3 --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/functions/UnixTimestampFunctionTest.kt @@ -0,0 +1,68 @@ +package org.partiql.lang.eval.builtins.functions + +import org.junit.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource +import org.partiql.lang.eval.EvaluatorTestBase +import org.partiql.lang.eval.builtins.Argument +import org.partiql.lang.eval.builtins.buildSessionWithNow +import org.partiql.lang.eval.builtins.ExprFunctionTestCase +import org.partiql.lang.eval.builtins.checkInvalidArgType +import org.partiql.lang.eval.builtins.checkInvalidArity +import org.partiql.lang.types.StaticType +import org.partiql.lang.util.ArgumentsProviderBase + +class UnixTimestampFunctionTest : EvaluatorTestBase() { + // Pass test cases + @ParameterizedTest + @ArgumentsSource(UnixTimestampPassCases::class) + fun runPassTests(tc: ExprFunctionTestCase) = assertEval(tc.source, tc.expected, tc.session) + + class UnixTimestampPassCases : ArgumentsProviderBase() { + private val epoch2020 = "1577836800" + private val epoch2020Decimal = "1577836800." + + override fun getParameters(): List = listOf( + // No args + ExprFunctionTestCase("unix_timestamp()", "0", buildSessionWithNow(0, 0)), // now = 0 + ExprFunctionTestCase("unix_timestamp()", "0", buildSessionWithNow(1, 0)), // now = 1ms + ExprFunctionTestCase("unix_timestamp()", "0", buildSessionWithNow(999, 0)), // now = 999ms + ExprFunctionTestCase("unix_timestamp()", "1", buildSessionWithNow(1000, 0)), // now = 1s + ExprFunctionTestCase("unix_timestamp()", "1", buildSessionWithNow(1001, 0)), // now = 1001ms + // time before the last epoch + ExprFunctionTestCase("unix_timestamp(`1969T`)", "-31536000"), + ExprFunctionTestCase("unix_timestamp(`1969-12-31T23:59:59.999Z`)", "-0.001"), + // exactly the last epoch + ExprFunctionTestCase("unix_timestamp(`1970T`)", "0"), + ExprFunctionTestCase("unix_timestamp(`1970-01-01T00:00:00.000Z`)", "0."), + // whole number unix epoch + ExprFunctionTestCase("unix_timestamp(`2020T`)", epoch2020), + ExprFunctionTestCase("unix_timestamp(`2020-01T`)", epoch2020), + ExprFunctionTestCase("unix_timestamp(`2020-01-01T`)", epoch2020), + ExprFunctionTestCase("unix_timestamp(`2020-01-01T00:00Z`)", epoch2020), + ExprFunctionTestCase("unix_timestamp(`2020-01-01T00:00:00Z`)", epoch2020), + // decimal unix epoch + ExprFunctionTestCase("unix_timestamp(`2020-01-01T00:00:00.0Z`)", epoch2020Decimal), + ExprFunctionTestCase("unix_timestamp(`2020-01-01T00:00:00.00Z`)", epoch2020Decimal), + ExprFunctionTestCase("unix_timestamp(`2020-01-01T00:00:00.000Z`)", epoch2020Decimal), + ExprFunctionTestCase("unix_timestamp(`2020-01-01T00:00:00.100Z`)", "1577836800.1") + ) + } + + // Error test cases: Invalid argument type + @Test + fun unixTimestampInvalidArgTypeTest() = checkInvalidArgType( + funcName = "unix_timestamp", + args = listOf( + Argument(1, StaticType.TIMESTAMP, ")") + ) + ) + + // Error test cases: Invalid arity + @Test + fun unixTimestampInvalidArityTest() = checkInvalidArity( + funcName = "unix_timestamp", + minArity = 0, + maxArity = 1 + ) +} diff --git a/lang/test/org/partiql/lang/eval/builtins/functions/UpperEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/functions/UpperEvaluationTest.kt new file mode 100644 index 0000000000..1c1bbabe7f --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/functions/UpperEvaluationTest.kt @@ -0,0 +1,53 @@ +package org.partiql.lang.eval.builtins.functions + +import org.junit.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource +import org.partiql.lang.eval.EvaluatorTestBase +import org.partiql.lang.eval.builtins.Argument +import org.partiql.lang.eval.builtins.ExprFunctionTestCase +import org.partiql.lang.eval.builtins.checkInvalidArgType +import org.partiql.lang.eval.builtins.checkInvalidArity +import org.partiql.lang.types.StaticType +import org.partiql.lang.util.ArgumentsProviderBase + +class UpperEvaluationTest : EvaluatorTestBase() { + // Pass test cases + @ParameterizedTest + @ArgumentsSource(UpperPassCases::class) + fun runPassTests(testCase: ExprFunctionTestCase) = assertEval(testCase.source, testCase.expected) + + class UpperPassCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + ExprFunctionTestCase("upper('')", "\"\""), + ExprFunctionTestCase("upper(`a`)", "\"A\""), + ExprFunctionTestCase("upper(`'a'`)", "\"A\""), + ExprFunctionTestCase("upper('a')", "\"A\""), + ExprFunctionTestCase("upper(`\"a\"`)", "\"A\""), + ExprFunctionTestCase("upper('abcdef')", "\"ABCDEF\""), + ExprFunctionTestCase("upper('ABCDEF')", "\"ABCDEF\""), + ExprFunctionTestCase("upper(null)", "null"), + ExprFunctionTestCase("upper(missing)", "null"), + ExprFunctionTestCase("upper('123\$%(*&')", "\"123\$%(*&\""), + ExprFunctionTestCase("upper('ȴȵ💩z💋')", "\"ȴȵ💩Z💋\""), + ExprFunctionTestCase("upper('話家身圧費谷料村能計税金')", "\"話家身圧費谷料村能計税金\"") + ) + } + + // Error test cases: Invalid argument type + @Test + fun upperInvalidArgTypeTest() = checkInvalidArgType( + funcName = "upper", + args = listOf( + Argument(1, StaticType.unionOf(StaticType.STRING, StaticType.SYMBOL), ")") + ) + ) + + // Error test cases: Invalid arity + @Test + fun upperInvalidArityTest() = checkInvalidArity( + funcName = "upper", + maxArity = 1, + minArity = 1 + ) +} diff --git a/lang/test/org/partiql/lang/eval/builtins/functions/UtcNowEvaluationTest.kt b/lang/test/org/partiql/lang/eval/builtins/functions/UtcNowEvaluationTest.kt new file mode 100644 index 0000000000..e5f3ad5ccb --- /dev/null +++ b/lang/test/org/partiql/lang/eval/builtins/functions/UtcNowEvaluationTest.kt @@ -0,0 +1,33 @@ +package org.partiql.lang.eval.builtins.functions + +import org.junit.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource +import org.partiql.lang.eval.EvaluatorTestBase +import org.partiql.lang.eval.builtins.ExprFunctionTestCase +import org.partiql.lang.eval.builtins.buildSessionWithNow +import org.partiql.lang.eval.builtins.checkInvalidArity +import org.partiql.lang.util.ArgumentsProviderBase + +class UtcNowEvaluationTest : EvaluatorTestBase() { + // Pass test cases + @ParameterizedTest + @ArgumentsSource(UtcNowPassCases::class) + fun runPassTests(tc: ExprFunctionTestCase) = assertEval(tc.source, tc.expected, tc.session) + + class UtcNowPassCases : ArgumentsProviderBase() { + override fun getParameters(): List = listOf( + ExprFunctionTestCase("utcnow()", "1970-01-01T00:00:00.000Z", buildSessionWithNow(0, 0)), + ExprFunctionTestCase("utcnow()", "1970-01-01T00:00:01.000Z", buildSessionWithNow(1_000, 0)), + ExprFunctionTestCase("utcnow()", "1970-01-01T00:05:00.000Z", buildSessionWithNow(5L * 60 * 1_000, 1)) // 1970-01-01T00:05:01.000+00:01 + ) + } + + // Error test cases: Invalid arity + @Test + fun utcNowInvalidArityTest() = checkInvalidArity( + funcName = "utcnow", + maxArity = 0, + minArity = 0 + ) +} diff --git a/lang/test/org/partiql/lang/eval/builtins/timestamp/TimestampFormatPatternLexerTest.kt b/lang/test/org/partiql/lang/eval/builtins/timestamp/TimestampFormatPatternLexerTest.kt index d1881f1484..71cdc1e668 100644 --- a/lang/test/org/partiql/lang/eval/builtins/timestamp/TimestampFormatPatternLexerTest.kt +++ b/lang/test/org/partiql/lang/eval/builtins/timestamp/TimestampFormatPatternLexerTest.kt @@ -10,7 +10,7 @@ class TimestampFormatPatternLexerTest { private fun pattern(s: String) = Token(TokenType.PATTERN, s) private fun assertTokens(s: String, vararg tokens: Token) = assertEquals(tokens.toList(), - TimestampFormatPatternLexer().tokenize(s)) + TimestampFormatPatternLexer().tokenize(s)) @Test fun singlePatternToken() = assertTokens("y", pattern("y")) diff --git a/lang/test/org/partiql/lang/eval/builtins/timestamp/TimestampFormatPatternParserTest.kt b/lang/test/org/partiql/lang/eval/builtins/timestamp/TimestampFormatPatternParserTest.kt index c98d0e6f25..b6673afff9 100644 --- a/lang/test/org/partiql/lang/eval/builtins/timestamp/TimestampFormatPatternParserTest.kt +++ b/lang/test/org/partiql/lang/eval/builtins/timestamp/TimestampFormatPatternParserTest.kt @@ -1,11 +1,11 @@ package org.partiql.lang.eval.builtins.timestamp -import junitparams.JUnitParamsRunner -import junitparams.Parameters +import org.partiql.lang.util.* +import junitparams.* +import org.junit.* import org.junit.Test -import org.junit.runner.RunWith -import org.partiql.lang.util.softAssert -import kotlin.test.assertEquals +import org.junit.runner.* +import kotlin.test.* @RunWith(JUnitParamsRunner::class) internal class TimestampFormatPatternParserTest { @@ -106,6 +106,7 @@ internal class TimestampFormatPatternParserTest { assertEquals(pair.second, formatPattern.formatItems) } + @Test fun mostPreciseField() { //NOTE: we can't parameterize this unless we want to expose TimestampParser.FormatPatternPrecision as public. @@ -122,6 +123,7 @@ internal class TimestampFormatPatternParserTest { } } + private data class MostPreciseFieldTestCase( val pattern: String, val expectedResult: TimestampField, @@ -180,4 +182,6 @@ internal class TimestampFormatPatternParserTest { //Valid symbols within quotes should not influence the result MostPreciseFieldTestCase("y'M d s'", TimestampField.YEAR), MostPreciseFieldTestCase("y'y'", TimestampField.YEAR)) + + } \ No newline at end of file diff --git a/lang/test/org/partiql/lang/eval/builtins/timestamp/ToTimestampFormatPatternValidationTest.kt b/lang/test/org/partiql/lang/eval/builtins/timestamp/ToTimestampFormatPatternValidationTest.kt index d6107082b0..b8767e40f8 100644 --- a/lang/test/org/partiql/lang/eval/builtins/timestamp/ToTimestampFormatPatternValidationTest.kt +++ b/lang/test/org/partiql/lang/eval/builtins/timestamp/ToTimestampFormatPatternValidationTest.kt @@ -7,10 +7,10 @@ import org.junit.runner.RunWith import org.partiql.lang.errors.ErrorCode import org.partiql.lang.errors.Property import org.partiql.lang.eval.EvaluatorTestBase - import org.partiql.lang.util.sourceLocationProperties import org.partiql.lang.util.to + @RunWith(JUnitParamsRunner::class) class ToTimestampFormatPatternValidationTest : EvaluatorTestBase() {