diff --git a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/ArrayMetaData.java b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/ArrayMetaData.java index 8027d9f5c5..cb898711d2 100644 --- a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/ArrayMetaData.java +++ b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/ArrayMetaData.java @@ -21,7 +21,9 @@ package com.apple.foundationdb.relational.api; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; +import com.apple.foundationdb.relational.api.metadata.DataType; +import javax.annotation.Nonnull; import java.sql.SQLException; import java.sql.Wrapper; @@ -59,4 +61,13 @@ default StructMetaData getElementStructMetaData() throws SQLException { * @throws SQLException if the type of the column is not an array, or if something else goes wrong. */ ArrayMetaData getElementArrayMetaData() throws SQLException; + + /** + * Get the {@link DataType} object equivalent of this metadata. + * + * @return the datatype object. + * @throws SQLException if something goes wrong. + */ + @Nonnull + DataType.ArrayType asRelationalType() throws SQLException; } diff --git a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/FieldDescription.java b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/FieldDescription.java deleted file mode 100644 index ea4c45acd1..0000000000 --- a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/FieldDescription.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * FieldDescription.java - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2021-2024 Apple Inc. and the FoundationDB project authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.apple.foundationdb.relational.api; - -import com.apple.foundationdb.annotation.API; - -import com.google.common.base.Suppliers; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.sql.Types; -import java.util.Objects; -import java.util.function.Supplier; - -/** - * A description of an individual field. - * - * Note that this representation is indicative of JDBC-API level information. That is, it is used to hold - * information that is necessary to represent a given column field in a JDBC MetaData API. - */ -@API(API.Status.EXPERIMENTAL) -public final class FieldDescription { - private final String fieldName; - private final int sqlTypeCode; //taken from java.sql.Types - - private final int nullable; - - private final StructMetaData fieldMetaData; - - private final ArrayMetaData arrayMetaData; - - //indicates a column that isn't part of the DDL for a query, but is part of the returned - //tuple (and therefore necessary to keep so that our positional ordering is intact) - private final boolean phantom; - private final Supplier hashCodeSupplier; - - /** - * Create a primitive field. - * - * This is a convenience factory method for a more complicated constructor. Equivalent to - * {@link #primitive(String, int, int, boolean)}, where {@code phantom == false}. - * - * @param fieldName the name of the field. - * @param sqlTypeCode the SQL type code for this field. Should match values found in {@link Types} - * @param nullable one of {@link java.sql.DatabaseMetaData#columnNoNulls}, - * {@link java.sql.DatabaseMetaData#columnNullable}, or {@link java.sql.DatabaseMetaData#columnNullableUnknown}. - * @return a FieldDescription for the field. - */ - public static FieldDescription primitive(@Nonnull String fieldName, int sqlTypeCode, int nullable) { - return primitive(fieldName, sqlTypeCode, nullable, false); - } - - /** - * Create a primitive field. - * - * This is a convenience factory method for a more complicated constructor. - * - * @param fieldName the name of the field. - * @param sqlTypeCode the SQL type code for this field. Should match values found in {@link Types} - * @param nullable one of {@link java.sql.DatabaseMetaData#columnNoNulls}, - * {@link java.sql.DatabaseMetaData#columnNullable}, or {@link java.sql.DatabaseMetaData#columnNullableUnknown}. - * @param phantom if true, this column should be treated as "phantom" in the API. That is, its entries - * are present in the returned row, but are not represented as part of the "return value". In other - * words, the values take up space in the physical arrays, but are not considered part of the actual - * return of the query, requiring the implementation to adjust for the field's position. - * @return a FieldDescription for the field. - */ - public static FieldDescription primitive(@Nonnull String fieldName, int sqlTypeCode, int nullable, boolean phantom) { - return new FieldDescription(fieldName, sqlTypeCode, nullable, phantom, null, null); - } - - /** - * Create a struct field. - * - * This is a convenience factory method for a more complicated constructor. - * - * @param fieldName the name of the field. - * @param nullable one of {@link java.sql.DatabaseMetaData#columnNoNulls}, - * {@link java.sql.DatabaseMetaData#columnNullable}, or {@link java.sql.DatabaseMetaData#columnNullableUnknown}. - * @param definition the definition of the struct value itself. - * @return a FieldDescription for the field. - */ - public static FieldDescription struct(@Nonnull String fieldName, int nullable, StructMetaData definition) { - return new FieldDescription(fieldName, Types.STRUCT, nullable, false, definition, null); - } - - /** - * Create an array field. - * - * This is a convenience factory method for a more complicated constructor. - * - * @param fieldName the name of the field. - * @param nullable one of {@link java.sql.DatabaseMetaData#columnNoNulls}, - * {@link java.sql.DatabaseMetaData#columnNullable}, or {@link java.sql.DatabaseMetaData#columnNullableUnknown}. - * @param definition the metadata description of the contents of the array. - * @return a FieldDescription for the field. - */ - public static FieldDescription array(@Nonnull String fieldName, int nullable, ArrayMetaData definition) { - return new FieldDescription(fieldName, Types.ARRAY, nullable, false, null, definition); - } - - private FieldDescription(String fieldName, - int sqlType, - int nullable, - boolean phantom, - @Nullable StructMetaData fieldMetaData, - @Nullable ArrayMetaData arrayMetaData) { - this.fieldName = fieldName; - this.sqlTypeCode = sqlType; - this.nullable = nullable; - this.phantom = phantom; - this.fieldMetaData = fieldMetaData; - this.arrayMetaData = arrayMetaData; - this.hashCodeSupplier = Suppliers.memoize(this::calculateHashCode); - } - - public String getName() { - return fieldName; - } - - public int getSqlTypeCode() { - return sqlTypeCode; - } - - public boolean isStruct() { - return sqlTypeCode == Types.STRUCT; - } - - public boolean isArray() { - return sqlTypeCode == Types.ARRAY; - } - - public StructMetaData getFieldMetaData() { - return fieldMetaData; - } - - public ArrayMetaData getArrayMetaData() { - return arrayMetaData; - } - - public int isNullable() { - return nullable; - } - - public boolean isPhantom() { - return phantom; - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof FieldDescription)) { - return false; - } - final var otherDescription = (FieldDescription) other; - if (otherDescription == this) { - return true; - } - if (!fieldName.equals(otherDescription.fieldName) || - sqlTypeCode != otherDescription.sqlTypeCode || - nullable != otherDescription.nullable) { - return false; - } - if (fieldMetaData == null) { - if (otherDescription.fieldMetaData != null) { - return false; - } - } else { - if (!fieldMetaData.equals(otherDescription.fieldMetaData)) { - return false; - } - } - if (arrayMetaData == null) { - return otherDescription.arrayMetaData == null; - } else { - return arrayMetaData.equals(otherDescription.arrayMetaData); - } - } - - @Override - public int hashCode() { - return hashCodeSupplier.get(); - } - - private int calculateHashCode() { - return Objects.hash(fieldName, sqlTypeCode, nullable, fieldMetaData, arrayMetaData); - } -} diff --git a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/RelationalArrayBuilder.java b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/RelationalArrayBuilder.java index bd88c493f3..2abd49a50e 100644 --- a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/RelationalArrayBuilder.java +++ b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/RelationalArrayBuilder.java @@ -22,6 +22,7 @@ import javax.annotation.Nonnull; import java.sql.SQLException; +import java.util.UUID; /** * Builder for {@link RelationalArray}. @@ -42,7 +43,11 @@ public interface RelationalArrayBuilder { RelationalArrayBuilder addString(@Nonnull String value) throws SQLException; - RelationalArrayBuilder addLong(@Nonnull long value) throws SQLException; + RelationalArrayBuilder addLong(long value) throws SQLException; + + RelationalArrayBuilder addUuid(@Nonnull UUID value) throws SQLException; + + RelationalArrayBuilder addObject(@Nonnull Object value) throws SQLException; RelationalArrayBuilder addStruct(RelationalStruct struct) throws SQLException; } diff --git a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/RelationalArrayMetaData.java b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/RelationalArrayMetaData.java index 900b1e9f36..d16fb64441 100644 --- a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/RelationalArrayMetaData.java +++ b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/RelationalArrayMetaData.java @@ -21,15 +21,14 @@ package com.apple.foundationdb.relational.api; import com.apple.foundationdb.annotation.API; - import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; - +import com.apple.foundationdb.relational.api.metadata.DataType; import com.google.common.base.Suppliers; import javax.annotation.Nonnull; +import java.sql.DatabaseMetaData; import java.sql.SQLException; -import java.sql.Types; import java.util.Objects; import java.util.function.Supplier; @@ -39,41 +38,42 @@ @API(API.Status.EXPERIMENTAL) public final class RelationalArrayMetaData implements ArrayMetaData { - private final FieldDescription element; + private final DataType.ArrayType type; private final Supplier hashCodeSupplier; - private RelationalArrayMetaData(@Nonnull FieldDescription element) { - this.element = element; + private RelationalArrayMetaData(@Nonnull DataType.ArrayType type) { + this.type = type; this.hashCodeSupplier = Suppliers.memoize(this::calculateHashCode); } - public static RelationalArrayMetaData ofPrimitive(int sqlType, int nullable) { - return new RelationalArrayMetaData(FieldDescription.primitive("VALUE", sqlType, nullable)); - } - - public static RelationalArrayMetaData ofStruct(@Nonnull StructMetaData metaData, int nullable) { - return new RelationalArrayMetaData(FieldDescription.struct("VALUE", nullable, metaData)); + @Nonnull + public static RelationalArrayMetaData of(@Nonnull DataType.ArrayType type) { + return new RelationalArrayMetaData(type); } @Override - public int isElementNullable() throws SQLException { - return element.isNullable(); + public int isElementNullable() { + if (type.getElementType().isNullable()) { + return DatabaseMetaData.columnNullable; + } else { + return DatabaseMetaData.columnNoNulls; + } } @Override public String getElementName() throws SQLException { - return element.getName(); + return "VALUE"; } @Override public int getElementType() throws SQLException { - return element.getSqlTypeCode(); + return type.getElementType().getJdbcSqlCode(); } @Override - public String getElementTypeName() throws SQLException { - return SqlTypeNamesSupport.getSqlTypeName(element.getSqlTypeCode()); + public String getElementTypeName() { + return SqlTypeNamesSupport.getSqlTypeName(type.getElementType().getJdbcSqlCode()); } /** @@ -86,10 +86,10 @@ public String getElementTypeName() throws SQLException { */ @Override public StructMetaData getElementStructMetaData() throws SQLException { - if (element.getSqlTypeCode() != Types.STRUCT) { + if (type.getElementType().getCode() != DataType.Code.STRUCT) { throw new RelationalException("Element is not of STRUCT type", ErrorCode.CANNOT_CONVERT_TYPE).toSqlException(); } - return element.getFieldMetaData(); + return RelationalStructMetaData.of((DataType.StructType) type.getElementType()); } /** @@ -102,15 +102,21 @@ public StructMetaData getElementStructMetaData() throws SQLException { */ @Override public ArrayMetaData getElementArrayMetaData() throws SQLException { - if (element.getSqlTypeCode() != Types.ARRAY) { + if (type.getElementType().getCode() != DataType.Code.ARRAY) { throw new RelationalException("Element is not of ARRAY type", ErrorCode.CANNOT_CONVERT_TYPE).toSqlException(); } - return element.getArrayMetaData(); + return RelationalArrayMetaData.of((DataType.ArrayType) type.getElementType()); + } + + @Nonnull + @Override + public DataType.ArrayType asRelationalType() throws SQLException { + return type; } @Nonnull - public FieldDescription getElementField() { - return element; + public DataType getElementDataType() { + return type.getElementType(); } @Override @@ -132,7 +138,7 @@ public boolean equals(Object other) { if (otherMetadata == this) { return true; } - return element.equals(otherMetadata.element); + return type.equals(otherMetadata.type); } @Override @@ -141,7 +147,7 @@ public int hashCode() { } private int calculateHashCode() { - return Objects.hash(element); + return Objects.hash(type); } } diff --git a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/RelationalStructBuilder.java b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/RelationalStructBuilder.java index c24da3ddd7..573b2dca54 100644 --- a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/RelationalStructBuilder.java +++ b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/RelationalStructBuilder.java @@ -23,6 +23,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.sql.SQLException; +import java.util.UUID; /** * For implementation by {@link RelationalStruct} Builder. @@ -40,8 +41,6 @@ public interface RelationalStructBuilder { RelationalStructBuilder addBoolean(String fieldName, boolean b) throws SQLException; - RelationalStructBuilder addShort(String fieldName, short b) throws SQLException; - RelationalStructBuilder addLong(String fieldName, long l) throws SQLException; RelationalStructBuilder addFloat(String fieldName, float f) throws SQLException; @@ -52,7 +51,9 @@ public interface RelationalStructBuilder { RelationalStructBuilder addString(String fieldName, @Nullable String s) throws SQLException; - RelationalStructBuilder addObject(String fieldName, @Nullable Object obj, int targetSqlType) throws SQLException; + RelationalStructBuilder addUuid(String fieldName, @Nullable UUID uuid) throws SQLException; + + RelationalStructBuilder addObject(String fieldName, @Nullable Object obj) throws SQLException; RelationalStructBuilder addStruct(String fieldName, @Nonnull RelationalStruct struct) throws SQLException; diff --git a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/RelationalStructMetaData.java b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/RelationalStructMetaData.java index c80f23e685..5e9fcc5fc1 100644 --- a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/RelationalStructMetaData.java +++ b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/RelationalStructMetaData.java @@ -21,56 +21,61 @@ package com.apple.foundationdb.relational.api; import com.apple.foundationdb.annotation.API; - import com.apple.foundationdb.relational.api.exceptions.InvalidColumnReferenceException; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.util.ExcludeFromJacocoGeneratedReport; - import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import javax.annotation.Nonnull; +import java.sql.DatabaseMetaData; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; -import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.function.Supplier; @API(API.Status.EXPERIMENTAL) public class RelationalStructMetaData implements StructMetaData { - @Nonnull - private final String name; + //TODO(bfines) eventually this should move into the Planner (or closer to there, anyway), but for now we will hold on to it here + private static final Set KNOWN_PHANTOM_COLUMNS = Set.of("__TYPE_KEY"); - private final FieldDescription[] columns; + @Nonnull + private final DataType.StructType type; //the number of phantom columns that are at the front of the metadata private final int leadingPhantomColumnOffset; private final Supplier hashCodeSupplier; - public RelationalStructMetaData(FieldDescription... columns) { - this("ANONYMOUS_STRUCT", columns); - } - - public RelationalStructMetaData(@Nonnull final String name, FieldDescription... columns) { - this.name = name; - this.columns = columns; + private RelationalStructMetaData(@Nonnull DataType.StructType type) { + this.type = type; this.leadingPhantomColumnOffset = countLeadingPhantomColumns(); this.hashCodeSupplier = Suppliers.memoize(this::calculateHashCode); } + @Nonnull + public static RelationalStructMetaData of(@Nonnull DataType.StructType type) { + return new RelationalStructMetaData(type); + } + @Override public String getTypeName() { - return name; + return type.getName(); } @Override public int getColumnCount() throws SQLException { - return columns.length - leadingPhantomColumnOffset; + return type.getFields().size() - leadingPhantomColumnOffset; } @Override public int isNullable(int oneBasedColumn) throws SQLException { - return getField(oneBasedColumn).isNullable(); + if (getField(oneBasedColumn).getType().isNullable()) { + return DatabaseMetaData.columnNullable; + } else { + return DatabaseMetaData.columnNoNulls; + } } @Override @@ -103,7 +108,7 @@ public String getCatalogName(int oneBasedColumn) throws SQLException { @Override public int getColumnType(int oneBasedColumn) throws SQLException { - return getField(oneBasedColumn).getSqlTypeCode(); + return getField(oneBasedColumn).getType().getJdbcSqlCode(); } @Override @@ -113,20 +118,20 @@ public String getColumnTypeName(int oneBasedColumn) throws SQLException { @Override public StructMetaData getStructMetaData(int oneBasedColumn) throws SQLException { - FieldDescription field = getField(oneBasedColumn); - if (field.isStruct()) { - return field.getFieldMetaData(); + final DataType type = getField(oneBasedColumn).getType(); + if (type.getCode() == DataType.Code.STRUCT) { + return RelationalStructMetaData.of((DataType.StructType) type); } throw new InvalidColumnReferenceException("Position <" + oneBasedColumn + "> is not a struct type").toSqlException(); } @Override public ArrayMetaData getArrayMetaData(int oneBasedColumn) throws SQLException { - FieldDescription field = getField(oneBasedColumn); - if (field.isArray()) { - return field.getArrayMetaData(); + final DataType type = getField(oneBasedColumn).getType(); + if (type.getCode() == DataType.Code.ARRAY) { + return RelationalArrayMetaData.of((DataType.ArrayType) type); } - throw new InvalidColumnReferenceException("Position <" + oneBasedColumn + "> is not a struct type").toSqlException(); + throw new InvalidColumnReferenceException("Position <" + oneBasedColumn + "> is not an array type").toSqlException(); } @Override @@ -134,6 +139,12 @@ public int getLeadingPhantomColumnCount() { return leadingPhantomColumnOffset; } + @Nonnull + @Override + public DataType.StructType getRelationalDataType() throws SQLException { + return type; + } + @Override public T unwrap(Class iface) throws SQLException { return iface.cast(this); @@ -145,15 +156,15 @@ public boolean isWrapperFor(Class iface) { } @Nonnull - public List getFields() { - return ImmutableList.copyOf(columns); + private List getFields() { + return ImmutableList.copyOf(type.getFields()); } @SuppressWarnings("PMD.PreserveStackTrace") - private FieldDescription getField(int oneBasedColumn) throws SQLException { + private DataType.StructType.Field getField(int oneBasedColumn) throws SQLException { try { int adjustedColPosition = leadingPhantomColumnOffset + (oneBasedColumn - 1); - return columns[adjustedColPosition]; + return type.getFields().get(adjustedColPosition); } catch (ArrayIndexOutOfBoundsException aie) { throw new InvalidColumnReferenceException("Position <" + oneBasedColumn + "> is not valid. Struct has " + getColumnCount() + " columns") .toSqlException(); @@ -164,12 +175,13 @@ private int countLeadingPhantomColumns() { /* * Adjust the one-based position to account for any leading Phantom columns */ - int count = columns.length; - for (int i = 0; i < columns.length; i++) { - if (!columns[i].isPhantom()) { - count = i; - break; + final var fields = getFields(); + int count = 0; + for (var field: fields) { + if (!KNOWN_PHANTOM_COLUMNS.contains(field.getName())) { + return count; } + count++; } return count; } @@ -184,7 +196,7 @@ public boolean equals(Object other) { return true; } return leadingPhantomColumnOffset == otherMetadata.leadingPhantomColumnOffset && - Arrays.equals(columns, otherMetadata.columns); + type.equals(otherMetadata.type); } @Override @@ -193,6 +205,6 @@ public int hashCode() { } private int calculateHashCode() { - return Objects.hash(Arrays.hashCode(columns), leadingPhantomColumnOffset); + return Objects.hash(type, leadingPhantomColumnOffset); } } diff --git a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/SqlTypeNamesSupport.java b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/SqlTypeNamesSupport.java index 897f396fec..f15a2a09f0 100644 --- a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/SqlTypeNamesSupport.java +++ b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/SqlTypeNamesSupport.java @@ -21,11 +21,11 @@ package com.apple.foundationdb.relational.api; import com.apple.foundationdb.annotation.API; +import com.apple.foundationdb.relational.api.metadata.DataType; -import java.sql.Array; -import java.sql.Struct; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.sql.Types; -import java.util.UUID; /** * Class to host method taken from SqlTypeSupport needed by @@ -96,31 +96,35 @@ public static int getSqlTypeCode(String sqlTypeName) { } } - public static int getSqlTypeCodeFromObject(Object obj) { - if (obj == null) { - return Types.NULL; - } else if (obj instanceof Long) { - return Types.BIGINT; - } else if (obj instanceof Integer) { - return Types.INTEGER; - } else if (obj instanceof Boolean) { - return Types.BOOLEAN; - } else if (obj instanceof byte[]) { - return Types.BINARY; - } else if (obj instanceof Float) { - return Types.FLOAT; - } else if (obj instanceof Double) { - return Types.DOUBLE; - } else if (obj instanceof String) { - return Types.VARCHAR; - } else if (obj instanceof Array) { - return Types.ARRAY; - } else if (obj instanceof Struct) { - return Types.STRUCT; - } else if (obj instanceof UUID) { - return Types.OTHER; - } else { - throw new IllegalStateException("Unexpected object type: " + obj.getClass().getName()); + /** + * This method does the best-case effort to match the JDBC SQL Type name with the corresponding non-nullable version + * of the DataType. As evident, we cannot actually match all the type, like composite type struct and array. In + * that case, we simply return a {@code null}. + * + * @param sqlTypeName the name of the JDBC-supported SQL types takem from {@link Types} + * @return the equivalent {@link DataType} for the type name + */ + @Nullable + public static DataType getDataTypeFromSqlTypeName(@Nonnull String sqlTypeName) { + switch (sqlTypeName) { + case "INTEGER": + return DataType.Primitives.INTEGER.type(); + case "BINARY": + return DataType.Primitives.BYTES.type(); + case "BIGINT": + return DataType.Primitives.LONG.type(); + case "FLOAT": + return DataType.Primitives.FLOAT.type(); + case "DOUBLE": + return DataType.Primitives.DOUBLE.type(); + case "STRING": + return DataType.Primitives.STRING.type(); + case "BOOLEAN": + return DataType.Primitives.BOOLEAN.type(); + case "NULL": + return DataType.Primitives.NULL.type(); + default: + return null; } } } diff --git a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/StructMetaData.java b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/StructMetaData.java index c802fb0ee5..6bbaa51555 100644 --- a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/StructMetaData.java +++ b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/StructMetaData.java @@ -20,6 +20,9 @@ package com.apple.foundationdb.relational.api; +import com.apple.foundationdb.relational.api.metadata.DataType; + +import javax.annotation.Nonnull; import java.sql.SQLException; import java.sql.Wrapper; @@ -87,4 +90,13 @@ public interface StructMetaData extends Wrapper { default int getLeadingPhantomColumnCount() { return 0; } + + /** + * Get the {@link DataType} object equivalent of this metadata. + * + * @return the datatype object. + * @throws SQLException if something goes wrong. + */ + @Nonnull + DataType.StructType getRelationalDataType() throws SQLException; } diff --git a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/StructResultSetMetaData.java b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/StructResultSetMetaData.java index da609ec76f..e9078a4492 100644 --- a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/StructResultSetMetaData.java +++ b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/StructResultSetMetaData.java @@ -21,7 +21,9 @@ package com.apple.foundationdb.relational.api; import com.apple.foundationdb.annotation.API; +import com.apple.foundationdb.relational.api.metadata.DataType; +import javax.annotation.Nonnull; import java.sql.SQLException; @API(API.Status.EXPERIMENTAL) @@ -69,6 +71,12 @@ public ArrayMetaData getArrayMetaData(int oneBasedColumn) throws SQLException { return metaData.getArrayMetaData(oneBasedColumn); } + @Nonnull + @Override + public DataType.StructType getRelationalDataType() throws SQLException { + return metaData.getRelationalDataType(); + } + @Override public StructMetaData getStructMetaData(int oneBasedColumn) throws SQLException { return metaData.getStructMetaData(oneBasedColumn); diff --git a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/metadata/DataType.java b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/metadata/DataType.java index b278edf961..db003333e0 100644 --- a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/metadata/DataType.java +++ b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/metadata/DataType.java @@ -20,21 +20,24 @@ package com.apple.foundationdb.relational.api.metadata; +import com.apple.foundationdb.relational.api.RelationalArray; +import com.apple.foundationdb.relational.api.RelationalStruct; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.util.Assert; import com.apple.foundationdb.relational.util.SpotBugsSuppressWarnings; - import com.google.common.base.Suppliers; -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; import com.google.common.collect.ImmutableList; import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.sql.SQLException; import java.sql.Types; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.UUID; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -54,10 +57,10 @@ */ public abstract class DataType { @Nonnull - private static final BiMap typeCodeJdbcTypeMap; + private static final Map typeCodeJdbcTypeMap; static { - typeCodeJdbcTypeMap = HashBiMap.create(); + typeCodeJdbcTypeMap = new HashMap<>(); typeCodeJdbcTypeMap.put(Code.BOOLEAN, Types.BOOLEAN); typeCodeJdbcTypeMap.put(Code.LONG, Types.BIGINT); @@ -65,11 +68,13 @@ public abstract class DataType { typeCodeJdbcTypeMap.put(Code.FLOAT, Types.FLOAT); typeCodeJdbcTypeMap.put(Code.DOUBLE, Types.DOUBLE); typeCodeJdbcTypeMap.put(Code.STRING, Types.VARCHAR); - typeCodeJdbcTypeMap.put(Code.ENUM, Types.JAVA_OBJECT); // TODO (Rethink Relational Enum mapping to SQL type) - typeCodeJdbcTypeMap.put(Code.UUID, Types.OTHER); // TODO (Rethink Relational Enum mapping to SQL type) + typeCodeJdbcTypeMap.put(Code.ENUM, Types.OTHER); + typeCodeJdbcTypeMap.put(Code.UUID, Types.OTHER); typeCodeJdbcTypeMap.put(Code.BYTES, Types.BINARY); + typeCodeJdbcTypeMap.put(Code.VERSION, Types.BINARY); typeCodeJdbcTypeMap.put(Code.STRUCT, Types.STRUCT); typeCodeJdbcTypeMap.put(Code.ARRAY, Types.ARRAY); + typeCodeJdbcTypeMap.put(Code.NULL, Types.NULL); } private final boolean isNullable; @@ -162,6 +167,54 @@ public interface Named { String getName(); } + /** + * Trait representing composite type. + */ + public interface CompositeType { + + /** + * Checks if the {@link DataType} has identical shape. + * + * @return {@code true} if the {@link DataType} has identical shape, else {@code false} + */ + default boolean hasIdenticalStructure(Object object) { + return this.equals(object); + } + } + + @Nonnull + public static DataType getDataTypeFromObject(@Nullable Object obj) { + try { + if (obj == null) { + return Primitives.NULL.type(); + } else if (obj instanceof Long) { + return Primitives.LONG.type(); + } else if (obj instanceof Integer) { + return Primitives.INTEGER.type(); + } else if (obj instanceof Boolean) { + return Primitives.BOOLEAN.type(); + } else if (obj instanceof byte[]) { + return Primitives.BYTES.type(); + } else if (obj instanceof Float) { + return Primitives.FLOAT.type(); + } else if (obj instanceof Double) { + return Primitives.DOUBLE.type(); + } else if (obj instanceof String) { + return Primitives.STRING.type(); + } else if (obj instanceof UUID) { + return Primitives.UUID.type(); + } else if (obj instanceof RelationalStruct) { + return ((RelationalStruct) obj).getMetaData().getRelationalDataType(); + } else if (obj instanceof RelationalArray) { + return ((RelationalArray) obj).getMetaData().asRelationalType(); + } else { + throw new IllegalStateException("Unexpected object type: " + obj.getClass().getName()); + } + } catch (SQLException e) { + throw new RelationalException(e).toUncheckedWrappedException(); + } + } + // todo: this is ugly, DataType should be an interface. public abstract static class NumericType extends DataType { private NumericType(boolean isNullable, boolean isPrimitive, @Nonnull Code code) { @@ -825,6 +878,72 @@ public String toString() { } } + public static final class NullType extends DataType { + + @Nonnull + private static final NullType INSTANCE = new NullType(); + + @Nonnull + private final Supplier hashCodeSupplier = Suppliers.memoize(this::computeHashCode); + + private NullType() { + super(true, true, Code.NULL); + } + + @Override + @Nonnull + public DataType withNullable(boolean isNullable) { + if (isNullable) { + return Primitives.NULL.type(); + } else { + throw new RelationalException("NULL type cannot be non-nullable", ErrorCode.INTERNAL_ERROR).toUncheckedWrappedException(); + } + } + + @Override + public boolean isResolved() { + return true; + } + + @Nonnull + @Override + public DataType resolve(@Nonnull Map resolutionMap) { + return this; + } + + @Nonnull + public static NullType nullable() { + return INSTANCE; + } + + private int computeHashCode() { + return Objects.hash(getCode(), isNullable()); + } + + @Override + public int hashCode() { + return hashCodeSupplier.get(); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof NullType)) { + return false; + } + final var otherUuidType = (NullType) other; + return this.isNullable() == otherUuidType.isNullable(); + } + + @Override + public String toString() { + return "∅"; + } + } + public static final class EnumType extends DataType implements Named { @Nonnull private final Supplier hashCodeSupplier = Suppliers.memoize(this::computeHashCode); @@ -940,7 +1059,7 @@ public boolean equals(Object other) { } } - public static final class ArrayType extends DataType { + public static final class ArrayType extends DataType implements CompositeType { @Nonnull private final Supplier hashCodeSupplier = Suppliers.memoize(this::computeHashCode); @@ -1018,9 +1137,29 @@ public boolean equals(Object other) { public String toString() { return "[" + elementType + "]" + (isNullable() ? " ∪ ∅" : ""); } + + @Override + @SuppressWarnings("PMD.CompareObjectsWithEquals") + public boolean hasIdenticalStructure(final Object other) { + if (this == other) { + return true; + } + if (!(other instanceof ArrayType)) { + return false; + } + final var otherArrayType = (ArrayType) other; + if (this.isNullable() != otherArrayType.isNullable()) { + return false; + } + if (this.elementType instanceof CompositeType) { + return ((CompositeType)this.elementType).hasIdenticalStructure(otherArrayType.elementType); + } else { + return this.elementType.equals(otherArrayType.elementType); + } + } } - public static final class StructType extends DataType implements Named { + public static final class StructType extends DataType implements Named, CompositeType { @Nonnull private final Supplier hashCodeSupplier = Suppliers.memoize(this::computeHashCode); @@ -1184,6 +1323,39 @@ public boolean equals(Object other) { fields.equals(otherStructType.fields); } + @Override + @SuppressWarnings("PMD.CompareObjectsWithEquals") + public boolean hasIdenticalStructure(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof StructType)) { + return false; + } + final var otherStructType = (StructType) other; + final var fields = this.getFields(); + final var otherFields = otherStructType.getFields(); + if (this.isNullable() != otherStructType.isNullable() || fields.size() != otherFields.size()) { + return false; + } + for (int i = 0; i < fields.size(); i++) { + if (!fields.get(i).getName().equals(otherFields.get(i).getName()) || fields.get(i).getIndex() != otherFields.get(i).getIndex()) { + return false; + } + final var type = fields.get(i).getType(); + if (type instanceof CompositeType) { + if (!((CompositeType) type).hasIdenticalStructure(otherFields.get(i).getType())) { + return false; + } + } else { + if (!type.equals(otherStructType.getFields().get(i).getType())) { + return false; + } + } + } + return true; + } + @Override public String toString() { return name.substring(0, Math.min(name.length(), 5)) + " { " + fields.stream().map(field -> field.getName() + ":" + field.getType()).collect(Collectors.joining(",")) + " } "; @@ -1334,7 +1506,8 @@ public enum Code { UUID, STRUCT, ARRAY, - UNKNOWN + UNKNOWN, + NULL } @SuppressWarnings("PMD.AvoidFieldNameMatchingTypeName") @@ -1357,7 +1530,8 @@ public enum Primitives { NULLABLE_STRING(StringType.nullable()), NULLABLE_BYTES(BytesType.nullable()), NULLABLE_VERSION(VersionType.nullable()), - NULLABLE_UUID(UuidType.nullable()) + NULLABLE_UUID(UuidType.nullable()), + NULL(NullType.INSTANCE) ; @Nonnull diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/EmbeddedRelationalArray.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/EmbeddedRelationalArray.java index a25e6c69c1..f1376d45d7 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/EmbeddedRelationalArray.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/EmbeddedRelationalArray.java @@ -22,15 +22,15 @@ import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.util.Assert; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.sql.DatabaseMetaData; import java.sql.SQLException; -import java.sql.Types; import java.util.ArrayList; import java.util.List; +import java.util.UUID; public interface EmbeddedRelationalArray extends RelationalArray { @@ -38,34 +38,28 @@ static RelationalArrayBuilder newBuilder() { return new Builder(); } - static RelationalArrayBuilder newBuilder(@Nonnull ArrayMetaData metaData) { - return new Builder(metaData); + static RelationalArrayBuilder newBuilder(@Nonnull DataType elementType) { + return new Builder(elementType); } class Builder implements RelationalArrayBuilder { - @Nullable private ArrayMetaData metaData; + @Nullable private DataType elementType; @Nonnull private final List elements = new ArrayList<>(); private Builder() { } - private Builder(@Nonnull ArrayMetaData metaData) { - this.metaData = metaData; + private Builder(@Nonnull DataType elementType) { + this.elementType = elementType; } @Override public EmbeddedRelationalArray build() throws SQLException { - try { - // This is a bit odd as we do not allow the arrayMetaData to be null, which can happen if there is no - // element added to the builder. The array can be empty in the case of nullable array. - // In the case of supporting an empty array, we need to explicitly provide an arrayMetaData, which is - // required to correctly decipher the "type" of array downstream. - Assert.that(this.metaData != null, ErrorCode.UNKNOWN_TYPE, "Creating ARRAY without metadata is not allowed."); - } catch (RelationalException ve) { - throw ve.toSqlException(); + if (elementType == null) { + return new RowArray(List.of(), RelationalArrayMetaData.of(DataType.ArrayType.from(DataType.Primitives.NULL.type()))); } - return new RowArray(elements, metaData); + return new RowArray(elements, RelationalArrayMetaData.of(DataType.ArrayType.from(elementType, false))); } @Override @@ -74,11 +68,13 @@ public Builder addAll(@Nonnull Object... values) throws SQLException { if (value == null) { throw new RelationalException("Cannot add NULL to an array.", ErrorCode.DATATYPE_MISMATCH).toSqlException(); } - final var sqlType = SqlTypeSupport.getSqlTypeCodeFromObject(value); - if (sqlType == Types.STRUCT) { - addStruct(((RelationalStruct) value)); + if (value instanceof RelationalStruct) { + addStruct((RelationalStruct) value) ; + } else if (value instanceof RelationalArray) { + throw new RelationalException("ARRAY element in ARRAY not supported.", ErrorCode.UNSUPPORTED_OPERATION).toSqlException(); } else { - addPrimitive(value, sqlType); + final var type = DataType.getDataTypeFromObject(value); + addField(value, type); } } return this; @@ -86,22 +82,39 @@ public Builder addAll(@Nonnull Object... values) throws SQLException { @Override public Builder addLong(long value) throws SQLException { - return addPrimitive(value, Types.BIGINT); + return addField(value, DataType.Primitives.LONG.type()); } @Override public Builder addString(@Nonnull String value) throws SQLException { - return addPrimitive(value, Types.VARCHAR); + return addField(value, DataType.Primitives.STRING.type()); } @Override public Builder addBytes(@Nonnull byte[] value) throws SQLException { - return addPrimitive(value, Types.BINARY); + return addField(value, DataType.Primitives.BYTES.type()); + } + + @Override + public Builder addUuid(@Nonnull UUID uuid) throws SQLException { + return addField(uuid, DataType.Primitives.UUID.type()); + } + + @Override + public Builder addObject(@Nonnull Object obj) throws SQLException { + if (obj instanceof RelationalStruct) { + return addStruct((RelationalStruct) obj); + } + if (obj instanceof RelationalArray) { + throw new RelationalException("Array objects in Array in supported", ErrorCode.INTERNAL_ERROR).toSqlException(); + } + return addField(obj, DataType.getDataTypeFromObject(obj)); } - private Builder addPrimitive(@Nonnull Object value, int sqlType) throws SQLException { + @Nonnull + private Builder addField(@Nonnull Object value, @Nonnull DataType type) throws SQLException { try { - checkMetadata(sqlType); + checkType(type); } catch (RelationalException ve) { throw ve.toSqlException(); } @@ -112,7 +125,7 @@ private Builder addPrimitive(@Nonnull Object value, int sqlType) throws SQLExcep @Override public Builder addStruct(RelationalStruct struct) throws SQLException { try { - checkMetadata(struct.getMetaData()); + checkType(struct.getMetaData().getRelationalDataType()); } catch (RelationalException ve) { throw ve.toSqlException(); } @@ -120,20 +133,13 @@ public Builder addStruct(RelationalStruct struct) throws SQLException { return this; } - private void checkMetadata(@Nonnull StructMetaData metaData) throws SQLException, RelationalException { - if (this.metaData != null) { - Assert.that(this.metaData.getElementType() == Types.STRUCT, ErrorCode.DATATYPE_MISMATCH, "Expected array element to be of type:%s, but found type:STRUCT", this.metaData.getElementTypeName()); - Assert.that(this.metaData.getElementStructMetaData().equals(metaData), ErrorCode.DATATYPE_MISMATCH, "Metadata of struct elements in array do not match!"); - } else { - this.metaData = RelationalArrayMetaData.ofStruct(metaData, DatabaseMetaData.columnNoNulls); - } - } - - private void checkMetadata(int sqlType) throws SQLException, RelationalException { - if (this.metaData != null) { - Assert.that(this.metaData.getElementType() == sqlType, ErrorCode.DATATYPE_MISMATCH, "Expected array element to be of type:%s, but found type:%s", this.metaData.getElementTypeName(), SqlTypeNamesSupport.getSqlTypeName(sqlType)); - } else { - this.metaData = RelationalArrayMetaData.ofPrimitive(sqlType, DatabaseMetaData.columnNoNulls); + private void checkType(DataType type) throws RelationalException { + if (this.elementType == null) { + this.elementType = type; + } else if (this.elementType instanceof DataType.CompositeType) { + Assert.that(((DataType.CompositeType) elementType).hasIdenticalStructure(type), ErrorCode.DATATYPE_MISMATCH, "Expected array element to be of type:%s, but found type:%s", this.elementType, type); + } else { + Assert.that(this.elementType.equals(type), ErrorCode.DATATYPE_MISMATCH, "Expected array element to be of type:%s, but found type:%s", this.elementType, type); } } } diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/EmbeddedRelationalStruct.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/EmbeddedRelationalStruct.java index 8edbd84475..e982962e2f 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/EmbeddedRelationalStruct.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/EmbeddedRelationalStruct.java @@ -20,18 +20,15 @@ package com.apple.foundationdb.relational.api; -import com.apple.foundationdb.relational.api.exceptions.ErrorCode; -import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.recordlayer.ArrayRow; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.sql.DatabaseMetaData; import java.sql.SQLException; -import java.sql.Types; import java.util.ArrayList; import java.util.List; -import java.util.Locale; +import java.util.UUID; public interface EmbeddedRelationalStruct extends RelationalStruct { @@ -41,95 +38,87 @@ static RelationalStructBuilder newBuilder() { class Builder implements RelationalStructBuilder { - final List fields = new ArrayList<>(); + final List fields = new ArrayList<>(); final List elements = new ArrayList<>(); @Override public EmbeddedRelationalStruct build() { - return new ImmutableRowStruct(new ArrayRow(elements.toArray()), new RelationalStructMetaData(fields.toArray(new FieldDescription[0]))); + // Ideally, the name of the struct should be something that the user can specify, however, this actually + // interferes with the DDL-defined types. Hence, making this random is the way to enforce coercion + // between the two - rather than confusing the downstream planner. + final var name = "ANONYMOUS_STRUCT_" + UUID.randomUUID().toString().replace("-", "_"); + final var type = DataType.StructType.from(name, fields, false); + return new ImmutableRowStruct(new ArrayRow(elements.toArray()), RelationalStructMetaData.of(type)); } @Override - public Builder addBoolean(String fieldName, boolean b) throws SQLException { - return addPrimitive(fieldName, Types.BOOLEAN, b); + public Builder addBoolean(String fieldName, boolean b) { + return addField(fieldName, DataType.Primitives.BOOLEAN.type(), b); } @Override - public Builder addShort(String fieldName, short b) throws SQLException { - return addPrimitive(fieldName, Types.SMALLINT, b); + public Builder addLong(String fieldName, long l) throws SQLException { + return addField(fieldName, DataType.Primitives.LONG.type(), l); } @Override - public Builder addLong(String fieldName, long l) throws SQLException { - return addPrimitive(fieldName, Types.BIGINT, l); + public Builder addFloat(String fieldName, float f) { + return addField(fieldName, DataType.Primitives.FLOAT.type(), f); } @Override - public Builder addFloat(String fieldName, float f) throws SQLException { - return addPrimitive(fieldName, Types.FLOAT, f); + public Builder addDouble(String fieldName, double d) { + return addField(fieldName, DataType.Primitives.DOUBLE.type(), d); } @Override - public Builder addDouble(String fieldName, double d) throws SQLException { - return addPrimitive(fieldName, Types.DOUBLE, d); + public Builder addBytes(String fieldName, byte[] bytes) { + return addField(fieldName, DataType.Primitives.BYTES.type(), bytes); } @Override - public Builder addBytes(String fieldName, byte[] bytes) throws SQLException { - return addPrimitive(fieldName, Types.BINARY, bytes); + public Builder addString(String fieldName, @Nullable String s) { + return addField(fieldName, DataType.Primitives.STRING.type(), s); } @Override - public Builder addString(String fieldName, @Nullable String s) throws SQLException { - return addPrimitive(fieldName, Types.VARCHAR, s); + public Builder addUuid(String fieldName, @Nullable UUID uuid) { + return addField(fieldName, DataType.Primitives.UUID.type(), uuid); } @Override - public RelationalStructBuilder addObject(String fieldName, @Nullable Object obj, int targetSqlType) throws SQLException { - if (targetSqlType == Types.STRUCT) { - if (!(obj instanceof RelationalStruct)) { - throw new RelationalException(String.format(Locale.ROOT, "Expected object to be of type:STRUCT, but found type:%s", - obj == null ? "" : SqlTypeNamesSupport.getSqlTypeName(SqlTypeSupport.getSqlTypeCodeFromObject(obj))), - ErrorCode.DATATYPE_MISMATCH).toSqlException(); - } + public RelationalStructBuilder addObject(String fieldName, @Nullable Object obj) throws SQLException { + if (obj instanceof RelationalStruct) { return addStruct(fieldName, (RelationalStruct) obj); } - if (targetSqlType == Types.ARRAY) { - if (!(obj instanceof RelationalArray)) { - throw new RelationalException(String.format(Locale.ROOT, "Expected object to be of type:ARRAY, but found type:%s", - obj == null ? "" : SqlTypeNamesSupport.getSqlTypeName(SqlTypeSupport.getSqlTypeCodeFromObject(obj))), - ErrorCode.DATATYPE_MISMATCH).toSqlException(); - } + if (obj instanceof RelationalArray) { return addArray(fieldName, (RelationalArray) obj); } - return addPrimitive(fieldName, targetSqlType, obj); + return addField(fieldName, DataType.getDataTypeFromObject(obj), obj); } @Override public Builder addStruct(String fieldName, @Nonnull RelationalStruct struct) throws SQLException { - fields.add(FieldDescription.struct(fieldName, DatabaseMetaData.columnNoNulls, struct.getMetaData())); - elements.add(struct); + addField(fieldName, struct.getMetaData().getRelationalDataType(), struct); return this; } @Override public Builder addArray(String fieldName, @Nonnull RelationalArray array) throws SQLException { - fields.add(FieldDescription.array(fieldName, DatabaseMetaData.columnNoNulls, array.getMetaData())); - elements.add(array); + addField(fieldName, array.getMetaData().asRelationalType(), array); return this; } @Override - public Builder addInt(String fieldName, int i) throws SQLException { - return addPrimitive(fieldName, Types.INTEGER, i); + public Builder addInt(String fieldName, int i) { + return addField(fieldName, DataType.Primitives.INTEGER.type(), i); } - private Builder addPrimitive(@Nonnull String fieldName, int sqlType, @Nullable Object o) { - fields.add(FieldDescription.primitive(fieldName, sqlType, DatabaseMetaData.columnNoNulls)); + private Builder addField(@Nonnull String fieldName, @Nonnull DataType type, @Nullable Object o) { + fields.add(DataType.StructType.Field.from(fieldName, type, fields.size() + 1)); elements.add(o); return this; } } - } diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/RowArray.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/RowArray.java index 2bb75f781e..57fb1f2e43 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/RowArray.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/RowArray.java @@ -21,16 +21,13 @@ package com.apple.foundationdb.relational.api; import com.apple.foundationdb.annotation.API; - +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.recordlayer.ArrayRow; import com.apple.foundationdb.relational.recordlayer.IteratorResultSet; - import com.google.common.base.Suppliers; import javax.annotation.Nonnull; -import java.sql.DatabaseMetaData; import java.sql.SQLException; -import java.sql.Types; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -73,22 +70,11 @@ public RelationalResultSet getResultSet(long oneBasedIndex, int count) throws SQ .skip(oneBasedIndex - 1) .limit(count) .collect(Collectors.toList()); - FieldDescription componentFieldType; - switch (arrayMetaData.getElementType()) { - case Types.ARRAY: - componentFieldType = FieldDescription.array("VALUE", arrayMetaData.isElementNullable(), arrayMetaData.getElementArrayMetaData()); - break; - case Types.STRUCT: - componentFieldType = FieldDescription.struct("VALUE", arrayMetaData.isElementNullable(), arrayMetaData.getElementStructMetaData()); - break; - default: - componentFieldType = FieldDescription.primitive("VALUE", arrayMetaData.getElementType(), arrayMetaData.isElementNullable()); - break; - } - return new IteratorResultSet(new RelationalStructMetaData("ARRAY_ROW", - FieldDescription.primitive("INDEX", Types.INTEGER, DatabaseMetaData.columnNoNulls), - componentFieldType - ), slice.iterator(), 0); + final var type = DataType.StructType.from("ARRAY_ROW", List.of( + DataType.StructType.Field.from("INDEX", DataType.Primitives.INTEGER.type(), 0), + DataType.StructType.Field.from("VALUE", arrayMetaData.asRelationalType().getElementType(), 1) + ), true); + return new IteratorResultSet(RelationalStructMetaData.of(type), slice.iterator(), 0); } @Override diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/RowStruct.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/RowStruct.java index c6309b7e9d..dc4a54cc6a 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/RowStruct.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/RowStruct.java @@ -240,8 +240,6 @@ public RelationalArray getArray(int oneBasedPosition) throws SQLException { for (final var t : coll) { if (t instanceof Message) { elements.add(new ImmutableRowStruct(new MessageTuple((Message) t), arrayMetaData.getElementStructMetaData())); - } else if (t instanceof ByteString) { - elements.add(((ByteString) t).toByteArray()); } else { elements.add(t); } @@ -255,15 +253,14 @@ public RelationalArray getArray(int oneBasedPosition) throws SQLException { throw new SQLException("Array", ErrorCode.CANNOT_CONVERT_TYPE.getErrorCode()); } Descriptors.FieldDescriptor fieldDescriptor = message.getDescriptorForType().findFieldByName(NullableArrayUtils.getRepeatedFieldName()); + final var fieldValues = (Collection) message.getField(fieldDescriptor); final var elements = new ArrayList<>(); - final var coll = (Collection) message.getField(fieldDescriptor); - for (final var t : coll) { - if (t instanceof Message) { - elements.add(new ImmutableRowStruct(new MessageTuple((Message) t), arrayMetaData.getElementStructMetaData())); - } else if (t instanceof ByteString) { - elements.add(((ByteString) t).toByteArray()); - } else { - elements.add(t); + for (var fieldValue : fieldValues) { + final var sanitizedFieldValue = MessageTuple.sanitizeField(fieldValue); + if (sanitizedFieldValue instanceof Message) { + elements.add(new ImmutableRowStruct(new MessageTuple((Message) sanitizedFieldValue), arrayMetaData.getElementStructMetaData())); + } else { + elements.add(sanitizedFieldValue); } } return new RowArray(elements, arrayMetaData); diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/SqlTypeSupport.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/SqlTypeSupport.java deleted file mode 100644 index 6d36ccd554..0000000000 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/SqlTypeSupport.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * SqlTypeSupport.java - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2021-2024 Apple Inc. and the FoundationDB project authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.apple.foundationdb.relational.api; - -import com.apple.foundationdb.annotation.API; - -import com.apple.foundationdb.record.query.plan.cascades.typing.Type; -import com.apple.foundationdb.relational.api.exceptions.ErrorCode; -import com.apple.foundationdb.relational.api.exceptions.RelationalException; - -import javax.annotation.Nonnull; -import java.sql.Array; -import java.sql.DatabaseMetaData; -import java.sql.Struct; -import java.sql.Types; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -@API(API.Status.EXPERIMENTAL) -public final class SqlTypeSupport { - //TODO(bfines) eventually this should move into the Planner (or closer to there, anyway), but for now - //we will hold on to it here - private static final Set KNOWN_PHANTOM_COLUMNS = Set.of("__TYPE_KEY"); - - private SqlTypeSupport() { - - } - - public static String getSqlTypeName(int sqlTypeCode) { - return SqlTypeNamesSupport.getSqlTypeName(sqlTypeCode); - } - - public static int getSqlTypeCodeFromObject(Object obj) { - if (obj instanceof Long) { - return Types.BIGINT; - } else if (obj instanceof Integer) { - return Types.INTEGER; - } else if (obj instanceof Boolean) { - return Types.BOOLEAN; - } else if (obj instanceof byte[]) { - return Types.BINARY; - } else if (obj instanceof Float) { - return Types.FLOAT; - } else if (obj instanceof Double) { - return Types.DOUBLE; - } else if (obj instanceof String) { - return Types.VARCHAR; - } else if (obj instanceof Array) { - return Types.ARRAY; - } else if (obj instanceof Struct) { - return Types.STRUCT; - } else { - throw new IllegalStateException("Unexpected object type: " + obj.getClass().getName()); - } - } - - @SuppressWarnings("DuplicateBranchesInSwitch") //intentional duplicates for readability - public static int recordTypeToSqlType(Type.TypeCode typeCode) { - switch (typeCode) { - case UNKNOWN: - return Types.JAVA_OBJECT; - case ANY: - return Types.JAVA_OBJECT; - case BOOLEAN: - return Types.BOOLEAN; - case BYTES: - return Types.BINARY; - case DOUBLE: - return Types.DOUBLE; - case FLOAT: - return Types.FLOAT; - case INT: - return Types.INTEGER; - case LONG: - return Types.BIGINT; - case STRING: - return Types.VARCHAR; - case VERSION: - return Types.BINARY; - case ENUM: - case UUID: - // There are no specific JDBC types for ENUM and UUID, hence, defaulting to Types.OTHER. - return Types.OTHER; - case RECORD: - return Types.STRUCT; - case ARRAY: - return Types.ARRAY; - case RELATION: - //TODO(bfines) not sure if this is correct or not - return Types.JAVA_OBJECT; - default: - throw new IllegalStateException("Unexpected Type Code " + typeCode); - } - } - - public static Type.TypeCode sqlTypeToRecordType(int sqlTypeCode) { - switch (sqlTypeCode) { - case Types.FLOAT: - return Type.TypeCode.FLOAT; - case Types.VARCHAR: - return Type.TypeCode.STRING; - case Types.BINARY: - return Type.TypeCode.BYTES; - case Types.DOUBLE: - return Type.TypeCode.DOUBLE; - case Types.BIGINT: - return Type.TypeCode.LONG; - case Types.INTEGER: - return Type.TypeCode.INT; - case Types.BOOLEAN: - return Type.TypeCode.BOOLEAN; - case Types.STRUCT: - return Type.TypeCode.RECORD; - case Types.ARRAY: - return Type.TypeCode.ARRAY; - case Types.NULL: - return Type.TypeCode.NULL; - default: - throw new IllegalStateException("No conversion for SQL type " + getSqlTypeName(sqlTypeCode)); - } - } - - @Nonnull - public static StructMetaData recordToMetaData(@Nonnull Type.Record record) throws RelationalException { - FieldDescription[] fields = new FieldDescription[record.getFields().size()]; - for (int i = 0; i < record.getFields().size(); i++) { - Type.Record.Field field = record.getFields().get(i); - final FieldDescription fieldDescription = fieldToDescription(field); - fields[i] = fieldDescription; - } - return new RelationalStructMetaData(record.getName(), fields); - } - - @Nonnull - public static Type.Record structMetadataToRecordType(@Nonnull StructMetaData metaData, boolean isNullable) { - final var fields = ((RelationalStructMetaData) metaData) - .getFields().stream() - .map(SqlTypeSupport::descriptionToField) - .collect(Collectors.toList()); - return Type.Record.fromFields(isNullable, fields); - } - - @Nonnull - public static Type.Array arrayMetadataToArrayType(@Nonnull ArrayMetaData metaData, boolean isNullable) { - final var field = descriptionToField(((RelationalArrayMetaData) metaData).getElementField()); - return new Type.Array(isNullable, field.getFieldType()); - } - - @Nonnull - private static Type.Record.Field descriptionToField(@Nonnull FieldDescription description) { - final Type.TypeCode recordTypeCode = sqlTypeToRecordType(description.getSqlTypeCode()); - final var isNullable = description.isNullable() == DatabaseMetaData.columnNullable; - Type type; - if (recordTypeCode.equals(Type.TypeCode.RECORD)) { - type = structMetadataToRecordType(description.getFieldMetaData(), isNullable); - } else if (recordTypeCode.equals(Type.TypeCode.ARRAY)) { - type = arrayMetadataToArrayType(description.getArrayMetaData(), isNullable); - } else if (recordTypeCode == Type.TypeCode.NULL) { - type = Type.nullType(); - } else { - type = Type.primitiveType(recordTypeCode, isNullable); - } - return Type.Record.Field.of(type, Optional.of(description.getName())); - } - - @Nonnull - private static FieldDescription fieldToDescription(@Nonnull Type.Record.Field field) throws RelationalException { - final Type fieldType = field.getFieldType(); - if (fieldType.isPrimitive() || fieldType instanceof Type.Enum || fieldType instanceof Type.Uuid) { - return FieldDescription.primitive(field.getFieldName(), - SqlTypeSupport.recordTypeToSqlType(fieldType.getTypeCode()), - fieldType.isNullable() ? DatabaseMetaData.columnNullable : DatabaseMetaData.columnNoNulls, - KNOWN_PHANTOM_COLUMNS.contains(field.getFieldName()) - ); - } else if (fieldType instanceof Type.Array) { - Type.Array arrayType = (Type.Array) fieldType; - Type elementType = Objects.requireNonNull(arrayType.getElementType()); - ArrayMetaData arrayMetadata; - if (elementType.isPrimitive()) { - arrayMetadata = RelationalArrayMetaData.ofPrimitive( - SqlTypeSupport.recordTypeToSqlType(elementType.getTypeCode()), - elementType.isNullable() ? DatabaseMetaData.columnNullable : DatabaseMetaData.columnNoNulls); - } else if (elementType instanceof Type.Record) { - //get a StructMetaData recursively - Type.Record structType = (Type.Record) elementType; - arrayMetadata = RelationalArrayMetaData.ofStruct( - recordToMetaData(structType), - elementType.isNullable() ? DatabaseMetaData.columnNullable : DatabaseMetaData.columnNoNulls); - } else { - //TODO(bfines) not sure if this is true or not, but I like the rule. - throw new RelationalException("Cannot have an array of arrays right now", ErrorCode.UNSUPPORTED_OPERATION); - } - return FieldDescription.array(field.getFieldName(), arrayType.isNullable() ? DatabaseMetaData.columnNullable : DatabaseMetaData.columnNoNulls, arrayMetadata); - } else if (fieldType instanceof Type.Record) { - Type.Record recType = (Type.Record) fieldType; - StructMetaData smd = recordToMetaData(recType); - return FieldDescription.struct(field.getFieldName(), - fieldType.isNullable() ? DatabaseMetaData.columnNullable : DatabaseMetaData.columnNoNulls, smd); - } else { - throw new RelationalException("Unexpected Data Type " + fieldType.getClass(), ErrorCode.UNSUPPORTED_OPERATION); - } - } - - @Nonnull - public static StructMetaData typeToMetaData(@Nonnull Type type) throws RelationalException { - if (type instanceof Type.Record) { - return recordToMetaData((Type.Record) type); - } else if (type instanceof Type.Relation) { - return typeToMetaData(((Type.Relation) type).getInnerType()); - } else { - throw new RelationalException("Unexpected Data type " + type.getTypeCode(), ErrorCode.UNSUPPORTED_OPERATION); - } - } -} diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/CatalogMetaData.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/CatalogMetaData.java index 3e14a5abbc..0880e621b2 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/CatalogMetaData.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/CatalogMetaData.java @@ -21,29 +21,27 @@ package com.apple.foundationdb.relational.recordlayer; import com.apple.foundationdb.annotation.API; - import com.apple.foundationdb.record.RecordMetaData; import com.apple.foundationdb.record.RecordMetaDataProto; import com.apple.foundationdb.record.expressions.RecordKeyExpressionProto; import com.apple.foundationdb.record.metadata.Index; import com.apple.foundationdb.record.metadata.MetaDataException; import com.apple.foundationdb.record.metadata.RecordType; -import com.apple.foundationdb.relational.api.FieldDescription; -import com.apple.foundationdb.relational.api.Row; import com.apple.foundationdb.relational.api.RelationalDatabaseMetaData; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.RelationalStructMetaData; +import com.apple.foundationdb.relational.api.Row; import com.apple.foundationdb.relational.api.catalog.StoreCatalog; import com.apple.foundationdb.relational.api.ddl.ProtobufDdlUtil; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.OperationUnsupportedException; -import com.apple.foundationdb.relational.api.exceptions.UncheckedRelationalException; import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.api.exceptions.UncheckedRelationalException; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.recordlayer.catalog.CatalogMetaDataProvider; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerSchema; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerSchemaTemplate; import com.apple.foundationdb.relational.util.Assert; - import com.google.protobuf.Descriptors; import javax.annotation.Nonnull; @@ -51,7 +49,6 @@ import java.sql.DatabaseMetaData; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; -import java.sql.Types; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; @@ -95,12 +92,11 @@ public RelationalResultSet getSchemas(String catalogStr, String schemaPattern) t }; simplifiedRows.add(new ArrayRow(data)); } - - FieldDescription[] fields = { - FieldDescription.primitive("TABLE_CATALOG", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("TABLE_SCHEM", Types.VARCHAR, DatabaseMetaData.columnNullable) - }; - return new IteratorResultSet(new RelationalStructMetaData(fields), simplifiedRows.iterator(), 0); + final var schemasStructType = DataType.StructType.from("SCHEMAS", List.of( + DataType.StructType.Field.from("TABLE_CATALOG", DataType.Primitives.NULLABLE_STRING.type(), 0), + DataType.StructType.Field.from("TABLE_SCHEM", DataType.Primitives.NULLABLE_STRING.type(), 1) + ), true); + return new IteratorResultSet(RelationalStructMetaData.of(schemasStructType), simplifiedRows.iterator(), 0); } catch (SQLException sqle) { throw new RelationalException(sqle); } @@ -139,13 +135,14 @@ public RelationalResultSet getTables(String database, String schema, String tabl .map(ArrayRow::new) .collect(Collectors.toList()); - FieldDescription[] fields = { - FieldDescription.primitive("TABLE_CAT", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("TABLE_SCHEM", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("TABLE_NAME", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("TABLE_VERSION", Types.BIGINT, DatabaseMetaData.columnNullable) - }; - return new IteratorResultSet(new RelationalStructMetaData(fields), tableList.iterator(), 0); + final var tablesStructType = DataType.StructType.from("TABLES", List.of( + DataType.StructType.Field.from("TABLE_CAT", DataType.Primitives.NULLABLE_STRING.type(), 0), + DataType.StructType.Field.from("TABLE_SCHEM", DataType.Primitives.NULLABLE_STRING.type(), 1), + DataType.StructType.Field.from("TABLE_NAME", DataType.Primitives.NULLABLE_STRING.type(), 2), + DataType.StructType.Field.from("TABLE_VERSION", DataType.Primitives.NULLABLE_LONG.type(), 3) + + ), true); + return new IteratorResultSet(RelationalStructMetaData.of(tablesStructType), tableList.iterator(), 0); }); } @@ -165,15 +162,16 @@ public RelationalResultSet getPrimaryKeys(String database, String schema, String pks.getValue()[pos], pos + 1, null))); - FieldDescription[] fields = { - FieldDescription.primitive("TABLE_CAT", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("TABLE_SCHEM", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("TABLE_NAME", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("COLUMN_NAME", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("KEY_SEQ", Types.INTEGER, DatabaseMetaData.columnNullable), - FieldDescription.primitive("PK_NAME", Types.VARCHAR, DatabaseMetaData.columnNullable), - }; - return new IteratorResultSet(new RelationalStructMetaData(fields), rows.iterator(), 0); + + final var primaryKeysStructType = DataType.StructType.from("PRIMARY_KEYS", List.of( + DataType.StructType.Field.from("TABLE_CAT", DataType.Primitives.NULLABLE_STRING.type(), 0), + DataType.StructType.Field.from("TABLE_SCHEM", DataType.Primitives.NULLABLE_STRING.type(), 1), + DataType.StructType.Field.from("TABLE_NAME", DataType.Primitives.NULLABLE_STRING.type(), 2), + DataType.StructType.Field.from("COLUMN_NAME", DataType.Primitives.NULLABLE_STRING.type(), 3), + DataType.StructType.Field.from("KEY_SEQ", DataType.Primitives.NULLABLE_INTEGER.type(), 4), + DataType.StructType.Field.from("PK_NAME", DataType.Primitives.NULLABLE_STRING.type(), 5) + ), true); + return new IteratorResultSet(RelationalStructMetaData.of(primaryKeysStructType), rows.iterator(), 0); }); } @@ -241,33 +239,33 @@ public RelationalResultSet getColumns(String database, String schema, String tab return new ArrayRow(row); }).collect(Collectors.toList()); - FieldDescription[] columns = { - FieldDescription.primitive("TABLE_CAT", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("TABLE_SCHEM", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("TABLE_NAME", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("COLUMN_NAME", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("DATA_TYPE", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("TYPE_NAME", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("COLUMN_SIZE", Types.INTEGER, DatabaseMetaData.columnNullable), - FieldDescription.primitive("BUFFER_LENGTH", Types.INTEGER, DatabaseMetaData.columnNullable), - FieldDescription.primitive("DECIMAL_DIGITS", Types.INTEGER, DatabaseMetaData.columnNullable), - FieldDescription.primitive("NUM_PREC_RADIX", Types.INTEGER, DatabaseMetaData.columnNullable), - FieldDescription.primitive("NULLABLE", Types.INTEGER, DatabaseMetaData.columnNullable), - FieldDescription.primitive("REMARKS", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("COLUMN_DEF", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("SQL_DATA_TYPE", Types.INTEGER, DatabaseMetaData.columnNullable), - FieldDescription.primitive("SQL_DATETIME_SUB", Types.INTEGER, DatabaseMetaData.columnNullable), - FieldDescription.primitive("CHAR_OCTET_LENGTH", Types.INTEGER, DatabaseMetaData.columnNullable), - FieldDescription.primitive("ORDINAL_POSITION", Types.INTEGER, DatabaseMetaData.columnNullable), - FieldDescription.primitive("IS_NULLABLE", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("SCOPE_CATALOG", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("SCOPE_SCHEMA", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("SCOPE_TABLE", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("SOURCE_DATA_TYPE", Types.SMALLINT, DatabaseMetaData.columnNullable), - FieldDescription.primitive("IS_AUTOINCREMENT", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("IS_GENERATEDCOLUMN", Types.VARCHAR, DatabaseMetaData.columnNullable) - }; - return new IteratorResultSet(new RelationalStructMetaData(columns), columnDefs.iterator(), 0); + final var columnsStructType = DataType.StructType.from("PRIMARY_KEYS", List.of( + DataType.StructType.Field.from("TABLE_CAT", DataType.Primitives.NULLABLE_STRING.type(), 0), + DataType.StructType.Field.from("TABLE_SCHEM", DataType.Primitives.NULLABLE_STRING.type(), 1), + DataType.StructType.Field.from("TABLE_NAME", DataType.Primitives.NULLABLE_STRING.type(), 2), + DataType.StructType.Field.from("COLUMN_NAME", DataType.Primitives.NULLABLE_STRING.type(), 3), + DataType.StructType.Field.from("DATA_TYPE", DataType.Primitives.NULLABLE_STRING.type(), 4), + DataType.StructType.Field.from("TYPE_NAME", DataType.Primitives.NULLABLE_STRING.type(), 5), + DataType.StructType.Field.from("COLUMN_SIZE", DataType.Primitives.NULLABLE_INTEGER.type(), 6), + DataType.StructType.Field.from("BUFFER_LENGTH", DataType.Primitives.NULLABLE_INTEGER.type(), 7), + DataType.StructType.Field.from("DECIMAL_DIGITS", DataType.Primitives.NULLABLE_INTEGER.type(), 8), + DataType.StructType.Field.from("NUM_PREC_RADIX", DataType.Primitives.NULLABLE_INTEGER.type(), 9), + DataType.StructType.Field.from("NULLABLE", DataType.Primitives.NULLABLE_INTEGER.type(), 10), + DataType.StructType.Field.from("REMARKS", DataType.Primitives.NULLABLE_STRING.type(), 11), + DataType.StructType.Field.from("COLUMN_DEF", DataType.Primitives.NULLABLE_STRING.type(), 12), + DataType.StructType.Field.from("SQL_DATA_TYPE", DataType.Primitives.NULLABLE_INTEGER.type(), 13), + DataType.StructType.Field.from("SQL_DATETIME_SUB", DataType.Primitives.NULLABLE_INTEGER.type(), 14), + DataType.StructType.Field.from("CHAR_OCTET_LENGTH", DataType.Primitives.NULLABLE_INTEGER.type(), 15), + DataType.StructType.Field.from("ORDINAL_POSITION", DataType.Primitives.NULLABLE_INTEGER.type(), 16), + DataType.StructType.Field.from("IS_NULLABLE", DataType.Primitives.NULLABLE_STRING.type(), 17), + DataType.StructType.Field.from("SCOPE_CATALOG", DataType.Primitives.NULLABLE_STRING.type(), 18), + DataType.StructType.Field.from("SCOPE_SCHEMA", DataType.Primitives.NULLABLE_STRING.type(), 19), + DataType.StructType.Field.from("SCOPE_TABLE", DataType.Primitives.NULLABLE_STRING.type(), 20), + DataType.StructType.Field.from("SOURCE_DATA_TYPE", DataType.Primitives.NULLABLE_INTEGER.type(), 21), + DataType.StructType.Field.from("IS_AUTOINCREMENT", DataType.Primitives.NULLABLE_STRING.type(), 22), + DataType.StructType.Field.from("IS_GENERATEDCOLUMN", DataType.Primitives.NULLABLE_STRING.type(), 23) + ), true); + return new IteratorResultSet(RelationalStructMetaData.of(columnsStructType), columnDefs.iterator(), 0); }); } @@ -321,22 +319,22 @@ public RelationalResultSet getIndexInfo(String database, String schema, String t throw new RelationalException("table <" + tablePattern + "> does not exist", ErrorCode.UNDEFINED_TABLE); } - FieldDescription[] columns = { - FieldDescription.primitive("TABLE_CAT", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("TABLE_SCHEM", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("TABLE_NAME", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("NON_UNIQUE", Types.BOOLEAN, DatabaseMetaData.columnNullable), - FieldDescription.primitive("INDEX_QUALIFIER", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("INDEX_NAME", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("TYPE", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("ORDINAL_POSITION", Types.INTEGER, DatabaseMetaData.columnNullable), - FieldDescription.primitive("COLUMN_NAME", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("ASC_OR_DESC", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("CARDINALITY", Types.INTEGER, DatabaseMetaData.columnNullable), - FieldDescription.primitive("PAGES", Types.INTEGER, DatabaseMetaData.columnNullable), - FieldDescription.primitive("FILTER_CONDITION", Types.VARCHAR, DatabaseMetaData.columnNullable) - }; - return new IteratorResultSet(new RelationalStructMetaData(columns), indexDefs.iterator(), 0); + final var indexInfoStructType = DataType.StructType.from("PRIMARY_KEYS", List.of( + DataType.StructType.Field.from("TABLE_CAT", DataType.Primitives.NULLABLE_STRING.type(), 0), + DataType.StructType.Field.from("TABLE_SCHEM", DataType.Primitives.NULLABLE_STRING.type(), 1), + DataType.StructType.Field.from("TABLE_NAME", DataType.Primitives.NULLABLE_STRING.type(), 2), + DataType.StructType.Field.from("NON_UNIQUE", DataType.Primitives.NULLABLE_BOOLEAN.type(), 3), + DataType.StructType.Field.from("INDEX_QUALIFIER", DataType.Primitives.NULLABLE_STRING.type(), 4), + DataType.StructType.Field.from("INDEX_NAME", DataType.Primitives.NULLABLE_STRING.type(), 5), + DataType.StructType.Field.from("TYPE", DataType.Primitives.NULLABLE_STRING.type(), 6), + DataType.StructType.Field.from("ORDINAL_POSITION", DataType.Primitives.NULLABLE_INTEGER.type(), 7), + DataType.StructType.Field.from("COLUMN_NAME", DataType.Primitives.NULLABLE_STRING.type(), 8), + DataType.StructType.Field.from("ASC_OR_DESC", DataType.Primitives.NULLABLE_STRING.type(), 9), + DataType.StructType.Field.from("CARDINALITY", DataType.Primitives.NULLABLE_INTEGER.type(), 10), + DataType.StructType.Field.from("PAGES", DataType.Primitives.NULLABLE_INTEGER.type(), 11), + DataType.StructType.Field.from("FILTER_CONDITION", DataType.Primitives.NULLABLE_STRING.type(), 12) + ), true); + return new IteratorResultSet(RelationalStructMetaData.of(indexInfoStructType), indexDefs.iterator(), 0); }); } diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalConnection.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalConnection.java index facb6e376a..6e7ee8e54c 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalConnection.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalConnection.java @@ -24,22 +24,16 @@ import com.apple.foundationdb.record.ExecuteProperties; import com.apple.foundationdb.record.IsolationLevel; import com.apple.foundationdb.record.RecordCoreException; -import com.apple.foundationdb.relational.api.ArrayMetaData; import com.apple.foundationdb.relational.api.EmbeddedRelationalDriver; -import com.apple.foundationdb.relational.api.FieldDescription; -import com.apple.foundationdb.relational.api.ImmutableRowStruct; +import com.apple.foundationdb.relational.api.EmbeddedRelationalStruct; import com.apple.foundationdb.relational.api.Options; -import com.apple.foundationdb.relational.api.RelationalArray; import com.apple.foundationdb.relational.api.RelationalArrayMetaData; import com.apple.foundationdb.relational.api.RelationalConnection; import com.apple.foundationdb.relational.api.RelationalDatabaseMetaData; import com.apple.foundationdb.relational.api.RelationalPreparedStatement; import com.apple.foundationdb.relational.api.RelationalStatement; -import com.apple.foundationdb.relational.api.RelationalStruct; -import com.apple.foundationdb.relational.api.RelationalStructMetaData; import com.apple.foundationdb.relational.api.RowArray; import com.apple.foundationdb.relational.api.SqlTypeNamesSupport; -import com.apple.foundationdb.relational.api.SqlTypeSupport; import com.apple.foundationdb.relational.api.Transaction; import com.apple.foundationdb.relational.api.TransactionManager; import com.apple.foundationdb.relational.api.catalog.StoreCatalog; @@ -48,13 +42,13 @@ import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.api.fluentsql.expression.ExpressionFactory; import com.apple.foundationdb.relational.api.fluentsql.statement.StatementBuilderFactory; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.api.metadata.SchemaTemplate; import com.apple.foundationdb.relational.api.metrics.MetricCollector; import com.apple.foundationdb.relational.recordlayer.metric.RecordLayerMetricCollector; import com.apple.foundationdb.relational.recordlayer.structuredsql.expression.ExpressionFactoryImpl; import com.apple.foundationdb.relational.recordlayer.structuredsql.statement.StatementBuilderFactoryImpl; import com.apple.foundationdb.relational.recordlayer.util.ExceptionUtil; -import com.apple.foundationdb.relational.util.Assert; import com.apple.foundationdb.relational.util.SpotBugsSuppressWarnings; import com.apple.foundationdb.relational.util.Supplier; import com.google.common.annotations.VisibleForTesting; @@ -64,12 +58,9 @@ import java.net.URI; import java.sql.Array; import java.sql.Connection; -import java.sql.DatabaseMetaData; import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.Struct; -import java.sql.Types; -import java.util.ArrayList; import java.util.Arrays; import java.util.Locale; import java.util.stream.Collectors; @@ -369,40 +360,31 @@ public SQLWarning getWarnings() throws SQLException { @Override public Array createArrayOf(String typeName, Object[] elements) throws SQLException { - int typeCode = SqlTypeNamesSupport.getSqlTypeCode(typeName); - ArrayMetaData arrayMetaData = RelationalArrayMetaData.ofPrimitive(typeCode, DatabaseMetaData.columnNoNulls); - try { - if (typeCode == Types.STRUCT) { - Assert.that(elements.length != 0, ErrorCode.INTERNAL_ERROR, "Cannot determine the complete component type of array of struct since it has no elements!"); - Assert.that(elements[0] instanceof RelationalStruct, ErrorCode.DATATYPE_MISMATCH, "Element of the array of struct is not of struct type!"); - arrayMetaData = RelationalArrayMetaData.ofStruct(((RelationalStruct) elements[0]).getMetaData(), DatabaseMetaData.columnNoNulls); + final var dataType = SqlTypeNamesSupport.getDataTypeFromSqlTypeName(typeName); + if (dataType != null) { + return new RowArray(Arrays.stream(elements).collect(Collectors.toList()), RelationalArrayMetaData.of(DataType.ArrayType.from(dataType, false))); + } else if (elements.length == 0) { + throw new RelationalException("Cannot determine the complete component type of array of struct since it has no elements!", ErrorCode.INTERNAL_ERROR).toSqlException(); + } else { + final var elementType = DataType.getDataTypeFromObject(elements[0]); + if (elementType instanceof DataType.ArrayType) { + throw new RelationalException("Nested arrays are not supported yet!", ErrorCode.UNSUPPORTED_OPERATION).toSqlException(); + } else if (elementType.getJdbcSqlCode() != SqlTypeNamesSupport.getSqlTypeCode(typeName)) { + throw new RelationalException("Element of the array is expected to be of type " + typeName, ErrorCode.DATATYPE_MISMATCH).toSqlException(); } - } catch (RelationalException ve) { - throw ve.toSqlException(); + return new RowArray(Arrays.stream(elements).collect(Collectors.toList()), RelationalArrayMetaData.of(DataType.ArrayType.from(elementType, false))); } - return new RowArray(Arrays.stream(elements).collect(Collectors.toList()), arrayMetaData); } @Override public Struct createStruct(String typeName, Object[] attributes) throws SQLException { + // We do not preserve the typeName, as there is no validation around that currently. + final var builder = EmbeddedRelationalStruct.newBuilder(); int nextFieldIndex = 0; - final var fieldDescriptions = new ArrayList<>(); - for (var atr : attributes) { - final var fieldName = "f" + nextFieldIndex++; - final int typeCode = SqlTypeSupport.getSqlTypeCodeFromObject(atr); - switch (typeCode) { - case Types.ARRAY: - fieldDescriptions.add(FieldDescription.array(fieldName, DatabaseMetaData.columnNoNulls, ((RelationalArray) atr).getMetaData())); - break; - case Types.STRUCT: - fieldDescriptions.add(FieldDescription.struct(fieldName, DatabaseMetaData.columnNoNulls, ((RelationalStruct) atr).getMetaData())); - break; - default: - fieldDescriptions.add(FieldDescription.primitive(fieldName, typeCode, DatabaseMetaData.columnNoNulls)); - break; - } + for (var atr: attributes) { + builder.addObject("f" + nextFieldIndex++, atr); } - return new ImmutableRowStruct(new ArrayRow(attributes), new RelationalStructMetaData(fieldDescriptions.toArray(FieldDescription[]::new))); + return builder.build(); } private void startTransaction() throws SQLException { diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/MessageTuple.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/MessageTuple.java index 97167f153f..62a7e685b3 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/MessageTuple.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/MessageTuple.java @@ -22,12 +22,14 @@ import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.record.TupleFieldsProto; +import com.apple.foundationdb.record.metadata.expressions.TupleFieldsHelper; import com.apple.foundationdb.relational.api.exceptions.InvalidColumnReferenceException; +import com.google.protobuf.ByteString; import com.google.protobuf.Descriptors; import com.google.protobuf.Message; -import com.google.protobuf.MessageOrBuilder; -import java.util.UUID; +import java.util.List; +import java.util.stream.Collectors; @API(API.Status.EXPERIMENTAL) public class MessageTuple extends AbstractRow { @@ -48,21 +50,31 @@ public Object getObject(int position) throws InvalidColumnReferenceException { throw InvalidColumnReferenceException.getExceptionForInvalidPositionNumber(position); } Descriptors.FieldDescriptor fieldDescriptor = message.getDescriptorForType().getFields().get(position); - if (fieldDescriptor.isRepeated() || message.hasField(fieldDescriptor)) { + if (fieldDescriptor.isRepeated()) { + final var list = (List) message.getField(message.getDescriptorForType().getFields().get(position)); + return list.stream().map(MessageTuple::sanitizeField).collect(Collectors.toList()); + } + if (message.hasField(fieldDescriptor)) { final var field = message.getField(message.getDescriptorForType().getFields().get(position)); - if (fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.ENUM) { - return ((Descriptors.EnumValueDescriptor) field).getName(); - } else if (fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.MESSAGE && fieldDescriptor.getMessageType().equals(TupleFieldsProto.UUID.getDescriptor())) { - final var dynamicMsg = (MessageOrBuilder) field; - return new UUID((Long) dynamicMsg.getField(dynamicMsg.getDescriptorForType().findFieldByName("most_significant_bits")), - (Long) dynamicMsg.getField(dynamicMsg.getDescriptorForType().findFieldByName("least_significant_bits"))); - } - return field; + return sanitizeField(field); } else { return null; } } + public static Object sanitizeField(final Object field) { + if (field instanceof Message && ((Message) field).getDescriptorForType().equals(TupleFieldsProto.UUID.getDescriptor())) { + return TupleFieldsHelper.fromProto((Message) field, TupleFieldsProto.UUID.getDescriptor()); + } + if (field instanceof Descriptors.EnumValueDescriptor) { + return ((Descriptors.EnumValueDescriptor) field).getName(); + } + if (field instanceof ByteString) { + return ((ByteString) field).toByteArray(); + } + return field; + } + @SuppressWarnings("unchecked") public M parseMessage() { return (M) message; diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RecordStoreIndex.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RecordStoreIndex.java index 53dfb76c86..97ace48935 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RecordStoreIndex.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RecordStoreIndex.java @@ -21,7 +21,6 @@ package com.apple.foundationdb.relational.recordlayer; import com.apple.foundationdb.annotation.API; - import com.apple.foundationdb.record.IndexEntry; import com.apple.foundationdb.record.RecordCursor; import com.apple.foundationdb.record.TupleRange; @@ -31,14 +30,15 @@ import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.relational.api.Continuation; import com.apple.foundationdb.relational.api.Options; +import com.apple.foundationdb.relational.api.RelationalStructMetaData; import com.apple.foundationdb.relational.api.Row; -import com.apple.foundationdb.relational.api.SqlTypeSupport; import com.apple.foundationdb.relational.api.StructMetaData; import com.apple.foundationdb.relational.api.Transaction; import com.apple.foundationdb.relational.api.ddl.ProtobufDdlUtil; import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.api.metadata.DataType; +import com.apple.foundationdb.relational.recordlayer.metadata.DataTypeUtils; import com.apple.foundationdb.relational.recordlayer.storage.BackingStore; - import com.google.protobuf.Descriptors; import javax.annotation.Nonnull; @@ -74,7 +74,7 @@ public StructMetaData getMetaData() throws RelationalException { final RecordType recType = table.loadRecordType(Options.NONE); final List fields = indexStruct.validate(recType.getDescriptor()); final Type.Record record = ProtobufDdlUtil.recordFromFieldDescriptors(fields); - return SqlTypeSupport.recordToMetaData(record); + return RelationalStructMetaData.of((DataType.StructType) DataTypeUtils.toRelationalType(record)); } @Override diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RecordTypeTable.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RecordTypeTable.java index e9b67395a1..a9b3f83be4 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RecordTypeTable.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RecordTypeTable.java @@ -21,7 +21,6 @@ package com.apple.foundationdb.relational.recordlayer; import com.apple.foundationdb.annotation.API; - import com.apple.foundationdb.record.RecordCoreException; import com.apple.foundationdb.record.RecordCursor; import com.apple.foundationdb.record.RecordMetaData; @@ -32,22 +31,21 @@ import com.apple.foundationdb.record.provider.foundationdb.FDBStoredRecord; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.relational.api.Continuation; -import com.apple.foundationdb.relational.api.FieldDescription; import com.apple.foundationdb.relational.api.ImmutableRowStruct; import com.apple.foundationdb.relational.api.Options; +import com.apple.foundationdb.relational.api.RelationalStruct; +import com.apple.foundationdb.relational.api.RelationalStructMetaData; import com.apple.foundationdb.relational.api.Row; -import com.apple.foundationdb.relational.api.SqlTypeSupport; import com.apple.foundationdb.relational.api.StructMetaData; import com.apple.foundationdb.relational.api.Transaction; -import com.apple.foundationdb.relational.api.RelationalStruct; -import com.apple.foundationdb.relational.api.RelationalStructMetaData; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.api.metadata.DataType; +import com.apple.foundationdb.relational.recordlayer.metadata.DataTypeUtils; import com.apple.foundationdb.relational.recordlayer.storage.BackingStore; import com.apple.foundationdb.relational.recordlayer.util.ExceptionUtil; import com.apple.foundationdb.relational.util.Assert; import com.apple.foundationdb.relational.util.NullableArrayUtils; - import com.google.protobuf.ByteString; import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; @@ -55,9 +53,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.sql.DatabaseMetaData; import java.sql.SQLException; -import java.sql.Types; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -127,7 +124,7 @@ public StructMetaData getMetaData() throws RelationalException { }); orderedFieldMap.putAll(descriptorLookupMap); final Type.Record record = Type.Record.fromFieldDescriptorsMap(orderedFieldMap); - return SqlTypeSupport.recordToMetaData(record); + return RelationalStructMetaData.of((DataType.StructType) DataTypeUtils.toRelationalType(record)); } @Override @@ -185,39 +182,50 @@ public boolean insertRecord(@Nonnull RelationalStruct insert, boolean replaceOnD public static Message toDynamicMessage(RelationalStruct struct, Descriptors.Descriptor descriptor) throws RelationalException { DynamicMessage.Builder builder = DynamicMessage.newBuilder(descriptor); try { - StructMetaData structMetaData = struct.getMetaData(); - for (int i = 1; i <= structMetaData.getColumnCount(); i++) { - String columnName = structMetaData.getColumnName(i); + final var type = struct.getMetaData().getRelationalDataType(); + final var fields = type.getFields(); + for (int i = 0; i < fields.size(); i++) { + final var field = fields.get(i); + final var columnName = fields.get(i).getName(); Descriptors.FieldDescriptor fd = builder.getDescriptorForType().findFieldByName(columnName); Assert.thatUnchecked(fd != null, ErrorCode.INVALID_PARAMETER, "Cannot find column name: " + columnName); // TODO: Add type checking, nudging, and coercion at this point! Per bfines, good place to do it! // TODO (Add type checking, nudging, and coercion) - switch (structMetaData.getColumnType(i)) { - case Types.BOOLEAN: - case Types.DOUBLE: - case Types.FLOAT: - case Types.INTEGER: - case Types.BIGINT: - case Types.VARCHAR: - final var obj = struct.getObject(i); + if (fd.getType() == Descriptors.FieldDescriptor.Type.ENUM) { + final var maybeEnumValue = struct.getString(i + 1); + if (maybeEnumValue != null) { + final var valueDescriptor = fd.getEnumType().findValueByName(maybeEnumValue); + Assert.thatUnchecked(valueDescriptor != null, ErrorCode.CANNOT_CONVERT_TYPE, "Invalid enum value: %s", maybeEnumValue); + builder.setField(fd, valueDescriptor); + } + continue; + } + switch (field.getType().getCode()) { + case BOOLEAN: + case DOUBLE: + case FLOAT: + case INTEGER: + case LONG: + case STRING: + final var obj = struct.getObject(i + 1); if (obj != null) { builder.setField(fd, obj); } break; - case Types.BINARY: - final var bytes = struct.getBytes(i); + case BYTES: + final var bytes = struct.getBytes(i + 1); if (bytes != null) { builder.setField(fd, ByteString.copyFrom(bytes)); } break; - case Types.STRUCT: - var subStruct = struct.getStruct(i); + case STRUCT: + var subStruct = struct.getStruct(i + 1); if (subStruct != null) { builder.setField(fd, toDynamicMessage(subStruct, fd.getMessageType())); } break; - case Types.ARRAY: - var array = struct.getArray(i); + case ARRAY: + var array = struct.getArray(i + 1); // if the fieldDescriptor is repeatable, then it's an unwrapped array if (fd.isRepeated()) { final var arrayItems = (Object[]) (array == null ? new Object[]{} : array.getArray()); @@ -232,33 +240,19 @@ public static Message toDynamicMessage(RelationalStruct struct, Descriptors.Desc if (fd.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) { Assert.thatUnchecked(NullableArrayUtils.isWrappedArrayDescriptor(fd.getMessageType())); // wrap array in a struct and call toDynamicMessage again - final var wrapper = new ImmutableRowStruct(new ArrayRow(array), new RelationalStructMetaData( - FieldDescription.array(NullableArrayUtils.REPEATED_FIELD_NAME, DatabaseMetaData.columnNullable, null))); + final var wrapper = new ImmutableRowStruct(new ArrayRow(array), RelationalStructMetaData.of( + DataType.StructType.from("STRUCT", List.of( + DataType.StructType.Field.from(NullableArrayUtils.REPEATED_FIELD_NAME, array.getMetaData().asRelationalType(), 0) + ), true))); builder.setField(fd, toDynamicMessage(wrapper, fd.getMessageType())); } else { Assert.failUnchecked("Field Type expected to be of Type ARRAY but is actually " + fd.getType()); } } break; - case Types.OTHER: - // if the type is not specific, try checking if it is an ENUM. - // Since the JDBC specifications do not directly provide for ENUM type, we assume it to be defined - // with ENUM value's string-representation having sql.Types OTHER - if (fd.getType() == Descriptors.FieldDescriptor.Type.ENUM) { - final var enumValue = struct.getString(i); - if (enumValue != null) { - final var valueDescriptor = fd.getEnumType().findValueByName(enumValue); - Assert.thatUnchecked(valueDescriptor != null, ErrorCode.CANNOT_CONVERT_TYPE, "Invalid enum value: %s", enumValue); - builder.setField(fd, fd.getEnumType().findValueByName(enumValue)); - } - } else { - Assert.failUnchecked(ErrorCode.INTERNAL_ERROR, (String.format(Locale.ROOT, "Cannot interpret value in Column type OTHER for column <%s>", - columnName))); - } - break; default: Assert.failUnchecked(ErrorCode.INTERNAL_ERROR, (String.format(Locale.ROOT, "Unexpected Column type <%s> for column <%s>", - structMetaData.getColumnType(i), columnName))); + struct.getMetaData().getColumnType(i), columnName))); break; } } diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalog.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalog.java index 290cc29c88..c40abccf8f 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalog.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalog.java @@ -36,20 +36,20 @@ import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; import com.apple.foundationdb.record.provider.foundationdb.FDBStoredRecord; import com.apple.foundationdb.record.provider.foundationdb.keyspace.KeySpace; -import com.apple.foundationdb.tuple.Tuple; import com.apple.foundationdb.relational.api.Continuation; import com.apple.foundationdb.relational.api.ProtobufDataBuilder; +import com.apple.foundationdb.relational.api.RelationalResultSet; +import com.apple.foundationdb.relational.api.RelationalStructMetaData; import com.apple.foundationdb.relational.api.Row; -import com.apple.foundationdb.relational.api.SqlTypeSupport; import com.apple.foundationdb.relational.api.StructMetaData; import com.apple.foundationdb.relational.api.Transaction; -import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.catalog.CatalogValidator; import com.apple.foundationdb.relational.api.catalog.SchemaTemplateCatalog; import com.apple.foundationdb.relational.api.catalog.StoreCatalog; import com.apple.foundationdb.relational.api.ddl.ProtobufDdlUtil; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.api.metadata.Schema; import com.apple.foundationdb.relational.api.metadata.SchemaTemplate; import com.apple.foundationdb.relational.recordlayer.ArrayRow; @@ -59,12 +59,13 @@ import com.apple.foundationdb.relational.recordlayer.RecordLayerResultSet; import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; import com.apple.foundationdb.relational.recordlayer.catalog.systables.SystemTableRegistry; +import com.apple.foundationdb.relational.recordlayer.metadata.DataTypeUtils; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerSchema; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerSchemaTemplate; import com.apple.foundationdb.relational.recordlayer.util.ExceptionUtil; import com.apple.foundationdb.relational.util.Assert; - import com.apple.foundationdb.relational.util.SpotBugsSuppressWarnings; +import com.apple.foundationdb.tuple.Tuple; import com.google.protobuf.Descriptors; import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; @@ -409,7 +410,7 @@ private void putSchema(@Nonnull final RecordLayerSchema schema, @Nonnull final F } private static StructMetaData getMetaData(Descriptors.Descriptor descriptor) throws RelationalException { - return SqlTypeSupport.recordToMetaData(ProtobufDdlUtil.recordFromDescriptor(descriptor)); + return RelationalStructMetaData.of((DataType.StructType) DataTypeUtils.toRelationalType(ProtobufDdlUtil.recordFromDescriptor(descriptor))); } @Nonnull diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreSchemaTemplateCatalog.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreSchemaTemplateCatalog.java index 95fb7b0434..0f78d768e5 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreSchemaTemplateCatalog.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreSchemaTemplateCatalog.java @@ -34,18 +34,17 @@ import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase; import com.apple.foundationdb.record.provider.foundationdb.FDBStoredRecord; import com.apple.foundationdb.record.provider.foundationdb.RecordAlreadyExistsException; -import com.apple.foundationdb.tuple.Tuple; import com.apple.foundationdb.relational.api.ProtobufDataBuilder; +import com.apple.foundationdb.relational.api.RelationalResultSet; +import com.apple.foundationdb.relational.api.RelationalStructMetaData; import com.apple.foundationdb.relational.api.Row; -import com.apple.foundationdb.relational.api.SqlTypeSupport; -import com.apple.foundationdb.relational.api.StructMetaData; import com.apple.foundationdb.relational.api.Transaction; -import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.catalog.SchemaTemplateCatalog; import com.apple.foundationdb.relational.api.ddl.ProtobufDdlUtil; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; -import com.apple.foundationdb.relational.api.exceptions.UncheckedRelationalException; import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.api.exceptions.UncheckedRelationalException; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.api.metadata.SchemaTemplate; import com.apple.foundationdb.relational.recordlayer.ArrayRow; import com.apple.foundationdb.relational.recordlayer.ContinuationImpl; @@ -54,12 +53,13 @@ import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; import com.apple.foundationdb.relational.recordlayer.catalog.systables.SchemaTemplateSystemTable; import com.apple.foundationdb.relational.recordlayer.catalog.systables.SystemTableRegistry; +import com.apple.foundationdb.relational.recordlayer.metadata.DataTypeUtils; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerSchema; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerSchemaTemplate; import com.apple.foundationdb.relational.recordlayer.util.ExceptionUtil; import com.apple.foundationdb.relational.util.Assert; - import com.apple.foundationdb.relational.util.SpotBugsSuppressWarnings; +import com.apple.foundationdb.tuple.Tuple; import com.google.protobuf.ByteString; import com.google.protobuf.Descriptors; import com.google.protobuf.InvalidProtocolBufferException; @@ -245,7 +245,7 @@ public RelationalResultSet listTemplates(@Nonnull Transaction txn) { ContinuationImpl.BEGIN.getExecutionState(), ScanProperties.FORWARD_SCAN); Descriptors.Descriptor d = recordStore.getRecordMetaData().getRecordMetaData() .getRecordType(SchemaTemplateSystemTable.TABLE_NAME).getDescriptor(); - StructMetaData structMetaData = SqlTypeSupport.recordToMetaData(ProtobufDdlUtil.recordFromDescriptor(d)); + final var structMetaData = RelationalStructMetaData.of((DataType.StructType) DataTypeUtils.toRelationalType(ProtobufDdlUtil.recordFromDescriptor(d))); return new RecordLayerResultSet(structMetaData, RecordLayerIterator.create(cursor, this::transformSchemaTemplates), null /* caller is responsible for managing tx state */); diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/ddl/RecordLayerCatalogQueryFactory.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/ddl/RecordLayerCatalogQueryFactory.java index 7a427f1c0c..1fff5757e2 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/ddl/RecordLayerCatalogQueryFactory.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/ddl/RecordLayerCatalogQueryFactory.java @@ -21,19 +21,17 @@ package com.apple.foundationdb.relational.recordlayer.ddl; import com.apple.foundationdb.annotation.API; - import com.apple.foundationdb.record.query.plan.cascades.typing.Type; -import com.apple.foundationdb.relational.api.FieldDescription; import com.apple.foundationdb.relational.api.ImmutableRowStruct; -import com.apple.foundationdb.relational.api.Row; -import com.apple.foundationdb.relational.api.Transaction; -import com.apple.foundationdb.relational.api.RelationalArrayMetaData; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.RelationalStructMetaData; +import com.apple.foundationdb.relational.api.Row; +import com.apple.foundationdb.relational.api.Transaction; import com.apple.foundationdb.relational.api.catalog.StoreCatalog; import com.apple.foundationdb.relational.api.ddl.CatalogQueryFactory; import com.apple.foundationdb.relational.api.ddl.DdlQuery; import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.api.metadata.Metadata; import com.apple.foundationdb.relational.api.metadata.Schema; import com.apple.foundationdb.relational.api.metadata.SchemaTemplate; @@ -42,8 +40,6 @@ import javax.annotation.Nonnull; import java.net.URI; -import java.sql.DatabaseMetaData; -import java.sql.Types; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -79,14 +75,13 @@ public RelationalResultSet executeAction(Transaction txn) throws RelationalExcep tableNames, indexNames); - final FieldDescription[] fields = { - FieldDescription.primitive("DATABASE_PATH", Types.VARCHAR, DatabaseMetaData.columnNoNulls), - FieldDescription.primitive("SCHEMA_NAME", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.array("TABLES", DatabaseMetaData.columnNullable, RelationalArrayMetaData.ofPrimitive(Types.VARCHAR, DatabaseMetaData.columnNoNulls)), - FieldDescription.array("INDEXES", DatabaseMetaData.columnNullable, RelationalArrayMetaData.ofPrimitive(Types.VARCHAR, DatabaseMetaData.columnNoNulls)) - }; - return new IteratorResultSet(new RelationalStructMetaData(fields), - Collections.singleton(tuple).iterator(), 0); + final var describeSchemaStructType = DataType.StructType.from("DESCRIBE_SCHEMA", List.of( + DataType.StructType.Field.from("DATABASE_PATH", DataType.Primitives.STRING.type(), 0), + DataType.StructType.Field.from("SCHEMA_NAME", DataType.Primitives.NULLABLE_STRING.type(), 1), + DataType.StructType.Field.from("TABLES", DataType.ArrayType.from(DataType.Primitives.STRING.type()), 2), + DataType.StructType.Field.from("INDEXES", DataType.ArrayType.from(DataType.Primitives.STRING.type()), 3) + ), true); + return new IteratorResultSet(RelationalStructMetaData.of(describeSchemaStructType), Collections.singleton(tuple).iterator(), 0); } }; } @@ -103,27 +98,29 @@ public Type getResultSetMetadata() { @Override public RelationalResultSet executeAction(Transaction txn) throws RelationalException { final SchemaTemplate schemaTemplate = catalog.getSchemaTemplateCatalog().loadSchemaTemplate(txn, schemaId); - final var columnStructMetadata = new RelationalStructMetaData( - FieldDescription.primitive("COLUMN_NAME", Types.VARCHAR, DatabaseMetaData.columnNoNulls), - FieldDescription.primitive("COLUMN_TYPE", Types.INTEGER, DatabaseMetaData.columnNoNulls)); - final var tableStructMetadata = new RelationalStructMetaData( - FieldDescription.primitive("TABLE_NAME", Types.VARCHAR, DatabaseMetaData.columnNoNulls), - FieldDescription.array("COLUMNS", DatabaseMetaData.columnNoNulls, RelationalArrayMetaData.ofStruct( - columnStructMetadata, DatabaseMetaData.columnNoNulls))); - final var schemaTemplateStructMetadata = new RelationalStructMetaData( - FieldDescription.primitive("TEMPLATE_NAME", Types.VARCHAR, DatabaseMetaData.columnNoNulls), - FieldDescription.array("TABLES", DatabaseMetaData.columnNullable, RelationalArrayMetaData.ofStruct( - tableStructMetadata, DatabaseMetaData.columnNoNulls))); + final var columnType = DataType.StructType.from("COLUMN", List.of( + DataType.StructType.Field.from("COLUMN_NAME", DataType.Primitives.STRING.type(), 0), + DataType.StructType.Field.from("COLUMN_TYPE", DataType.Primitives.INTEGER.type(), 1)), + true); + final var tableType = DataType.StructType.from("TABLE", List.of( + DataType.StructType.Field.from("TABLE_NAME", DataType.Primitives.STRING.type(), 0), + DataType.StructType.Field.from("COLUMNS", DataType.ArrayType.from(columnType, true), 1)), + true); + final var describeSchemaType = DataType.StructType.from("DESCRIBE_SCHEMA_TEMPLATE", List.of( + DataType.StructType.Field.from("TEMPLATE_NAME", DataType.Primitives.STRING.type(), 0), + DataType.StructType.Field.from("TABLES", DataType.ArrayType.from(tableType, true), 1)), + true); + final var tableStructs = new ArrayList<>(); for (var table : schemaTemplate.getTables()) { final var columnStructs = new ArrayList<>(); for (var col : table.getColumns()) { - columnStructs.add(new ImmutableRowStruct(new ArrayRow(col.getName(), col.getDatatype().getJdbcSqlCode()), columnStructMetadata)); + columnStructs.add(new ImmutableRowStruct(new ArrayRow(col.getName(), col.getDatatype().getJdbcSqlCode()), RelationalStructMetaData.of(columnType))); } - tableStructs.add(new ImmutableRowStruct(new ArrayRow(table.getName(), columnStructs), tableStructMetadata)); + tableStructs.add(new ImmutableRowStruct(new ArrayRow(table.getName(), columnStructs), RelationalStructMetaData.of(tableType))); } final Row tuple = new ArrayRow(schemaTemplate.getName(), tableStructs); - return new IteratorResultSet(schemaTemplateStructMetadata, Collections.singleton(tuple).iterator(), 0); + return new IteratorResultSet(RelationalStructMetaData.of(describeSchemaType), Collections.singleton(tuple).iterator(), 0); } }; } diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/DataTypeUtils.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/DataTypeUtils.java index 38faa96b94..840805f547 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/DataTypeUtils.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/DataTypeUtils.java @@ -123,7 +123,7 @@ public static Type toRecordLayerType(@Nonnull final DataType type) { // but since in RL we store the elements as a 'repeated' field, there is not a way to tell if an element is explicitly 'null'. // The current RL behavior loses the nullability information even if the constituent of Type.Array is explicitly marked 'nullable'. Hence, // the check here avoids silently swallowing the requirement. - Assert.thatUnchecked(!asArray.getElementType().isNullable(), ErrorCode.UNSUPPORTED_OPERATION, "No support for nullable array elements."); + Assert.thatUnchecked(asArray.getElementType().getCode() == DataType.Code.NULL || !asArray.getElementType().isNullable(), ErrorCode.UNSUPPORTED_OPERATION, "No support for nullable array elements."); return new Type.Array(asArray.isNullable(), toRecordLayerType(asArray.getElementType())); case ENUM: final var asEnum = (DataType.EnumType) type; @@ -159,5 +159,6 @@ public static Type toRecordLayerType(@Nonnull final DataType type) { primitivesMap.put(DataType.Primitives.NULLABLE_STRING.type(), Type.primitiveType(Type.TypeCode.STRING, true)); primitivesMap.put(DataType.Primitives.NULLABLE_VERSION.type(), Type.primitiveType(Type.TypeCode.VERSION, true)); primitivesMap.put(DataType.Primitives.NULLABLE_UUID.type(), Type.uuidType(true)); + primitivesMap.put(DataType.Primitives.NULL.type(), Type.nullType()); } } diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/AstNormalizer.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/AstNormalizer.java index cbd9fe487f..6ed4aaf55d 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/AstNormalizer.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/AstNormalizer.java @@ -21,13 +21,11 @@ package com.apple.foundationdb.relational.recordlayer.query; import com.apple.foundationdb.annotation.API; - import com.apple.foundationdb.record.PlanHashable; import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.apple.foundationdb.relational.api.Options; -import com.apple.foundationdb.relational.api.SqlTypeSupport; import com.apple.foundationdb.relational.api.RelationalStruct; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; @@ -35,10 +33,10 @@ import com.apple.foundationdb.relational.api.metrics.RelationalMetric; import com.apple.foundationdb.relational.generated.RelationalParser; import com.apple.foundationdb.relational.generated.RelationalParserBaseVisitor; +import com.apple.foundationdb.relational.recordlayer.metadata.DataTypeUtils; import com.apple.foundationdb.relational.recordlayer.query.cache.QueryCacheKey; import com.apple.foundationdb.relational.recordlayer.util.ExceptionUtil; import com.apple.foundationdb.relational.util.Assert; - import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Suppliers; import com.google.common.hash.Hasher; @@ -509,8 +507,8 @@ private void processStructParameter(@Nonnull final Struct param, @Nullable Integ for (int i = 0; i < attributes.length; i++) { processParameterValue(attributes[i], unnamedParameterIndex, parameterName, i); } - final var resolvedType = SqlTypeSupport.structMetadataToRecordType(((RelationalStruct) param).getMetaData(), false); - context.finishStructLiteral(resolvedType, unnamedParameterIndex, parameterName, tokenIndex); + final var resolvedType = DataTypeUtils.toRecordLayerType(((RelationalStruct) param).getMetaData().getRelationalDataType()); + context.finishStructLiteral((Type.Record) resolvedType, unnamedParameterIndex, parameterName, tokenIndex); } catch (SQLException e) { throw new RuntimeException(e); } diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LiteralsUtils.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LiteralsUtils.java index 188150d3ac..218de368b3 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LiteralsUtils.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LiteralsUtils.java @@ -21,16 +21,14 @@ package com.apple.foundationdb.relational.recordlayer.query; import com.apple.foundationdb.annotation.API; - import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.typing.TypeRepository; import com.apple.foundationdb.record.query.plan.serialization.PlanSerialization; -import com.apple.foundationdb.relational.api.SqlTypeSupport; -import com.apple.foundationdb.relational.api.RelationalStruct; import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.continuation.LiteralObject; +import com.apple.foundationdb.relational.recordlayer.metadata.DataTypeUtils; import com.apple.foundationdb.relational.util.Assert; - import com.google.common.collect.Lists; import com.google.protobuf.DynamicMessage; import com.google.protobuf.InvalidProtocolBufferException; @@ -38,11 +36,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.sql.SQLException; -import java.sql.Types; import java.util.List; import java.util.Objects; -import java.util.stream.Collectors; import static com.apple.foundationdb.relational.api.exceptions.ErrorCode.DATATYPE_MISMATCH; import static com.apple.foundationdb.relational.api.exceptions.ErrorCode.INTERNAL_ERROR; @@ -53,27 +48,22 @@ private LiteralsUtils() { // prevent instantiation } + // This is not sufficient, as we should try to coercion struct types rather than checking for them being + // identical. https://github.com/FoundationDB/fdb-record-layer/issues/3472 @Nonnull public static Type.Array resolveArrayTypeFromObjectsList(List objects) { - final var distinctTypes = objects.stream().filter(Objects::nonNull).map(SqlTypeSupport::getSqlTypeCodeFromObject).distinct().collect(Collectors.toList()); - if (distinctTypes.isEmpty()) { - // has all nulls or is empty - return new Type.Array(Type.nullType()); - } - Assert.thatUnchecked(distinctTypes.size() == 1, DATATYPE_MISMATCH, "could not determine type of array literal"); - final var theType = distinctTypes.get(0); - if (theType != Types.STRUCT) { - return new Type.Array(Type.primitiveType(SqlTypeSupport.sqlTypeToRecordType(theType), false)); - } - final var distinctStructMetadata = objects.stream().filter(Objects::nonNull).map(o -> { - try { - return ((RelationalStruct) o).getMetaData(); - } catch (SQLException e) { - throw new RelationalException(e).toUncheckedWrappedException(); + DataType distinctType = null; + for (var object: objects) { + final var objectType = DataType.getDataTypeFromObject(object); + if (distinctType == null) { + distinctType = objectType; + } else if (distinctType instanceof DataType.CompositeType) { + Assert.thatUnchecked(((DataType.CompositeType)distinctType).hasIdenticalStructure(objectType), DATATYPE_MISMATCH, "could not determine type of array literal"); + } else { + Assert.thatUnchecked(distinctType.equals(objectType), DATATYPE_MISMATCH, "could not determine type of array literal"); } - }).distinct().collect(Collectors.toList()); - Assert.thatUnchecked(distinctStructMetadata.size() == 1, DATATYPE_MISMATCH, "Elements of struct array literal are not of identical shape!"); - return new Type.Array(SqlTypeSupport.structMetadataToRecordType(distinctStructMetadata.get(0), false)); + } + return new Type.Array(distinctType == null ? Type.nullType() : DataTypeUtils.toRecordLayerType(distinctType)); } @Nonnull diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/MutablePlanGenerationContext.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/MutablePlanGenerationContext.java index 3ef8ece6f2..9d1c9afdc7 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/MutablePlanGenerationContext.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/MutablePlanGenerationContext.java @@ -36,10 +36,10 @@ import com.apple.foundationdb.record.query.plan.cascades.values.LiteralValue; import com.apple.foundationdb.record.query.plan.cascades.values.OfTypeValue; import com.apple.foundationdb.record.query.plan.cascades.values.Value; -import com.apple.foundationdb.relational.api.SqlTypeSupport; import com.apple.foundationdb.relational.api.RelationalArray; import com.apple.foundationdb.relational.api.RelationalStruct; import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.recordlayer.metadata.DataTypeUtils; import com.apple.foundationdb.relational.util.Assert; import com.apple.foundationdb.relational.util.SpotBugsSuppressWarnings; @@ -309,7 +309,7 @@ public ConstantObjectValue processPreparedStatementArrayParameter(@Nonnull Array final var arrayElements = new ArrayList<>(); try { if (type == null) { - resolvedType = SqlTypeSupport.arrayMetadataToArrayType(((RelationalArray) param).getMetaData(), false); + resolvedType = (Type.Array) DataTypeUtils.toRecordLayerType(((RelationalArray) param).getMetaData().asRelationalType()); } try (ResultSet rs = param.getResultSet()) { while (rs.next()) { @@ -342,7 +342,7 @@ public Value processPreparedStatementStructParameter(@Nonnull Struct param, Object[] attributes; try { if (type == null) { - resolvedType = SqlTypeSupport.structMetadataToRecordType(((RelationalStruct) param).getMetaData(), false); + resolvedType = (Type.Record) DataTypeUtils.toRecordLayerType(((RelationalStruct) param).getMetaData().getRelationalDataType()); } attributes = param.getAttributes(); } catch (SQLException e) { diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/QueryExecutionContext.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/QueryExecutionContext.java index 5eccb6da8a..18a4baabd9 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/QueryExecutionContext.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/QueryExecutionContext.java @@ -23,11 +23,11 @@ import com.apple.foundationdb.record.EvaluationContext; import com.apple.foundationdb.record.ExecuteProperties; import com.apple.foundationdb.record.PlanHashable; +import com.apple.foundationdb.record.metadata.expressions.TupleFieldsHelper; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.typing.TypeRepository; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.util.Assert; - import com.google.common.base.Suppliers; import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; @@ -46,6 +46,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Stack; +import java.util.UUID; import java.util.function.Supplier; public interface QueryExecutionContext { @@ -220,7 +221,7 @@ void finishStructLiteral(@Nonnull final Type.Record type, for (final OrderedLiteral fieldOrderedLiteral : fields) { final var fieldValue = fieldOrderedLiteral.getLiteralObject(); if (fieldValue != null) { - messageBuilder.setField(fieldDescriptors.get(i), fieldValue); + messageBuilder.setField(fieldDescriptors.get(i), transformFieldValue(fieldValue)); } i++; } @@ -231,6 +232,13 @@ void finishStructLiteral(@Nonnull final Type.Record type, structLiteralScopeCount--; } + private static Object transformFieldValue(@Nonnull Object fieldValue) { + if (fieldValue instanceof UUID) { + return TupleFieldsHelper.toProto((UUID) fieldValue); + } + return fieldValue; + } + boolean isAddingComplexLiteral() { return arrayLiteralScopeCount + structLiteralScopeCount != 0; } diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/QueryPlan.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/QueryPlan.java index d196162add..4dcae9599a 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/QueryPlan.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/QueryPlan.java @@ -54,18 +54,16 @@ import com.apple.foundationdb.record.query.plan.plans.RecordQueryUpdatePlan; import com.apple.foundationdb.record.query.plan.serialization.DefaultPlanSerializationRegistry; import com.apple.foundationdb.relational.api.Continuation; -import com.apple.foundationdb.relational.api.FieldDescription; import com.apple.foundationdb.relational.api.ImmutableRowStruct; import com.apple.foundationdb.relational.api.Options; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.RelationalStructMetaData; import com.apple.foundationdb.relational.api.Row; -import com.apple.foundationdb.relational.api.SqlTypeSupport; -import com.apple.foundationdb.relational.api.StructMetaData; import com.apple.foundationdb.relational.api.Transaction; import com.apple.foundationdb.relational.api.ddl.DdlQuery; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.api.metrics.RelationalMetric; import com.apple.foundationdb.relational.continuation.CompiledStatement; import com.apple.foundationdb.relational.continuation.TypedQueryArgument; @@ -78,6 +76,7 @@ import com.apple.foundationdb.relational.recordlayer.RecordLayerResultSet; import com.apple.foundationdb.relational.recordlayer.RecordLayerSchema; import com.apple.foundationdb.relational.recordlayer.ResumableIterator; +import com.apple.foundationdb.relational.recordlayer.metadata.DataTypeUtils; import com.apple.foundationdb.relational.recordlayer.util.ExceptionUtil; import com.apple.foundationdb.relational.util.Assert; import com.google.common.base.Suppliers; @@ -89,10 +88,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.sql.DatabaseMetaData; import java.sql.SQLException; import java.sql.Struct; -import java.sql.Types; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -277,35 +274,39 @@ protected void validatePlanAgainstEnvironment(@Nonnull final @Nonnull private RelationalResultSet executeExplain(@Nonnull ContinuationImpl parsedContinuation) { - StructMetaData continuationMetadata = new RelationalStructMetaData( - FieldDescription.primitive("EXECUTION_STATE", Types.BINARY, DatabaseMetaData.columnNoNulls), - FieldDescription.primitive("VERSION", Types.INTEGER, DatabaseMetaData.columnNoNulls), - FieldDescription.primitive("PLAN_HASH_MODE", Types.VARCHAR, DatabaseMetaData.columnNoNulls) - ); - StructMetaData plannerMetricsMetadata = new RelationalStructMetaData( - FieldDescription.primitive("TASK_COUNT", Types.BIGINT, DatabaseMetaData.columnNoNulls), - FieldDescription.primitive("TASK_TOTAL_TIME_NS", Types.BIGINT, DatabaseMetaData.columnNoNulls), - FieldDescription.primitive("TRANSFORM_COUNT", Types.BIGINT, DatabaseMetaData.columnNoNulls), - FieldDescription.primitive("TRANSFORM_TIME_NS", Types.BIGINT, DatabaseMetaData.columnNoNulls), - FieldDescription.primitive("TRANSFORM_YIELD_COUNT", Types.BIGINT, DatabaseMetaData.columnNoNulls), - FieldDescription.primitive("INSERT_TIME_NS", Types.BIGINT, DatabaseMetaData.columnNoNulls), - FieldDescription.primitive("INSERT_NEW_COUNT", Types.BIGINT, DatabaseMetaData.columnNoNulls), - FieldDescription.primitive("INSERT_REUSED_COUNT", Types.BIGINT, DatabaseMetaData.columnNoNulls) - ); - StructMetaData metaData = new RelationalStructMetaData( - FieldDescription.primitive("PLAN", Types.VARCHAR, DatabaseMetaData.columnNoNulls), - FieldDescription.primitive("PLAN_HASH", Types.INTEGER, DatabaseMetaData.columnNoNulls), - FieldDescription.primitive("PLAN_DOT", Types.VARCHAR, DatabaseMetaData.columnNoNulls), - FieldDescription.primitive("PLAN_GML", Types.VARCHAR, DatabaseMetaData.columnNoNulls), - FieldDescription.struct("PLAN_CONTINUATION", DatabaseMetaData.columnNullable, continuationMetadata), - FieldDescription.struct("PLANNER_METRICS", DatabaseMetaData.columnNullable, plannerMetricsMetadata) - ); + final var continuationStructType = DataType.StructType.from( + "PLAN_CONTINUATION", List.of( + DataType.StructType.Field.from("EXECUTION_STATE", DataType.Primitives.BYTES.type(), 0), + DataType.StructType.Field.from("VERSION", DataType.Primitives.INTEGER.type(), 1), + DataType.StructType.Field.from("PLAN_HASH_MODE", DataType.Primitives.STRING.type(), 2)), + true); + final var plannerMetricsStructType = DataType.StructType.from( + "PLANNER_METRICS", List.of( + DataType.StructType.Field.from("TASK_COUNT", DataType.Primitives.LONG.type(), 0), + DataType.StructType.Field.from("TASK_TOTAL_TIME_NS", DataType.Primitives.LONG.type(), 1), + DataType.StructType.Field.from("TRANSFORM_COUNT", DataType.Primitives.LONG.type(), 2), + DataType.StructType.Field.from("TRANSFORM_TIME_NS", DataType.Primitives.LONG.type(), 3), + DataType.StructType.Field.from("TRANSFORM_YIELD_COUNT", DataType.Primitives.LONG.type(), 4), + DataType.StructType.Field.from("INSERT_TIME_NS", DataType.Primitives.LONG.type(), 5), + DataType.StructType.Field.from("INSERT_NEW_COUNT", DataType.Primitives.LONG.type(), 6), + DataType.StructType.Field.from("INSERT_REUSED_COUNT", DataType.Primitives.LONG.type(), 7)), + true); + final var explainStructType = DataType.StructType.from( + "EXPLAIN", List.of( + DataType.StructType.Field.from("PLAN", DataType.Primitives.STRING.type(), 0), + DataType.StructType.Field.from("PLAN_HASH", DataType.Primitives.INTEGER.type(), 1), + DataType.StructType.Field.from("PLAN_DOT", DataType.Primitives.STRING.type(), 2), + DataType.StructType.Field.from("PLAN_GML", DataType.Primitives.STRING.type(), 3), + DataType.StructType.Field.from("PLAN_CONTINUATION", continuationStructType, 4), + DataType.StructType.Field.from("PLANNER_METRICS", plannerMetricsStructType, 5)), + true); + final Struct continuationInfo = ContinuationImpl.BEGIN.equals(parsedContinuation) ? null : new ImmutableRowStruct(new ArrayRow( parsedContinuation.getExecutionState(), parsedContinuation.getVersion(), parsedContinuation.getCompiledStatement() == null ? null : parsedContinuation.getCompiledStatement().getPlanSerializationMode() - ), continuationMetadata); + ), RelationalStructMetaData.of(continuationStructType)); final Struct plannerMetrics; if (plannerStatsMaps == null) { @@ -331,11 +332,11 @@ private RelationalResultSet executeExplain(@Nonnull ContinuationImpl parsedConti insertIntoMemoStats.map(s -> s.getCount(Debugger.Location.REUSED)).orElse(0L), parsedContinuation.getVersion(), parsedContinuation.getCompiledStatement() == null ? null : parsedContinuation.getCompiledStatement().getPlanSerializationMode() - ), plannerMetricsMetadata); + ), RelationalStructMetaData.of(plannerMetricsStructType)); } final var plannerGraph = Objects.requireNonNull(recordQueryPlan.acceptPropertyVisitor(PlannerGraphProperty.forExplain())); - return new IteratorResultSet(metaData, Collections.singleton(new ArrayRow( + return new IteratorResultSet(RelationalStructMetaData.of(explainStructType), Collections.singleton(new ArrayRow( explain(), planHashSupplier.get(), PlannerGraphProperty.exportToDot(plannerGraph), @@ -370,10 +371,10 @@ private RelationalResultSet executePhysicalPlan(@Nonnull final RecordLayerSchema parsedContinuation.getExecutionState(), executeProperties)); final var currentPlanHashMode = getCurrentPlanHashMode(options); - final var metaData = SqlTypeSupport.typeToMetaData(type); + final var dataType = (DataType.StructType) DataTypeUtils.toRelationalType(type); return executionContext.metricCollector.clock(RelationalMetric.RelationalEvent.CREATE_RESULT_SET_ITERATOR, () -> { final ResumableIterator iterator = RecordLayerIterator.create(cursor, messageFDBQueriedRecord -> new MessageTuple(messageFDBQueriedRecord.getMessage())); - return new RecordLayerResultSet(metaData, iterator, connection, + return new RecordLayerResultSet(RelationalStructMetaData.of(dataType), iterator, connection, (continuation, reason) -> enrichContinuation(continuation, fdbRecordStore.getRecordMetaData(), currentPlanHashMode, reason, getContinuationsContainsCompiledStatements(options))); }); diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/ExpressionVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/ExpressionVisitor.java index 42c7011c06..07b2cf8970 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/ExpressionVisitor.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/ExpressionVisitor.java @@ -21,7 +21,6 @@ package com.apple.foundationdb.relational.recordlayer.query.visitors; import com.apple.foundationdb.annotation.API; - import com.apple.foundationdb.record.query.plan.cascades.Quantifier; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.values.AbstractArrayConstructorValue; @@ -738,16 +737,31 @@ private Expression parseRecordField(@Nonnull ParserRuleContext parserRuleContext } @Nonnull + @SuppressWarnings("PMD.CompareObjectsWithEquals") private static Expression coerceIfNecessary(@Nonnull Expression expression, @Nonnull Type targetType) { final var value = expression.getUnderlying(); + final var maybeCoercedValue = coerceValueIfNecessary(expression.getUnderlying(), targetType); + if (value != maybeCoercedValue) { + return new Expression(expression.getName(), DataTypeUtils.toRelationalType(maybeCoercedValue.getResultType()), maybeCoercedValue); + } else { + return expression; + } + } + + @Nonnull + private static Value coerceValueIfNecessary(@Nonnull Value value, @Nonnull Type targetType) { final var resultType = value.getResultType(); if (resultType.isUnresolved() || (resultType.isPrimitive() && PromoteValue.isPromotionNeeded(resultType, targetType))) { - final var promoteValue = PromoteValue.inject(value, targetType); - return new Expression(expression.getName(), DataTypeUtils.toRelationalType(promoteValue.getResultType()), promoteValue); + return PromoteValue.inject(value, targetType); } - return expression; + if (resultType.isArray() && PromoteValue.isPromotionNeeded(resultType, targetType) && value instanceof AbstractArrayConstructorValue) { + Assert.thatUnchecked(targetType.isArray(), "Cannot convert array type to non-array type"); + final var targetElementType = ((Type.Array) targetType).getElementType(); + return AbstractArrayConstructorValue.LightArrayConstructorValue.of(Streams.stream(value.getChildren()).map(c -> coerceValueIfNecessary(c, targetElementType)).collect(Collectors.toList())); + } + return value; } @Nonnull diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/datagen/PrimitiveFieldGenerator.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/datagen/PrimitiveFieldGenerator.java index ec9fe0469d..74fca3ab11 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/datagen/PrimitiveFieldGenerator.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/datagen/PrimitiveFieldGenerator.java @@ -21,51 +21,51 @@ package com.apple.foundationdb.relational.autotest.datagen; import com.apple.foundationdb.relational.api.RelationalStructBuilder; +import com.apple.foundationdb.relational.api.metadata.DataType; import java.sql.SQLException; -import java.sql.Types; public class PrimitiveFieldGenerator implements FieldGenerator { private final String fieldName; - private final int fieldSqlType; + private final DataType dataType; private final RandomDataSource dataSource; public PrimitiveFieldGenerator(String fieldName, - int fieldSqlType, + DataType dataType, RandomDataSource dataSource) { this.fieldName = fieldName; - this.fieldSqlType = fieldSqlType; + this.dataType = dataType; this.dataSource = dataSource; } @Override public void generateValue(RelationalStructBuilder builder) throws SQLException { Object o; - switch (fieldSqlType) { - case Types.INTEGER: + switch (dataType.getCode()) { + case INTEGER: o = dataSource.nextInt(); break; - case Types.BIGINT: + case LONG: o = dataSource.nextLong(); break; - case Types.FLOAT: + case FLOAT: o = dataSource.nextFloat(); break; - case Types.DOUBLE: + case DOUBLE: o = dataSource.nextDouble(); break; - case Types.BOOLEAN: + case BOOLEAN: o = dataSource.nextBoolean(); break; - case Types.VARCHAR: + case STRING: o = dataSource.nextUtf8(); break; - case Types.BINARY: + case BYTES: o = dataSource.nextBytes(); break; default: throw new IllegalStateException("Only primitive fields can be generated by this generator"); } - builder.addObject(fieldName, o, fieldSqlType); + builder.addObject(fieldName, o); } } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/datagen/SchemaGenerator.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/datagen/SchemaGenerator.java index 113a4bdb09..7c4bd5d30e 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/datagen/SchemaGenerator.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/datagen/SchemaGenerator.java @@ -21,21 +21,14 @@ package com.apple.foundationdb.relational.autotest.datagen; import com.apple.foundationdb.record.util.pair.NonnullPair; -import com.apple.foundationdb.relational.api.FieldDescription; -import com.apple.foundationdb.relational.api.StructMetaData; -import com.apple.foundationdb.relational.api.RelationalArrayMetaData; import com.apple.foundationdb.relational.api.RelationalStructMetaData; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.autotest.SchemaDescription; import com.apple.foundationdb.relational.autotest.TableDescription; import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.sql.DatabaseMetaData; -import java.sql.SQLException; -import java.sql.Types; import java.util.AbstractMap; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -49,11 +42,11 @@ */ public class SchemaGenerator { - private static final List> primitiveDataTypes = List.of( - NonnullPair.of("bigint", Types.BIGINT), - NonnullPair.of("double", Types.DOUBLE), - NonnullPair.of("string", Types.VARCHAR), - NonnullPair.of("bytes", Types.BINARY) + private static final List> primitiveDataTypes = List.of( + NonnullPair.of("bigint", DataType.Primitives.LONG.type()), + NonnullPair.of("double", DataType.Primitives.DOUBLE.type()), + NonnullPair.of("string", DataType.Primitives.STRING.type()), + NonnullPair.of("bytes", DataType.Primitives.BYTES.type()) // removing boolean to avoid pk violations with small tables ); private final RandomDataSource random; @@ -68,16 +61,14 @@ public SchemaGenerator(RandomDataSource random, int maxTables, int maxNumStructs this.maxNumColumns = maxNumColumns; } - public SchemaDescription generateSchemaDescription(String templateName, String schemaName) throws SQLException { - List> availableColumnTypes = new ArrayList<>(primitiveDataTypes); + public SchemaDescription generateSchemaDescription(String templateName, String schemaName) { + List> availableColumnTypes = new ArrayList<>(primitiveDataTypes); List schemaEntries = new ArrayList<>(); - Map structMap = new HashMap<>(); int numStructs = random.nextInt(maxNumStructs + 1); for (int i = 0; i < numStructs; i++) { - Map.Entry struct = generateStruct(availableColumnTypes); + Map.Entry struct = generateStructTypes(availableColumnTypes); //add the structs to the column type so that you can CREATE TYPE AS STRUCTs within structs - availableColumnTypes.add(NonnullPair.of(struct.getKey().getTypeName(), Types.STRUCT)); - structMap.put(struct.getKey().getTypeName(), struct.getKey()); + availableColumnTypes.add(NonnullPair.of(struct.getKey().getName(), struct.getKey())); schemaEntries.add(struct.getValue()); } //now generate a random number of tables with the specified types @@ -85,7 +76,7 @@ public SchemaDescription generateSchemaDescription(String templateName, String s List tableNames = new ArrayList<>(); for (int tableNum = 0; tableNum < numTables; tableNum++) { - Map.Entry table = generateTable(availableColumnTypes, structMap); + Map.Entry table = generateTable(availableColumnTypes); schemaEntries.add(table.getValue()); tableNames.add(table.getKey()); } @@ -95,51 +86,43 @@ public SchemaDescription generateSchemaDescription(String templateName, String s return new SchemaDescription(templateName, templateDescription, schemaName, templateName + "_" + schemaName, tableNames); } - private Map.Entry generateTable(List> availableColumnTypes, Map structMetaDataMap) { - List columns = generateColumns(availableColumnTypes, structMetaDataMap); + private Map.Entry generateTable(List> availableColumnTypes) { + List columns = generateColumns(availableColumnTypes); List pkColumns = selectPrimaryKeys(columns); String typeName = "table_" + random.nextAlphaNumeric(5); String sb = "CREATE TABLE \"" + typeName + "\"(" + columns.stream().map(Object::toString).collect(Collectors.joining(",")) + ", PRIMARY KEY(" + pkColumns.stream().map(pk -> "\"" + pk + "\"").collect(Collectors.joining(",")) + ")" + ")"; - final var fieldDescs = new FieldDescription[columns.size()]; + final var fields = new ArrayList(); for (int i = 0; i < columns.size(); i++) { final var col = columns.get(i); - if (col.sqlType == Types.STRUCT) { - if (col.isRepeated) { - fieldDescs[i] = FieldDescription.array(col.name, DatabaseMetaData.columnNoNulls, RelationalArrayMetaData.ofStruct(col.structMetaData, DatabaseMetaData.columnNoNulls)); - } else { - fieldDescs[i] = FieldDescription.struct(col.name, DatabaseMetaData.columnNoNulls, col.structMetaData); - } + if (col.isRepeated) { + fields.add(DataType.StructType.Field.from(col.name, DataType.ArrayType.from(col.type), i)); } else { - if (col.isRepeated) { - fieldDescs[i] = FieldDescription.array(col.name, DatabaseMetaData.columnNoNulls, RelationalArrayMetaData.ofPrimitive(col.sqlType, DatabaseMetaData.columnNoNulls)); - } else { - fieldDescs[i] = FieldDescription.primitive(col.name, col.sqlType, DatabaseMetaData.columnNoNulls); - } + fields.add(DataType.StructType.Field.from(col.name, col.type, i)); } } - TableDescription tableDef = new TableDescription(new RelationalStructMetaData(typeName, fieldDescs), pkColumns); + TableDescription tableDef = new TableDescription(RelationalStructMetaData.of(DataType.StructType.from(typeName, fields, true)), pkColumns); return new AbstractMap.SimpleEntry<>(tableDef, sb); } - private Map.Entry generateStruct(List> availableColumnTypes) { - List columns = generateColumns(availableColumnTypes, null); - final var fieldDescs = new FieldDescription[columns.size()]; + private Map.Entry generateStructTypes(List> availableColumnTypes) { + List columns = generateColumns(availableColumnTypes); + final var fields = new ArrayList(); for (int i = 0; i < columns.size(); i++) { final var col = columns.get(0); - fieldDescs[i] = FieldDescription.primitive(col.name, col.sqlType, DatabaseMetaData.columnNoNulls); + fields.add(DataType.StructType.Field.from(col.name, col.type, i)); } String typeName = "struct_" + random.nextAlphaNumeric(5); - final var metaData = new RelationalStructMetaData(typeName, fieldDescs); + final var type = DataType.StructType.from(typeName, fields, false); String sb = "CREATE TYPE AS STRUCT \"" + typeName + "\" (" + columns.stream().map(Object::toString).collect(Collectors.joining(",")) + ")"; - return new AbstractMap.SimpleEntry<>(metaData, sb); + return new AbstractMap.SimpleEntry<>(type, sb); } - private List generateColumns(List> availableColumnTypes, @Nullable Map structMetaDataMap) { + private List generateColumns(List> availableColumnTypes) { //generate some columns, but we need at least 1 int numCols = random.nextInt(1, maxNumColumns); List columnDescs = new ArrayList<>(numCols); @@ -153,7 +136,7 @@ private List generateColumns(List> avai * change the PK generation logic to not have that restriction, we can remove this code block */ - NonnullPair primitiveType = primitiveDataTypes.get(random.nextInt(primitiveDataTypes.size())); + NonnullPair primitiveType = primitiveDataTypes.get(random.nextInt(primitiveDataTypes.size())); int ptColNumber = random.nextInt(numCols); ColumnDesc reqPrimitiveType = new ColumnDesc("col_" + ptColNumber, primitiveType.getRight(), primitiveType.getLeft(), false); columnDescs.add(reqPrimitiveType); @@ -162,7 +145,7 @@ private List generateColumns(List> avai Set takenColumnNumbers = new HashSet<>(); takenColumnNumbers.add(ptColNumber); OUTER: while (columnDescs.size() < numCols) { - NonnullPair nameAndType = availableColumnTypes.get(random.nextInt(availableColumnTypes.size())); + NonnullPair nameAndType = availableColumnTypes.get(random.nextInt(availableColumnTypes.size())); int colNum = random.nextInt(numCols); int finalColNum = colNum; while (takenColumnNumbers.contains(colNum)) { @@ -172,11 +155,7 @@ private List generateColumns(List> avai break OUTER; } } - if (nameAndType.getRight() == Types.STRUCT) { - columnDescs.add(new ColumnDesc("col_" + colNum, nameAndType.getRight(), nameAndType.getLeft(), random.nextBoolean(), structMetaDataMap.get(nameAndType.getLeft()))); - } else { - columnDescs.add(new ColumnDesc("col_" + colNum, nameAndType.getRight(), nameAndType.getLeft(), random.nextBoolean())); - } + columnDescs.add(new ColumnDesc("col_" + colNum, nameAndType.getRight(), nameAndType.getLeft(), random.nextBoolean())); takenColumnNumbers.add(colNum); } @@ -209,34 +188,27 @@ private List selectPrimaryKeys(List columns) { private static class ColumnDesc { private final String name; - private final int sqlType; + private final DataType type; private final String sqlName; private final boolean isRepeated; - @Nullable - private final StructMetaData structMetaData; - public ColumnDesc(String name, int sqlType, @Nonnull String sqlName, boolean isRepeated, @Nullable StructMetaData structMetaData) { + public ColumnDesc(String name, DataType type, @Nonnull String sqlName, boolean isRepeated) { this.name = name; - this.sqlType = sqlType; + this.type = type; this.sqlName = sqlName; this.isRepeated = isRepeated; - this.structMetaData = structMetaData; - } - - public ColumnDesc(String name, int sqlType, @Nonnull String sqlName, boolean isRepeated) { - this(name, sqlType, sqlName, isRepeated, null); } /* * returns true if this column is a non-repeated primitive type */ public boolean isSinglePrimitiveType() { - return !isRepeated && primitiveDataTypes.stream().map(NonnullPair::getRight).anyMatch(v -> v.equals(sqlType)); + return !isRepeated && primitiveDataTypes.stream().map(NonnullPair::getRight).anyMatch(v -> v.equals(type)); } public boolean allowedInPrimaryKey() { //TODO(bfines) allow booleans in pks again, but right now the cardinality is goofy - return isSinglePrimitiveType() && Types.BOOLEAN != sqlType; + return isSinglePrimitiveType() && DataType.Primitives.BOOLEAN.type() != type; } @Override diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/datagen/StructFieldGenerator.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/datagen/StructFieldGenerator.java index 48454e6e58..8e436ad694 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/datagen/StructFieldGenerator.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/datagen/StructFieldGenerator.java @@ -25,6 +25,7 @@ import com.apple.foundationdb.relational.api.SqlTypeNamesSupport; import com.apple.foundationdb.relational.api.StructMetaData; import com.apple.foundationdb.relational.api.RelationalStructBuilder; +import com.apple.foundationdb.relational.api.metadata.DataType; import javax.annotation.Nonnull; import java.sql.SQLException; @@ -32,6 +33,9 @@ import java.util.ArrayList; import java.util.List; +import static com.apple.foundationdb.relational.api.metadata.DataType.Code.ARRAY; +import static com.apple.foundationdb.relational.api.metadata.DataType.Code.STRUCT; + public class StructFieldGenerator implements FieldGenerator { private final String fieldName; protected final List nestedFieldGenerators; @@ -57,13 +61,13 @@ public StructFieldGenerator(String fieldName, this.maxArraySize = maxArraySize; this.nestedFieldGenerators = new ArrayList<>(structMetaData.getColumnCount()); for (int i = 0; i < structMetaData.getColumnCount(); i++) { - final var sqlType = structMetaData.getColumnType(i + 1); - if (sqlType == Types.STRUCT) { + final var field = structMetaData.getRelationalDataType().getFields().get(i); + if (field.getType().getCode() == STRUCT) { nestedFieldGenerators.add(getStructFieldGenerator(structMetaData.getColumnName(i + 1), structMetaData.getStructMetaData(i + 1))); - } else if (sqlType == Types.ARRAY) { + } else if (field.getType().getCode() == ARRAY) { nestedFieldGenerators.add(getArrayFieldGenerator(structMetaData.getColumnName(i + 1), structMetaData.getArrayMetaData(i + 1))); } else { - nestedFieldGenerators.add(getPrimitiveFieldGenerator(structMetaData.getColumnName(i + 1), sqlType)); + nestedFieldGenerators.add(getPrimitiveFieldGenerator(structMetaData.getColumnName(i + 1), field.getType())); } } } @@ -86,23 +90,23 @@ private FieldGenerator getArrayFieldGenerator(@Nonnull String name, @Nonnull Arr if (arrayMetaData.getElementType() == Types.STRUCT) { componentGenerator = getStructFieldGenerator("na", arrayMetaData.getElementStructMetaData()); } else { - componentGenerator = getPrimitiveFieldGenerator("na", arrayMetaData.getElementType()); + componentGenerator = getPrimitiveFieldGenerator("na", arrayMetaData.asRelationalType()); } return new ArrayFieldGenerator(name, componentGenerator, randomSource, maxArraySize); } - private FieldGenerator getPrimitiveFieldGenerator(@Nonnull String name, int sqlType) { - switch (sqlType) { - case Types.INTEGER: - case Types.BIGINT: - case Types.FLOAT: - case Types.DOUBLE: - case Types.BOOLEAN: - case Types.VARCHAR: - case Types.BINARY: - return new PrimitiveFieldGenerator(name, sqlType, randomSource); + private FieldGenerator getPrimitiveFieldGenerator(@Nonnull String name, DataType dataType) { + switch (dataType.getCode()) { + case INTEGER: + case LONG: + case FLOAT: + case DOUBLE: + case BOOLEAN: + case STRING: + case BYTES: + return new PrimitiveFieldGenerator(name, dataType, randomSource); default: - throw new IllegalStateException("Unexpected field type: " + SqlTypeNamesSupport.getSqlTypeName(sqlType)); + throw new IllegalStateException("Unexpected field type: " + SqlTypeNamesSupport.getSqlTypeName(dataType.getJdbcSqlCode())); } } } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/memory/InMemorySchemaTemplateCatalog.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/memory/InMemorySchemaTemplateCatalog.java index e9be9a7c57..84b11ed9a3 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/memory/InMemorySchemaTemplateCatalog.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/memory/InMemorySchemaTemplateCatalog.java @@ -20,7 +20,6 @@ package com.apple.foundationdb.relational.memory; -import com.apple.foundationdb.relational.api.FieldDescription; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.RelationalStructMetaData; import com.apple.foundationdb.relational.api.Row; @@ -28,15 +27,15 @@ import com.apple.foundationdb.relational.api.catalog.SchemaTemplateCatalog; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.api.metadata.SchemaTemplate; import com.apple.foundationdb.relational.recordlayer.IteratorResultSet; import com.apple.foundationdb.relational.recordlayer.ValueTuple; import javax.annotation.Nonnull; -import java.sql.DatabaseMetaData; -import java.sql.Types; import java.util.Comparator; import java.util.Iterator; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -136,8 +135,10 @@ public RelationalResultSet listTemplates(@Nonnull Transaction txn) { Iterator iter = strings.stream() .map(name -> (Row) new ValueTuple(name)) .collect(Collectors.toList()).iterator(); - FieldDescription field = FieldDescription.primitive("TEMPLATE_NAME", Types.VARCHAR, DatabaseMetaData.columnNoNulls); - return new IteratorResultSet(new RelationalStructMetaData(field), iter, 0); + final var type = DataType.StructType.from("TEMPLATES", List.of( + DataType.StructType.Field.from("TEMPLATE_NAME", DataType.Primitives.STRING.type(), 0) + ), true); + return new IteratorResultSet(RelationalStructMetaData.of(type), iter, 0); } @Override diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/memory/InMemoryTable.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/memory/InMemoryTable.java index 6cf9f1a802..d86cfdaac9 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/memory/InMemoryTable.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/memory/InMemoryTable.java @@ -24,19 +24,20 @@ import com.apple.foundationdb.record.metadata.expressions.KeyExpression; import com.apple.foundationdb.record.provider.foundationdb.FDBStoredRecordBuilder; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; -import com.apple.foundationdb.tuple.ByteArrayUtil; import com.apple.foundationdb.relational.api.DynamicMessageBuilder; import com.apple.foundationdb.relational.api.KeySet; import com.apple.foundationdb.relational.api.ProtobufDataBuilder; -import com.apple.foundationdb.relational.api.SqlTypeSupport; -import com.apple.foundationdb.relational.api.StructMetaData; import com.apple.foundationdb.relational.api.RelationalStruct; +import com.apple.foundationdb.relational.api.RelationalStructMetaData; +import com.apple.foundationdb.relational.api.StructMetaData; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; -import com.apple.foundationdb.relational.api.exceptions.UncheckedRelationalException; import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.api.exceptions.UncheckedRelationalException; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.recordlayer.RecordTypeTable; +import com.apple.foundationdb.relational.recordlayer.metadata.DataTypeUtils; import com.apple.foundationdb.relational.recordlayer.util.ExceptionUtil; - +import com.apple.foundationdb.tuple.ByteArrayUtil; import com.google.protobuf.Descriptors; import com.google.protobuf.Message; @@ -160,6 +161,6 @@ public StructMetaData getMetaData() throws RelationalException { }); orderedFieldMap.putAll(descriptorLookupMap); final Type.Record record = Type.Record.fromFieldDescriptorsMap(orderedFieldMap); - return SqlTypeSupport.recordToMetaData(record); + return RelationalStructMetaData.of((DataType.StructType) DataTypeUtils.toRelationalType(record)); } } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/InsertTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/InsertTest.java index 7446a65534..4e435f6d24 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/InsertTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/InsertTest.java @@ -22,7 +22,6 @@ import com.apple.foundationdb.relational.api.EmbeddedRelationalArray; import com.apple.foundationdb.relational.api.EmbeddedRelationalStruct; -import com.apple.foundationdb.relational.api.FieldDescription; import com.apple.foundationdb.relational.api.KeySet; import com.apple.foundationdb.relational.api.Options; import com.apple.foundationdb.relational.api.RelationalArrayMetaData; @@ -30,23 +29,21 @@ import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.RelationalStatement; import com.apple.foundationdb.relational.api.RelationalStruct; -import com.apple.foundationdb.relational.api.RelationalStructMetaData; +import com.apple.foundationdb.relational.api.RowArray; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; -import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.api.metadata.DataType; +import com.apple.foundationdb.relational.utils.RelationalAssertions; import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.SimpleDatabaseRule; import com.apple.foundationdb.relational.utils.TestSchemas; -import com.apple.foundationdb.relational.utils.RelationalAssertions; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.sql.DatabaseMetaData; import java.sql.DriverManager; import java.sql.SQLException; -import java.sql.Types; +import java.util.List; import java.util.Map; public class InsertTest { @@ -221,12 +218,12 @@ void canNotAddNullElementToArray() { } @Test - void canInsertNullableArray() throws SQLException, RelationalException { - final var itemMetadata = new RelationalStructMetaData( - FieldDescription.primitive("NAME", Types.VARCHAR, DatabaseMetaData.columnNullable), - FieldDescription.primitive("PRICE", Types.FLOAT, DatabaseMetaData.columnNullable) - ); - final var itemsMetadata = RelationalArrayMetaData.ofStruct(itemMetadata, DatabaseMetaData.columnNoNulls); + void canInsertNullableArray() throws SQLException { + final var itemsArrayType = DataType.ArrayType.from(DataType.StructType.from("ITEM", List.of( + DataType.StructType.Field.from("NAME", DataType.Primitives.NULLABLE_STRING.type(), 0), + DataType.StructType.Field.from("PRICE", DataType.Primitives.NULLABLE_FLOAT.type(), 1) + + ), false), false); try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { @@ -235,7 +232,7 @@ void canInsertNullableArray() throws SQLException, RelationalException { var restMenu = EmbeddedRelationalStruct.newBuilder() .addLong("ID", 1L) .addLong("REST_NO", 23L) - .addObject("CUISINE", "japanese", Types.OTHER) + .addObject("CUISINE", "japanese") .addArray("ITEMS", EmbeddedRelationalArray.newBuilder() .addStruct(EmbeddedRelationalStruct.newBuilder() .addString("NAME", "katsu curry") @@ -264,7 +261,7 @@ void canInsertNullableArray() throws SQLException, RelationalException { restMenu = EmbeddedRelationalStruct.newBuilder() .addLong("ID", 2L) .addLong("REST_NO", 23L) - .addObject("CUISINE", "japanese", Types.OTHER) + .addObject("CUISINE", "japanese") .build(); s.executeInsert("RESTAURANT_MENU", restMenu, Options.NONE); @@ -275,7 +272,7 @@ void canInsertNullableArray() throws SQLException, RelationalException { .hasColumn("ID", restMenu.getLong(1)) .hasColumn("REST_NO", restMenu.getLong(2)) .hasColumn("CUISINE", restMenu.getString(3)) - .hasColumn("ITEMS", EmbeddedRelationalArray.newBuilder(itemsMetadata).build()) + .hasColumn("ITEMS", new RowArray(List.of(), RelationalArrayMetaData.of(itemsArrayType))) .hasColumn("REVIEWS", null) .hasNoNextRow(); } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/IteratorResultSetTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/IteratorResultSetTest.java index 108c14de04..1e7c837ac9 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/IteratorResultSetTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/IteratorResultSetTest.java @@ -21,28 +21,25 @@ package com.apple.foundationdb.relational.recordlayer; import com.apple.foundationdb.relational.api.Continuation; -import com.apple.foundationdb.relational.api.FieldDescription; -import com.apple.foundationdb.relational.api.Row; import com.apple.foundationdb.relational.api.RelationalStructMetaData; -import com.apple.foundationdb.relational.api.exceptions.RelationalException; - +import com.apple.foundationdb.relational.api.Row; +import com.apple.foundationdb.relational.api.metadata.DataType; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.sql.DatabaseMetaData; import java.sql.SQLException; -import java.sql.Types; import java.util.Collections; +import java.util.List; class IteratorResultSetTest { @Test - void continuationAtBeginning() throws RelationalException, SQLException { - FieldDescription[] fields = new FieldDescription[]{ - FieldDescription.primitive("testField", Types.VARCHAR, DatabaseMetaData.columnNoNulls) - }; - try (IteratorResultSet irs = new IteratorResultSet(new RelationalStructMetaData(fields), Collections.singleton((Row) (new ArrayRow(new Object[]{"test"}))).iterator(), 0)) { - Assertions.assertThrows(SQLException.class, () -> irs.getContinuation()); + void continuationAtBeginning() throws SQLException { + final var type = DataType.StructType.from("STRUCT", List.of( + DataType.StructType.Field.from("testField", DataType.Primitives.STRING.type(), 0) + ), true); + try (IteratorResultSet irs = new IteratorResultSet(RelationalStructMetaData.of(type), Collections.singleton((Row) (new ArrayRow(new Object[]{"test"}))).iterator(), 0)) { + Assertions.assertThrows(SQLException.class, irs::getContinuation); //now iterate irs.next(); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RecordLayerResultSetTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RecordLayerResultSetTest.java index ebdc90d173..0ab227e69b 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RecordLayerResultSetTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RecordLayerResultSetTest.java @@ -20,22 +20,19 @@ package com.apple.foundationdb.relational.recordlayer; -import com.apple.foundationdb.relational.api.FieldDescription; -import com.apple.foundationdb.relational.api.Row; -import com.apple.foundationdb.relational.api.StructMetaData; import com.apple.foundationdb.relational.api.RelationalStructMetaData; +import com.apple.foundationdb.relational.api.Row; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.utils.RelationalAssertions; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import java.sql.DatabaseMetaData; import java.sql.ResultSetMetaData; import java.sql.SQLException; -import java.sql.Types; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -46,14 +43,13 @@ class RecordLayerResultSetTest { ResumableIterator cursor; @SuppressWarnings("unchecked") - RecordLayerResultSetTest() throws RelationalException { + RecordLayerResultSetTest() { cursor = (ResumableIterator) Mockito.mock(ResumableIterator.class); - StructMetaData smd = new RelationalStructMetaData( - FieldDescription.primitive("a", Types.INTEGER, DatabaseMetaData.columnNullable), - FieldDescription.primitive("b", Types.VARCHAR, DatabaseMetaData.columnNullable) - ); + final var type = DataType.StructType.from("STRUCT", List.of( + DataType.StructType.Field.from("a", DataType.Primitives.NULLABLE_INTEGER.type(), 0), + DataType.StructType.Field.from("b", DataType.Primitives.NULLABLE_STRING.type(), 1)), true); resultSet = new RecordLayerResultSet( - smd, + RelationalStructMetaData.of(type), cursor, Mockito.mock(EmbeddedRelationalConnection.class)); } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalArrayTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalArrayTest.java index 529c3fe247..713ae72d7e 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalArrayTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalArrayTest.java @@ -44,6 +44,7 @@ import java.sql.SQLException; import java.sql.Types; import java.util.List; +import java.util.UUID; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertArrayEquals; @@ -73,8 +74,10 @@ public class RelationalArrayTest { "string_null string array, string_not_null string array not null, " + "bytes_null bytes array, bytes_not_null bytes array not null, " + "struct_null structure array, struct_not_null structure array not null, " + + "uuid_null uuid array, uuid_not_null uuid array not null, " + // "enumeration_null enumeration array, enumeration_not_null enumeration array not null, " + - "primary key(pk))"; + "primary key(pk)) " + + "CREATE TABLE B(pk integer, uuid_null uuid array, primary key(pk))"; public void insertQuery(@Nonnull String q) throws SQLException { try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { @@ -91,6 +94,14 @@ void testInsertArraysViaQuerySimpleStatement() throws SQLException { insertArraysViaQuerySimpleStatement(); } + @Test + public void uuidArray() throws SQLException { + insertQuery("INSERT INTO B VALUES (" + + "1," + + "['e5711bed-c606-49e2-a682-316348bf4091', '14b387cd-79ad-4860-9588-9c4e81588af0'])"); + + } + private void insertArraysViaQuerySimpleStatement() throws SQLException { insertQuery("INSERT INTO T VALUES (" + "1," + @@ -101,7 +112,8 @@ private void insertArraysViaQuerySimpleStatement() throws SQLException { "[11, 22], [11, 22], " + "['11', '22'], ['11', '22'], " + "[x'31', x'32'], [x'31', x'32'], " + - "[(11, '11'), (22, '22')], [(11, '11'), (22, '22')] " + + "[(11, '11'), (22, '22')], [(11, '11'), (22, '22')], " + + "['e5711bed-c606-49e2-a682-316348bf4091', '14b387cd-79ad-4860-9588-9c4e81588af0'], ['e5711bed-c606-49e2-a682-316348bf4091', '14b387cd-79ad-4860-9588-9c4e81588af0'] " + ")"); insertQuery("INSERT INTO T VALUES (" + "2," + @@ -112,7 +124,8 @@ private void insertArraysViaQuerySimpleStatement() throws SQLException { "null, [11, 22], " + "null, ['11', '22'], " + "null, [x'31', x'32'], " + - "null, [(11, '11'), (22, '22')] " + + "null, [(11, '11'), (22, '22')], " + + "null, ['e5711bed-c606-49e2-a682-316348bf4091', '14b387cd-79ad-4860-9588-9c4e81588af0'] " + ")"); RelationalAssertions.assertThrowsSqlException(() -> insertQuery("INSERT INTO T VALUES (" + "3," + @@ -123,7 +136,8 @@ private void insertArraysViaQuerySimpleStatement() throws SQLException { "[11, 22], null, " + "['11', '22'], null, " + "[x'31', x'32'], null, " + - "[(11, '11'), (22, '22')], null " + + "[(11, '11'), (22, '22')], null, " + + "['e5711bed-c606-49e2-a682-316348bf4091', '14b387cd-79ad-4860-9588-9c4e81588af0'], null " + ")")).hasErrorCode(ErrorCode.INTERNAL_ERROR); RelationalAssertions.assertThrowsSqlException(() -> insertQuery("INSERT INTO T (pk) VALUES (4)")) .hasErrorCode(ErrorCode.NOT_NULL_VIOLATION); @@ -133,10 +147,11 @@ private void insertArraysViaQuerySimpleStatement() throws SQLException { void testInsertArraysViaQueryPreparedStatement() throws SQLException { final var statement = "INSERT INTO T (pk, boolean_null, boolean_not_null, integer_null, integer_not_null, " + "bigint_null, bigint_not_null, float_null, float_not_null, double_null, double_not_null, string_null, " + - "string_not_null, bytes_null, bytes_not_null, struct_null, struct_not_null) VALUES (?pk, ?boolean_null, " + - "?boolean_not_null, ?integer_null, ?integer_not_null, ?bigint_null, ?bigint_not_null, ?float_null, " + - "?float_not_null, ?double_null, ?double_not_null, ?string_null, ?string_not_null, ?bytes_null, " + - "?bytes_not_null, ?struct_null, ?struct_not_null)"; + "string_not_null, bytes_null, bytes_not_null, struct_null, struct_not_null, uuid_null, uuid_not_null) " + + "VALUES (?pk, ?boolean_null, ?boolean_not_null, ?integer_null, ?integer_not_null, ?bigint_null, " + + "?bigint_not_null, ?float_null, ?float_not_null, ?double_null, ?double_not_null, ?string_null, " + + "?string_not_null, ?bytes_null, ?bytes_not_null, ?struct_null, ?struct_not_null, ?uuid_null, " + + "?uuid_not_null)"; try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { conn.setSchema(database.getSchemaName()); @@ -159,6 +174,8 @@ void testInsertArraysViaQueryPreparedStatement() throws SQLException { ps.setArray("bytes_not_null", EmbeddedRelationalArray.newBuilder().addAll(new byte[]{49}, new byte[]{50}).build()); ps.setArray("struct_null", EmbeddedRelationalArray.newBuilder().addAll(EmbeddedRelationalStruct.newBuilder().addInt("a", 11).addString("b", "11").build(), EmbeddedRelationalStruct.newBuilder().addInt("a", 22).addString("b", "22").build()).build()); ps.setArray("struct_not_null", EmbeddedRelationalArray.newBuilder().addAll(EmbeddedRelationalStruct.newBuilder().addInt("a", 11).addString("b", "11").build(), EmbeddedRelationalStruct.newBuilder().addInt("a", 22).addString("b", "22").build()).build()); + ps.setArray("uuid_null", EmbeddedRelationalArray.newBuilder().addAll(UUID.fromString("e5711bed-c606-49e2-a682-316348bf4091"), UUID.fromString("14b387cd-79ad-4860-9588-9c4e81588af0")).build()); + ps.setArray("uuid_not_null", EmbeddedRelationalArray.newBuilder().addAll(UUID.fromString("e5711bed-c606-49e2-a682-316348bf4091"), UUID.fromString("14b387cd-79ad-4860-9588-9c4e81588af0")).build()); Assertions.assertEquals(1, ps.executeUpdate()); } } @@ -184,6 +201,8 @@ void testInsertArraysViaQueryPreparedStatement() throws SQLException { ps.setArray("bytes_not_null", EmbeddedRelationalArray.newBuilder().addAll(new byte[]{49}, new byte[]{50}).build()); ps.setNull("struct_null", Types.ARRAY); ps.setArray("struct_not_null", EmbeddedRelationalArray.newBuilder().addAll(EmbeddedRelationalStruct.newBuilder().addInt("a", 11).addString("b", "11").build(), EmbeddedRelationalStruct.newBuilder().addInt("a", 22).addString("b", "22").build()).build()); + ps.setNull("uuid_null", Types.ARRAY); + ps.setArray("uuid_not_null", EmbeddedRelationalArray.newBuilder().addAll(UUID.fromString("e5711bed-c606-49e2-a682-316348bf4091"), UUID.fromString("14b387cd-79ad-4860-9588-9c4e81588af0")).build()); Assertions.assertEquals(1, ps.executeUpdate()); } } @@ -209,6 +228,8 @@ void testInsertArraysViaQueryPreparedStatement() throws SQLException { ps.setNull("bytes_not_null", Types.ARRAY); ps.setArray("struct_null", EmbeddedRelationalArray.newBuilder().addAll(EmbeddedRelationalStruct.newBuilder().addInt("a", 11).addString("b", "11").build(), EmbeddedRelationalStruct.newBuilder().addInt("a", 22).addString("b", "22").build()).build()); ps.setNull("struct_not_null", Types.ARRAY); + ps.setArray("uuid_null", EmbeddedRelationalArray.newBuilder().addAll(UUID.fromString("e5711bed-c606-49e2-a682-316348bf4091"), UUID.fromString("14b387cd-79ad-4860-9588-9c4e81588af0")).build()); + ps.setNull("uuid_not_null", Types.ARRAY); RelationalAssertions.assertThrowsSqlException(ps::executeUpdate).hasErrorCode(ErrorCode.INTERNAL_ERROR); } } @@ -249,8 +270,8 @@ void testNullFieldsGetAutomaticallyFilled() throws SQLException { conn.setAutoCommit(true); try (final var ps = ((RelationalPreparedStatement) conn.prepareStatement("INSERT INTO T (pk, boolean_not_null, " + "integer_not_null, bigint_not_null, float_not_null, double_not_null, string_not_null, bytes_not_null, " + - "struct_not_null) VALUES (?pk, ?boolean_not_null, ?integer_not_null, ?bigint_not_null, ?float_not_null, " + - "?double_not_null, ?string_not_null, ?bytes_not_null, ?struct_not_null)"))) { + "struct_not_null, uuid_not_null) VALUES (?pk, ?boolean_not_null, ?integer_not_null, ?bigint_not_null, " + + "?float_not_null, ?double_not_null, ?string_not_null, ?bytes_not_null, ?struct_not_null, ?uuid_not_null)"))) { ps.setInt("pk", 2); ps.setArray("boolean_not_null", EmbeddedRelationalArray.newBuilder().addAll(true, false).build()); ps.setArray("integer_not_null", EmbeddedRelationalArray.newBuilder().addAll(11, 22).build()); @@ -260,6 +281,7 @@ void testNullFieldsGetAutomaticallyFilled() throws SQLException { ps.setArray("string_not_null", EmbeddedRelationalArray.newBuilder().addAll("11", "22").build()); ps.setArray("bytes_not_null", EmbeddedRelationalArray.newBuilder().addAll(new byte[]{49}, new byte[]{50}).build()); ps.setArray("struct_not_null", EmbeddedRelationalArray.newBuilder().addAll(EmbeddedRelationalStruct.newBuilder().addInt("a", 11).addString("b", "11").build(), EmbeddedRelationalStruct.newBuilder().addInt("a", 22).addString("b", "22").build()).build()); + ps.setArray("uuid_not_null", EmbeddedRelationalArray.newBuilder().addAll(UUID.fromString("e5711bed-c606-49e2-a682-316348bf4091"), UUID.fromString("14b387cd-79ad-4860-9588-9c4e81588af0")).build()); assertEquals(1, ps.executeUpdate()); } @@ -273,7 +295,8 @@ void testNullFieldsGetAutomaticallyFilled() throws SQLException { .hasColumn("double_null", null) .hasColumn("string_null", null) .hasColumn("bytes_null", null) - .hasColumn("struct_null", null); + .hasColumn("struct_null", null) + .hasColumn("uuid_null", null); } } } @@ -312,7 +335,12 @@ private static Stream provideTypesForArrayTests() throws SQLException Arguments.of(16, 17, List.of( EmbeddedRelationalStruct.newBuilder().addInt("A", 11).addString("B", "11").build(), EmbeddedRelationalStruct.newBuilder().addInt("A", 22).addString("B", "22").build()), - Types.STRUCT) + Types.STRUCT), + Arguments.of(18, 19, List.of( + UUID.fromString("e5711bed-c606-49e2-a682-316348bf4091"), + UUID.fromString("14b387cd-79ad-4860-9588-9c4e81588af0")), + Types.OTHER) + ); } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RowStructTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RowStructTest.java index f53c7e2c18..abd7a49129 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RowStructTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RowStructTest.java @@ -20,18 +20,17 @@ package com.apple.foundationdb.relational.recordlayer; -import com.apple.foundationdb.relational.api.FieldDescription; import com.apple.foundationdb.relational.api.ImmutableRowStruct; import com.apple.foundationdb.relational.api.MutableRowStruct; import com.apple.foundationdb.relational.api.RelationalStructMetaData; import com.apple.foundationdb.relational.api.RowStruct; +import com.apple.foundationdb.relational.api.metadata.DataType; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import java.sql.DatabaseMetaData; import java.sql.SQLException; -import java.sql.Types; +import java.util.List; public class RowStructTest { @@ -53,10 +52,11 @@ void wasNullWorksWithToString(boolean mutable) throws SQLException { } private static RowStruct createStruct(boolean mutable) { - final var metadata = new RelationalStructMetaData( - FieldDescription.primitive("fInt", Types.INTEGER, DatabaseMetaData.columnNullable), - FieldDescription.primitive("fLong", Types.BIGINT, DatabaseMetaData.columnNullable) - ); + final var type = DataType.StructType.from("BLAH", List.of( + DataType.StructType.Field.from("fInt", DataType.Primitives.NULLABLE_INTEGER.type(), 0), + DataType.StructType.Field.from("fInt", DataType.Primitives.NULLABLE_LONG.type(), 1) + ), true); + final var metadata = RelationalStructMetaData.of(type); final var row = new ArrayRow(null, 1L); if (mutable) { final var toReturn = new MutableRowStruct(metadata); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithEnumTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithEnumTest.java index ed1e0442fd..76d3744b71 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithEnumTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithEnumTest.java @@ -31,15 +31,14 @@ import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.SimpleDatabaseRule; import com.apple.foundationdb.relational.utils.TestSchemas; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import java.sql.SQLException; -import java.sql.Types; import java.util.List; +import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -67,7 +66,7 @@ public class TableWithEnumTest { @Test void canInsertViaDirectAccess() throws Exception { - final var inserted = insertCard(42, "HEARTS", 8); + insertCard(42, "HEARTS", 8); KeySet keys = new KeySet() .setKeyColumn("ID", 42); @@ -75,7 +74,7 @@ void canInsertViaDirectAccess() throws Exception { try (RelationalResultSet resultSet = statement.executeGet("CARD", keys, Options.NONE)) { ResultSetAssert.assertThat(resultSet) .hasNextRow() - .isRowExactly(inserted) + .hasColumns(Map.of("ID", 42L, "SUIT", "HEARTS", "RANK", 8L)) .hasNoNextRow(); } } @@ -83,12 +82,11 @@ void canInsertViaDirectAccess() throws Exception { @Test void canInsertViaQuerySimpleStatement() throws SQLException { statement.execute("INSERT INTO CARD (id, suit, rank) VALUES (1, 'HEARTS', 4)"); - final var expectedStruct = getStructToInsert(1, "HEARTS", 4); try (RelationalResultSet resultSet = statement.executeQuery("SELECT * FROM CARD WHERE ID = 1")) { ResultSetAssert.assertThat(resultSet) .hasNextRow() - .isRowExactly(expectedStruct) + .hasColumns(Map.of("ID", 1L, "SUIT", "HEARTS", "RANK", 4L)) .hasNoNextRow(); } } @@ -102,11 +100,10 @@ void canInsertViaQueryPreparedStatement() throws SQLException { final var count = preparedStmt.executeUpdate(); assertThat(count).isEqualTo(1); - final var expectedStruct = getStructToInsert(1, "HEARTS", 4); try (RelationalResultSet resultSet = statement.executeQuery("SELECT * FROM CARD WHERE ID = 1")) { ResultSetAssert.assertThat(resultSet) .hasNextRow() - .isRowExactly(expectedStruct) + .hasColumns(Map.of("ID", 1L, "SUIT", "HEARTS", "RANK", 4L)) .hasNoNextRow(); } } @@ -228,7 +225,7 @@ private void insert52Cards() throws Exception { private RelationalStruct getStructToInsert(long id, Object suit, int rank) throws SQLException { return EmbeddedRelationalStruct.newBuilder() .addLong("ID", id) - .addObject("SUIT", suit, Types.OTHER) + .addObject("SUIT", suit) .addLong("RANK", rank) .build(); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionBoundDatabaseWithEnumTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionBoundDatabaseWithEnumTest.java index f877b50358..245ebaa3f6 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionBoundDatabaseWithEnumTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionBoundDatabaseWithEnumTest.java @@ -33,16 +33,15 @@ import com.apple.foundationdb.relational.api.EmbeddedRelationalStruct; import com.apple.foundationdb.relational.api.KeySet; import com.apple.foundationdb.relational.api.Options; -import com.apple.foundationdb.relational.api.Transaction; import com.apple.foundationdb.relational.api.RelationalConnection; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.RelationalStatement; +import com.apple.foundationdb.relational.api.Transaction; import com.apple.foundationdb.relational.api.exceptions.ContextualSQLException; import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.transactionbound.TransactionBoundEmbeddedRelationalEngine; import com.apple.foundationdb.relational.utils.SimpleDatabaseRule; import com.apple.foundationdb.relational.utils.TestSchemas; - import com.google.protobuf.DescriptorProtos; import com.google.protobuf.Descriptors; import com.google.protobuf.Message; @@ -53,7 +52,6 @@ import javax.annotation.Nonnull; import java.sql.SQLException; -import java.sql.Types; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.fail; @@ -83,7 +81,7 @@ void simpleInsertAndSelect() throws RelationalException, SQLException { try (RelationalStatement statement = conn.createStatement()) { statement.executeInsert("Card", EmbeddedRelationalStruct.newBuilder() .addLong("id", 1L) - .addObject("suit", "DIAMONDS", Types.OTHER) + .addObject("suit", "DIAMONDS") .addInt("rank", 1) .build() ); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java index 41b10729b9..fd0d8d7d1c 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java @@ -859,7 +859,7 @@ void prepareInListWrongTypeInArray() throws Exception { // IN list parameter is an array of structs, but constituent is not a struct. try (var ps = ddl.setSchemaAndGetConnection().prepareStatement("SELECT * FROM RestaurantComplexRecord WHERE (rest_no, name) in ?")) { RelationalAssertions.assertThrowsSqlException(() -> ps.setArray(1, ddl.getConnection().createArrayOf("STRUCT", new Object[]{100L}))) - .hasMessage("Element of the array of struct is not of struct type!") + .hasMessage("Element of the array is expected to be of type STRUCT") .hasErrorCode(ErrorCode.DATATYPE_MISMATCH); } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/StandardQueryTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/StandardQueryTests.java index 61000544f7..9e3f5e92d8 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/StandardQueryTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/StandardQueryTests.java @@ -23,23 +23,25 @@ import com.apple.foundationdb.relational.api.Continuation; import com.apple.foundationdb.relational.api.EmbeddedRelationalArray; import com.apple.foundationdb.relational.api.EmbeddedRelationalStruct; -import com.apple.foundationdb.relational.api.FieldDescription; +import com.apple.foundationdb.relational.api.ImmutableRowStruct; import com.apple.foundationdb.relational.api.RelationalArrayMetaData; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.RelationalStatement; import com.apple.foundationdb.relational.api.RelationalStruct; import com.apple.foundationdb.relational.api.RelationalStructMetaData; +import com.apple.foundationdb.relational.api.RowArray; import com.apple.foundationdb.relational.api.exceptions.ContextualSQLException; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; +import com.apple.foundationdb.relational.api.metadata.DataType; +import com.apple.foundationdb.relational.recordlayer.ArrayRow; import com.apple.foundationdb.relational.recordlayer.ContinuationImpl; import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalStatement; import com.apple.foundationdb.relational.recordlayer.Utils; import com.apple.foundationdb.relational.util.Assert; import com.apple.foundationdb.relational.utils.Ddl; -import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.RelationalAssertions; - +import com.apple.foundationdb.relational.utils.ResultSetAssert; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; import org.junit.jupiter.api.Assertions; @@ -54,14 +56,14 @@ import java.nio.charset.StandardCharsets; import java.sql.Array; import java.sql.Connection; -import java.sql.DatabaseMetaData; import java.sql.SQLException; import java.sql.Statement; -import java.sql.Types; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -1454,15 +1456,84 @@ void selfJoinTest() throws Exception { } } + @Test + void testInsertUuidTest() throws Exception { + final String schemaTemplate = "CREATE TABLE T1(pk bigint, a UUID, PRIMARY KEY(pk))"; + final var actualUuidValue = UUID.fromString("14b387cd-79ad-4860-9588-9c4e81588af0"); + try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var insert = ddl.setSchemaAndGetConnection().prepareStatement("insert into t1 values (1, '14b387cd-79ad-4860-9588-9c4e81588af0')")) { + final var numActualInserted = insert.executeUpdate(); + Assertions.assertEquals(1, numActualInserted); + } + try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { + Assertions.assertTrue(statement.execute("select * from t1")); + try (final RelationalResultSet resultSet = statement.getResultSet()) { + ResultSetAssert.assertThat(resultSet) + .hasNextRow() + .isRowExactly(1L, UUID.fromString("14b387cd-79ad-4860-9588-9c4e81588af0")) + .hasNoNextRow(); + } + } + } + try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var insert = ddl.setSchemaAndGetConnection().prepareStatement("insert into t1 values (?pk, ?a)")) { + insert.setLong("pk", 1L); + insert.setUUID("a", actualUuidValue); + final var numActualInserted = insert.executeUpdate(); + Assertions.assertEquals(1, numActualInserted); + } + try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { + Assertions.assertTrue(statement.execute("select * from t1")); + try (final RelationalResultSet resultSet = statement.getResultSet()) { + ResultSetAssert.assertThat(resultSet) + .hasNextRow() + .isRowExactly(1L, actualUuidValue) + .hasNoNextRow(); + } + } + } + } + + @Test + void testInsertStructWithUuidTest() throws Exception { + final String schemaTemplate = "CREATE TYPE AS STRUCT S1(a bigint, b uuid) " + + "CREATE TABLE T1(pk bigint, a UUID, b s1, PRIMARY KEY(pk))"; + final var actualUuidValue1 = UUID.randomUUID(); + final var actualUuidValue2 = UUID.randomUUID(); + final var structWithUuid = EmbeddedRelationalStruct.newBuilder() + .addLong("A", 2L) + .addUuid("B", actualUuidValue2) + .build(); + try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var insert = ddl.setSchemaAndGetConnection().prepareStatement("insert into t1 values (?pk, ?a, ?b)")) { + insert.setLong("pk", 1L); + insert.setUUID("a", actualUuidValue1); + insert.setObject("b", structWithUuid); + final var numActualInserted = insert.executeUpdate(); + Assertions.assertEquals(1, numActualInserted); + } + try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { + Assertions.assertTrue(statement.execute("select * from t1")); + try (final RelationalResultSet resultSet = statement.getResultSet()) { + ResultSetAssert.assertThat(resultSet) + .hasNextRow() + .hasColumn("PK", 1L) + .hasColumn("A", actualUuidValue1) + .hasColumn("B", structWithUuid) + .hasNoNextRow(); + } + } + } + } + // todo (yhatem) add more tests for queries w and w/o index definition. - private RelationalStruct insertTypeConflictRecords(RelationalStatement s) throws SQLException { + private void insertTypeConflictRecords(RelationalStatement s) throws SQLException { final var recBuilder = EmbeddedRelationalStruct.newBuilder() .addString("NAME", "Sophia"); final var rec = recBuilder.build(); int cnt = s.executeInsert("CLASSA", rec); Assertions.assertEquals(1, cnt, "Incorrect insertion count"); - return rec; } private RelationalStruct insertRestaurantComplexRecord(RelationalStatement s) throws SQLException { @@ -1486,22 +1557,12 @@ private RelationalStruct insertRestaurantComplexRecord(RelationalStatement s, Lo .addString("LATITUDE", "1") .addString("LONGITUDE", "1") .build()); - final var reviewsArrayBuilder = EmbeddedRelationalArray.newBuilder(RelationalArrayMetaData.ofStruct(new RelationalStructMetaData( - FieldDescription.primitive("REVIEWER", Types.BIGINT, DatabaseMetaData.columnNoNulls), - FieldDescription.primitive("RATING", Types.BIGINT, DatabaseMetaData.columnNoNulls), - FieldDescription.array("ENDORSEMENTS", DatabaseMetaData.columnNoNulls, RelationalArrayMetaData.ofStruct(new RelationalStructMetaData( - FieldDescription.primitive("endorsementId", Types.BIGINT, DatabaseMetaData.columnNoNulls), - FieldDescription.primitive("endorsementText", Types.VARCHAR, DatabaseMetaData.columnNoNulls) - ), DatabaseMetaData.columnNoNulls)) - ), DatabaseMetaData.columnNoNulls)); + final var reviewsArrayBuilder = EmbeddedRelationalArray.newBuilder(); for (final Triple>> review : reviews) { final var reviewBuilder = EmbeddedRelationalStruct.newBuilder() .addLong("REVIEWER", review.getLeft()) .addLong("RATING", review.getMiddle()); - final var endorsementsArrayBuilder = EmbeddedRelationalArray.newBuilder(RelationalArrayMetaData.ofStruct(new RelationalStructMetaData( - FieldDescription.primitive("endorsementId", Types.BIGINT, DatabaseMetaData.columnNoNulls), - FieldDescription.primitive("endorsementText", Types.VARCHAR, DatabaseMetaData.columnNoNulls) - ), DatabaseMetaData.columnNoNulls)); + final var endorsementsArrayBuilder = EmbeddedRelationalArray.newBuilder(); for (var endorsement : review.getRight()) { endorsementsArrayBuilder.addStruct(EmbeddedRelationalStruct.newBuilder() .addLong("endorsementId", endorsement.getLeft()) @@ -1512,13 +1573,50 @@ private RelationalStruct insertRestaurantComplexRecord(RelationalStatement s, Lo reviewsArrayBuilder.addStruct(reviewBuilder.build()); } recBuilder2.addArray("REVIEWS", reviewsArrayBuilder.build()); - final RelationalStruct struct = recBuilder2.build(); - int cnt = s.executeInsert("RESTAURANTCOMPLEXRECORD", struct); + final RelationalStruct structToInsert = recBuilder2.build(); + int cnt = s.executeInsert("RESTAURANTCOMPLEXRECORD", structToInsert); Assertions.assertEquals(1, cnt, "Incorrect insertion count"); - return struct; - } - - private RelationalStruct insertRestaurantComplexRecord(RelationalStatement s, int recordNumber, @Nonnull final String recordName, byte[] blob) throws SQLException { + return getExpected(recordNumber, recordName, reviews); + } + + private static RelationalStruct getExpected(Long recordNumber, @Nonnull final String recordName, @Nonnull final List>>> reviews) { + final var locationType = DataType.StructType.from("LOCATION", List.of( + DataType.StructType.Field.from("ADDRESS", DataType.Primitives.STRING.type(), 1), + DataType.StructType.Field.from("LATITUDE", DataType.Primitives.STRING.type(), 2), + DataType.StructType.Field.from("LONGITUDE", DataType.Primitives.STRING.type(), 3) + ), false); + final var endorsementType = DataType.StructType.from("ENDORSEMENT", List.of( + DataType.StructType.Field.from("endorsementId", DataType.Primitives.LONG.type(), 1), + DataType.StructType.Field.from("endorsementText", DataType.Primitives.STRING.type(), 2) + ), false); + final var reviewType = DataType.StructType.from("LOCATION", List.of( + DataType.StructType.Field.from("REVIEWER", DataType.Primitives.LONG.type(), 1), + DataType.StructType.Field.from("RATING", DataType.Primitives.LONG.type(), 2), + DataType.StructType.Field.from("ENDORSEMENTS", DataType.ArrayType.from(endorsementType, false), 3) + ), false); + final var restaurantComplexRecordType = DataType.StructType.from("RESTAURANTCOMPLEXRECORD", List.of( + DataType.StructType.Field.from("REST_NO", DataType.Primitives.LONG.type(), 1), + DataType.StructType.Field.from("NAME", DataType.Primitives.STRING.type(), 2), + DataType.StructType.Field.from("LOCATION", locationType, 3), + DataType.StructType.Field.from("REVIEWS", DataType.ArrayType.from(reviewType), 4) + ), false); + final var locationStruct = new ImmutableRowStruct(new ArrayRow("address", 1, 1), RelationalStructMetaData.of(locationType)); + final var reviewsList = new ArrayList(); + reviews.forEach(review -> { + final var endorsementsList = review.getRight().stream() + .map(e -> new ImmutableRowStruct(new ArrayRow(e.getLeft(), e.getRight()), RelationalStructMetaData.of(endorsementType))) + .collect(Collectors.toList()); + reviewsList.add(new ImmutableRowStruct(new ArrayRow( + review.getLeft(), + review.getMiddle(), + new RowArray(endorsementsList, RelationalArrayMetaData.of(DataType.ArrayType.from(endorsementType, false))) + ), RelationalStructMetaData.of(reviewType))); + }); + final var reviewsArray = new RowArray(reviewsList, RelationalArrayMetaData.of(DataType.ArrayType.from(reviewType))); + return new ImmutableRowStruct(new ArrayRow(recordNumber, recordName, locationStruct, reviewsArray), RelationalStructMetaData.of(restaurantComplexRecordType)); + } + + private void insertRestaurantComplexRecord(RelationalStatement s, int recordNumber, @Nonnull final String recordName, byte[] blob) throws SQLException { var struct = EmbeddedRelationalStruct.newBuilder() .addLong("REST_NO", recordNumber) .addString("NAME", recordName) @@ -1529,6 +1627,5 @@ private RelationalStruct insertRestaurantComplexRecord(RelationalStatement s, in .build(); int cnt = s.executeInsert("RESTAURANTCOMPLEXRECORD", struct); Assertions.assertEquals(1, cnt, "Incorrect insertion count"); - return struct; } } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/storage/BackingLocatableResolverStoreTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/storage/BackingLocatableResolverStoreTest.java index ff3fc538f3..1c3a85e907 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/storage/BackingLocatableResolverStoreTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/storage/BackingLocatableResolverStoreTest.java @@ -67,7 +67,6 @@ import java.net.URI; import java.nio.charset.StandardCharsets; import java.sql.SQLException; -import java.sql.Types; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; @@ -661,7 +660,7 @@ private void assertStateMatches(RelationalStatement statement, long version, Str private void insertResolverState(RelationalStatement statement, long version, String lock) throws SQLException { final var struct = EmbeddedRelationalStruct.newBuilder() .addInt(LocatableResolverMetaDataProvider.VERSION_FIELD_NAME, (int) version) - .addObject(LocatableResolverMetaDataProvider.LOCK_FIELD_NAME, lock, Types.OTHER) + .addObject(LocatableResolverMetaDataProvider.LOCK_FIELD_NAME, lock) .build(); Options options = Options.builder() .withOption(Options.Name.REPLACE_ON_DUPLICATE_PK, true) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/DdlPermutationGenerator.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/DdlPermutationGenerator.java index b999472723..4ff5f6fd71 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/DdlPermutationGenerator.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/DdlPermutationGenerator.java @@ -20,11 +20,12 @@ package com.apple.foundationdb.relational.utils; -import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.relational.api.EmbeddedRelationalArray; import com.apple.foundationdb.relational.api.EmbeddedRelationalStruct; -import com.apple.foundationdb.relational.api.SqlTypeSupport; import com.apple.foundationdb.relational.api.RelationalStruct; +import com.apple.foundationdb.relational.api.exceptions.ErrorCode; +import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.api.metadata.DataType; import javax.annotation.Nonnull; import java.sql.SQLException; @@ -72,7 +73,7 @@ public RelationalStruct getPermutation(String typeName) throws SQLException { List rows = new ArrayList<>(); int colNum = 0; for (String col : columnTypes) { - int typeCode = SqlTypeSupport.recordTypeToSqlType(toRecordLayerType(col)); + int typeCode = toDataType(col).getJdbcSqlCode(); rows.add(EmbeddedRelationalStruct.newBuilder() .addString("COLUMN_NAME", "COL" + colNum) .addInt("COLUMN_TYPE", typeCode) @@ -123,24 +124,24 @@ private DdlPermutationGenerator() { } @Nonnull - public static Type.TypeCode toRecordLayerType(@Nonnull final String text) { + public static DataType toDataType(@Nonnull final String text) { switch (text.toUpperCase(Locale.ROOT)) { case "STRING": - return Type.TypeCode.STRING; + return DataType.Primitives.STRING.type(); case "INTEGER": - return Type.TypeCode.INT; + return DataType.Primitives.INTEGER.type(); case "BIGINT": - return Type.TypeCode.LONG; + return DataType.Primitives.LONG.type(); case "FLOAT": - return Type.TypeCode.FLOAT; + return DataType.Primitives.FLOAT.type(); case "DOUBLE": - return Type.TypeCode.DOUBLE; + return DataType.Primitives.DOUBLE.type(); case "BOOLEAN": - return Type.TypeCode.BOOLEAN; + return DataType.Primitives.BOOLEAN.type(); case "BYTES": - return Type.TypeCode.BYTES; + return DataType.Primitives.BYTES.type(); default: // assume it is a custom type, will fail in upper layers if the type can not be resolved. - return Type.TypeCode.RECORD; + throw new RelationalException("Invalid type", ErrorCode.INTERNAL_ERROR).toUncheckedWrappedException(); } } } diff --git a/fdb-relational-core/src/testFixtures/java/com/apple/foundationdb/relational/utils/RelationalStructAssert.java b/fdb-relational-core/src/testFixtures/java/com/apple/foundationdb/relational/utils/RelationalStructAssert.java index 6b280eb308..bebb3a8566 100644 --- a/fdb-relational-core/src/testFixtures/java/com/apple/foundationdb/relational/utils/RelationalStructAssert.java +++ b/fdb-relational-core/src/testFixtures/java/com/apple/foundationdb/relational/utils/RelationalStructAssert.java @@ -21,12 +21,10 @@ package com.apple.foundationdb.relational.utils; import com.apple.foundationdb.annotation.API; - -import com.apple.foundationdb.relational.api.SqlTypeNamesSupport; -import com.apple.foundationdb.relational.api.StructMetaData; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.RelationalStruct; - +import com.apple.foundationdb.relational.api.SqlTypeNamesSupport; +import com.apple.foundationdb.relational.api.StructMetaData; import com.google.protobuf.Descriptors; import org.assertj.core.api.AbstractAssert; import org.assertj.core.api.Assertions; @@ -415,8 +413,13 @@ public RelationalStructAssert isEqualTo(RelationalStruct expected) { assertions.assertThat(actual.getBytes(i)).containsExactly(expected.getBytes(i)); break; default: - assertions.assertThat(actual.getObject(i)).isEqualTo(expected.getObject(i)); - + final var actualValue = actual.getObject(i); + if (actualSqlType == Types.OTHER) { + // maybe an ENUM value or a UUID + assertions.assertThat(actualValue.toString()).isEqualTo(expected.getObject(i).toString()); + } else { + assertions.assertThat(actual.getObject(i)).isEqualTo(expected.getObject(i)); + } } } } catch (SQLException se) { diff --git a/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/RelationalArrayFacade.java b/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/RelationalArrayFacade.java index c129747272..fff506f751 100644 --- a/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/RelationalArrayFacade.java +++ b/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/RelationalArrayFacade.java @@ -21,12 +21,14 @@ package com.apple.foundationdb.relational.jdbc; import com.apple.foundationdb.relational.api.ArrayMetaData; -import com.apple.foundationdb.relational.api.SqlTypeNamesSupport; import com.apple.foundationdb.relational.api.RelationalArray; import com.apple.foundationdb.relational.api.RelationalArrayBuilder; +import com.apple.foundationdb.relational.api.RelationalArrayMetaData; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.RelationalStruct; +import com.apple.foundationdb.relational.api.SqlTypeNamesSupport; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.jdbc.grpc.v1.ResultSet; import com.apple.foundationdb.relational.jdbc.grpc.v1.ResultSetMetadata; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.Array; @@ -35,13 +37,18 @@ import com.apple.foundationdb.relational.jdbc.grpc.v1.column.ListColumn; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.ListColumnMetadata; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.Struct; +import com.apple.foundationdb.relational.jdbc.grpc.v1.column.Type; import com.apple.foundationdb.relational.util.Assert; import com.apple.foundationdb.relational.util.PositionalIndex; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.Types; +import java.util.UUID; /** * Facade over grpc protobuf objects that offers a {@link RelationalArray} view. @@ -56,6 +63,7 @@ class RelationalArrayFacade implements RelationalArray { * Package-private so protobuf is available to serializer (in same package). */ private final ColumnMetadata delegateMetadata; + private final Supplier type; /** * Array data as protobuf. @@ -66,9 +74,16 @@ class RelationalArrayFacade implements RelationalArray { RelationalArrayFacade(@Nonnull ColumnMetadata delegateMetadata, Array array) { this.delegateMetadata = delegateMetadata; + this.type = Suppliers.memoize(this::computeType); this.delegate = array; } + @Nullable + private DataType.ArrayType computeType() { + return delegateMetadata.getType() == Type.UNKNOWN ? null : + DataType.ArrayType.from(RelationalStructFacade.RelationalStructFacadeMetaData.getDataType(delegateMetadata.getType(), delegateMetadata, delegateMetadata.getNullable())); + } + /** * Package-private so protobuf is available to serializer (in same package). * @return The backing protobuf used to keep Array data. @@ -87,6 +102,9 @@ ColumnMetadata getDelegateMetadata() { @Override public ArrayMetaData getMetaData() throws SQLException { + if (type.get() != null) { + return RelationalArrayMetaData.of(type.get()); + } throw new SQLFeatureNotSupportedException("get Metadata not supported in JDBC Relational Arrays"); } @@ -164,26 +182,27 @@ public RelationalResultSet getResultSet(long oneBasedIndex, int askedForCount) t int count = getCount(askedForCount, this.delegate.getElementCount(), index); var resultSetBuilder = ResultSet.newBuilder(); - final var componentType = this.delegateMetadata.getJavaSqlTypesCode(); - final var componentColumnBuilder = ColumnMetadata.newBuilder().setName("VALUE").setJavaSqlTypesCode(componentType); - if (componentType == Types.ARRAY) { + final var componentType = this.delegateMetadata.getType(); + final var componentSqlType = this.delegateMetadata.getJavaSqlTypesCode(); + final var componentColumnBuilder = ColumnMetadata.newBuilder().setName("VALUE").setType(componentType).setJavaSqlTypesCode(componentSqlType); + if (componentSqlType == Types.ARRAY) { componentColumnBuilder.setArrayMetadata(this.delegateMetadata.getArrayMetadata()); - } else if (componentType == Types.STRUCT) { + } else if (componentSqlType == Types.STRUCT) { componentColumnBuilder.setStructMetadata(this.delegateMetadata.getStructMetadata()); } resultSetBuilder.setMetadata(ResultSetMetadata.newBuilder().setColumnMetadata(ListColumnMetadata.newBuilder() - .addColumnMetadata(ColumnMetadata.newBuilder().setName("INDEX").setJavaSqlTypesCode(Types.INTEGER).build()) + .addColumnMetadata(ColumnMetadata.newBuilder().setName("INDEX").setType(Type.INTEGER).setJavaSqlTypesCode(Types.INTEGER).build()) .addColumnMetadata(componentColumnBuilder.build()).build()).build()); for (int i = index; i < count; i++) { final var listColumnBuilder = ListColumn.newBuilder(); listColumnBuilder.addColumn(Column.newBuilder().setInteger(i + 1).build()); final var valueColumnBuilder = Column.newBuilder(); - if (componentType == Types.STRUCT) { + if (componentSqlType == Types.STRUCT) { valueColumnBuilder.setStruct(delegate.getElement(i).getStruct()); - } else if (componentType == Types.INTEGER) { + } else if (componentSqlType == Types.INTEGER) { valueColumnBuilder.setInteger(delegate.getElement(i).getInteger()); } else { - Assert.failUnchecked(ErrorCode.UNKNOWN_TYPE, "Type not supported: " + SqlTypeNamesSupport.getSqlTypeName(componentType)); + Assert.failUnchecked(ErrorCode.UNKNOWN_TYPE, "Type not supported: " + SqlTypeNamesSupport.getSqlTypeName(componentSqlType)); } resultSetBuilder.addRow(Struct.newBuilder() .setColumns(listColumnBuilder @@ -238,6 +257,16 @@ public RelationalArrayBuilder addLong(@Nonnull long value) throws SQLException { throw new SQLFeatureNotSupportedException(); } + @Override + public RelationalArrayBuilder addUuid(@Nonnull final UUID value) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public RelationalArrayBuilder addObject(@Nonnull final Object value) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + @Override public RelationalArrayBuilder addStruct(RelationalStruct struct) throws SQLException { final var structFacade = struct.unwrap(RelationalStructFacade.class); @@ -248,7 +277,7 @@ public RelationalArrayBuilder addStruct(RelationalStruct struct) throws SQLExcep private void initOrCheckMetadata(ListColumnMetadata innerMetadata) { if (metadata == null) { - final var builder = ColumnMetadata.newBuilder().setName("ARRAY").setJavaSqlTypesCode(Types.STRUCT); + final var builder = ColumnMetadata.newBuilder().setName("ARRAY").setJavaSqlTypesCode(Types.STRUCT).setType(Type.STRUCT); builder.setStructMetadata(innerMetadata); metadata = builder.build(); } else { diff --git a/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/RelationalResultSetFacade.java b/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/RelationalResultSetFacade.java index d06ba4bd04..bbc1635af5 100644 --- a/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/RelationalResultSetFacade.java +++ b/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/RelationalResultSetFacade.java @@ -25,13 +25,17 @@ import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.RelationalResultSetMetaData; import com.apple.foundationdb.relational.api.RelationalStruct; +import com.apple.foundationdb.relational.api.RelationalStructMetaData; +import com.apple.foundationdb.relational.api.StructResultSetMetaData; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.jdbc.grpc.v1.ResultSet; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.Column; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.ColumnMetadata; import com.apple.foundationdb.relational.util.ExcludeFromJacocoGeneratedReport; import com.apple.foundationdb.relational.util.PositionalIndex; import com.apple.foundationdb.relational.util.SpotBugsSuppressWarnings; +import com.google.common.base.Suppliers; import javax.annotation.Nonnull; import java.sql.SQLException; @@ -40,12 +44,15 @@ import java.sql.Types; import java.util.UUID; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; /** * Facade over grpc protobuf objects that offers a {@link RelationalResultSet} view. */ class RelationalResultSetFacade implements RelationalResultSet { + private final Supplier type; + private final ResultSet delegate; private final int rows; /** @@ -66,6 +73,7 @@ class RelationalResultSetFacade implements RelationalResultSet { RelationalResultSetFacade(ResultSet delegate) { this.delegate = delegate; + this.type = Suppliers.memoize(() -> TypeConversion.getStructDataType(delegate.getMetadata().getColumnMetadata().getColumnMetadataList(), false)); this.rows = delegate.getRowCount(); } @@ -262,7 +270,7 @@ public void clearWarnings() throws SQLException { @Override public RelationalResultSetMetaData getMetaData() throws SQLException { - return new RelationalResultSetMetaDataFacade(this.delegate.getMetadata()); + return type.get() != null ? new StructResultSetMetaData(RelationalStructMetaData.of(type.get())) : new RelationalResultSetMetaDataFacade(this.delegate.getMetadata()); } @Override diff --git a/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/RelationalResultSetMetaDataFacade.java b/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/RelationalResultSetMetaDataFacade.java index 8b75ef7724..b52b2f6900 100644 --- a/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/RelationalResultSetMetaDataFacade.java +++ b/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/RelationalResultSetMetaDataFacade.java @@ -24,10 +24,12 @@ import com.apple.foundationdb.relational.api.StructMetaData; import com.apple.foundationdb.relational.api.RelationalResultSetMetaData; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.jdbc.grpc.v1.ResultSetMetadata; import com.apple.foundationdb.relational.util.ExcludeFromJacocoGeneratedReport; import com.apple.foundationdb.relational.util.PositionalIndex; +import javax.annotation.Nonnull; import java.sql.JDBCType; import java.sql.SQLException; @@ -47,6 +49,12 @@ public ArrayMetaData getArrayMetaData(int oneBasedColumn) throws SQLException { throw new SQLException("Not implemented", ErrorCode.UNSUPPORTED_OPERATION.getErrorCode()); } + @Nonnull + @Override + public DataType.StructType getRelationalDataType() throws SQLException { + throw new SQLException("Not implemented", ErrorCode.UNSUPPORTED_OPERATION.getErrorCode()); + } + @Override public StructMetaData getStructMetaData(int oneBasedColumn) throws SQLException { return new RelationalStructFacade.RelationalStructFacadeMetaData( diff --git a/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/RelationalStructFacade.java b/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/RelationalStructFacade.java index 9dc5f30aa4..d4459aa164 100644 --- a/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/RelationalStructFacade.java +++ b/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/RelationalStructFacade.java @@ -21,20 +21,26 @@ package com.apple.foundationdb.relational.jdbc; import com.apple.foundationdb.relational.api.ArrayMetaData; -import com.apple.foundationdb.relational.api.StructMetaData; import com.apple.foundationdb.relational.api.RelationalArray; import com.apple.foundationdb.relational.api.RelationalStruct; import com.apple.foundationdb.relational.api.RelationalStructBuilder; +import com.apple.foundationdb.relational.api.RelationalStructMetaData; +import com.apple.foundationdb.relational.api.StructMetaData; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; +import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.Column; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.ColumnMetadata; +import com.apple.foundationdb.relational.jdbc.grpc.v1.column.EnumMetadata; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.ListColumn; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.ListColumnMetadata; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.Struct; +import com.apple.foundationdb.relational.jdbc.grpc.v1.column.Type; +import com.apple.foundationdb.relational.jdbc.grpc.v1.column.Uuid; import com.apple.foundationdb.relational.util.PositionalIndex; - import com.apple.foundationdb.relational.util.SpotBugsSuppressWarnings; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Suppliers; import com.google.protobuf.ByteString; import javax.annotation.Nonnull; @@ -46,6 +52,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.function.Supplier; /** * Facade over grpc protobuf objects that offers a {@link RelationalStruct} view. @@ -63,6 +70,7 @@ class RelationalStructFacade implements RelationalStruct { * A StructMetaData facade over {@link #delegateMetadata}. */ private final StructMetaData structMetaData; + private final Supplier type; /** * Column metadata for this Struct. @@ -79,6 +87,7 @@ class RelationalStructFacade implements RelationalStruct { private boolean wasNull; RelationalStructFacade(ListColumnMetadata delegateMetadata, Struct delegate) { + type = Suppliers.memoize(() -> TypeConversion.getStructDataType(delegateMetadata.getColumnMetadataList(), false)); this.delegate = delegate; this.delegateMetadata = delegateMetadata; this.structMetaData = new RelationalStructFacadeMetaData(delegateMetadata); @@ -106,7 +115,7 @@ ListColumnMetadata getDelegateMetadata() { */ @Override public StructMetaData getMetaData() { - return this.structMetaData; + return type.get() != null ? RelationalStructMetaData.of(type.get()) : structMetaData; } @Override @@ -256,6 +265,9 @@ public Object getObject(int oneBasedColumn) throws SQLException { case Types.INTEGER: obj = getInt(oneBasedColumn); break; + case Types.OTHER: + obj = getUUID(oneBasedColumn); + break; default: throw new SQLException("Unsupported object type: " + type); } @@ -307,12 +319,19 @@ public RelationalArray getArray(String fieldName) throws SQLException { @Override public UUID getUUID(final int oneBasedPosition) throws SQLException { - throw new SQLException("Not implemented", ErrorCode.UNSUPPORTED_OPERATION.getErrorCode()); + Column c = getColumnInternal(oneBasedPosition); + if (wasNull) { + return null; + } + if (!(c.hasUuid())) { + throw new SQLException("UUID", ErrorCode.CANNOT_CONVERT_TYPE.getErrorCode()); + } + return new UUID(c.getUuid().getMostSignificantBits(), c.getUuid().getLeastSignificantBits()); } @Override public UUID getUUID(final String fieldName) throws SQLException { - throw new SQLException("Not implemented", ErrorCode.UNSUPPORTED_OPERATION.getErrorCode()); + return getUUID(RelationalStruct.getOneBasedPosition(fieldName, this)); } @Override @@ -414,21 +433,16 @@ int getZeroBasedOffsetOrThrow(String fieldName) throws SQLException { public RelationalStructBuilder addBoolean(String fieldName, boolean b) throws SQLException { // Add the metadata and get offset at where to insert data. int offset = addMetadata(ColumnMetadata.newBuilder() - .setName(fieldName).setJavaSqlTypesCode(Types.BOOLEAN).build()); + .setName(fieldName).setJavaSqlTypesCode(Types.BOOLEAN).setType(Type.BOOLEAN).build()); // Add field data. this.listColumnBuilder.addColumn(offset, Column.newBuilder().setBoolean(b).build()); return this; } - @Override - public RelationalStructBuilder addShort(String fieldName, short b) throws SQLException { - throw new SQLException("Not implemented " + Thread.currentThread() .getStackTrace()[1] .getMethodName()); - } - @Override public RelationalStructBuilder addLong(String fieldName, long l) throws SQLException { int offset = addMetadata(ColumnMetadata.newBuilder() - .setName(fieldName).setJavaSqlTypesCode(Types.BIGINT).build()); + .setName(fieldName).setJavaSqlTypesCode(Types.BIGINT).setType(Type.LONG).build()); this.listColumnBuilder.addColumn(offset, Column.newBuilder().setLong(l).build()); return this; } @@ -441,7 +455,7 @@ public RelationalStructBuilder addFloat(String fieldName, float f) throws SQLExc @Override public RelationalStructBuilder addDouble(String fieldName, double d) throws SQLException { int offset = addMetadata(ColumnMetadata.newBuilder() - .setName(fieldName).setJavaSqlTypesCode(Types.DOUBLE).build()); + .setName(fieldName).setJavaSqlTypesCode(Types.DOUBLE).setType(Type.DOUBLE).build()); this.listColumnBuilder.addColumn(offset, Column.newBuilder().setDouble(d).build()); return this; } @@ -449,7 +463,7 @@ public RelationalStructBuilder addDouble(String fieldName, double d) throws SQLE @Override public RelationalStructBuilder addBytes(String fieldName, byte[] bytes) throws SQLException { int offset = addMetadata(ColumnMetadata.newBuilder() - .setName(fieldName).setJavaSqlTypesCode(Types.BINARY).build()); + .setName(fieldName).setJavaSqlTypesCode(Types.BINARY).setType(Type.BYTES).build()); this.listColumnBuilder.addColumn(offset, Column.newBuilder().setBinary(ByteString.copyFrom(bytes)).build()); return this; } @@ -458,14 +472,27 @@ public RelationalStructBuilder addBytes(String fieldName, byte[] bytes) throws S @SpotBugsSuppressWarnings("NP") public RelationalStructBuilder addString(String fieldName, @Nullable String s) throws SQLException { int offset = addMetadata(ColumnMetadata.newBuilder() - .setName(fieldName).setJavaSqlTypesCode(Types.VARCHAR).build()); + .setName(fieldName).setJavaSqlTypesCode(Types.VARCHAR).setType(Type.STRING).build()); // TODO: setString requires a non-null string, but this method takes a nullable string this.listColumnBuilder.addColumn(offset, Column.newBuilder().setString(s).build()); return this; } @Override - public RelationalStructBuilder addObject(String fieldName, @Nullable Object obj, int targetSqlType) throws SQLException { + public RelationalStructBuilder addUuid(final String fieldName, @Nullable final UUID uuid) { + int offset = addMetadata(ColumnMetadata.newBuilder() + .setName(fieldName).setJavaSqlTypesCode(Types.OTHER).setType(Type.UUID).build()); + if (uuid == null) { + this.listColumnBuilder.addColumn(offset, Column.newBuilder().build()); + } else { + final var uuidColumn = Uuid.newBuilder().setMostSignificantBits(uuid.getMostSignificantBits()).setLeastSignificantBits(uuid.getLeastSignificantBits()).build(); + this.listColumnBuilder.addColumn(offset, Column.newBuilder().setUuid(uuidColumn).build()); + } + return this; + } + + @Override + public RelationalStructBuilder addObject(final String fieldName, @Nullable final Object obj) throws SQLException { throw new SQLException("Not implemented " + Thread.currentThread() .getStackTrace()[1] .getMethodName()); } @@ -476,7 +503,7 @@ public RelationalStructBuilder addStruct(String fieldName, @Nonnull RelationalSt // Insert the data portion of RelationalStruct here. RelationalStructFacade relationalStructFacade = struct.unwrap(RelationalStructFacade.class); int offset = addMetadata(ColumnMetadata.newBuilder().setName(fieldName) - .setJavaSqlTypesCode(Types.STRUCT).setStructMetadata(relationalStructFacade.delegateMetadata).build()); + .setJavaSqlTypesCode(Types.STRUCT).setType(Type.STRUCT).setStructMetadata(relationalStructFacade.delegateMetadata).build()); this.listColumnBuilder .addColumn(offset, Column.newBuilder().setStruct(relationalStructFacade.delegate).build()); return this; @@ -489,7 +516,7 @@ public RelationalStructBuilder addArray(String fieldName, @Nonnull RelationalArr // Insert the data portion of RelationalStruct here. RelationalArrayFacade relationalArrayFacade = array.unwrap(RelationalArrayFacade.class); int offset = addMetadata(ColumnMetadata.newBuilder() - .setName(fieldName).setJavaSqlTypesCode(Types.ARRAY) + .setName(fieldName).setJavaSqlTypesCode(Types.ARRAY).setType(Type.ARRAY) .setArrayMetadata(relationalArrayFacade.getDelegateMetadata()) .build()); this.listColumnBuilder.addColumn(offset, @@ -500,7 +527,7 @@ public RelationalStructBuilder addArray(String fieldName, @Nonnull RelationalArr @Override public RelationalStructBuilder addInt(String fieldName, int i) throws SQLException { int offset = addMetadata(ColumnMetadata.newBuilder() - .setName(fieldName).setJavaSqlTypesCode(Types.INTEGER).build()); + .setName(fieldName).setType(Type.INTEGER).build()); this.listColumnBuilder.addColumn(offset, Column.newBuilder().setInteger(i).build()); return this; } @@ -519,9 +546,65 @@ public RelationalStruct build() { */ static final class RelationalStructFacadeMetaData implements StructMetaData { private final ListColumnMetadata metadata; + private final Supplier type; RelationalStructFacadeMetaData(ListColumnMetadata metadata) { this.metadata = metadata; + this.type = Suppliers.memoize(this::computeType); + } + + private DataType.StructType computeType() { + return getStructDataType(metadata.getColumnMetadataList(), false); + } + + private static DataType.EnumType getEnumDataType(@Nonnull EnumMetadata enumMetadata, boolean nullable) { + final var enumValues = new ArrayList(); + int i = 1; + for (var value: enumMetadata.getValuesList()) { + enumValues.add(DataType.EnumType.EnumValue.of(value, i++)); + } + return DataType.EnumType.from(enumMetadata.getName(), enumValues, nullable); + } + + private static DataType.StructType getStructDataType(@Nonnull List columnMetadataList, boolean nullable) { + final var fields = new ArrayList(); + for (int i = 0; i < columnMetadataList.size(); i++) { + final var columnMetadata = columnMetadataList.get(i); + fields.add(DataType.StructType.Field.from(columnMetadata.getName(), getDataType(columnMetadata.getType(), columnMetadata, columnMetadata.getNullable()), i)); + } + return DataType.StructType.from("ANONYMOUS_STRUCT", fields, nullable); + } + + static DataType getDataType(@Nonnull Type type, @Nonnull ColumnMetadata columnMetadata, boolean nullable) { + switch (type) { + case LONG: + return nullable ? DataType.Primitives.NULLABLE_LONG.type() : DataType.Primitives.LONG.type(); + case INTEGER: + return nullable ? DataType.Primitives.NULLABLE_INTEGER.type() : DataType.Primitives.INTEGER.type(); + case DOUBLE: + return nullable ? DataType.Primitives.NULLABLE_DOUBLE.type() : DataType.Primitives.DOUBLE.type(); + case FLOAT: + return nullable ? DataType.Primitives.NULLABLE_FLOAT.type() : DataType.Primitives.FLOAT.type(); + case BOOLEAN: + return nullable ? DataType.Primitives.NULLABLE_BOOLEAN.type() : DataType.Primitives.BOOLEAN.type(); + case BYTES: + return nullable ? DataType.Primitives.NULLABLE_BYTES.type() : DataType.Primitives.BYTES.type(); + case UUID: + return nullable ? DataType.Primitives.NULLABLE_UUID.type() : DataType.Primitives.UUID.type(); + case STRING: + return nullable ? DataType.Primitives.NULLABLE_STRING.type() : DataType.Primitives.STRING.type(); + case VERSION: + return nullable ? DataType.Primitives.NULLABLE_VERSION.type() : DataType.Primitives.VERSION.type(); + case STRUCT: + return getStructDataType(columnMetadata.getStructMetadata().getColumnMetadataList(), nullable); + case ENUM: + return getEnumDataType(columnMetadata.getEnumMetadata(), nullable); + case ARRAY: + final var arrayMetadata = columnMetadata.getArrayMetadata(); + return DataType.ArrayType.from(getDataType(arrayMetadata.getType(), arrayMetadata, arrayMetadata.getNullable()), nullable); + default: + throw new RelationalException("Not implemeneted: " + type.name(), ErrorCode.INTERNAL_ERROR).toUncheckedWrappedException(); + } } @Override @@ -589,6 +672,12 @@ public int getLeadingPhantomColumnCount() { return -1000; } + @Nonnull + @Override + public DataType.StructType getRelationalDataType() { + return type.get(); + } + @Override public T unwrap(Class iface) throws SQLException { throw new SQLException("Not implemented", ErrorCode.UNSUPPORTED_OPERATION.getErrorCode()); diff --git a/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/TypeConversion.java b/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/TypeConversion.java index 00437eb271..f4f449351d 100644 --- a/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/TypeConversion.java +++ b/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/TypeConversion.java @@ -21,35 +21,37 @@ package com.apple.foundationdb.relational.jdbc; import com.apple.foundationdb.annotation.API; - import com.apple.foundationdb.relational.api.ArrayMetaData; import com.apple.foundationdb.relational.api.Continuation; -import com.apple.foundationdb.relational.api.SqlTypeNamesSupport; -import com.apple.foundationdb.relational.api.StructMetaData; import com.apple.foundationdb.relational.api.RelationalArray; import com.apple.foundationdb.relational.api.RelationalResultSet; -import com.apple.foundationdb.relational.api.RelationalResultSetMetaData; import com.apple.foundationdb.relational.api.RelationalStruct; -import com.apple.foundationdb.relational.api.RelationalStructMetaData; +import com.apple.foundationdb.relational.api.StructMetaData; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; +import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.jdbc.grpc.v1.KeySet; import com.apple.foundationdb.relational.jdbc.grpc.v1.KeySetValue; import com.apple.foundationdb.relational.jdbc.grpc.v1.ResultSet; -import com.apple.foundationdb.relational.jdbc.grpc.v1.RpcContinuationReason; import com.apple.foundationdb.relational.jdbc.grpc.v1.ResultSetMetadata; import com.apple.foundationdb.relational.jdbc.grpc.v1.RpcContinuation; +import com.apple.foundationdb.relational.jdbc.grpc.v1.RpcContinuationReason; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.Array; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.Column; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.ColumnMetadata; +import com.apple.foundationdb.relational.jdbc.grpc.v1.column.EnumMetadata; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.ListColumn; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.ListColumnMetadata; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.Struct; +import com.apple.foundationdb.relational.jdbc.grpc.v1.column.Type; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.Uuid; import com.apple.foundationdb.relational.util.PositionalIndex; import com.google.common.annotations.VisibleForTesting; import com.google.protobuf.ByteString; import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.sql.DatabaseMetaData; import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; @@ -58,6 +60,8 @@ import java.util.UUID; import java.util.function.BiFunction; +import static com.apple.foundationdb.relational.jdbc.RelationalStructFacade.RelationalStructFacadeMetaData.getDataType; + /** * Utility for converting types used by JDBC from Relational and FDB such as KeySet, RelationalStruct and RelationalArray. * Utility is mostly into and out-of generated protobufs and type convertions such as a list of RelationalStructs to @@ -67,13 +71,13 @@ public class TypeConversion { /** * Return {@link RelationalStruct} instance found at rowIndex and oneBasedColumn offsets. + * Manipulation of protobufs. Exploits package-private internal attributes of {@link RelationalStructFacade}. * @param resultSet Protobuf ResultSet to fetch {@link RelationalStruct} from. * @param rowIndex Current 'row' offset into resultSet * @param oneBasedColumn One-based column index where we'll find the {@link RelationalStruct} instance. * @return {@link RelationalStruct} instance pulled from resultSet * @throws SQLException If failed get of resultSet metadata. */ - // Manipulation of protobufs. Exploits package-private internal ottributes of {@link RelationalStructFacade}. static RelationalStruct getStruct(ResultSet resultSet, int rowIndex, int oneBasedColumn) throws SQLException { int index = PositionalIndex.toProtobuf(oneBasedColumn); var metadata = @@ -162,30 +166,74 @@ public static com.apple.foundationdb.relational.api.KeySet fromProtobuf(KeySet p return keySet; } - private static ColumnMetadata toColumnMetadata(StructMetaData metadata, int oneBasedIndex) + private static ColumnMetadata toColumnMetadata(StructMetaData metadata, int oneBasedIndex, int fieldIndex) throws SQLException { + final var type = metadata.getRelationalDataType().getFields().get(fieldIndex).getType(); + final var protobufType = toProtobufType(type); var columnMetadataBuilder = ColumnMetadata.newBuilder() .setName(metadata.getColumnName(oneBasedIndex)) - .setJavaSqlTypesCode(metadata.getColumnType(oneBasedIndex)); - // TODO nullable. + .setJavaSqlTypesCode(metadata.getColumnType(oneBasedIndex)) + .setNullable(metadata.isNullable(oneBasedIndex) == DatabaseMetaData.columnNullable) + .setType(protobufType); // TODO phantom. // TODO: label // One-offs - switch (metadata.getColumnType(oneBasedIndex)) { - case Types.STRUCT: - var listColumnMetadata = toListColumnMetadataProtobuf((RelationalStructMetaData) metadata.getStructMetaData(oneBasedIndex)); + switch (protobufType) { + case STRUCT: + var listColumnMetadata = toListColumnMetadataProtobuf(metadata.getStructMetaData(oneBasedIndex)); columnMetadataBuilder.setStructMetadata(listColumnMetadata); break; - case Types.ARRAY: + case ARRAY: var columnMetadata = toColumnMetadata(metadata.getArrayMetaData(oneBasedIndex)); columnMetadataBuilder.setArrayMetadata(columnMetadata); break; + case ENUM: + var enumMetadata = toEnumMetadata((DataType.EnumType) type); + columnMetadataBuilder.setEnumMetadata(enumMetadata); + break; default: break; } return columnMetadataBuilder.build(); } + private static EnumMetadata toEnumMetadata(@Nonnull DataType.EnumType enumType) { + final var builder = EnumMetadata.newBuilder().setName(enumType.getName()); + enumType.getValues().forEach(v -> builder.addValues(v.getName())); + return builder.build(); + } + + private static Type toProtobufType(@Nonnull DataType type) { + switch (type.getCode()) { + case LONG: + return Type.LONG; + case INTEGER: + return Type.INTEGER; + case BOOLEAN: + return Type.BOOLEAN; + case BYTES: + return Type.BYTES; + case DOUBLE: + return Type.DOUBLE; + case FLOAT: + return Type.FLOAT; + case STRING: + return Type.STRING; + case UUID: + return Type.UUID; + case STRUCT: + return Type.STRUCT; + case ARRAY: + return Type.ARRAY; + case VERSION: + return Type.VERSION; + case ENUM: + return Type.ENUM; + default: + throw new RelationalException("not supported in toProtobuf: " + type, ErrorCode.INTERNAL_ERROR).toUncheckedWrappedException(); + } + } + /** * The below is about making an Array. */ @@ -193,14 +241,15 @@ private static ColumnMetadata toColumnMetadata(@Nonnull ArrayMetaData metadata) throws SQLException { var columnMetadataBuilder = ColumnMetadata.newBuilder() .setName(metadata.getElementName()) - .setJavaSqlTypesCode(metadata.getElementType()); - // TODO nullable. + .setJavaSqlTypesCode(metadata.getElementType()) + .setType(toProtobufType(metadata.asRelationalType().getElementType())) + .setNullable(metadata.isElementNullable() == DatabaseMetaData.columnNullable); // TODO phantom. // TODO: label // One-offs switch (metadata.getElementType()) { case Types.STRUCT: - var listColumnMetadata = toListColumnMetadataProtobuf((RelationalStructMetaData) metadata.getElementStructMetaData()); + var listColumnMetadata = toListColumnMetadataProtobuf(metadata.getElementStructMetaData()); columnMetadataBuilder.setStructMetadata(listColumnMetadata); break; case Types.ARRAY: @@ -213,21 +262,21 @@ private static ColumnMetadata toColumnMetadata(@Nonnull ArrayMetaData metadata) return columnMetadataBuilder.build(); } - private static ListColumnMetadata toListColumnMetadataProtobuf(@Nonnull RelationalStructMetaData metadata) throws SQLException { + private static ListColumnMetadata toListColumnMetadataProtobuf(@Nonnull StructMetaData metadata) throws SQLException { var listColumnMetadataBuilder = ListColumnMetadata.newBuilder(); for (int oneBasedIndex = 1; oneBasedIndex <= metadata.getColumnCount(); oneBasedIndex++) { - var columnMetadata = toColumnMetadata(metadata, oneBasedIndex); + var columnMetadata = toColumnMetadata(metadata, oneBasedIndex, oneBasedIndex - 1 + metadata.getLeadingPhantomColumnCount()); listColumnMetadataBuilder.addColumnMetadata(columnMetadata); } return listColumnMetadataBuilder.build(); } - private static ResultSetMetadata toResultSetMetaData(RelationalResultSetMetaData metadata, int columnCount) throws SQLException { + private static ResultSetMetadata toResultSetMetaData(RelationalResultSet resultSet, int columnCount) throws SQLException { var listColumnMetadataBuilder = ListColumnMetadata.newBuilder(); for (int oneBasedIndex = 1; oneBasedIndex <= columnCount; oneBasedIndex++) { - listColumnMetadataBuilder.addColumnMetadata(toColumnMetadata(metadata, oneBasedIndex)); + listColumnMetadataBuilder.addColumnMetadata(toColumnMetadata(resultSet.getMetaData(), oneBasedIndex, oneBasedIndex - 1 + resultSet.getMetaData().getLeadingPhantomColumnCount())); } - return ResultSetMetadata.newBuilder().setColumnMetadata(listColumnMetadataBuilder.build()).build(); + return ResultSetMetadata.newBuilder().setColumnMetadata(toListColumnMetadataProtobuf(resultSet.getMetaData())).build(); } private static Array toArray(RelationalArray relationalArray) throws SQLException { @@ -235,7 +284,8 @@ private static Array toArray(RelationalArray relationalArray) throws SQLExceptio if (relationalArray != null) { var relationalResultSet = relationalArray.getResultSet(); while (relationalResultSet.next()) { - arrayBuilder.addElement(toColumn(relationalResultSet, 2)); + final var value = relationalResultSet.getObject(2); + arrayBuilder.addElement(toColumn(relationalResultSet.getMetaData().getRelationalDataType().getFields().get(1), value, relationalResultSet.wasNull())); } } return arrayBuilder.build(); @@ -244,8 +294,13 @@ private static Array toArray(RelationalArray relationalArray) throws SQLExceptio private static Struct toStruct(RelationalStruct relationalStruct) throws SQLException { // TODO: The call to get metadata below is expensive? And all we want is column count. Revisit. var listColumnBuilder = ListColumn.newBuilder(); - for (int oneBasedIndex = 1; oneBasedIndex <= relationalStruct.getMetaData().getColumnCount(); oneBasedIndex++) { - listColumnBuilder.addColumn(toColumn(relationalStruct, oneBasedIndex)); + final var leadingPhantomCount = relationalStruct.getMetaData().getLeadingPhantomColumnCount(); + final var fields = relationalStruct.getMetaData().getRelationalDataType().getFields(); + for (int i = 0; i < fields.size(); i++) { + if (i >= leadingPhantomCount) { + final var value = relationalStruct.getObject(i + 1); + listColumnBuilder.addColumn(toColumn(fields.get(i), value, relationalStruct.wasNull())); + } } return Struct.newBuilder().setColumns(listColumnBuilder.build()).build(); } @@ -335,7 +390,7 @@ public static Array toArray(@Nonnull java.sql.Array array) throws SQLException { * @throws SQLException in case of error */ public static Column toColumn(int columnType, @Nonnull Object obj) throws SQLException { - if (columnType != SqlTypeNamesSupport.getSqlTypeCodeFromObject(obj)) { + if (columnType != DataType.getDataTypeFromObject(obj).getJdbcSqlCode()) { throw new SQLException("Column element type does not match object type: " + columnType + " / " + obj.getClass().getSimpleName(), ErrorCode.WRONG_OBJECT_TYPE.getErrorCode()); } @@ -373,77 +428,72 @@ public static Column toColumn(int columnType, @Nonnull Object obj) throws SQLExc return builder.build(); } - private static Column toColumn(RelationalStruct relationalStruct, int oneBasedIndex) throws SQLException { - int columnType = relationalStruct.getMetaData().getColumnType(oneBasedIndex); + private static Column toColumn(@Nonnull DataType.StructType.Field field, @Nonnull Object value, boolean wasNull) throws SQLException { Column column; - switch (columnType) { - case Types.STRUCT: - RelationalStruct struct = relationalStruct.getStruct(oneBasedIndex); - column = toColumn(struct == null ? null : toStruct(struct), + switch (field.getType().getCode()) { + case STRUCT: + column = toColumn(wasNull ? null : toStruct((RelationalStruct) value), (a, b) -> a == null ? b.clearStruct() : b.setStruct(a)); break; - case Types.ARRAY: - RelationalArray array = relationalStruct.getArray(oneBasedIndex); - column = toColumn(array == null ? null : toArray(array), + case ARRAY: + column = toColumn(wasNull ? null : toArray((RelationalArray) value), (a, b) -> a == null ? b.clearArray() : b.setArray(a)); break; - case Types.BIGINT: - long l = relationalStruct.getLong(oneBasedIndex); - column = toColumn(relationalStruct.wasNull() ? null : l, + case LONG: + column = toColumn(wasNull ? null : (Long) value, (a, b) -> a == null ? b.clearLong() : b.setLong(a)); break; - case Types.INTEGER: - int i = relationalStruct.getInt(oneBasedIndex); - column = toColumn(relationalStruct.wasNull() ? null : i, + case INTEGER: + column = toColumn(wasNull ? null : (Integer) value, (a, b) -> a == null ? b.clearInteger() : b.setInteger(a)); break; - case Types.BOOLEAN: - boolean bool = relationalStruct.getBoolean(oneBasedIndex); - column = toColumn(relationalStruct.wasNull() ? null : bool, + case BOOLEAN: + column = toColumn(wasNull ? null : (Boolean) value, (a, b) -> a == null ? b.clearBoolean() : b.setBoolean(a)); break; - case Types.VARCHAR: - column = toColumn(relationalStruct.getString(oneBasedIndex), + case STRING: + case ENUM: + column = toColumn(wasNull ? null : (String) value, (a, b) -> a == null ? b.clearString() : b.setString(a)); break; - case Types.BINARY: - column = toColumn(relationalStruct.getBytes(oneBasedIndex), + case BYTES: + case VERSION: + column = toColumn(wasNull ? null : (byte[]) value, (a, b) -> a == null ? b.clearBinary() : b.setBinary(ByteString.copyFrom(a))); break; - case Types.DOUBLE: - double d = relationalStruct.getDouble(oneBasedIndex); - column = toColumn(relationalStruct.wasNull() ? null : d, + case DOUBLE: + column = toColumn(wasNull ? null : (Double) value, (a, b) -> a == null ? b.clearDouble() : b.setDouble(a)); break; - case Types.OTHER: - final Object object = relationalStruct.getObject(oneBasedIndex); - if (object instanceof String) { - // an enum. Enums are effectively just strings with a constraint, but this is how some other - // databases support them. - column = toColumn(relationalStruct.wasNull() ? null : (String) object, - (value, protobuf) -> value == null ? protobuf.clearString() : protobuf.setString(value)); - break; - } else if (object instanceof UUID) { - column = toColumn(relationalStruct.wasNull() ? null : (UUID) object, (value, protobuf) -> - value == null ? protobuf.clearUuid() : protobuf.setUuid(Uuid.newBuilder() - .setMostSignificantBits(value.getMostSignificantBits()) - .setLeastSignificantBits(value.getLeastSignificantBits()) - .build())); - break; - } - if (object == null) { - column = toColumn(null, (value, protobuf) -> protobuf.clearString()); - break; - } - throw new SQLException("java.sql.Type=" + columnType + " not supported with " + object.getClass(), - ErrorCode.UNSUPPORTED_OPERATION.getErrorCode()); + case UUID: + column = toColumn(wasNull ? null : (UUID) value, + (a, b) -> a == null ? b.clearUuid() : b.setUuid(Uuid.newBuilder() + .setMostSignificantBits(a.getMostSignificantBits()) + .setLeastSignificantBits(a.getLeastSignificantBits()) + .build())); + break; default: - throw new SQLException("java.sql.Type=" + columnType + " not supported", + throw new SQLException("DataType: " + field.getType() + " not supported", ErrorCode.UNSUPPORTED_OPERATION.getErrorCode()); } return column; } + @Nullable + static DataType.StructType getStructDataType(@Nonnull List columnMetadataList, boolean nullable) { + final var structFields = new ArrayList(); + for (int i = 0; i < columnMetadataList.size(); i++) { + final var colMetadata = columnMetadataList.get(i); + if (colMetadata.getType() == Type.UNKNOWN) { + return null; + } + final var dataType = getDataType(colMetadata.getType(), colMetadata, colMetadata.getNullable()); + structFields.add(DataType.StructType.Field.from(colMetadata.getName(), dataType, i)); + } + // we do not preserve struct name + return DataType.StructType.from("ANONYMOUS_STRUCT", structFields, nullable); + } + /** * Build a Column from p. * @param p Particular of the generic to build the Column with (can be null). @@ -456,29 +506,16 @@ static

Column toColumn(P p, BiFunction f) return f.apply(p, Column.newBuilder()).build(); } - /** - * Map a Relational-Core ResultSet row to a protobuf Struct. - */ - private static Struct toRow(RelationalResultSet relationalResultSet) throws SQLException { - var listColumnBuilder = ListColumn.newBuilder(); - for (int oneBasedIndex = 1; oneBasedIndex <= relationalResultSet.getMetaData().getColumnCount(); - oneBasedIndex++) { - listColumnBuilder.addColumn(toColumn(relationalResultSet, oneBasedIndex)); - } - return Struct.newBuilder().setColumns(listColumnBuilder.build()).build(); - } - public static ResultSet toProtobuf(RelationalResultSet relationalResultSet) throws SQLException { if (relationalResultSet == null) { return null; } var resultSetBuilder = ResultSet.newBuilder(); - var metadata = relationalResultSet.getMetaData(); while (relationalResultSet.next()) { if (!resultSetBuilder.hasMetadata()) { - resultSetBuilder.setMetadata(toResultSetMetaData(relationalResultSet.getMetaData(), metadata.getColumnCount())); + resultSetBuilder.setMetadata(toResultSetMetaData(relationalResultSet, relationalResultSet.getMetaData().getColumnCount())); } - resultSetBuilder.addRow(toRow(relationalResultSet)); + resultSetBuilder.addRow(toStruct(relationalResultSet)); } // Set the continuation after all the rows have been traversed Continuation existingContinuation = relationalResultSet.getContinuation(); diff --git a/fdb-relational-grpc/src/main/proto/grpc/relational/jdbc/v1/column.proto b/fdb-relational-grpc/src/main/proto/grpc/relational/jdbc/v1/column.proto index 548203c7d3..1eb8c13d1d 100644 --- a/fdb-relational-grpc/src/main/proto/grpc/relational/jdbc/v1/column.proto +++ b/fdb-relational-grpc/src/main/proto/grpc/relational/jdbc/v1/column.proto @@ -26,20 +26,39 @@ option java_multiple_files = true; option java_package = "com.apple.foundationdb.relational.jdbc.grpc.v1.column"; option java_outer_classname = "ColumnProto"; +enum Type { + UNKNOWN = 0; + INTEGER = 1; + LONG = 2; + FLOAT = 3; + DOUBLE = 4; + STRING = 5; + BOOLEAN = 6; + BYTES = 7; + UUID = 8; + ENUM = 9; + VERSION = 10; + STRUCT = 11; + ARRAY = 12; + NULL = 13; +} + message ColumnMetadata { string name = 1; string label = 2; //taken from java.sql.Types int32 java_sql_types_code = 3; - int32 nullable = 4; + bool nullable = 4; // Indicates a column that isn't part of the DDL for a query, but is part of the returned // tuple (and therefore necessary to keep so that our positional ordering is intact) bool phantom = 5; - // If this column has a Struct or an Array, their metadata is here. + // If this column has a Struct or Array, or is of Enum type, their metadata is here. oneof metadata { ListColumnMetadata structMetadata = 6; ColumnMetadata arrayMetadata = 7; + EnumMetadata enumMetadata = 8; } + Type type = 9; } // Relational Struct. @@ -106,3 +125,8 @@ message ListColumn { message ListColumnMetadata { repeated ColumnMetadata columnMetadata = 1; } + +message EnumMetadata { + string name = 1; + repeated string values = 2; +} diff --git a/fdb-relational-grpc/src/main/proto/grpc/relational/jdbc/v1/jdbc.proto b/fdb-relational-grpc/src/main/proto/grpc/relational/jdbc/v1/jdbc.proto index 8ab8e7f4ad..0638549337 100644 --- a/fdb-relational-grpc/src/main/proto/grpc/relational/jdbc/v1/jdbc.proto +++ b/fdb-relational-grpc/src/main/proto/grpc/relational/jdbc/v1/jdbc.proto @@ -106,6 +106,7 @@ message Parameter { // Reuse the column type here. It has much of what we need carrying // across parameters as well as null support, etc. Column parameter = 2; + Type type = 3; } message Options { diff --git a/fdb-relational-grpc/src/test/java/com/apple/foundationdb/relational/jdbc/MockResultSetMetadata.java b/fdb-relational-grpc/src/test/java/com/apple/foundationdb/relational/jdbc/MockResultSetMetadata.java index 6975dd7062..6f2c0c17c5 100644 --- a/fdb-relational-grpc/src/test/java/com/apple/foundationdb/relational/jdbc/MockResultSetMetadata.java +++ b/fdb-relational-grpc/src/test/java/com/apple/foundationdb/relational/jdbc/MockResultSetMetadata.java @@ -21,10 +21,13 @@ package com.apple.foundationdb.relational.jdbc; import com.apple.foundationdb.relational.api.ArrayMetaData; -import com.apple.foundationdb.relational.api.StructMetaData; import com.apple.foundationdb.relational.api.RelationalResultSetMetaData; +import com.apple.foundationdb.relational.api.StructMetaData; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; +import com.apple.foundationdb.relational.api.metadata.DataType; +import javax.annotation.Nonnull; +import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.List; @@ -32,17 +35,19 @@ * A testing implementation of an result set metadata. The columns are all assumed to be integers. */ public class MockResultSetMetadata implements RelationalResultSetMetaData { - private final String typeName; - private final List columnTypes; + private final DataType.StructType type; - public MockResultSetMetadata(String typeName, List columnTypes) { - this.typeName = typeName; - this.columnTypes = columnTypes; + public MockResultSetMetadata() { + this.type = DataType.StructType.from("testType", List.of( + DataType.StructType.Field.from("f1", DataType.Primitives.INTEGER.type(), 0), + DataType.StructType.Field.from("f2", DataType.Primitives.INTEGER.type(), 1), + DataType.StructType.Field.from("f2", DataType.Primitives.INTEGER.type(), 2) + ), false); } @Override public String getTypeName() throws SQLException { - return typeName; + return type.getName(); } @Override @@ -55,9 +60,15 @@ public ArrayMetaData getArrayMetaData(int oneBasedColumn) throws SQLException { throw new SQLException("Unsupported operation", ErrorCode.UNSUPPORTED_OPERATION.getErrorCode()); } + @Nonnull + @Override + public DataType.StructType getRelationalDataType() throws SQLException { + return type; + } + @Override public int getColumnCount() throws SQLException { - return columnTypes.size(); + return type.getFields().size(); } @Override @@ -67,6 +78,11 @@ public String getColumnName(int column) throws SQLException { @Override public int getColumnType(int column) throws SQLException { - return columnTypes.get(column - 1); + return type.getFields().get(column - 1).getType().getJdbcSqlCode(); + } + + @Override + public int isNullable(int column) { + return type.getFields().get(column - 1).getType().isNullable() ? ResultSetMetaData.columnNullable : ResultSetMetaData.columnNoNulls; } } diff --git a/fdb-relational-grpc/src/test/java/com/apple/foundationdb/relational/jdbc/ProtobufConversionTest.java b/fdb-relational-grpc/src/test/java/com/apple/foundationdb/relational/jdbc/ProtobufConversionTest.java index a95339c417..76cbb50181 100644 --- a/fdb-relational-grpc/src/test/java/com/apple/foundationdb/relational/jdbc/ProtobufConversionTest.java +++ b/fdb-relational-grpc/src/test/java/com/apple/foundationdb/relational/jdbc/ProtobufConversionTest.java @@ -27,12 +27,10 @@ import com.apple.foundationdb.relational.jdbc.grpc.v1.RpcContinuationReason; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.Column; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.Struct; - import com.google.protobuf.ByteString; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.sql.Types; import java.util.List; import java.util.function.BiFunction; import java.util.stream.Collectors; @@ -77,8 +75,6 @@ public void testToColumnIntegerBiFunction() { void testResultSetWithContinuation() throws Exception { MockContinuation continuation = new MockContinuation(Continuation.Reason.TRANSACTION_LIMIT_REACHED, null, false, false); RelationalResultSet resultSet = TestUtils.resultSet( - "TestType", - List.of(Types.INTEGER, Types.INTEGER, Types.INTEGER), continuation, TestUtils.row(1, 2, 3), TestUtils.row(4, 5, 6)); ResultSet converted = TypeConversion.toProtobuf(resultSet); diff --git a/fdb-relational-grpc/src/test/java/com/apple/foundationdb/relational/jdbc/RelationalStructFacadeTest.java b/fdb-relational-grpc/src/test/java/com/apple/foundationdb/relational/jdbc/RelationalStructFacadeTest.java index 6004b9b0b8..9123812af6 100644 --- a/fdb-relational-grpc/src/test/java/com/apple/foundationdb/relational/jdbc/RelationalStructFacadeTest.java +++ b/fdb-relational-grpc/src/test/java/com/apple/foundationdb/relational/jdbc/RelationalStructFacadeTest.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test; import java.sql.SQLException; +import java.util.UUID; public class RelationalStructFacadeTest { @Test @@ -74,12 +75,27 @@ public void testSimpleLong() throws SQLException { Assertions.assertEquals(relationalStruct.getLong(key), value); } + @Test + public void testSimpleUuid() throws SQLException { + String key = "only-field"; + final var uuid = UUID.randomUUID(); + var relationalStruct = RelationalStructFacade.newBuilder().addUuid(key, uuid).build(); + Assertions.assertEquals(relationalStruct.getUUID(key), uuid); + } + @Test public void testMultipleFields() throws SQLException { - var relationalStruct = RelationalStructFacade.newBuilder().addLong("field1", 1L).addString("field2", "hello").build(); + final var UuidValue = UUID.randomUUID(); + var relationalStruct = RelationalStructFacade.newBuilder() + .addLong("field1", 1L) + .addString("field2", "hello") + .addUuid("field3", UuidValue) + .build(); Assertions.assertEquals(relationalStruct.getLong("field1"), 1L); Assertions.assertEquals(relationalStruct.getString("field2"), "hello"); + Assertions.assertEquals(relationalStruct.getUUID("field3"), UuidValue); Assertions.assertEquals(relationalStruct.getLong(1), 1L); Assertions.assertEquals(relationalStruct.getString(2), "hello"); + Assertions.assertEquals(relationalStruct.getUUID(3), UuidValue); } } diff --git a/fdb-relational-grpc/src/test/java/com/apple/foundationdb/relational/jdbc/ResultSetContinuationTest.java b/fdb-relational-grpc/src/test/java/com/apple/foundationdb/relational/jdbc/ResultSetContinuationTest.java index 4f0caef8f9..d1faf3da67 100644 --- a/fdb-relational-grpc/src/test/java/com/apple/foundationdb/relational/jdbc/ResultSetContinuationTest.java +++ b/fdb-relational-grpc/src/test/java/com/apple/foundationdb/relational/jdbc/ResultSetContinuationTest.java @@ -23,7 +23,6 @@ import com.apple.foundationdb.relational.api.Continuation; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.jdbc.grpc.v1.ResultSet; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -31,7 +30,6 @@ import org.junit.jupiter.params.provider.MethodSource; import java.sql.SQLException; -import java.sql.Types; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; @@ -51,8 +49,6 @@ public static Stream continuationSource() { @MethodSource("continuationSource") void testResultSetWithContinuation(Continuation continuation) throws Exception { RelationalResultSet resultSet = TestUtils.resultSet( - "TestType", - List.of(Types.INTEGER, Types.INTEGER, Types.INTEGER), continuation, TestUtils.row(1, 2, 3), TestUtils.row(4, 5, 6)); ResultSet rsProto = TypeConversion.toProtobuf(resultSet); @@ -69,8 +65,6 @@ void testResultSetWithContinuation(Continuation continuation) throws Exception { @MethodSource("continuationSource") void testContinuationRoundTrip(Continuation continuation) throws Exception { RelationalResultSet resultSet = TestUtils.resultSet( - "TestType", - List.of(Types.INTEGER, Types.INTEGER, Types.INTEGER), continuation, TestUtils.row(1, 2, 3), TestUtils.row(4, 5, 6)); ResultSet rsProto = TypeConversion.toProtobuf(resultSet); @@ -87,8 +81,6 @@ void testContinuationRoundTrip(Continuation continuation) throws Exception { @Test void testMidContinuationFails() throws Exception { RelationalResultSet resultSet = TestUtils.resultSet( - "TestType", - List.of(Types.INTEGER, Types.INTEGER, Types.INTEGER), MockContinuation.BEGIN, TestUtils.row(1, 2, 3), TestUtils.row(4, 5, 6)); ResultSet rsProto = TypeConversion.toProtobuf(resultSet); @@ -115,8 +107,6 @@ void testMidContinuationFails() throws Exception { @Test void testResultSetWithNullContinuationFails() throws Exception { RelationalResultSet resultSet = TestUtils.resultSet( - "TestType", - List.of(Types.INTEGER, Types.INTEGER, Types.INTEGER), null, TestUtils.row(1, 2, 3), TestUtils.row(4, 5, 6)); Assertions.assertThrows(NullPointerException.class, () -> TypeConversion.toProtobuf(resultSet)); diff --git a/fdb-relational-grpc/src/test/java/com/apple/foundationdb/relational/jdbc/TestUtils.java b/fdb-relational-grpc/src/test/java/com/apple/foundationdb/relational/jdbc/TestUtils.java index 9391163500..b01b81f678 100644 --- a/fdb-relational-grpc/src/test/java/com/apple/foundationdb/relational/jdbc/TestUtils.java +++ b/fdb-relational-grpc/src/test/java/com/apple/foundationdb/relational/jdbc/TestUtils.java @@ -27,9 +27,9 @@ import java.util.List; public class TestUtils { - public static RelationalResultSet resultSet(String typeName, List columnTypes, Continuation continuation, MockResultSetRow... rows) { + public static RelationalResultSet resultSet(Continuation continuation, MockResultSetRow... rows) { return new MockResultSet( - new MockResultSetMetadata(typeName, columnTypes), + new MockResultSetMetadata(), Arrays.stream(rows).iterator(), continuation); } diff --git a/fdb-relational-jdbc/src/main/java/com/apple/foundationdb/relational/jdbc/ParameterHelper.java b/fdb-relational-jdbc/src/main/java/com/apple/foundationdb/relational/jdbc/ParameterHelper.java index afb1cd5934..7986b871bb 100644 --- a/fdb-relational-jdbc/src/main/java/com/apple/foundationdb/relational/jdbc/ParameterHelper.java +++ b/fdb-relational-jdbc/src/main/java/com/apple/foundationdb/relational/jdbc/ParameterHelper.java @@ -20,10 +20,11 @@ package com.apple.foundationdb.relational.jdbc; -import com.apple.foundationdb.relational.api.SqlTypeNamesSupport; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.jdbc.grpc.v1.Parameter; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.Column; +import com.apple.foundationdb.relational.jdbc.grpc.v1.column.Type; import com.apple.foundationdb.relational.jdbc.grpc.v1.column.Uuid; import com.google.protobuf.ByteString; @@ -38,6 +39,7 @@ public class ParameterHelper { public static Parameter ofBoolean(boolean b) { return Parameter.newBuilder() + .setType(Type.BOOLEAN) .setJavaSqlTypesCode(Types.BOOLEAN) .setParameter(Column.newBuilder().setBoolean(b)) .build(); @@ -45,6 +47,7 @@ public static Parameter ofBoolean(boolean b) { public static Parameter ofInt(int i) { return Parameter.newBuilder() + .setType(Type.INTEGER) .setJavaSqlTypesCode(Types.INTEGER) .setParameter(Column.newBuilder().setInteger(i)) .build(); @@ -52,6 +55,7 @@ public static Parameter ofInt(int i) { public static Parameter ofLong(long l) { return Parameter.newBuilder() + .setType(Type.LONG) .setJavaSqlTypesCode(Types.BIGINT) .setParameter(Column.newBuilder().setLong(l)) .build(); @@ -59,6 +63,7 @@ public static Parameter ofLong(long l) { public static Parameter ofFloat(float f) { return Parameter.newBuilder() + .setType(Type.FLOAT) .setJavaSqlTypesCode(Types.FLOAT) .setParameter(Column.newBuilder().setFloat(f)) .build(); @@ -66,6 +71,7 @@ public static Parameter ofFloat(float f) { public static Parameter ofDouble(double d) { return Parameter.newBuilder() + .setType(Type.DOUBLE) .setJavaSqlTypesCode(Types.DOUBLE) .setParameter(Column.newBuilder().setDouble(d)) .build(); @@ -73,6 +79,7 @@ public static Parameter ofDouble(double d) { public static Parameter ofString(String s) { return Parameter.newBuilder() + .setType(Type.STRING) .setJavaSqlTypesCode(Types.VARCHAR) .setParameter(Column.newBuilder().setString(s)) .build(); @@ -80,30 +87,26 @@ public static Parameter ofString(String s) { public static Parameter ofUUID(UUID id) { return Parameter.newBuilder() + .setType(Type.UUID) .setJavaSqlTypesCode(Types.OTHER) - .setParameter(Column.newBuilder().setString(id.toString())) + .setParameter(Column.newBuilder().setUuid(Uuid.newBuilder() + .setMostSignificantBits(id.getMostSignificantBits()) + .setLeastSignificantBits(id.getLeastSignificantBits()) + .build())) .build(); } public static Parameter ofBytes(byte[] bytes) { return Parameter.newBuilder() + .setType(Type.BYTES) .setJavaSqlTypesCode(Types.BINARY) .setParameter(Column.newBuilder().setBinary(ByteString.copyFrom(bytes))) .build(); } - public static Parameter ofUuid(UUID uuid) { - return Parameter.newBuilder() - .setJavaSqlTypesCode(Types.OTHER) - .setParameter(Column.newBuilder().setUuid(Uuid.newBuilder() - .setMostSignificantBits(uuid.getMostSignificantBits()) - .setLeastSignificantBits(uuid.getLeastSignificantBits()) - .build())) - .build(); - } - - public static Parameter ofNull(int sqlType) throws SQLException { + public static Parameter ofNull(int sqlType) { return Parameter.newBuilder() + .setType(Type.NULL) .setJavaSqlTypesCode(Types.NULL) .setParameter(Column.newBuilder().setNullType(sqlType)) .build(); @@ -116,17 +119,10 @@ public static Parameter ofArray(final Array a) throws SQLException { JDBCArrayImpl arrayImpl = (JDBCArrayImpl)a; elements.addAll(arrayImpl.getUnderlying().getElementList()); } else { - // TODO: Do we even want to allow creation of parameter from an array created by another connection? - Object[] arrayElements = (Object[])a.getArray(); - for (Object o : arrayElements) { - Parameter p = ofObject(o); - if (p.getJavaSqlTypesCode() != a.getBaseType()) { - throw new SQLException("Array base type does not match element type: " + a.getBaseType() + ":" + p.getJavaSqlTypesCode(), ErrorCode.ARRAY_ELEMENT_ERROR.getErrorCode()); - } - elements.add(p.getParameter()); - } + throw new SQLException("Array type not supported: " + a.getClass().getName(), ErrorCode.INVALID_PARAMETER.getErrorCode()); } return Parameter.newBuilder() + .setType(Type.ARRAY) .setJavaSqlTypesCode(Types.ARRAY) .setParameter(Column.newBuilder() .setArray(com.apple.foundationdb.relational.jdbc.grpc.v1.column.Array.newBuilder() @@ -136,35 +132,34 @@ public static Parameter ofArray(final Array a) throws SQLException { } public static Parameter ofObject(Object x) throws SQLException { - final int typeCodeFromObject = SqlTypeNamesSupport.getSqlTypeCodeFromObject(x); - switch (typeCodeFromObject) { - case Types.BIGINT: + // We need to keep an exception for the case of Array, because JDBC client array creations does not inherit + // RelationalArray. Since the Relational dataType works with Relational constructs, this is an exception. We + // should probably strive to bring this under Relational umbrella, until then treat the case separately. + if (x instanceof Array) { + return ofArray((Array)x); + } + final DataType type = DataType.getDataTypeFromObject(x); + switch (type.getCode()) { + case LONG: return ofLong((Long)x); - case Types.INTEGER: + case INTEGER: return ofInt((Integer)x); - case Types.BOOLEAN: + case BOOLEAN: return ofBoolean((Boolean)x); - case Types.BINARY: + case BYTES: return ofBytes((byte[])x); - case Types.FLOAT: + case FLOAT: return ofFloat((Float)x); - case Types.DOUBLE: + case DOUBLE: return ofDouble((Double)x); - case Types.VARCHAR: + case STRING: return ofString((String)x); - case Types.NULL: - return ofNull(Types.NULL); // TODO: THis would be generic null... - case Types.ARRAY: - return ofArray((Array)x); - case Types.OTHER: - if (x instanceof UUID) { - return ofUuid((UUID)x); - } else { - throw new SQLException("setObject Unrecognized type OTHER of class: " + x.getClass().getName(), - ErrorCode.UNSUPPORTED_OPERATION.getErrorCode()); - } + case NULL: + return ofNull(type.getJdbcSqlCode()); // TODO: THis would be generic null... + case UUID: + return ofUUID((UUID) x); default: - throw new SQLException("setObject Not supported for type " + typeCodeFromObject, + throw new SQLException("setObject Not supported for type: " + type, ErrorCode.UNSUPPORTED_OPERATION.getErrorCode()); } } diff --git a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java index d3fbb68035..3d6aeafafa 100644 --- a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java +++ b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java @@ -58,7 +58,6 @@ import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; -import java.sql.Types; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -233,51 +232,35 @@ public Response execute(String database, String schema, String sql, List !! '99e8e8b1-ac34-4f4d-9f01-1f4a7debf4d6' !!