From 34e2adf5841454e03633f564453f80bfd74fa51c Mon Sep 17 00:00:00 2001 From: John Ed Quinn Date: Tue, 23 Apr 2024 12:15:36 -0700 Subject: [PATCH] Initializes PartiQLData and PartiQLValueLoader --- .../partiql/cli/format/ExplainFormatter.kt | 1 - .../org/partiql/eval/PartiQLEngineDefault.kt | 4 +- .../kotlin/org/partiql/eval/PartiQLResult.kt | 4 +- .../eval/internal/PartiQLEngineDefaultTest.kt | 5 +- partiql-types/build.gradle.kts | 9 + .../java/org/partiql/value/PartiQLData.java | 158 ++++++++ .../org/partiql/value/PartiQLDataDefault.java | 354 ++++++++++++++++++ .../org/partiql/value/PartiQLValueLoader.java | 30 ++ .../value/PartiQLValueLoaderDefault.java | 133 +++++++ .../partiql/runner/executor/EvalExecutor.kt | 13 +- 10 files changed, 701 insertions(+), 10 deletions(-) create mode 100644 partiql-types/src/main/java/org/partiql/value/PartiQLData.java create mode 100644 partiql-types/src/main/java/org/partiql/value/PartiQLDataDefault.java create mode 100644 partiql-types/src/main/java/org/partiql/value/PartiQLValueLoader.java create mode 100644 partiql-types/src/main/java/org/partiql/value/PartiQLValueLoaderDefault.java diff --git a/partiql-cli/src/main/kotlin/org/partiql/cli/format/ExplainFormatter.kt b/partiql-cli/src/main/kotlin/org/partiql/cli/format/ExplainFormatter.kt index 1d9404eb8b..e7488a39b4 100644 --- a/partiql-cli/src/main/kotlin/org/partiql/cli/format/ExplainFormatter.kt +++ b/partiql-cli/src/main/kotlin/org/partiql/cli/format/ExplainFormatter.kt @@ -14,7 +14,6 @@ package org.partiql.cli.format - // internal object ExplainFormatter { // // internal fun format(result: PartiQLResult.Explain.Domain): String { diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/PartiQLEngineDefault.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/PartiQLEngineDefault.kt index 9cf78860d8..d594abe4e1 100644 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/PartiQLEngineDefault.kt +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/PartiQLEngineDefault.kt @@ -4,6 +4,7 @@ import org.partiql.eval.internal.Compiler import org.partiql.eval.internal.Environment import org.partiql.eval.internal.Symbols import org.partiql.plan.PartiQLPlan +import org.partiql.value.PartiQLData import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental @@ -33,7 +34,8 @@ internal class PartiQLEngineDefault : PartiQLEngine { return when (statement) { is PartiQLStatement.Query -> try { val value = statement.execute() - PartiQLResult.Value(value) + val data = PartiQLData.of(value) + PartiQLResult.Value(data) } catch (ex: Exception) { PartiQLResult.Error(ex) } diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/PartiQLResult.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/PartiQLResult.kt index 3a1f6c1a30..1d97c61c20 100644 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/PartiQLResult.kt +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/PartiQLResult.kt @@ -1,12 +1,12 @@ package org.partiql.eval -import org.partiql.value.PartiQLValue +import org.partiql.value.PartiQLData import org.partiql.value.PartiQLValueExperimental public sealed interface PartiQLResult { @OptIn(PartiQLValueExperimental::class) - public data class Value(public val value: PartiQLValue) : PartiQLResult + public data class Value(public val value: PartiQLData) : PartiQLResult public data class Error(public val cause: Throwable) : PartiQLResult } diff --git a/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt b/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt index d8dc6ecb9f..d4a2f3c219 100644 --- a/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt +++ b/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt @@ -21,6 +21,7 @@ import org.partiql.types.StaticType import org.partiql.value.CollectionValue import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental +import org.partiql.value.PartiQLValueLoader import org.partiql.value.bagValue import org.partiql.value.boolValue import org.partiql.value.decimalValue @@ -1215,7 +1216,7 @@ class PartiQLEngineDefaultTest { throw returned.cause } } - val output = result.value + val output = PartiQLValueLoader.standard().loadSingleValue(result.value) assert(expected == output) { comparisonString(expected, output, plan.plan) } @@ -1284,7 +1285,7 @@ class PartiQLEngineDefaultTest { val plan = planner.plan(statement, session) val prepared = engine.prepare(plan.plan, PartiQLEngine.Session(mapOf("memory" to connector), mode = mode)) when (val result = engine.execute(prepared)) { - is PartiQLResult.Value -> return result.value to plan.plan + is PartiQLResult.Value -> return PartiQLValueLoader.standard().loadSingleValue(result.value) to plan.plan is PartiQLResult.Error -> throw result.cause } } diff --git a/partiql-types/build.gradle.kts b/partiql-types/build.gradle.kts index b7559baacf..f7f12f4a4e 100644 --- a/partiql-types/build.gradle.kts +++ b/partiql-types/build.gradle.kts @@ -23,6 +23,15 @@ dependencies { implementation(Deps.kotlinxCollections) } +// Need to add this as we have both Java and Kotlin sources. Dokka already handles multi-language projects. If +// Javadoc is enabled, we end up overwriting index.html (causing compilation errors). +tasks.withType() { + enabled = false +} +tasks.withType() { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + publish { artifactId = "partiql-types" name = "PartiQL Types" diff --git a/partiql-types/src/main/java/org/partiql/value/PartiQLData.java b/partiql-types/src/main/java/org/partiql/value/PartiQLData.java new file mode 100644 index 0000000000..8d0e75eaa1 --- /dev/null +++ b/partiql-types/src/main/java/org/partiql/value/PartiQLData.java @@ -0,0 +1,158 @@ +package org.partiql.value; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Iterator; +import org.jetbrains.annotations.NotNull; +import org.partiql.value.datetime.Date; +import org.partiql.value.datetime.Time; +import org.partiql.value.datetime.Timestamp; + +/** + * Data representing a database result set, which is usually generated by executing a statement that queries the database. + *

