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 @@ -387,13 +387,27 @@ private Expression getConvertExpression(

case VARBINARY:
case BINARY:
int binaryPrecision = targetType.getPrecision();
switch (sourceType.getSqlTypeName()) {
case CHAR:
case VARCHAR:
return Expressions.call(BuiltInMethod.STRING_TO_BINARY.method, operand,
new ConstantExpression(Charset.class, sourceType.getCharset()));
case UUID:
return Expressions.call(BuiltInMethod.UUID_TO_BINARY.method, operand);
case BIGINT:
case INTEGER:
case SMALLINT:
case TINYINT:
case UBIGINT:
case UINTEGER:
case USMALLINT:
case UTINYINT:
return Expressions.call(
BuiltInMethod.INT_TO_BINARY.method,
operand,
Expressions.constant(binaryPrecision),
Expressions.constant(targetType.getSqlTypeName() == SqlTypeName.BINARY));
default:
return defaultExpression.get();
}
Expand Down
61 changes: 61 additions & 0 deletions core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
Expand Down Expand Up @@ -1272,6 +1273,66 @@ public static String formatNumber(BigDecimal value, int decimalVal) {
return numberFormat.format(value);
}

/** Implements casts from integer to binary values.
*
* @param value Value converted; an integer or unsigned value.
* @param resultSize Size of result in bytes; negative if unspecified.
* @param fixed True if the result type is BINARY; false for VARBINARY.
* @return A ByteString containing the conversion result.
*
* <p>Most SQL dialects which support this feature seem to convert integers to big endian values,
* and then truncate or pad on the left when the size of the target BINARY does not exactly
* match the integer's size.
*/
public static ByteString intToBinary(Object value, int resultSize, boolean fixed) {
ByteBuffer buffer;
if (value instanceof Byte) {
buffer = ByteBuffer.allocate(1)
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.

Do we need a method like this to maintain the length of the type?

private static int getByteSize(Object value) {
    if (value instanceof Byte) {
        return 1;
    } else if (value instanceof Short) {
        return 2;
    } else if (value instanceof Integer) {
        return 4;
    } else if (value instanceof Long) {
        return 8;
    } else if (value instanceof UByte) {
        return 1;
    } else if (value instanceof UShort) {
        return 2;
    } else if (value instanceof UInteger) {
        return 4;
    } else if (value instanceof ULong) {
        return 8;
    }
    throw new IllegalArgumentException("Unexpected type: " + value.getClass());
}

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.

we could, but you still need the ifs because the method you use to write each integer is different.

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.

Yes, let's keep things as they are.

.order(ByteOrder.BIG_ENDIAN)
.put((byte) value);
} else if (value instanceof Short) {
buffer = ByteBuffer.allocate(2)
.order(ByteOrder.BIG_ENDIAN)
.putShort((short) value);
} else if (value instanceof Integer) {
buffer = ByteBuffer.allocate(4)
.order(ByteOrder.BIG_ENDIAN)
.putInt((Integer) value);
} else if (value instanceof Long) {
buffer = ByteBuffer.allocate(8)
.order(ByteOrder.BIG_ENDIAN)
.putLong((Long) value);
} else if (value instanceof UByte) {
buffer = ByteBuffer.allocate(1)
.order(ByteOrder.BIG_ENDIAN)
.put(((UByte) value).byteValue());
} else if (value instanceof UShort) {
buffer = ByteBuffer.allocate(2)
.order(ByteOrder.BIG_ENDIAN)
.putShort(((UShort) value).shortValue());
} else if (value instanceof UInteger) {
buffer = ByteBuffer.allocate(4)
.order(ByteOrder.BIG_ENDIAN)
.putInt(((UInteger) value).intValue());
} else if (value instanceof ULong) {
buffer = ByteBuffer.allocate(8)
.order(ByteOrder.BIG_ENDIAN)
.putLong(((ULong) value).longValue());
} else {
throw new IllegalArgumentException("Unexpected argument type " + value);
}
ByteString result = new ByteString(buffer.array());
if (resultSize >= 0) {
if (resultSize < result.length()) {
result = SqlFunctions.right(result, resultSize);
} else if (fixed && resultSize > result.length()) {
// pad on left
result = new ByteString(new byte[resultSize - result.length()]).concat(result);
}
}
return result;
}

public static String formatNumber(long value, String format) {
DecimalFormat numberFormat = getNumberFormat(format);
return numberFormat.format(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,7 @@ public enum BuiltInMethod {
UUID_FROM_STRING(UUID.class, "fromString", 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),
BINARY_TO_UUID(SqlFunctions.class, "binaryToUuid", ByteString.class),
INITCAP(SqlFunctions.class, "initcap", String.class),
SUBSTRING(SqlFunctions.class, "substring", String.class, int.class,
Expand Down
25 changes: 25 additions & 0 deletions testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4056,6 +4056,31 @@ void checkIsNull(SqlOperatorFixture f, SqlOperator operator) {
f.setFor(SqlStdOperatorTable.EXISTS, VM_EXPAND);
}

/** Test cases for <a href="https://issues.apache.org/jira/browse/CALCITE-7368">[CALCITE-7368]
* The validator accepts CAST(INT TO BINARY), but the runtime does not implement them</a>. */
@Test void testCastIntToBinary() {
final SqlOperatorFixture f = fixture();
f.checkNull("cast(CAST(NULL AS INT) AS VARBINARY)");
f.checkScalar("cast(10 as BINARY(4))", "0000000a", "BINARY(4) NOT NULL");
f.checkScalar("cast(10 AS BINARY(2))", "000a", "BINARY(2) NOT NULL");
f.checkScalar("cast(10 as VARBINARY(4))", "0000000a", "VARBINARY(4) NOT NULL");
f.checkScalar("cast(10 as VARBINARY(2))", "000a", "VARBINARY(2) NOT NULL");
f.checkScalar("cast(cast(10 AS INT UNSIGNED) AS BINARY(4))", "0000000a", "BINARY(4) NOT NULL");
f.checkScalar("cast(-1 AS BINARY(4))", "ffffffff", "BINARY(4) NOT NULL");
f.checkScalar("cast(-1 AS VARBINARY(8))", "ffffffff", "VARBINARY(8) NOT NULL");
f.checkScalar("cast(10 AS VARBINARY)", "0000000a", "VARBINARY NOT NULL");
f.checkScalar("cast(cast(10 AS TINYINT) AS VARBINARY)", "0a", "VARBINARY NOT NULL");
f.checkScalar("cast(cast(-1 AS TINYINT) AS VARBINARY)", "ff", "VARBINARY NOT NULL");
f.checkScalar("cast(cast(-1 AS TINYINT) AS BINARY(4))", "000000ff", "BINARY(4) NOT NULL");
f.checkScalar("cast(cast(10 AS BIGINT) AS VARBINARY(16))", "000000000000000a",
"VARBINARY(16) NOT NULL");
f.checkScalar("cast(cast(10 AS BIGINT) AS VARBINARY(8))", "000000000000000a",
"VARBINARY(8) NOT NULL");
f.checkScalar("cast(cast(10 AS BIGINT) AS VARBINARY(6))", "00000000000a",
"VARBINARY(6) NOT NULL");
f.checkScalar("cast(cast(10 AS BIGINT) AS VARBINARY(4))", "0000000a", "VARBINARY(4) NOT NULL");
}

@Test void testNotOperator() {
final SqlOperatorFixture f = fixture();
f.setFor(SqlStdOperatorTable.NOT, VmName.EXPAND);
Expand Down
Loading