Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

import static org.apache.calcite.util.ReflectUtil.isStatic;
Expand Down Expand Up @@ -226,6 +227,8 @@ private static Type fieldType(Field field) {
return ByteString.class;
case GEOMETRY:
return Geometry.class;
case UUID:
return UUID.class;
case SYMBOL:
return Enum.class;
case ANY:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import org.apache.calcite.rex.RexWindowBound;
import org.apache.calcite.rex.RexWindowBounds;
import org.apache.calcite.rex.RexWindowExclusion;
import org.apache.calcite.runtime.SqlFunctions;
import org.apache.calcite.sql.SqlAggFunction;
import org.apache.calcite.sql.SqlFunction;
import org.apache.calcite.sql.SqlIdentifier;
Expand Down Expand Up @@ -874,7 +875,7 @@ public RexNode toRex(RelOptCluster cluster, Object o) {
} else if (sqlTypeName == SqlTypeName.BINARY || sqlTypeName == SqlTypeName.VARBINARY) {
literal = ByteString.of((String) literal, 16);
} else if (sqlTypeName == SqlTypeName.UUID) {
literal = UUID.fromString((String) literal);
literal = SqlFunctions.stringToUuid((String) literal);
}
return rexBuilder.makeLiteral(literal, type);
}
Expand Down
66 changes: 64 additions & 2 deletions core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.apache.calcite.sql.SqlIntervalQualifier;
import org.apache.calcite.sql.SqlUtil;
import org.apache.calcite.sql.fun.SqlLibraryOperators;
import org.apache.calcite.sql.parser.SqlParserUtil;
import org.apache.calcite.util.NumberUtil;
import org.apache.calcite.util.TimeWithTimeZoneString;
import org.apache.calcite.util.TimestampWithTimeZoneString;
Expand Down Expand Up @@ -351,9 +352,70 @@ public static String uuidToString(UUID uuid) {
return uuid.toString();
}

/** Converts a string to a UUID: 32 hexadecimal digits. All of the following give
* the UUID {@code 123e4567-e89b-12d3-a456-426655440000}:
*
* <blockquote><pre>
* 123e4567-e89b-12d3-a456-426655440000
* 123E4567-E89B-12D3-A456-426655440000
* 123e4567e89b12d3a456426655440000
* {123e4567-e89b-12d3-a456-426655440000}
* {123e4567e89b12d3a456426655440000}
* 123e-4567-e89b-12d3-a456-4266-5544-0000
* 123e4567-e89b12d3-a4564266-55440000
* 123e-4567e89b-12d3a456426655440000
* </pre></blockquote>
*
* <p>and each of the following is an error:
*
* <blockquote><pre>
* 1-2-3-4-5 a group is not four digits wide
* 123e456-7e89b-12d3-a456-426655440000 as above, though 36 characters long
* 123e4567--e89b-12d3-a456-426655440000 empty group
* -123e4567e89b12d3a456426655440000 leading hyphen
* 123e4567e89b12d3a456426655440000- trailing hyphen
* {123e4567-e89b-12d3-a456-426655440000 unbalanced brace
* 123e4567-e89b-12d3-a456-42665544000 31 digits
* </pre></blockquote>
*
* <p>Blanks are never trimmed.
*/
public static UUID stringToUuid(String s) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handling of blank string does not appear to be clearly defined; it needs to be consistent with PostgreSQL.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will add a test for this

String body = s;
if (body.length() > 1
&& body.charAt(0) == '{'
&& body.charAt(body.length() - 1) == '}') {
body = body.substring(1, body.length() - 1);
}
final StringBuilder digits = new StringBuilder(32);
for (int i = 0; i < body.length(); i++) {
final char c = body.charAt(i);
if (c == '-') {
// A hyphen separates groups, so it must follow a complete group of four
// digits and cannot be the last character
if (digits.length() == 0
|| digits.length() % 4 != 0
|| digits.length() == 32
|| body.charAt(i - 1) == '-') {
throw new IllegalArgumentException("Invalid UUID string: " + s);
}
} else if (SqlParserUtil.isHexDigit(c) && digits.length() < 32) {
digits.append(c);
} else {
throw new IllegalArgumentException("Invalid UUID string: " + s);
}
}
if (digits.length() != 32) {
throw new IllegalArgumentException("Invalid UUID string: " + s);
}
return new UUID(
Long.parseUnsignedLong(digits.substring(0, 16), 16),
Long.parseUnsignedLong(digits.substring(16), 16));
}