+ * A {@link PartiQLData} object maintains a cursor pointing to its current position in the underlying data. Initially the + * cursor is positioned before the first value. The {@link #next()} method moves the cursor to the next value. Please use + * {@link #hasNext()} before calling {@link #next()}. + * + * @see PartiQLValueLoader#loadSingleValue(PartiQLData) + * @see PartiQLValue + */ +public interface PartiQLData extends AutoCloseable, Iterator { + + /** + * Positions the reader just before the contents of the current value, which must be a container (list, bag, + * sexp, or struct). There's no current value immediately after stepping in, so the next thing you'll want to do is call + * {@link #hasNext()} and {@link #next()} to move onto the first child value. + *

+ * If the container itself is the null value, stepIn() shall fail. Please use {@link #isNullValue()} before + * invoking this. + *

+ * At any time {@link #stepOut()} may be called to move the cursor back to (just after) the parent value, even if + * there are more children remaining. + */ + public void stepIn(); + + /** + * Positions the iterator after the current parent's value, moving up one level in the data hierarchy. There's no + * current value immediately after stepping out, so the next thing you'll want to do is call {@link #hasNext()} and + * {@link #next()} to move onto the following value. + */ + public void stepOut(); + + /** + * Determines whether the current value is a null value of any type (for example, null or null.int). It should be + * called before calling getters that return value types (int, long, boolean, double). + */ + public boolean isNullValue(); + + /** + * Determines whether the current value is the missing value. Similarly, one can invoke {@link #getType()}. + */ + public boolean isMissingValue(); + + /** + * @return the type of the data at the cursor. + */ + @NotNull + public PartiQLValueType getType(); + + /** + * @return the field name of the current value; or null if there is no valid current value or if the current value + * is not a field of a struct. + */ + public String getFieldName(); + + /** + * This is only applicable when the current value's type is {@link PartiQLValueType#STRING}, + * {@link PartiQLValueType#CHAR}, or {@link PartiQLValueType#SYMBOL}. + */ + @NotNull + String getStringValue(); + + /** + * This is only applicable when the current value's type is {@link PartiQLValueType#BOOL}. + */ + public boolean getBoolValue(); + + /** + * This is only applicable when the current value's type is {@link PartiQLValueType#BINARY}, + * {@link PartiQLValueType#BLOB}, or {@link PartiQLValueType#CLOB}. + */ + public byte[] getBytes(); + + /** + * This is only applicable when the current value's type is {@link PartiQLValueType#INT8} or + * {@link PartiQLValueType#BYTE}. + */ + public byte getByteValue(); + + /** + * This is only applicable when the current value's type is {@link PartiQLValueType#DATE}. + */ + @NotNull + public Date getDate(); + + /** + * This is only applicable when the current value's type is {@link PartiQLValueType#TIME}. + */ + @NotNull + public Time getTime(); + + /** + * This is only applicable when the current value's type is {@link PartiQLValueType#TIMESTAMP}. + */ + @NotNull + public Timestamp getTimestamp(); + + /** + * This is only applicable when the current value's type is {@link PartiQLValueType#INTERVAL}. + */ + @Deprecated + public long getIntervalValue(); + + /** + * This is only applicable when the current value's type is {@link PartiQLValueType#INT16}. + */ + public short getShortValue(); + + /** + * This is only applicable when the current value's type is {@link PartiQLValueType#INT32}. + */ + public int getIntValue(); + + /** + * This is only applicable when the current value's type is {@link PartiQLValueType#INT64}. + */ + public long getLongValue(); + + /** + * This is only applicable when the current value's type is {@link PartiQLValueType#INT}. + */ + @NotNull + public BigInteger getBigIntValue(); + + /** + * This is only applicable when the current value's type is {@link PartiQLValueType#FLOAT32}. + */ + public float getFloatValue(); + + /** + * This is only applicable when the current value's type is {@link PartiQLValueType#FLOAT64}. + */ + public double getDoubleValue(); + + /** + * This is only applicable when the current value's type is {@link PartiQLValueType#DECIMAL} and + * {@link PartiQLValueType#DECIMAL_ARBITRARY}. + */ + @NotNull + public BigDecimal getBigDecimalValue(); + + /** + * Converts a {@link PartiQLValue} into {@link PartiQLData}. + */ + static PartiQLData of(PartiQLValue value) { + return new PartiQLDataDefault(value); + } +} diff --git a/partiql-types/src/main/java/org/partiql/value/PartiQLDataDefault.java b/partiql-types/src/main/java/org/partiql/value/PartiQLDataDefault.java new file mode 100644 index 0000000000..79ef012c47 --- /dev/null +++ b/partiql-types/src/main/java/org/partiql/value/PartiQLDataDefault.java @@ -0,0 +1,354 @@ +package org.partiql.value; + +import kotlin.Pair; +import org.jetbrains.annotations.NotNull; +import org.partiql.value.datetime.Date; +import org.partiql.value.datetime.Time; +import org.partiql.value.datetime.Timestamp; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Stack; + +class PartiQLDataDefault implements PartiQLData { + + @NotNull + private final Stack> iteratorStack; + + @NotNull + private Iterator currentIter; + + private NamedValue currentValue; + + PartiQLDataDefault(PartiQLValue delegate) { + List wrappedList = new ArrayList<>(); + wrappedList.add(delegate); + Iterator topLevelIterator = unnamed(wrappedList.iterator()); + this.iteratorStack = new Stack<>(); + this.iteratorStack.push(topLevelIterator); + this.currentIter = topLevelIterator; + this.currentValue = null; + } + + @Override + public void close() { + currentIter = Collections.emptyIterator(); + currentValue = null; + iteratorStack.empty(); + } + + @Override + public boolean hasNext() { + return currentIter.hasNext(); + } + + @Override + public PartiQLValueType next() { + currentValue = currentIter.next(); + return currentValue.value.getType(); + } + + @Override + public void stepIn() { + org.partiql.value.PartiQLValue value = currentValue.value; + PartiQLValueType type = currentValue.value.getType(); + Iterator children; + switch (type) { + case LIST: + @SuppressWarnings("unchecked") + ListValue list = (ListValue) value; + children = unnamed(list.iterator()); + break; + case BAG: + @SuppressWarnings("unchecked") + BagValue bag = (BagValue) value; + children = unnamed(bag.iterator()); + break; + case SEXP: + @SuppressWarnings("unchecked") + SexpValue sexp = (SexpValue) value; + children = unnamed(sexp.iterator()); + break; + case STRUCT: + @SuppressWarnings("unchecked") + StructValue struct = (StructValue) value; + children = named(struct.getEntries()); + break; + default: + throw new IllegalStateException(); + } + iteratorStack.push(children); + currentValue = null; + currentIter = iteratorStack.peek(); + } + + @Override + public void stepOut() { + iteratorStack.pop(); + currentValue = null; + currentIter = iteratorStack.peek(); + } + + @Override + public boolean isNullValue() { + return currentValue.value.isNull(); + } + + @Override + public boolean isMissingValue() { + return currentValue.value.getType() == PartiQLValueType.MISSING; + } + + @NotNull + @Override + public PartiQLValueType getType() { + return currentValue.value.getType(); + } + + @Override + public String getFieldName() { + return currentValue.name; + } + + @NotNull + @Override + public String getStringValue() { + if (currentValue.value.getType() == PartiQLValueType.STRING) { + return Objects.requireNonNull(((StringValue) currentValue.value).getValue()); + } + throw new IllegalStateException(); + } + + @Override + public boolean getBoolValue() { + PartiQLValueType type = currentValue.value.getType(); + if (type == PartiQLValueType.BOOL) { + BoolValue value = (BoolValue) (currentValue.value); + return Boolean.TRUE.equals(value.getValue()); + } else { + throw new IllegalStateException(); + } + } + + @Override + public byte[] getBytes() { + PartiQLValueType type = currentValue.value.getType(); + switch (type) { + case BINARY: + BinaryValue binaryValue = (BinaryValue) (currentValue.value); + return Objects.requireNonNull(binaryValue.getValue()).toByteArray(); + case BLOB: + BlobValue blobValue = (BlobValue) (currentValue.value); + return Objects.requireNonNull(blobValue.getValue()); + case CLOB: + ClobValue clobValue = (ClobValue) (currentValue.value); + return Objects.requireNonNull(clobValue.getValue()); + default: + throw new IllegalStateException(); + } + } + + @Override + public byte getByteValue() { + PartiQLValueType type = currentValue.value.getType(); + switch (type) { + case INT8: + Int8Value binaryValue = (Int8Value) (currentValue.value); + return Objects.requireNonNull(binaryValue.getValue()); + case BYTE: + ByteValue byteValue = (ByteValue) (currentValue.value); + return Objects.requireNonNull(byteValue.getValue()); + default: + throw new IllegalStateException(); + } + } + + @Override + @NotNull + public Date getDate() { + PartiQLValueType type = currentValue.value.getType(); + if (type == PartiQLValueType.DATE) { + DateValue value = (DateValue) (currentValue.value); + return Objects.requireNonNull(value.getValue()); + } + throw new IllegalStateException(); + } + + @Override + @NotNull + public Time getTime() { + PartiQLValueType type = currentValue.value.getType(); + if (type == PartiQLValueType.TIME) { + TimeValue value = (TimeValue) (currentValue.value); + return Objects.requireNonNull(value.getValue()); + } + throw new IllegalStateException(); + } + + @Override + @NotNull + public Timestamp getTimestamp() { + PartiQLValueType type = currentValue.value.getType(); + if (type == PartiQLValueType.TIMESTAMP) { + TimestampValue value = (TimestampValue) (currentValue.value); + return Objects.requireNonNull(value.getValue()); + } + throw new IllegalStateException(); + } + + @Deprecated + @Override + public long getIntervalValue() { + PartiQLValueType type = currentValue.value.getType(); + if (type == PartiQLValueType.INTERVAL) { + IntervalValue value = (IntervalValue) (currentValue.value); + return Objects.requireNonNull(value.getValue()); + } + throw new IllegalStateException(); + } + + @Override + public short getShortValue() { + PartiQLValueType type = currentValue.value.getType(); + if (type == PartiQLValueType.INT16) { + Int16Value value = (Int16Value) (currentValue.value); + return Objects.requireNonNull(value.getValue()); + } + throw new IllegalStateException(); + } + + @Override + public int getIntValue() { + PartiQLValueType type = currentValue.value.getType(); + if (type == PartiQLValueType.INT32) { + Int32Value value = (Int32Value) (currentValue.value); + return Objects.requireNonNull(value.getValue()); + } + throw new IllegalStateException(); + } + + @Override + public long getLongValue() { + PartiQLValueType type = currentValue.value.getType(); + if (type == PartiQLValueType.INT64) { + Int64Value value = (Int64Value) (currentValue.value); + return Objects.requireNonNull(value.getValue()); + } + throw new IllegalStateException(); + } + + @Override + @NotNull + public BigInteger getBigIntValue() { + PartiQLValueType type = currentValue.value.getType(); + if (type == PartiQLValueType.INT) { + IntValue value = (IntValue) (currentValue.value); + return Objects.requireNonNull(value.getValue()); + } + throw new IllegalStateException(); + } + + @Override + public float getFloatValue() { + PartiQLValueType type = currentValue.value.getType(); + if (type == PartiQLValueType.FLOAT32) { + Float32Value value = (Float32Value) (currentValue.value); + return Objects.requireNonNull(value.getValue()); + } + throw new IllegalStateException(); + } + + @Override + public double getDoubleValue() { + PartiQLValueType type = currentValue.value.getType(); + if (type == PartiQLValueType.FLOAT64) { + Float64Value value = (Float64Value) (currentValue.value); + return Objects.requireNonNull(value.getValue()); + } + throw new IllegalStateException(); + } + + @Override + @NotNull + public BigDecimal getBigDecimalValue() { + PartiQLValueType type = currentValue.value.getType(); + switch (type) { + case DECIMAL: + case DECIMAL_ARBITRARY: + DecimalValue value = (DecimalValue) (currentValue.value); + return Objects.requireNonNull(value.getValue()); + default: + throw new IllegalStateException(); + } + } + + private NamedIterator named(Iterable> values) { + return new NamedIterator(values); + } + + private UnnamedIterator unnamed(Iterator values) { + return new UnnamedIterator(values); + } + + private static class UnnamedIterator implements Iterator { + + @NotNull + Iterator values; + + UnnamedIterator(@NotNull Iterator values) { + this.values = values; + } + + @Override + public boolean hasNext() { + return values.hasNext(); + } + + @Override + public NamedValue next() { + return new NamedValue(values.next()); + } + } + + private static class NamedIterator implements Iterator { + + @NotNull + Iterator> values; + + NamedIterator(@NotNull Iterable> values) { + this.values = values.iterator(); + } + + @Override + public boolean hasNext() { + return values.hasNext(); + } + + @Override + public NamedValue next() { + Pair next = values.next(); + return new NamedValue(next.getFirst(), next.getSecond()); + } + } + + private static class NamedValue { + public String name; + + @NotNull + public PartiQLValue value; + + private NamedValue(String name, @NotNull PartiQLValue value) { + this.name = name; + this.value = value; + } + + private NamedValue(@NotNull PartiQLValue value) { + this.name = null; + this.value = value; + } + } +} diff --git a/partiql-types/src/main/java/org/partiql/value/PartiQLValueLoader.java b/partiql-types/src/main/java/org/partiql/value/PartiQLValueLoader.java new file mode 100644 index 0000000000..cf51b2e296 --- /dev/null +++ b/partiql-types/src/main/java/org/partiql/value/PartiQLValueLoader.java @@ -0,0 +1,30 @@ +package org.partiql.value; + +import org.jetbrains.annotations.NotNull; + +/** + * Provides functions for loading {@link PartiQLData} into {@link PartiQLValue} instances. + * + * @see PartiQLData + */ +public interface PartiQLValueLoader { + /** + * Loads un-materialized {@link PartiQLData} into a materialized {@link PartiQLValue}. The {@link PartiQLData} cursor + * must be set before the value that you'd like to load. + *

+ * This method will invoke {@link PartiQLData#next()}. This method will not throw an error is there is + * more data to be processed after the value immediately following the cursor. + * + * @param data the PartiQL data to load. + * @return a materialized, in-memory instance of a {@link PartiQLValue} containing the contents of the {@code data}. + */ + @NotNull + PartiQLValue loadSingleValue(@NotNull PartiQLData data); + + /** + * @return a basic implementation of {@link PartiQLValueLoader}. + */ + static PartiQLValueLoader standard() { + return new PartiQLValueLoaderDefault(); + } +} diff --git a/partiql-types/src/main/java/org/partiql/value/PartiQLValueLoaderDefault.java b/partiql-types/src/main/java/org/partiql/value/PartiQLValueLoaderDefault.java new file mode 100644 index 0000000000..585d3b7544 --- /dev/null +++ b/partiql-types/src/main/java/org/partiql/value/PartiQLValueLoaderDefault.java @@ -0,0 +1,133 @@ +package org.partiql.value; + +import kotlin.Pair; +import kotlin.jvm.functions.Function1; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; + +import static org.partiql.value.PartiQL.*; + +class PartiQLValueLoaderDefault implements PartiQLValueLoader { + @NotNull + @Override + public PartiQLValue loadSingleValue(@NotNull PartiQLData data) { + data.next(); + return _loadSingleValue(data); + } + + /** + * MUST place the cursor on the data before executing. + * + * @param data the input PartiQL Data. + * @return a materialized PartiQL Value + */ + @NotNull + private PartiQLValue _loadSingleValue(@NotNull PartiQLData data) { + PartiQLValueType type = data.getType(); + switch (type) { + case BOOL: + return boolValue(orNull(data, PartiQLData::getBoolValue)); + case INT8: + return int8Value(orNull(data, PartiQLData::getByteValue)); + case INT16: + return int16Value(orNull(data, PartiQLData::getShortValue)); + case INT32: + return int32Value(orNull(data, PartiQLData::getIntValue)); + case INT64: + return int64Value(orNull(data, PartiQLData::getLongValue)); + case INT: + return intValue(orNull(data, PartiQLData::getBigIntValue)); + case LIST: + return collectionValue(data, PartiQL::listValue); + case BAG: + return collectionValue(data, PartiQL::bagValue); + case SEXP: + return collectionValue(data, PartiQL::sexpValue); + case STRUCT: + return createStructValue(data); + case NULL: + return nullValue(); + case STRING: + return stringValue(orNull(data, PartiQLData::getStringValue)); + case SYMBOL: + return symbolValue(orNull(data, PartiQLData::getStringValue)); + case CHAR: + // TODO: The implementation of CHAR VALUE is wrong. + String val = orNull(data, PartiQLData::getStringValue); + if (val == null) { + return charValue(null); + } + return charValue(val.charAt(0)); + case MISSING: + return missingValue(); + case DECIMAL_ARBITRARY: + case DECIMAL: + return decimalValue(orNull(data, PartiQLData::getBigDecimalValue)); + case INTERVAL: + return intervalValue(orNull(data, PartiQLData::getIntervalValue)); + case TIMESTAMP: + return timestampValue(orNull(data, PartiQLData::getTimestamp)); + case DATE: + return dateValue(orNull(data, PartiQLData::getDate)); + case CLOB: + return clobValue(orNull(data, PartiQLData::getBytes)); + case BLOB: + return blobValue(orNull(data, PartiQLData::getBytes)); + case BINARY: + byte[] bytes = orNull(data, PartiQLData::getBytes); + if (bytes == null) { + return binaryValue(null); + } + return binaryValue(BitSet.valueOf(bytes)); + case BYTE: + return byteValue(orNull(data, PartiQLData::getByteValue)); + case TIME: + return timeValue(orNull(data, PartiQLData::getTime)); + case FLOAT32: + return float32Value(orNull(data, PartiQLData::getFloatValue)); + case FLOAT64: + return float64Value(orNull(data, PartiQLData::getDoubleValue)); + case ANY: + default: + throw new IllegalStateException("Cannot load data of type: " + type); + } + } + + private R orNull(PartiQLData data, Function1 result) { + return data.isNullValue() ? null : result.invoke(data); + } + + private PartiQLValue collectionValue(PartiQLData data, Function1, PartiQLValue> collectionConstructor) { + if (data.isNullValue()) { + return collectionConstructor.invoke(null); + } + data.stepIn(); + List values = new ArrayList<>(); + while (data.hasNext()) { + data.next(); + PartiQLValue value = this._loadSingleValue(data); + values.add(value); + } + data.stepOut(); + return collectionConstructor.invoke(values); + } + + private PartiQLValue createStructValue(PartiQLData data) { + if (data.isNullValue()) { + return structValue((Iterable>) null); + } + data.stepIn(); + List> values = new ArrayList<>(); + while (data.hasNext()) { + data.next(); + String name = data.getFieldName(); + PartiQLValue value = this._loadSingleValue(data); + values.add(new Pair<>(name, value)); + } + data.stepOut(); + return structValue(values); + } +} diff --git a/test/partiql-tests-runner/src/test/kotlin/org/partiql/runner/executor/EvalExecutor.kt b/test/partiql-tests-runner/src/test/kotlin/org/partiql/runner/executor/EvalExecutor.kt index a824209dcd..37e7c9ee3e 100644 --- a/test/partiql-tests-runner/src/test/kotlin/org/partiql/runner/executor/EvalExecutor.kt +++ b/test/partiql-tests-runner/src/test/kotlin/org/partiql/runner/executor/EvalExecutor.kt @@ -26,8 +26,10 @@ import org.partiql.spi.BindingPath import org.partiql.spi.connector.Connector import org.partiql.spi.connector.ConnectorSession import org.partiql.types.StaticType +import org.partiql.value.PartiQLData import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental +import org.partiql.value.PartiQLValueLoader import org.partiql.value.io.PartiQLValueIonReaderBuilder import org.partiql.value.toIon @@ -49,20 +51,23 @@ class EvalExecutor( override fun fromIon(value: IonValue): PartiQLResult { val partiql = PartiQLValueIonReaderBuilder.standard().build(value.toIonElement()).read() - - return PartiQLResult.Value(partiql) + val data = PartiQLData.of(partiql) + return PartiQLResult.Value(data) } override fun toIon(value: PartiQLResult): IonValue { if (value is PartiQLResult.Value) { - return value.value.toIon().toIonValue(ION) + val actualValue = PartiQLValueLoader.standard().loadSingleValue(value.value) + return actualValue.toIon().toIonValue(ION) } error("PartiQLResult cannot be converted to Ion") } override fun compare(actual: PartiQLResult, expect: PartiQLResult): Boolean { if (actual is PartiQLResult.Value && expect is PartiQLResult.Value) { - return valueComparison(actual.value, expect.value) + val value = PartiQLValueLoader.standard().loadSingleValue(actual.value) + val expectedValue = PartiQLValueLoader.standard().loadSingleValue(expect.value) + return valueComparison(value, expectedValue) } error("Cannot compare different types of PartiQLResult") }