diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnBetween.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnBetween.kt index f0c0778fd3..a60d7d6469 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnBetween.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnBetween.kt @@ -7,6 +7,16 @@ import org.partiql.spi.fn.Fn import org.partiql.spi.fn.FnExperimental import org.partiql.spi.fn.FnParameter import org.partiql.spi.fn.FnSignature +import org.partiql.value.ClobValue +import org.partiql.value.DateValue +import org.partiql.value.DecimalValue +import org.partiql.value.Float32Value +import org.partiql.value.Float64Value +import org.partiql.value.Int16Value +import org.partiql.value.Int32Value +import org.partiql.value.Int64Value +import org.partiql.value.Int8Value +import org.partiql.value.IntValue import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental import org.partiql.value.PartiQLValueType.BOOL @@ -24,6 +34,11 @@ import org.partiql.value.PartiQLValueType.STRING import org.partiql.value.PartiQLValueType.SYMBOL import org.partiql.value.PartiQLValueType.TIME import org.partiql.value.PartiQLValueType.TIMESTAMP +import org.partiql.value.TextValue +import org.partiql.value.TimeValue +import org.partiql.value.TimestampValue +import org.partiql.value.boolValue +import org.partiql.value.check @OptIn(PartiQLValueExperimental::class, FnExperimental::class) internal object Fn_BETWEEN__INT8_INT8_INT8__BOOL : Fn { @@ -41,7 +56,10 @@ internal object Fn_BETWEEN__INT8_INT8_INT8__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function between not implemented") + val value = args[0].check().value!! + val lower = args[1].check().value!! + val upper = args[2].check().value!! + return boolValue(value in lower..upper) } } @@ -61,7 +79,10 @@ internal object Fn_BETWEEN__INT16_INT16_INT16__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function between not implemented") + val value = args[0].check().value!! + val lower = args[1].check().value!! + val upper = args[2].check().value!! + return boolValue(value in lower..upper) } } @@ -81,7 +102,10 @@ internal object Fn_BETWEEN__INT32_INT32_INT32__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function between not implemented") + val value = args[0].check().value!! + val lower = args[1].check().value!! + val upper = args[2].check().value!! + return boolValue(value in lower..upper) } } @@ -101,7 +125,10 @@ internal object Fn_BETWEEN__INT64_INT64_INT64__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function between not implemented") + val value = args[0].check().value!! + val lower = args[1].check().value!! + val upper = args[2].check().value!! + return boolValue(value in lower..upper) } } @@ -121,7 +148,10 @@ internal object Fn_BETWEEN__INT_INT_INT__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function between not implemented") + val value = args[0].check().value!! + val lower = args[1].check().value!! + val upper = args[2].check().value!! + return boolValue(value in lower..upper) } } @@ -141,7 +171,10 @@ internal object Fn_BETWEEN__DECIMAL_ARBITRARY_DECIMAL_ARBITRARY_DECIMAL_ARBITRAR ) override fun invoke(args: Array): PartiQLValue { - TODO("Function between not implemented") + val value = args[0].check().value!! + val lower = args[1].check().value!! + val upper = args[2].check().value!! + return boolValue(value in lower..upper) } } @@ -161,7 +194,10 @@ internal object Fn_BETWEEN__FLOAT32_FLOAT32_FLOAT32__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function between not implemented") + val value = args[0].check().value!! + val lower = args[1].check().value!! + val upper = args[2].check().value!! + return boolValue(value in lower..upper) } } @@ -181,7 +217,10 @@ internal object Fn_BETWEEN__FLOAT64_FLOAT64_FLOAT64__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function between not implemented") + val value = args[0].check().value!! + val lower = args[1].check().value!! + val upper = args[2].check().value!! + return boolValue(value in lower..upper) } } @@ -201,7 +240,10 @@ internal object Fn_BETWEEN__STRING_STRING_STRING__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function between not implemented") + val value = args[0].check>().value!! + val lower = args[1].check>().value!! + val upper = args[2].check>().value!! + return boolValue(value in lower..upper) } } @@ -221,7 +263,10 @@ internal object Fn_BETWEEN__SYMBOL_SYMBOL_SYMBOL__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function between not implemented") + val value = args[0].check>().value!! + val lower = args[1].check>().value!! + val upper = args[2].check>().value!! + return boolValue(value in lower..upper) } } @@ -241,7 +286,10 @@ internal object Fn_BETWEEN__CLOB_CLOB_CLOB__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function between not implemented") + val value = args[0].check().value!!.toString(Charsets.UTF_8) + val lower = args[1].check().value!!.toString(Charsets.UTF_8) + val upper = args[2].check().value!!.toString(Charsets.UTF_8) + return boolValue(value in lower..upper) } } @@ -261,7 +309,10 @@ internal object Fn_BETWEEN__DATE_DATE_DATE__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function between not implemented") + val value = args[0].check().value!! + val lower = args[1].check().value!! + val upper = args[2].check().value!! + return boolValue(value in lower..upper) } } @@ -281,7 +332,10 @@ internal object Fn_BETWEEN__TIME_TIME_TIME__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function between not implemented") + val value = args[0].check().value!! + val lower = args[1].check().value!! + val upper = args[2].check().value!! + return boolValue(value in lower..upper) } } @@ -301,6 +355,9 @@ internal object Fn_BETWEEN__TIMESTAMP_TIMESTAMP_TIMESTAMP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function between not implemented") + val value = args[0].check().value!! + val lower = args[1].check().value!! + val upper = args[2].check().value!! + return boolValue(value in lower..upper) } } diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnInCollection.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnInCollection.kt index b401f665cc..eca17cbde4 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnInCollection.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnInCollection.kt @@ -3,10 +3,30 @@ package org.partiql.spi.connector.sql.builtins +import org.partiql.errors.TypeCheckException import org.partiql.spi.fn.Fn import org.partiql.spi.fn.FnExperimental import org.partiql.spi.fn.FnParameter import org.partiql.spi.fn.FnSignature +import org.partiql.value.BagValue +import org.partiql.value.BinaryValue +import org.partiql.value.BlobValue +import org.partiql.value.BoolValue +import org.partiql.value.ByteValue +import org.partiql.value.CharValue +import org.partiql.value.ClobValue +import org.partiql.value.DateValue +import org.partiql.value.DecimalValue +import org.partiql.value.Float32Value +import org.partiql.value.Float64Value +import org.partiql.value.Int16Value +import org.partiql.value.Int32Value +import org.partiql.value.Int64Value +import org.partiql.value.Int8Value +import org.partiql.value.IntValue +import org.partiql.value.IntervalValue +import org.partiql.value.ListValue +import org.partiql.value.NullValue import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental import org.partiql.value.PartiQLValueType.ANY @@ -37,6 +57,14 @@ import org.partiql.value.PartiQLValueType.STRUCT import org.partiql.value.PartiQLValueType.SYMBOL import org.partiql.value.PartiQLValueType.TIME import org.partiql.value.PartiQLValueType.TIMESTAMP +import org.partiql.value.SexpValue +import org.partiql.value.StringValue +import org.partiql.value.StructValue +import org.partiql.value.SymbolValue +import org.partiql.value.TimeValue +import org.partiql.value.TimestampValue +import org.partiql.value.boolValue +import org.partiql.value.check @OptIn(PartiQLValueExperimental::class, FnExperimental::class) internal object Fn_IN_COLLECTION__ANY_BAG__BOOL : Fn { @@ -53,7 +81,16 @@ internal object Fn_IN_COLLECTION__ANY_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0] + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -72,7 +109,16 @@ internal object Fn_IN_COLLECTION__ANY_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0] + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -91,7 +137,16 @@ internal object Fn_IN_COLLECTION__ANY_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0] + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -110,7 +165,16 @@ internal object Fn_IN_COLLECTION__BOOL_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -129,7 +193,16 @@ internal object Fn_IN_COLLECTION__BOOL_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -148,7 +221,16 @@ internal object Fn_IN_COLLECTION__BOOL_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -167,7 +249,16 @@ internal object Fn_IN_COLLECTION__INT8_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -186,7 +277,16 @@ internal object Fn_IN_COLLECTION__INT8_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -205,7 +305,16 @@ internal object Fn_IN_COLLECTION__INT8_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -224,7 +333,16 @@ internal object Fn_IN_COLLECTION__INT16_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -243,7 +361,16 @@ internal object Fn_IN_COLLECTION__INT16_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -262,7 +389,16 @@ internal object Fn_IN_COLLECTION__INT16_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -281,7 +417,16 @@ internal object Fn_IN_COLLECTION__INT32_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -300,7 +445,16 @@ internal object Fn_IN_COLLECTION__INT32_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -319,7 +473,16 @@ internal object Fn_IN_COLLECTION__INT32_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -338,7 +501,16 @@ internal object Fn_IN_COLLECTION__INT64_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -357,7 +529,16 @@ internal object Fn_IN_COLLECTION__INT64_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -376,7 +557,16 @@ internal object Fn_IN_COLLECTION__INT64_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -395,7 +585,16 @@ internal object Fn_IN_COLLECTION__INT_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -414,7 +613,16 @@ internal object Fn_IN_COLLECTION__INT_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -433,7 +641,16 @@ internal object Fn_IN_COLLECTION__INT_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -452,7 +669,16 @@ internal object Fn_IN_COLLECTION__DECIMAL_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -471,7 +697,16 @@ internal object Fn_IN_COLLECTION__DECIMAL_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -490,7 +725,16 @@ internal object Fn_IN_COLLECTION__DECIMAL_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -509,7 +753,16 @@ internal object Fn_IN_COLLECTION__DECIMAL_ARBITRARY_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -528,7 +781,16 @@ internal object Fn_IN_COLLECTION__DECIMAL_ARBITRARY_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -547,7 +809,16 @@ internal object Fn_IN_COLLECTION__DECIMAL_ARBITRARY_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -566,7 +837,16 @@ internal object Fn_IN_COLLECTION__FLOAT32_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -585,7 +865,16 @@ internal object Fn_IN_COLLECTION__FLOAT32_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -604,7 +893,16 @@ internal object Fn_IN_COLLECTION__FLOAT32_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -623,7 +921,16 @@ internal object Fn_IN_COLLECTION__FLOAT64_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -642,7 +949,16 @@ internal object Fn_IN_COLLECTION__FLOAT64_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -661,7 +977,16 @@ internal object Fn_IN_COLLECTION__FLOAT64_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -680,7 +1005,16 @@ internal object Fn_IN_COLLECTION__CHAR_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -699,7 +1033,16 @@ internal object Fn_IN_COLLECTION__CHAR_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -718,7 +1061,16 @@ internal object Fn_IN_COLLECTION__CHAR_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -737,7 +1089,16 @@ internal object Fn_IN_COLLECTION__STRING_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -756,7 +1117,16 @@ internal object Fn_IN_COLLECTION__STRING_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -775,7 +1145,16 @@ internal object Fn_IN_COLLECTION__STRING_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -794,7 +1173,16 @@ internal object Fn_IN_COLLECTION__SYMBOL_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -813,7 +1201,16 @@ internal object Fn_IN_COLLECTION__SYMBOL_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -832,7 +1229,16 @@ internal object Fn_IN_COLLECTION__SYMBOL_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -851,7 +1257,16 @@ internal object Fn_IN_COLLECTION__BINARY_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -870,7 +1285,16 @@ internal object Fn_IN_COLLECTION__BINARY_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -889,7 +1313,16 @@ internal object Fn_IN_COLLECTION__BINARY_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -908,7 +1341,16 @@ internal object Fn_IN_COLLECTION__BYTE_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -927,7 +1369,16 @@ internal object Fn_IN_COLLECTION__BYTE_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -946,7 +1397,16 @@ internal object Fn_IN_COLLECTION__BYTE_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -965,7 +1425,16 @@ internal object Fn_IN_COLLECTION__BLOB_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -984,7 +1453,16 @@ internal object Fn_IN_COLLECTION__BLOB_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1003,7 +1481,16 @@ internal object Fn_IN_COLLECTION__BLOB_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1022,7 +1509,16 @@ internal object Fn_IN_COLLECTION__CLOB_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1041,7 +1537,16 @@ internal object Fn_IN_COLLECTION__CLOB_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1060,7 +1565,16 @@ internal object Fn_IN_COLLECTION__CLOB_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1079,7 +1593,16 @@ internal object Fn_IN_COLLECTION__DATE_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1098,7 +1621,16 @@ internal object Fn_IN_COLLECTION__DATE_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1117,7 +1649,16 @@ internal object Fn_IN_COLLECTION__DATE_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1136,7 +1677,16 @@ internal object Fn_IN_COLLECTION__TIME_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1155,7 +1705,16 @@ internal object Fn_IN_COLLECTION__TIME_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1174,7 +1733,16 @@ internal object Fn_IN_COLLECTION__TIME_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1193,7 +1761,16 @@ internal object Fn_IN_COLLECTION__TIMESTAMP_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1212,7 +1789,16 @@ internal object Fn_IN_COLLECTION__TIMESTAMP_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1231,7 +1817,16 @@ internal object Fn_IN_COLLECTION__TIMESTAMP_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1250,7 +1845,16 @@ internal object Fn_IN_COLLECTION__INTERVAL_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1269,7 +1873,16 @@ internal object Fn_IN_COLLECTION__INTERVAL_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1288,7 +1901,16 @@ internal object Fn_IN_COLLECTION__INTERVAL_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1307,7 +1929,16 @@ internal object Fn_IN_COLLECTION__BAG_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check>() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1326,7 +1957,16 @@ internal object Fn_IN_COLLECTION__BAG_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check>() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1345,7 +1985,16 @@ internal object Fn_IN_COLLECTION__BAG_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check>() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1364,7 +2013,16 @@ internal object Fn_IN_COLLECTION__LIST_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check>() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1383,7 +2041,16 @@ internal object Fn_IN_COLLECTION__LIST_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check>() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1402,7 +2069,16 @@ internal object Fn_IN_COLLECTION__LIST_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check>() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1421,7 +2097,16 @@ internal object Fn_IN_COLLECTION__SEXP_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check>() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1440,7 +2125,16 @@ internal object Fn_IN_COLLECTION__SEXP_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check>() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1459,7 +2153,16 @@ internal object Fn_IN_COLLECTION__SEXP_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check>() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1478,7 +2181,16 @@ internal object Fn_IN_COLLECTION__STRUCT_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check>() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1497,7 +2209,16 @@ internal object Fn_IN_COLLECTION__STRUCT_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check>() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1516,7 +2237,16 @@ internal object Fn_IN_COLLECTION__STRUCT_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check>() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1535,7 +2265,16 @@ internal object Fn_IN_COLLECTION__NULL_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1554,7 +2293,16 @@ internal object Fn_IN_COLLECTION__NULL_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1573,7 +2321,16 @@ internal object Fn_IN_COLLECTION__NULL_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + val value = args[0].check() + val collection = args[1].check>() + val iter = collection.iterator() + while (iter.hasNext()) { + val v = iter.next() + if (PartiQLValue.comparator().compare(value, v) == 0) { + return boolValue(true) + } + } + return boolValue(false) } } @@ -1592,7 +2349,7 @@ internal object Fn_IN_COLLECTION__MISSING_BAG__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + throw TypeCheckException() } } @@ -1611,7 +2368,7 @@ internal object Fn_IN_COLLECTION__MISSING_LIST__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + throw TypeCheckException() } } @@ -1630,6 +2387,6 @@ internal object Fn_IN_COLLECTION__MISSING_SEXP__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function in_collection not implemented") + throw TypeCheckException() } } diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnLike.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnLike.kt index f2a62f9469..3a73855a8e 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnLike.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnLike.kt @@ -3,16 +3,23 @@ package org.partiql.spi.connector.sql.builtins +import org.partiql.spi.connector.sql.utils.PatternUtils.matchRegexPattern +import org.partiql.spi.connector.sql.utils.PatternUtils.parsePattern import org.partiql.spi.fn.Fn import org.partiql.spi.fn.FnExperimental import org.partiql.spi.fn.FnParameter import org.partiql.spi.fn.FnSignature +import org.partiql.value.ClobValue import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental import org.partiql.value.PartiQLValueType.BOOL import org.partiql.value.PartiQLValueType.CLOB import org.partiql.value.PartiQLValueType.STRING import org.partiql.value.PartiQLValueType.SYMBOL +import org.partiql.value.TextValue +import org.partiql.value.boolValue +import org.partiql.value.check +import java.util.regex.Pattern @OptIn(PartiQLValueExperimental::class, FnExperimental::class) internal object Fn_LIKE__STRING_STRING__BOOL : Fn { @@ -29,7 +36,16 @@ internal object Fn_LIKE__STRING_STRING__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function like not implemented") + val value = args[0].check>().value!! + val pattern = args[1].check>().value!! + val likeRegexPattern = when { + pattern.isEmpty() -> Pattern.compile("") + else -> parsePattern(pattern, null) + } + return when (matchRegexPattern(value, likeRegexPattern)) { + true -> boolValue(true) + else -> boolValue(false) + } } } @@ -48,7 +64,16 @@ internal object Fn_LIKE__SYMBOL_SYMBOL__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function like not implemented") + val value = args[0].check>().value!! + val pattern = args[1].check>().value!! + val likeRegexPattern = when { + pattern.isEmpty() -> Pattern.compile("") + else -> parsePattern(pattern, null) + } + return when (matchRegexPattern(value, likeRegexPattern)) { + true -> boolValue(true) + else -> boolValue(false) + } } } @@ -67,6 +92,15 @@ internal object Fn_LIKE__CLOB_CLOB__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function like not implemented") + val value = args[0].check().value!!.toString(Charsets.UTF_8) + val pattern = args[1].check().value!!.toString(Charsets.UTF_8) + val likeRegexPattern = when { + pattern.isEmpty() -> Pattern.compile("") + else -> parsePattern(pattern, null) + } + return when (matchRegexPattern(value, likeRegexPattern)) { + true -> boolValue(true) + else -> boolValue(false) + } } } diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnLikeEscape.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnLikeEscape.kt index fadb6fca72..6c1a86e28e 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnLikeEscape.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnLikeEscape.kt @@ -3,16 +3,25 @@ package org.partiql.spi.connector.sql.builtins +import org.partiql.errors.TypeCheckException +import org.partiql.spi.connector.sql.utils.PatternUtils +import org.partiql.spi.connector.sql.utils.PatternUtils.checkPattern +import org.partiql.spi.connector.sql.utils.PatternUtils.parsePattern import org.partiql.spi.fn.Fn import org.partiql.spi.fn.FnExperimental import org.partiql.spi.fn.FnParameter import org.partiql.spi.fn.FnSignature +import org.partiql.value.ClobValue import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental import org.partiql.value.PartiQLValueType.BOOL import org.partiql.value.PartiQLValueType.CLOB import org.partiql.value.PartiQLValueType.STRING import org.partiql.value.PartiQLValueType.SYMBOL +import org.partiql.value.TextValue +import org.partiql.value.boolValue +import org.partiql.value.check +import java.util.regex.Pattern @OptIn(PartiQLValueExperimental::class, FnExperimental::class) internal object Fn_LIKE_ESCAPE__STRING_STRING_STRING__BOOL : Fn { @@ -30,7 +39,23 @@ internal object Fn_LIKE_ESCAPE__STRING_STRING_STRING__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function like_escape not implemented") + val value = args[0].check>().value!! + val pattern = args[1].check>().value!! + val escape = args[2].check>().value!! + val (patternString, escapeChar) = + try { + checkPattern(pattern, escape) + } catch (e: IllegalStateException) { + throw TypeCheckException() + } + val likeRegexPattern = when { + patternString.isEmpty() -> Pattern.compile("") + else -> parsePattern(patternString, escapeChar) + } + return when (PatternUtils.matchRegexPattern(value, likeRegexPattern)) { + true -> boolValue(true) + else -> boolValue(false) + } } } @@ -50,7 +75,23 @@ internal object Fn_LIKE_ESCAPE__SYMBOL_SYMBOL_SYMBOL__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function like_escape not implemented") + val value = args[0].check>().value!! + val pattern = args[1].check>().value!! + val escape = args[2].check>().value!! + val (patternString, escapeChar) = + try { + checkPattern(pattern, escape) + } catch (e: IllegalStateException) { + throw TypeCheckException() + } + val likeRegexPattern = when { + patternString.isEmpty() -> Pattern.compile("") + else -> parsePattern(patternString, escapeChar) + } + return when (PatternUtils.matchRegexPattern(value, likeRegexPattern)) { + true -> boolValue(true) + else -> boolValue(false) + } } } @@ -70,6 +111,22 @@ internal object Fn_LIKE_ESCAPE__CLOB_CLOB_CLOB__BOOL : Fn { ) override fun invoke(args: Array): PartiQLValue { - TODO("Function like_escape not implemented") + val value = args[0].check().value!!.toString(Charsets.UTF_8) + val pattern = args[1].check().value!!.toString(Charsets.UTF_8) + val escape = args[2].check().value!!.toString(Charsets.UTF_8) + val (patternString, escapeChar) = + try { + checkPattern(pattern, escape) + } catch (e: IllegalStateException) { + throw TypeCheckException() + } + val likeRegexPattern = when { + patternString.isEmpty() -> Pattern.compile("") + else -> parsePattern(patternString, escapeChar) + } + return when (PatternUtils.matchRegexPattern(value, likeRegexPattern)) { + true -> boolValue(true) + else -> boolValue(false) + } } } diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnPosition.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnPosition.kt index bf81154678..0f6b457254 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnPosition.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnPosition.kt @@ -3,6 +3,7 @@ package org.partiql.spi.connector.sql.builtins +import org.partiql.spi.connector.sql.utils.StringUtils.codepointPosition import org.partiql.spi.fn.Fn import org.partiql.spi.fn.FnExperimental import org.partiql.spi.fn.FnParameter diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnSubstring.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnSubstring.kt index 33fc29a880..92c1566626 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnSubstring.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnSubstring.kt @@ -5,6 +5,7 @@ package org.partiql.spi.connector.sql.builtins import org.partiql.errors.DataException import org.partiql.errors.TypeCheckException +import org.partiql.spi.connector.sql.utils.StringUtils.codepointSubstring import org.partiql.spi.fn.Fn import org.partiql.spi.fn.FnExperimental import org.partiql.spi.fn.FnParameter diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrim.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrim.kt index 75cb1efe46..e9d7b230d6 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrim.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrim.kt @@ -3,6 +3,7 @@ package org.partiql.spi.connector.sql.builtins +import org.partiql.spi.connector.sql.utils.StringUtils.codepointTrim import org.partiql.spi.fn.Fn import org.partiql.spi.fn.FnExperimental import org.partiql.spi.fn.FnParameter diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrimChars.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrimChars.kt index 60f08bb5ec..a355fcfde2 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrimChars.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrimChars.kt @@ -3,6 +3,7 @@ package org.partiql.spi.connector.sql.builtins +import org.partiql.spi.connector.sql.utils.StringUtils.codepointTrim import org.partiql.spi.fn.Fn import org.partiql.spi.fn.FnExperimental import org.partiql.spi.fn.FnParameter diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrimLeading.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrimLeading.kt index 8760dd4117..ddf8fecbeb 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrimLeading.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrimLeading.kt @@ -3,6 +3,7 @@ package org.partiql.spi.connector.sql.builtins +import org.partiql.spi.connector.sql.utils.StringUtils.codepointTrimLeading import org.partiql.spi.fn.Fn import org.partiql.spi.fn.FnExperimental import org.partiql.spi.fn.FnParameter diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrimLeadingChars.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrimLeadingChars.kt index 1c140e2e21..605d7164fe 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrimLeadingChars.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrimLeadingChars.kt @@ -3,6 +3,7 @@ package org.partiql.spi.connector.sql.builtins +import org.partiql.spi.connector.sql.utils.StringUtils.codepointTrimLeading import org.partiql.spi.fn.Fn import org.partiql.spi.fn.FnExperimental import org.partiql.spi.fn.FnParameter diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrimTrailing.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrimTrailing.kt index 4f4f67ef17..c0e24bdf0f 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrimTrailing.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrimTrailing.kt @@ -3,6 +3,7 @@ package org.partiql.spi.connector.sql.builtins +import org.partiql.spi.connector.sql.utils.StringUtils.codepointTrimTrailing import org.partiql.spi.fn.Fn import org.partiql.spi.fn.FnExperimental import org.partiql.spi.fn.FnParameter diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrimTrailingChars.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrimTrailingChars.kt index 2018ca53ba..05a4fa8cf6 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrimTrailingChars.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnTrimTrailingChars.kt @@ -3,6 +3,7 @@ package org.partiql.spi.connector.sql.builtins +import org.partiql.spi.connector.sql.utils.StringUtils.codepointTrimTrailing import org.partiql.spi.fn.Fn import org.partiql.spi.fn.FnExperimental import org.partiql.spi.fn.FnParameter diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/utils.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/utils.kt deleted file mode 100644 index a38188ca54..0000000000 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/utils.kt +++ /dev/null @@ -1,158 +0,0 @@ -package org.partiql.spi.connector.sql.builtins - -// String.codePoints() is from Java 9+ -private fun String.toIntArray() = this.codePoints().toArray() - -// Default codepoints to remove -private val SPACE = intArrayOf(" ".codePointAt(0)) - -/** - * Removes the given string (" " by default) from both ends of this - */ -internal fun String.codepointTrim(toRemove: String? = null): String { - val codepoints = this.toIntArray() - val codepointsToRemove = toRemove?.toIntArray() ?: SPACE - return codepoints.trim(codepointsToRemove) -} - -/** - * Removes the given string (" " by default) from the leading end of this - */ -internal fun String.codepointTrimLeading(toRemove: String? = null): String { - val codepoints = this.toIntArray() - val codepointsToRemove = toRemove?.toIntArray() ?: SPACE - return codepoints.trimLeading(codepointsToRemove) -} - -/** - * Removes the given string (" " by default) from the trailing end of this - */ -internal fun String.codepointTrimTrailing(toRemove: String? = null): String { - val codepoints = this.toIntArray() - val codepointsToRemove = toRemove?.toIntArray() ?: SPACE - return codepoints.trimTrailing(codepointsToRemove) -} - -/** - * Returns the first 1-indexed position of probe in this; else 0 - */ -internal fun String.codepointPosition(probe: String): Int { - if (probe.length > this.length) return 0 - val codepoints = this.toIntArray() - val codepointsToFind = probe.toIntArray() - return codepoints.positionOf(codepointsToFind) -} - -/** - * Replaces this with overlay from 1-indexed position `startPosition` for up to `length` codepoints - */ -internal fun String.codepointOverlay(overlay: String, position: Int, length: Int? = null): String { - if (this.isEmpty()) return this - val codepoints = this.toIntArray() - val codepointsToOverlay = overlay.toIntArray() - return codepoints.overlay(codepointsToOverlay, position, length) -} - -/** - * Substring defined by SQL-92 page 135. - * - * @param start - * @param end - * @return - */ -internal fun String.codepointSubstring(start: Int, end: Int? = null): String { - val codePointCount = this.codePointCount(0, this.length) - if (start > codePointCount) { - return "" - } - - // startPosition starts at 1 - // calculate this before adjusting start position to account for negative startPosition - val endPosition = when (end) { - null -> codePointCount - else -> Integer.min(codePointCount, start + end - 1) - } - - // Clamp start indexes to values that make sense for java substring - val adjustedStartPosition = Integer.max(0, start - 1) - - if (endPosition < adjustedStartPosition) { - return "" - } - - val byteIndexStart = this.offsetByCodePoints(0, adjustedStartPosition) - val byteIndexEnd = this.offsetByCodePoints(0, endPosition) - - return this.substring(byteIndexStart, byteIndexEnd) -} - -internal fun IntArray.trim(toRemove: IntArray? = null): String { - val codepointsToRemove = toRemove ?: SPACE - val leadingOffset = trimLeadingOffset(this, codepointsToRemove) - val trailingOffset = trimTrailingOffset(this, codepointsToRemove) - val length = 0.coerceAtLeast(this.size - trailingOffset - leadingOffset) - return String(this, leadingOffset, length) -} - -internal fun IntArray.trimLeading(toRemove: IntArray? = null): String { - val codepointsToRemove = toRemove ?: SPACE - val offset = trimLeadingOffset(this, codepointsToRemove) - return String(this, offset, this.size - offset) -} - -internal fun IntArray.trimTrailing(toRemove: IntArray? = null): String { - val codepointsToRemove = toRemove ?: SPACE - val offset = trimTrailingOffset(this, codepointsToRemove) - return String(this, 0, this.size - offset) -} - -internal fun IntArray.trimLeadingOffset(codepoints: IntArray, toRemove: IntArray): Int { - var offset = 0 - while (offset < this.size && toRemove.contains(codepoints[offset])) offset += 1 - return offset -} - -internal fun IntArray.trimTrailingOffset(codepoints: IntArray, toRemove: IntArray): Int { - var offset = 0 - while (offset < this.size && toRemove.contains(codepoints[this.size - offset - 1])) offset += 1 - return offset -} - -internal fun IntArray.positionOf(probe: IntArray): Int { - val extent = this.size - probe.size - if (extent < 0) return 0 - var start = 0 - window@ while (start <= extent) { - // check current window for equality - for (i in probe.indices) { - if (probe[i] != this[start + i]) { - start += 1 - continue@window - } - } - // nothing was not equal — everything was equal - return start + 1 - } - return 0 -} - -internal fun IntArray.overlay(overlay: IntArray, position: Int, length: Int? = null): String { - val len = (length ?: overlay.size) - val prefixLen = (position - 1).coerceAtMost(this.size) - val suffixLen = (this.size - (len + prefixLen)).coerceAtLeast(0) - val buffer = IntArray(prefixLen + overlay.size + suffixLen) - var i = 0 - // Fill prefix - for (j in 0 until prefixLen) { - buffer[i++] = this[j] - } - // Fill overlay - for (j in overlay.indices) { - buffer[i++] = overlay[j] - } - // Fill suffix - for (j in 0 until suffixLen) { - buffer[i++] = this[prefixLen + len + j] - } - return String(buffer, 0, buffer.size) -} diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/utils/PatternUtils.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/utils/PatternUtils.kt new file mode 100644 index 0000000000..82ab8642b5 --- /dev/null +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/utils/PatternUtils.kt @@ -0,0 +1,163 @@ +package org.partiql.spi.connector.sql.utils + +import org.partiql.spi.connector.sql.utils.StringUtils.codePointSequence +import java.util.regex.Pattern + +internal object PatternUtils { + + private const val ANY_MANY = '%'.code + private const val ANY_ONE = '_'.code + + private const val PATTERN_ADDITIONAL_BUFFER = 8 + + /** + * Translates a SQL-style `LIKE` pattern to a regular expression. + * + * Roughly the algorithm is to + * - call `Pattern.quote` on the literal parts of the pattern + * - translate a single `_` (with no contiguous `%`) to `.` + * - translate a consecutive `_` (with no contiguous `%`) to `.{n,n}` + * - translate any number of consecutive `%` to `.*?` + * - translate any number of consecutive `%` with a `_` contiguous to `.+?` + * - translate any number of consecutive `%` with `_` contiguous to `.{n,}?` + * - prefix the pattern translated via the above rule with '^' and suffix with '$' + * + * @param likePattern A `LIKE` match pattern (i.e. a string where '%' means zero or more and '_' means 1 ). + * @param escapeChar The escape character for the `LIKE` pattern. + * + * @return a [Pattern] which is a regular expression corresponding to the specified `LIKE` pattern. + * + * Examples: + * ``` + * val ESCAPE = '\\'.toInt() + * + * assertEquals("^.*?\\Qfoo\\E$", parsePattern("%foo", ESCAPE).pattern()) + * assertEquals("^\\Qfoo\\E.*?$", parsePattern("foo%", ESCAPE).pattern()) + * assertEquals("^\\Qfoo\\E.*?\\Qbar\\E$", parsePattern("foo%bar", ESCAPE).pattern()) + * assertEquals("^\\Qfoo\\E.*?\\Qbar\\E$", parsePattern("foo%%bar", ESCAPE).pattern()) + * assertEquals("^\\Qfoo\\E.*?\\Qbar\\E$", parsePattern("foo%%%bar", ESCAPE).pattern()) + * assertEquals("^\\Qfoo\\E.*?\\Qbar\\E$", parsePattern("foo%%%%bar", ESCAPE).pattern()) + * assertEquals("^.*?\\Qfoo\\E.*?\\Qbar\\E.*?$", + * parsePattern("%foo%%%%bar%", ESCAPE).pattern()) + * assertEquals("^\\Qfoo\\E.{2,}?\\Qbar\\E$", parsePattern("foo_%_bar", ESCAPE).pattern()) + * assertEquals("^\\Qfoo\\E.{2,}?\\Qbar\\E$", parsePattern("foo_%_%bar", ESCAPE).pattern()) + * assertEquals("^\\Qfoo\\E.{2,}?\\Qbar\\E$", parsePattern("foo%_%%_%bar", ESCAPE).pattern()) + * ``` + * + * + * @see java.util.regex.Pattern + */ + internal fun parsePattern(likePattern: String, escapeChar: Int?): Pattern { + val buf = StringBuilder(likePattern.length + PATTERN_ADDITIONAL_BUFFER) + buf.append("^") + + var isEscaped = false + var wildcardMin = -1 + var wildcardUnbounded = false + val literal = StringBuilder() + + // If a wildcard (e.g. a sequence of '%' and '_') has been accumulated, write out the regex equivalent + val flushWildcard = { + if (wildcardMin != -1) { + if (wildcardUnbounded) { + when (wildcardMin) { + 0 -> buf.append(".*?") + 1 -> buf.append(".+?") + else -> buf.append(".{$wildcardMin,}?") + } + } else { + when (wildcardMin) { + 1 -> buf.append(".") + else -> buf.append(".{$wildcardMin,$wildcardMin}") + } + } + wildcardMin = -1 + wildcardUnbounded = false + } + } + + // if a literal has been accumulated, write it out, regex-quoted + val flushLiteral = { + if (literal.isNotEmpty()) { + buf.append(Pattern.quote(literal.toString())) + literal.clear() + } + } + + for (codepoint in likePattern.codePoints()) { + if (!isEscaped) { + if (codepoint == escapeChar) { + isEscaped = true + continue // skip to the next codepoint + } + when (codepoint) { + ANY_ONE -> { + flushLiteral() + wildcardMin = maxOf(wildcardMin, 0) + 1 + } + ANY_MANY -> { + flushLiteral() + wildcardMin = maxOf(wildcardMin, 0) + wildcardUnbounded = true + } + else -> { + flushWildcard() + literal.appendCodePoint(codepoint) + } + } + } else { + flushWildcard() + literal.appendCodePoint(codepoint) + isEscaped = false + } + } + + flushLiteral() + flushWildcard() + + buf.append("$") + return Pattern.compile(buf.toString()) + } + + internal fun matchRegexPattern(value: String, likePattern: Pattern): Boolean = likePattern.matcher(value).matches() + + /** + * A search pattern is valid when + * 1. pattern is not null + * 1. pattern contains characters where `_` means any 1 character and `%` means any string of length 0 or more + * 1. if the escape character is specified then pattern can be deterministically partitioned into character groups where + * 1. A length 1 character group consists of any character other than the ESCAPE character + * 1. A length 2 character group consists of the ESCAPE character followed by either `_` or `%` or the ESCAPE character itself + */ + internal fun checkPattern( + pattern: String, + escape: String, + ): Pair { + val escapeCharString = checkEscapeChar(escape) + val escapeCharCodePoint = escapeCharString.codePointAt(0) // escape is a string of length 1 + val validEscapedChars = setOf(ANY_MANY, ANY_ONE, escapeCharCodePoint) + val iter = pattern.codePointSequence().iterator() + + while (iter.hasNext()) { + val current = iter.next() + if (current == escapeCharCodePoint && (!iter.hasNext() || !validEscapedChars.contains(iter.next()))) { + throw IllegalStateException("Invalid Escape Sequence") + } + } + return Pair(pattern, escapeCharCodePoint) + } + + private fun checkEscapeChar(escape: String): String { + when { + escape.isEmpty() -> { + throw IllegalStateException("Cannot use empty character as ESCAPE character in a LIKE predicate: $escape") + } + else -> { + if (escape.trim().length != 1) { + throw IllegalStateException("Escape character must have size 1 : $escape") + } + } + } + return escape + } +} diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/utils/StringUtils.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/utils/StringUtils.kt new file mode 100644 index 0000000000..08ad56a7e2 --- /dev/null +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/utils/StringUtils.kt @@ -0,0 +1,177 @@ +package org.partiql.spi.connector.sql.utils + +internal object StringUtils { + + // String.codePoints() is from Java 9+ + private fun String.toIntArray() = this.codePoints().toArray() + + // Default codepoints to remove + private val SPACE = intArrayOf(" ".codePointAt(0)) + + /** Provides a lazy sequence over the code points in the given string. */ + internal fun String.codePointSequence(): Sequence { + val text = this + return Sequence { + var pos = 0 + object : Iterator { + override fun hasNext(): Boolean = pos < text.length + override fun next(): Int { + val cp = text.codePointAt(pos) + pos += Character.charCount(cp) + return cp + } + } + } + } + + /** + * Removes the given string (" " by default) from both ends of this + */ + internal fun String.codepointTrim(toRemove: String? = null): String { + val codepoints = this.toIntArray() + val codepointsToRemove = toRemove?.toIntArray() ?: SPACE + return codepoints.trim(codepointsToRemove) + } + + /** + * Removes the given string (" " by default) from the leading end of this + */ + internal fun String.codepointTrimLeading(toRemove: String? = null): String { + val codepoints = this.toIntArray() + val codepointsToRemove = toRemove?.toIntArray() ?: SPACE + return codepoints.trimLeading(codepointsToRemove) + } + + /** + * Removes the given string (" " by default) from the trailing end of this + */ + internal fun String.codepointTrimTrailing(toRemove: String? = null): String { + val codepoints = this.toIntArray() + val codepointsToRemove = toRemove?.toIntArray() ?: SPACE + return codepoints.trimTrailing(codepointsToRemove) + } + + /** + * Returns the first 1-indexed position of probe in this; else 0 + */ + internal fun String.codepointPosition(probe: String): Int { + if (probe.length > this.length) return 0 + val codepoints = this.toIntArray() + val codepointsToFind = probe.toIntArray() + return codepoints.positionOf(codepointsToFind) + } + + /** + * Replaces this with overlay from 1-indexed position `startPosition` for up to `length` codepoints + */ + internal fun String.codepointOverlay(overlay: String, position: Int, length: Int? = null): String { + if (this.isEmpty()) return this + val codepoints = this.toIntArray() + val codepointsToOverlay = overlay.toIntArray() + return codepoints.overlay(codepointsToOverlay, position, length) + } + + /** + * Substring defined by SQL-92 page 135. + * + * @param start + * @param end + * @return + */ + internal fun String.codepointSubstring(start: Int, end: Int? = null): String { + val codePointCount = this.codePointCount(0, this.length) + if (start > codePointCount) { + return "" + } + + // startPosition starts at 1 + // calculate this before adjusting start position to account for negative startPosition + val endPosition = when (end) { + null -> codePointCount + else -> Integer.min(codePointCount, start + end - 1) + } + + // Clamp start indexes to values that make sense for java substring + val adjustedStartPosition = Integer.max(0, start - 1) + + if (endPosition < adjustedStartPosition) { + return "" + } + + val byteIndexStart = this.offsetByCodePoints(0, adjustedStartPosition) + val byteIndexEnd = this.offsetByCodePoints(0, endPosition) + + return this.substring(byteIndexStart, byteIndexEnd) + } + + internal fun IntArray.trim(toRemove: IntArray? = null): String { + val codepointsToRemove = toRemove ?: SPACE + val leadingOffset = trimLeadingOffset(this, codepointsToRemove) + val trailingOffset = trimTrailingOffset(this, codepointsToRemove) + val length = 0.coerceAtLeast(this.size - trailingOffset - leadingOffset) + return String(this, leadingOffset, length) + } + + internal fun IntArray.trimLeading(toRemove: IntArray? = null): String { + val codepointsToRemove = toRemove ?: SPACE + val offset = trimLeadingOffset(this, codepointsToRemove) + return String(this, offset, this.size - offset) + } + + internal fun IntArray.trimTrailing(toRemove: IntArray? = null): String { + val codepointsToRemove = toRemove ?: SPACE + val offset = trimTrailingOffset(this, codepointsToRemove) + return String(this, 0, this.size - offset) + } + + internal fun IntArray.trimLeadingOffset(codepoints: IntArray, toRemove: IntArray): Int { + var offset = 0 + while (offset < this.size && toRemove.contains(codepoints[offset])) offset += 1 + return offset + } + + internal fun IntArray.trimTrailingOffset(codepoints: IntArray, toRemove: IntArray): Int { + var offset = 0 + while (offset < this.size && toRemove.contains(codepoints[this.size - offset - 1])) offset += 1 + return offset + } + + internal fun IntArray.positionOf(probe: IntArray): Int { + val extent = this.size - probe.size + if (extent < 0) return 0 + var start = 0 + window@ while (start <= extent) { + // check current window for equality + for (i in probe.indices) { + if (probe[i] != this[start + i]) { + start += 1 + continue@window + } + } + // nothing was not equal — everything was equal + return start + 1 + } + return 0 + } + + internal fun IntArray.overlay(overlay: IntArray, position: Int, length: Int? = null): String { + val len = (length ?: overlay.size) + val prefixLen = (position - 1).coerceAtMost(this.size) + val suffixLen = (this.size - (len + prefixLen)).coerceAtLeast(0) + val buffer = IntArray(prefixLen + overlay.size + suffixLen) + var i = 0 + // Fill prefix + for (j in 0 until prefixLen) { + buffer[i++] = this[j] + } + // Fill overlay + for (j in overlay.indices) { + buffer[i++] = overlay[j] + } + // Fill suffix + for (j in 0 until suffixLen) { + buffer[i++] = this[prefixLen + len + j] + } + return String(buffer, 0, buffer.size) + } +}