public static UUID binaryToUuid(ByteString bytes) {
if (bytes.length() < 16) {
throw new IllegalArgumentException("Need at least 16 bytes for UUID");
if (bytes.length() != 16) {
throw new IllegalArgumentException("Need exactly 16 bytes for UUID");
}
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes.getBytes());
long mostSignificantBits = byteBuffer.getLong();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.apache.calcite.config.CalciteSystemProperty;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.runtime.CalciteContextException;
import org.apache.calcite.runtime.SqlFunctions;
import org.apache.calcite.sql.SqlBinaryOperator;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlDateLiteral;
Expand Down Expand Up @@ -407,7 +408,7 @@ public static SqlTimestampLiteral parseTimestampWithLocalTimeZoneLiteral(
}

public static SqlUuidLiteral parseUuidLiteral(String s, SqlParserPos pos) {
UUID uuid = UUID.fromString(s);
UUID uuid = SqlFunctions.stringToUuid(s);
return SqlLiteral.createUuid(uuid, pos);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,13 +291,17 @@ protected boolean needToCast(SqlValidatorScope scope, SqlNode node,
return false;
}

// No casts to binary except from strings
if (SqlTypeUtil.isBinary(fromType) && !SqlTypeUtil.isString(toType)) {
// No casts from binary except to strings and UUID
if (SqlTypeUtil.isBinary(fromType)
&& !SqlTypeUtil.isString(toType)
&& toType.getSqlTypeName() != SqlTypeName.UUID) {
return false;
}

// No casts from binary except to strings
if (SqlTypeUtil.isBinary(toType) && !SqlTypeUtil.isString(fromType)) {
// No casts to binary except from strings and UUID
if (SqlTypeUtil.isBinary(toType)
&& !SqlTypeUtil.isString(fromType)
&& fromType.getSqlTypeName() != SqlTypeName.UUID) {
return false;
}

Expand Down Expand Up @@ -525,14 +529,15 @@ private RelDataType getTightestCommonTypeOrThrow(
return factory.leastRestrictive(ImmutableList.of(type1, type2));
}

// CHARACTER or BINARY < UUID -> UUID, similar to CHAR < INT -> INT
if ((SqlTypeUtil.isCharacter(type1) || SqlTypeUtil.isBinary(type1))
&& type2.getSqlTypeName() == SqlTypeName.UUID) {
return factory.createTypeWithNullability(type1, anyNullable);
&& typeName2 == SqlTypeName.UUID) {
return factory.createTypeWithNullability(type2, anyNullable);
}

if ((SqlTypeUtil.isCharacter(type2) || SqlTypeUtil.isBinary(type2))
&& type1.getSqlTypeName() == SqlTypeName.UUID) {
return factory.createTypeWithNullability(type2, anyNullable);
&& typeName1 == SqlTypeName.UUID) {
return factory.createTypeWithNullability(type1, anyNullable);
}

// DATETIME < CHARACTER -> DATETIME
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ public enum BuiltInMethod {
IS_JSON_ARRAY(JsonFunctions.class, "isJsonArray", String.class),
IS_JSON_SCALAR(JsonFunctions.class, "isJsonScalar", String.class),
ST_GEOM_FROM_EWKT(SpatialTypeFunctions.class, "ST_GeomFromEWKT", String.class),
UUID_FROM_STRING(UUID.class, "fromString", String.class),
UUID_FROM_STRING(SqlFunctions.class, "stringToUuid", String.class),
UUID_TO_STRING(SqlFunctions.class, "uuidToString", UUID.class),
UUID_TO_BINARY(SqlFunctions.class, "uuidToBinary", UUID.class),
INT_TO_BINARY(SqlFunctions.class, "intToBinary", Object.class, int.class, boolean.class),
Expand Down
4 changes: 2 additions & 2 deletions core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexProgramBuilder;
import org.apache.calcite.rex.RexWindowBounds;
import org.apache.calcite.runtime.SqlFunctions;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.sql.SqlExplainFormat;
import org.apache.calcite.sql.SqlExplainLevel;
Expand Down Expand Up @@ -99,7 +100,6 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
Expand Down Expand Up @@ -641,7 +641,7 @@ private static Fixture relFn(Function<RelBuilder, RelNode> relFn) {
.build();
return b.values(rowType, 0).project(
b.getRexBuilder().makeUuidLiteral(
UUID.fromString("123e4567-e89b-12d3-a456-426655440000")))
SqlFunctions.stringToUuid("123e4567-e89b-12d3-a456-426655440000")))
.build();
};
relFn(relFn)
Expand Down
22 changes: 22 additions & 0 deletions core/src/test/java/org/apache/calcite/test/TypeCoercionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,26 @@ private static ImmutableList<RelDataType> combine(
f.comparisonCommonType(f.charType, f.varcharType, f.varcharType);
f.comparisonCommonType(f.intType, f.charType, f.intType);
f.comparisonCommonType(f.doubleType, f.charType, f.doubleType);
// Test cases for [CALCITE-7727] Comparing UUID <> '' always returns FALSE.
final RelDataType char0Type = f.typeFactory.createSqlType(SqlTypeName.CHAR, 0);
final RelDataType char36Type = f.typeFactory.createSqlType(SqlTypeName.CHAR, 36);
final RelDataType char40Type = f.typeFactory.createSqlType(SqlTypeName.CHAR, 40);
final RelDataType binary16Type =
f.typeFactory.createSqlType(SqlTypeName.BINARY, 16);
final RelDataType binary20Type =
f.typeFactory.createSqlType(SqlTypeName.BINARY, 20);
f.comparisonCommonType(f.uuidType, char0Type, f.uuidType);
f.comparisonCommonType(f.uuidType, f.charType, f.uuidType);
f.comparisonCommonType(f.uuidType, char36Type, f.uuidType);
f.comparisonCommonType(f.uuidType, char40Type, f.uuidType);
f.comparisonCommonType(f.uuidType, f.varchar20Type, f.uuidType);
f.comparisonCommonType(f.uuidType, f.varcharType, f.uuidType);
f.comparisonCommonType(f.uuidType, f.binaryType, f.uuidType);
f.comparisonCommonType(f.uuidType, binary16Type, f.uuidType);
f.comparisonCommonType(f.uuidType, binary20Type, f.uuidType);
f.comparisonCommonType(f.uuidType, f.varbinaryType, f.uuidType);
f.comparisonCommonType(f.uuidType, f.uuidType, f.uuidType);

// TIMESTAMP
f.comparisonCommonType(f.timestampType, f.timestampType, f.timestampType);
f.comparisonCommonType(f.dateType, f.timestampType, f.timestampType);
Expand Down Expand Up @@ -796,6 +816,7 @@ static class Fixture {
final RelDataType nullableVarchar20Type;
final RelDataType geometryType;
final RelDataType nullableGeometryType;
final RelDataType uuidType;

/** Creates a Fixture. */
public static Fixture create(SqlTestFactory testFactory) {
Expand Down Expand Up @@ -846,6 +867,7 @@ protected Fixture(RelDataTypeFactory typeFactory,
nullableVarchar20Type = this.typeFactory.createTypeWithNullability(varchar20Type, true);
geometryType = this.typeFactory.createSqlType(SqlTypeName.GEOMETRY);
nullableGeometryType = this.typeFactory.createTypeWithNullability(geometryType, true);
uuidType = this.typeFactory.createSqlType(SqlTypeName.UUID);

// Initialize category types

Expand Down
Loading
Loading