This is an automated email from the ASF dual-hosted git repository.
palashc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/phoenix.git
The following commit(s) were added to refs/heads/master by this push:
new acdadc265f PHOENIX-7588 : SUBBINARY() - Substring like function for
Binary data types (#2126)
acdadc265f is described below
commit acdadc265fa2b2da0136944d306f10750f70ccd8
Author: Palash Chauhan <[email protected]>
AuthorDate: Mon Apr 28 18:13:10 2025 -0700
PHOENIX-7588 : SUBBINARY() - Substring like function for Binary data types
(#2126)
Co-authored-by: Palash Chauhan
<[email protected]>
---
.../apache/phoenix/expression/ExpressionType.java | 4 +-
.../expression/function/SubBinaryFunction.java | 231 +++++++++++++
.../phoenix/end2end/SubBinaryFunctionIT.java | 377 +++++++++++++++++++++
3 files changed, 611 insertions(+), 1 deletion(-)
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/expression/ExpressionType.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/expression/ExpressionType.java
index 453925ef5d..19e0ec81a0 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/expression/ExpressionType.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/expression/ExpressionType.java
@@ -203,7 +203,9 @@ public enum ExpressionType {
PartitionIdFunction(PartitionIdFunction.class),
DecodeBinaryFunction(DecodeBinaryFunction.class),
EncodeBinaryFunction(EncodeBinaryFunction.class),
- DecodeViewIdFunction(DecodeViewIndexIdFunction.class);
+ DecodeViewIdFunction(DecodeViewIndexIdFunction.class),
+ SubBinaryFunction(SubBinaryFunction.class);
+
ExpressionType(Class<? extends Expression> clazz) {
this.clazz = clazz;
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/expression/function/SubBinaryFunction.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/expression/function/SubBinaryFunction.java
new file mode 100644
index 0000000000..946aa1ab82
--- /dev/null
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/expression/function/SubBinaryFunction.java
@@ -0,0 +1,231 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.phoenix.expression.function;
+
+import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.phoenix.expression.Expression;
+import org.apache.phoenix.expression.LiteralExpression;
+import org.apache.phoenix.parse.FunctionParseNode;
+import org.apache.phoenix.schema.SortOrder;
+import org.apache.phoenix.schema.tuple.Tuple;
+import org.apache.phoenix.schema.types.PBinary;
+import org.apache.phoenix.schema.types.PDataType;
+import org.apache.phoenix.schema.types.PLong;
+import org.apache.phoenix.schema.types.PVarbinary;
+import org.apache.phoenix.schema.types.PVarbinaryEncoded;
+
+import java.io.DataInput;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ *
+ * Implementation of the {@code SUBBINARY(<binary>,<offset>[,<length>]) }
built-in function
+ * where {@code <offset> } is the offset from the start of {@code <binary>
}. A positive offset
+ * is treated as 1-based, a zero offset is treated as 0-based, and a negative
+ * offset starts from the end of the byte array working backwards. The optional
+ * {@code <length> } argument is the number of bytes to return. In the absence
of the
+ * {@code <length> } argument, the rest of the byte array starting from
{@code <offset> } is returned.
+ * If {@code <length> } is less than 1, null is returned.
+
+ */
[email protected](name=SubBinaryFunction.NAME, args={
+
@FunctionParseNode.Argument(allowedTypes={PBinary.class,PVarbinary.class,
PVarbinaryEncoded.class}),
+ @FunctionParseNode.Argument(allowedTypes={PLong.class}), // These are
LONG because negative numbers end up as longs
+
@FunctionParseNode.Argument(allowedTypes={PLong.class},defaultValue="null")} )
+public class SubBinaryFunction extends PrefixFunction {
+
+ public static final String NAME = "SUBBINARY";
+ private boolean hasLengthExpression;
+ private boolean isOffsetConstant;
+ private boolean isLengthConstant;
+ private boolean isFixedWidth;
+ private Integer maxLength;
+
+ public SubBinaryFunction() {
+ }
+
+ public SubBinaryFunction(List<Expression> children) {
+ super(children);
+ init();
+ }
+
+ private void init() {
+ isOffsetConstant = getOffsetExpression() instanceof LiteralExpression;
+ isLengthConstant = getLengthExpression() instanceof LiteralExpression;
+ hasLengthExpression = !isLengthConstant ||
((LiteralExpression)getLengthExpression()).getValue() != null;
+ isFixedWidth = getBinaryExpression().getDataType().isFixedWidth() &&
((hasLengthExpression && isLengthConstant) || (!hasLengthExpression &&
isOffsetConstant));
+ if (hasLengthExpression && isLengthConstant) {
+ Integer maxLength =
((Number)((LiteralExpression)getLengthExpression()).getValue()).intValue();
+ this.maxLength = maxLength >=0 ? maxLength : 0;
+ } else if (isOffsetConstant) {
+ Number offsetNumber =
(Number)((LiteralExpression)getOffsetExpression()).getValue();
+ if (offsetNumber != null) {
+ int offset = offsetNumber.intValue();
+ PDataType type = getBinaryExpression().getDataType();
+ if (type.isFixedWidth()) {
+ if (offset >= 0) {
+ Integer maxLength =
getBinaryExpression().getMaxLength();
+ this.maxLength = maxLength - offset + (offset == 0 ? 0
: 1);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean evaluate(Tuple tuple, ImmutableBytesWritable ptr) {
+ Expression offsetExpression = getOffsetExpression();
+ if (!offsetExpression.evaluate(tuple, ptr)) {
+ return false;
+ }
+ if (ptr.getLength() == 0) {
+ return true;
+ }
+ int offset = offsetExpression.getDataType().getCodec().decodeInt(ptr,
offsetExpression.getSortOrder());
+ int length = -1;
+ if (hasLengthExpression) {
+ Expression lengthExpression = getLengthExpression();
+ if (!lengthExpression.evaluate(tuple, ptr)) {
+ return false;
+ }
+ if (ptr.getLength() == 0) {
+ return true;
+ }
+ length = lengthExpression.getDataType().getCodec().decodeInt(ptr,
lengthExpression.getSortOrder());
+ if (length <= 0) {
+ return false;
+ }
+ }
+ if (!getBinaryExpression().evaluate(tuple, ptr)) {
+ return false;
+ }
+ if (ptr.getLength()==0) {
+ return true;
+ }
+ byte[] bytes = new byte[]{};
+ int binLength;
+ if (getDataType() == PVarbinaryEncoded.INSTANCE) {
+ // get decoded bytes
+ bytes = (byte[]) PVarbinaryEncoded.INSTANCE.toObject(ptr.get(),
ptr.getOffset(),
+ ptr.getLength());
+ binLength = bytes.length;
+ } else {
+ binLength = ptr.getLength();
+ }
+ // Account for 1 versus 0-based offset
+ offset = offset - (offset <= 0 ? 0 : 1);
+ if (offset < 0) { // Offset < 0 means get from end
+ offset = binLength + offset;
+ }
+ if (offset < 0 || offset >= binLength) {
+ return false;
+ }
+ int maxLength = binLength - offset;
+ length = length == -1 ? maxLength : Math.min(length,maxLength);
+ if (getDataType() == PVarbinaryEncoded.INSTANCE) {
+ // get the slice of decoded bytes based on offset and length
+ byte[] result = Arrays.copyOfRange(bytes, offset, offset + length);
+ // encode the slice and set
+ ptr.set(PVarbinaryEncoded.INSTANCE.toBytes(result));
+ }
+ else {
+ ptr.set(ptr.get(), ptr.getOffset() + offset, length);
+ }
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public PDataType getDataType() {
+ return getBinaryExpression().getDataType();
+ }
+
+ @Override
+ public boolean isNullable() {
+ return getBinaryExpression().isNullable() || !isFixedWidth ||
getOffsetExpression().isNullable();
+ }
+
+ @Override
+ public Integer getMaxLength() {
+ return maxLength;
+ }
+
+ @Override
+ public SortOrder getSortOrder() {
+ return getBinaryExpression().getSortOrder();
+ }
+
+ @Override
+ public void readFields(DataInput input) throws IOException {
+ super.readFields(input);
+ init();
+ }
+
+ @Override
+ public OrderPreserving preservesOrder() {
+ if (isOffsetConstant) {
+ LiteralExpression literal = (LiteralExpression)
getOffsetExpression();
+ Number offsetNumber = (Number) literal.getValue();
+ if (offsetNumber != null) {
+ int offset = offsetNumber.intValue();
+ if ((offset == 0 || offset == 1) && (!hasLengthExpression ||
isLengthConstant)) {
+ return OrderPreserving.YES_IF_LAST;
+ }
+ }
+ }
+ return OrderPreserving.NO;
+ }
+
+ private Expression getBinaryExpression() {
+ return children.get(0);
+ }
+
+ private Expression getOffsetExpression() {
+ return children.get(1);
+ }
+
+ private Expression getLengthExpression() {
+ return children.get(2);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder(getName() + "(");
+ if (children.isEmpty())
+ return buf.append(")").toString();
+ if (hasLengthExpression) {
+ buf.append(getBinaryExpression());
+ buf.append(", ");
+ buf.append(getOffsetExpression());
+ buf.append(", ");
+ buf.append(getLengthExpression());
+ } else {
+ buf.append(getBinaryExpression());
+ buf.append(", ");
+ buf.append(getOffsetExpression());
+ }
+ buf.append(")");
+ return buf.toString();
+ }
+}
diff --git
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SubBinaryFunctionIT.java
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SubBinaryFunctionIT.java
new file mode 100644
index 0000000000..8ba75c0064
--- /dev/null
+++
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SubBinaryFunctionIT.java
@@ -0,0 +1,377 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.phoenix.end2end;
+
+import org.apache.phoenix.util.QueryUtil;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@Category(ParallelStatsDisabledTest.class)
+public class SubBinaryFunctionIT extends ParallelStatsDisabledIT {
+
+ @Test
+ public void testBinary() throws Exception {
+ String tableName = generateUniqueName();
+ Connection conn = DriverManager.getConnection(getUrl());
+ conn.createStatement().execute("CREATE TABLE " + tableName + "(" +
+ " id INTEGER NOT NULL,\n" +
+ " BIN_PK BINARY(4) NOT NULL,\n" +
+ " BIN_COL BINARY(8) \n" +
+ " CONSTRAINT pk PRIMARY KEY (id, BIN_PK)" +
+ ")");
+
+ byte[] b11 = new byte[] {83, -101, -102, 91};
+ byte[] b12 = new byte[] {4, 1, -19, 8, 0, -73, 3, 4};
+ byte[] b21 = new byte[] {-1, 1, 20, -28,};
+ byte[] b22 = new byte[] {10, 55, 0, 19, -5, -34, 0, 0};
+ PreparedStatement stmt = conn.prepareStatement("UPSERT INTO " +
tableName + " VALUES(?, ?, ?)");
+ upsertRow(stmt, 1, b11, b12);
+ upsertRow(stmt, 2, b21, b22);
+ conn.commit();
+
+ ResultSet rs = conn.createStatement().executeQuery("SELECT
SUBBINARY(BIN_PK, 1, 3), SUBBINARY(BIN_COL, 0, 4) FROM " + tableName);
+ rs.next();
+ assertSubBinary(b11, rs.getBytes(1), 0, 3);
+ assertSubBinary(b12, rs.getBytes(2), 0, 4);
+ rs.next();
+ assertSubBinary(b21, rs.getBytes(1), 0, 3);
+ assertSubBinary(b22, rs.getBytes(2), 0, 4);
+ Assert.assertFalse(rs.next());
+
+ rs = conn.createStatement().executeQuery("SELECT SUBBINARY(BIN_PK, 2),
SUBBINARY(BIN_COL, 5) FROM " + tableName);
+ rs.next();
+ assertSubBinary(b11, rs.getBytes(1), 1, 3);
+ assertSubBinary(b12, rs.getBytes(2), 4, 4);
+ rs.next();
+ assertSubBinary(b21, rs.getBytes(1), 1, 3);
+ assertSubBinary(b22, rs.getBytes(2), 4, 4);
+ Assert.assertFalse(rs.next());
+
+ rs = conn.createStatement().executeQuery("SELECT SUBBINARY(BIN_PK, -3,
2), SUBBINARY(BIN_COL, -6, 3) FROM " + tableName);
+ rs.next();
+ assertSubBinary(b11, rs.getBytes(1), 1, 2);
+ assertSubBinary(b12, rs.getBytes(2), 2, 3);
+ rs.next();
+ assertSubBinary(b21, rs.getBytes(1), 1, 2);
+ assertSubBinary(b22, rs.getBytes(2), 2, 3);
+ Assert.assertFalse(rs.next());
+
+ rs = conn.createStatement().executeQuery("SELECT SUBBINARY(BIN_PK,
-1), SUBBINARY(BIN_COL, -3) FROM " + tableName);
+ rs.next();
+ assertSubBinary(b11, rs.getBytes(1), 3, 1);
+ assertSubBinary(b12, rs.getBytes(2), 5, 3);
+ rs.next();
+ assertSubBinary(b21, rs.getBytes(1), 3, 1);
+ assertSubBinary(b22, rs.getBytes(2), 5, 3);
+ Assert.assertFalse(rs.next());
+
+ PreparedStatement stmt2 = conn.prepareStatement("SELECT id FROM " +
tableName + " WHERE SUBBINARY(BIN_COL, 2, 6) = ?");
+ stmt2.setBytes(1, new byte[] {55, 0, 19, -5, -34, 0});
+ rs = stmt2.executeQuery();
+ Assert.assertTrue(rs.next());
+ Assert.assertEquals(2, rs.getInt(1));
+ Assert.assertFalse(rs.next());
+ }
+
+ @Test
+ public void testVarbinary() throws Exception {
+ String tableName = generateUniqueName();
+ Connection conn = DriverManager.getConnection(getUrl());
+ conn.createStatement().execute("CREATE TABLE " + tableName + "(" +
+ " id INTEGER NOT NULL,\n" +
+ " BIN_PK VARBINARY NOT NULL,\n" +
+ " BIN_COL VARBINARY \n" +
+ " CONSTRAINT pk PRIMARY KEY (id, BIN_PK)" +
+ ")");
+
+
+ byte[] b11 = new byte[] {56, 50, 19, 0, 34, 83, -101, -102, 91, 92};
+ byte[] b12 = new byte[] {10, 55, 0, 19, -5, -34, 0, -12, 0, 0, 0, 1};
+ byte[] b21 = new byte[] {-11, 55, -119, 0, 8, 0, 1, 2, -4, 33};
+ byte[] b22 = new byte[] {1, 1, 20, -28, 0, -1, 0, -11, -21, -1};
+ PreparedStatement stmt = conn.prepareStatement("UPSERT INTO " +
tableName + " VALUES(?, ?, ?)");
+ upsertRow(stmt, 1, b11, b12);
+ upsertRow(stmt, 2, b21, b22);
+ conn.commit();
+
+ ResultSet rs = conn.createStatement().executeQuery("SELECT
SUBBINARY(BIN_PK, 0, 4), SUBBINARY(BIN_COL, 1, 3) FROM " + tableName);
+ rs.next();
+ assertSubBinary(b11, rs.getBytes(1), 0, 4);
+ assertSubBinary(b12, rs.getBytes(2), 0, 3);
+ rs.next();
+ assertSubBinary(b21, rs.getBytes(1), 0, 4);
+ assertSubBinary(b22, rs.getBytes(2), 0, 3);
+ Assert.assertFalse(rs.next());
+
+ rs = conn.createStatement().executeQuery("SELECT SUBBINARY(BIN_PK, 5),
SUBBINARY(BIN_COL, 7) FROM " + tableName);
+ rs.next();
+ assertSubBinary(b11, rs.getBytes(1), 4, 6);
+ assertSubBinary(b12, rs.getBytes(2), 6, 6);
+ rs.next();
+ assertSubBinary(b21, rs.getBytes(1), 4, 6);
+ assertSubBinary(b22, rs.getBytes(2), 6, 4);
+ Assert.assertFalse(rs.next());
+
+ rs = conn.createStatement().executeQuery("SELECT SUBBINARY(BIN_PK, -4,
3), SUBBINARY(BIN_COL, -3, 1) FROM " + tableName);
+ rs.next();
+ assertSubBinary(b11, rs.getBytes(1), 6, 3);
+ assertSubBinary(b12, rs.getBytes(2), 9, 1);
+ rs.next();
+ assertSubBinary(b21, rs.getBytes(1), 6, 3);
+ assertSubBinary(b22, rs.getBytes(2), 7, 1);
+ Assert.assertFalse(rs.next());
+
+ rs = conn.createStatement().executeQuery("SELECT SUBBINARY(BIN_PK,
-2), SUBBINARY(BIN_COL, -2) FROM " + tableName);
+ rs.next();
+ assertSubBinary(b11, rs.getBytes(1), 8, 2);
+ assertSubBinary(b12, rs.getBytes(2), 10, 2);
+ rs.next();
+ assertSubBinary(b21, rs.getBytes(1), 8, 2);
+ assertSubBinary(b22, rs.getBytes(2), 8, 2);
+ Assert.assertFalse(rs.next());
+
+ PreparedStatement stmt2 = conn.prepareStatement("SELECT id FROM " +
tableName + " WHERE SUBBINARY(BIN_COL, 2, 6) = ?");
+ stmt2.setBytes(1, new byte[] {1, 20, -28, 0, -1, 0});
+ rs = stmt2.executeQuery();
+ Assert.assertTrue(rs.next());
+ Assert.assertEquals(2, rs.getInt(1));
+ Assert.assertFalse(rs.next());
+ }
+
+ @Test
+ public void testVarbinaryEncoded() throws Exception {
+ String tableName = generateUniqueName();
+ Connection conn = DriverManager.getConnection(getUrl());
+ conn.createStatement().execute("CREATE TABLE " + tableName + "(" +
+ " id INTEGER NOT NULL,\n" +
+ " BIN_PK VARBINARY_ENCODED NOT NULL,\n" +
+ " BIN_COL VARBINARY_ENCODED \n" +
+ " CONSTRAINT pk PRIMARY KEY (id, BIN_PK)" +
+ ")");
+
+
+ byte[] b11 = new byte[] {56, 50, 19, 0, 34, 83, -101, -102, 91, 92};
+ byte[] b12 = new byte[] {10, 55, -1, 19, -5, -34, 0, -12, 0, 0, 0, 1};
+ byte[] b21 = new byte[] {-11, 55, -119, 0, 8, 0, 1, 2, -4, 33};
+ byte[] b22 = new byte[] {1, 1, 20, -28, 0, -1, 0, -11, -21, -1};
+ byte[] b112 = new byte[] {56, 50, 19, 0, -1, -11, -12, 1, 9};
+ byte[] b122 = new byte[] {5, 1, 0, 0, 0, 0, 22, 122, 48, -121, 73, 3,
0, 23};
+ byte[] b31 = new byte[] {10, 55, 0, 19, -5, -34, 0, -12, 0, 0, 0, 1};
+ byte[] b32 = new byte[] {-11, 55, -119, 0, 8, 0, 1, 2, -4, 33};
+ PreparedStatement stmt = conn.prepareStatement("UPSERT INTO " +
tableName + " VALUES(?, ?, ?)");
+ upsertRow(stmt, 1, b11, b12);
+ upsertRow(stmt, 2, b21, b22);
+ conn.commit();
+
+ ResultSet rs = conn.createStatement().executeQuery("SELECT
SUBBINARY(BIN_PK, 0, 4), SUBBINARY(BIN_COL, 1, 3) FROM " + tableName);
+ rs.next();
+ assertSubBinary(b11, rs.getBytes(1), 0, 4);
+ assertSubBinary(b12, rs.getBytes(2), 0, 3);
+ rs.next();
+ assertSubBinary(b21, rs.getBytes(1), 0, 4);
+ assertSubBinary(b22, rs.getBytes(2), 0, 3);
+ Assert.assertFalse(rs.next());
+
+ rs = conn.createStatement().executeQuery("SELECT SUBBINARY(BIN_PK, 5),
SUBBINARY(BIN_COL, 7) FROM " + tableName);
+ rs.next();
+ assertSubBinary(b11, rs.getBytes(1), 4, 6);
+ assertSubBinary(b12, rs.getBytes(2), 6, 6);
+ rs.next();
+ assertSubBinary(b21, rs.getBytes(1), 4, 6);
+ assertSubBinary(b22, rs.getBytes(2), 6, 4);
+ Assert.assertFalse(rs.next());
+
+ rs = conn.createStatement().executeQuery("SELECT SUBBINARY(BIN_PK, -4,
3), SUBBINARY(BIN_COL, -3, 1) FROM " + tableName);
+ rs.next();
+ assertSubBinary(b11, rs.getBytes(1), 6, 3);
+ assertSubBinary(b12, rs.getBytes(2), 9, 1);
+ rs.next();
+ assertSubBinary(b21, rs.getBytes(1), 6, 3);
+ assertSubBinary(b22, rs.getBytes(2), 7, 1);
+ Assert.assertFalse(rs.next());
+
+ rs = conn.createStatement().executeQuery("SELECT SUBBINARY(BIN_PK,
-1), SUBBINARY(BIN_COL, -1) FROM " + tableName);
+ rs.next();
+ assertSubBinary(b11, rs.getBytes(1), 9, 1);
+ assertSubBinary(b12, rs.getBytes(2), 11, 1);
+ rs.next();
+ assertSubBinary(b21, rs.getBytes(1), 9, 1);
+ assertSubBinary(b22, rs.getBytes(2), 9, 1);
+ Assert.assertFalse(rs.next());
+
+
+ PreparedStatement stmt2 = conn.prepareStatement("SELECT id FROM " +
tableName + " WHERE SUBBINARY(BIN_COL, 2, 6) = ?");
+ stmt2.setBytes(1, new byte[] {1, 20, -28, 0, -1, 0});
+ rs = stmt2.executeQuery();
+ Assert.assertTrue(rs.next());
+ Assert.assertEquals(2, rs.getInt(1));
+ Assert.assertFalse(rs.next());
+
+ stmt2 = conn.prepareStatement("SELECT id FROM " + tableName + " WHERE
SUBBINARY(BIN_COL, 0, 3) = ?");
+ stmt2.setBytes(1, new byte[] {10, 55, -1});
+ rs = stmt2.executeQuery();
+ Assert.assertTrue(rs.next());
+ Assert.assertEquals(1, rs.getInt(1));
+ Assert.assertFalse(rs.next());
+
+ stmt2 = conn.prepareStatement("SELECT id FROM " + tableName + " WHERE
SUBBINARY(BIN_COL, 1, 3) = ?");
+ stmt2.setBytes(1, new byte[] {10, 55, -1});
+ rs = stmt2.executeQuery();
+ Assert.assertTrue(rs.next());
+ Assert.assertEquals(1, rs.getInt(1));
+ Assert.assertFalse(rs.next());
+
+ stmt2 = conn.prepareStatement("SELECT id FROM " + tableName + " WHERE
SUBBINARY(BIN_COL, -5, 3) = ?");
+ stmt2.setBytes(1, new byte[] {-1, 0, -11});
+ rs = stmt2.executeQuery();
+ Assert.assertTrue(rs.next());
+ Assert.assertEquals(2, rs.getInt(1));
+ Assert.assertFalse(rs.next());
+
+ upsertRow(stmt, 1, b112, b122);
+ upsertRow(stmt, 3, b31, b32);
+ conn.commit();
+
+ stmt2 = conn.prepareStatement("SELECT BIN_COL FROM " + tableName + "
WHERE id = 1 AND SUBBINARY(BIN_PK, 0, 4) = ?");
+ stmt2.setBytes(1, new byte[] {56, 50, 19, 0});
+ rs = stmt2.executeQuery();
+ Assert.assertTrue(rs.next());
+ Assert.assertArrayEquals(b12, rs.getBytes(1));
+ Assert.assertTrue(rs.next());
+ Assert.assertArrayEquals(b122, rs.getBytes(1));
+ Assert.assertFalse(rs.next());
+
+ }
+
+
+
+ @Test
+ public void testSubBinaryFunction() throws Exception {
+ String tableName = generateUniqueName();
+ Connection conn = DriverManager.getConnection(getUrl());
+ conn.createStatement().execute("CREATE TABLE " + tableName + "(" +
+ " id INTEGER PRIMARY KEY,\n" +
+ " VAR_BIN_COL VARBINARY,\n" +
+ " BIN_COL BINARY(8)" +
+ ")");
+ conn.createStatement().execute("UPSERT INTO " + tableName + " VALUES
(1, X'010203040506', X'01020304')");
+ conn.createStatement().execute("UPSERT INTO " + tableName + " VALUES
(2, X'000000000000', X'00000000')");
+ conn.createStatement().execute("UPSERT INTO " + tableName + " VALUES
(3, X'FFEEAABB', X'FFEEAABBFFEEAABB')");
+ conn.createStatement().execute("UPSERT INTO " + tableName + " VALUES
(4, null, X'0101010101010101')");
+ conn.createStatement().execute("UPSERT INTO " + tableName + " VALUES
(5, X'', X'04030201')");
+ conn.createStatement().execute("UPSERT INTO " + tableName + " VALUES
(6, X'0204', X'')");
+ conn.createStatement().execute("UPSERT INTO " + tableName + " VALUES
(7, X'010101', null)");
+ conn.commit();
+
+ ResultSet rs;
+
+ // first n bytes
+ rs = conn.createStatement().executeQuery("SELECT * FROM " + tableName
+ " WHERE SUBBINARY(VAR_BIN_COL, 1, 4) = X'01020304'");
+ Assert.assertTrue(rs.next() && rs.getInt(1) == 1);
+ rs = conn.createStatement().executeQuery("SELECT * FROM " + tableName
+ " WHERE SUBBINARY(BIN_COL, 1, 3) = X'FFEEAA'");
+ Assert.assertTrue(rs.next() && rs.getInt(1) == 3);
+
+ // length not provided, padding for fixed-length binary
+ rs = conn.createStatement().executeQuery("SELECT * FROM " + tableName
+ " WHERE SUBBINARY(VAR_BIN_COL, 2) = X'EEAABB'");
+ Assert.assertTrue(rs.next() && rs.getInt(1) == 3);
+ rs = conn.createStatement().executeQuery("SELECT * FROM " + tableName
+ " WHERE SUBBINARY(BIN_COL, 2) = X'03020100000000'");
+ Assert.assertTrue(rs.next() && rs.getInt(1) == 5);
+
+ // offset > length of column value
+ rs = conn.createStatement().executeQuery("SELECT
SUBBINARY(VAR_BIN_COL, 15, 2) FROM " + tableName + " WHERE id = 1");
+ Assert.assertTrue(rs.next() && rs.getBytes(1) == null);
+ rs = conn.createStatement().executeQuery("SELECT SUBBINARY(BIN_COL,
15, 2) FROM " + tableName + " WHERE id = 2");
+ Assert.assertTrue(rs.next() && rs.getBytes(1) == null);
+
+ // negative offset with length
+ rs = conn.createStatement().executeQuery("SELECT * FROM " + tableName
+ " WHERE SUBBINARY(VAR_BIN_COL, -2, 1) = X'05'");
+ Assert.assertTrue(rs.next() && rs.getInt(1) == 1);
+ rs = conn.createStatement().executeQuery("SELECT * FROM " + tableName
+ " WHERE SUBBINARY(BIN_COL, -2, 1) = X'AA'");
+ Assert.assertTrue(rs.next() && rs.getInt(1) == 3);
+
+ //negative offset without length
+ rs = conn.createStatement().executeQuery("SELECT * FROM " + tableName
+ " WHERE SUBBINARY(VAR_BIN_COL, -3) = X'000000'");
+ Assert.assertTrue(rs.next() && rs.getInt(1) == 2);
+ rs = conn.createStatement().executeQuery("SELECT * FROM " + tableName
+ " WHERE SUBBINARY(BIN_COL, -2) = X'0101'");
+ Assert.assertTrue(rs.next() && rs.getInt(1) == 4);
+
+ // empty column value
+ rs = conn.createStatement().executeQuery("SELECT
SUBBINARY(VAR_BIN_COL, 2, 3) FROM " + tableName + " WHERE id = 5");
+ Assert.assertTrue(rs.next() && rs.getBytes(1) == null);
+ rs = conn.createStatement().executeQuery("SELECT SUBBINARY(BIN_COL, 1,
4) FROM " + tableName + " WHERE id = 6");
+ Assert.assertTrue(rs.next() && rs.getBytes(1) == null);
+
+ // null values
+ rs = conn.createStatement().executeQuery("SELECT
SUBBINARY(VAR_BIN_COL, 1, 4) FROM " + tableName + " WHERE id = 4");
+ Assert.assertTrue(rs.next() && rs.getBytes(1) == null);
+ rs = conn.createStatement().executeQuery("SELECT SUBBINARY(BIN_COL, 2,
3) FROM " + tableName + " WHERE id = 7");
+ Assert.assertTrue(rs.next() && rs.getBytes(1) == null);
+ }
+
+ @Test
+ public void testExplainPlanWithSubBinaryFunctionInPK() throws Exception {
+ String tableName = generateUniqueName();
+ Connection conn = DriverManager.getConnection(getUrl());
+ conn.createStatement().execute("CREATE TABLE " + tableName
+ + " (id INTEGER NOT NULL, VAR_BIN_COL VARBINARY NOT NULL,
DESCRIPTION VARCHAR CONSTRAINT pk PRIMARY KEY (id, VAR_BIN_COL))");
+ conn.createStatement().execute("UPSERT INTO " + tableName + " VALUES
(1, X'0102030405', 'desc1')");
+ conn.createStatement().execute("UPSERT INTO " + tableName + " VALUES
(1, X'010101', 'desc5')");
+ conn.createStatement().execute("UPSERT INTO " + tableName + " VALUES
(2, X'0000000001', 'desc2')");
+ conn.createStatement().execute("UPSERT INTO " + tableName + " VALUES
(3, X'FFEEAABB', 'desc3')");
+ conn.createStatement().execute("UPSERT INTO " + tableName + " VALUES
(4, X'AA', 'desc4')");
+ conn.createStatement().execute("UPSERT INTO " + tableName + " VALUES
(6, X'AAFFAA', 'desc6')");
+ conn.createStatement().execute("UPSERT INTO " + tableName + " VALUES
(7, X'BB', 'desc7')");
+ conn.commit();
+
+ String sql = "SELECT * FROM " + tableName + " WHERE id = 1 AND
SUBBINARY(VAR_BIN_COL, 0, 1) = X'01'";
+ ResultSet rs = conn.createStatement().executeQuery(sql);
+ int count = 0;
+ while (rs.next()) {
+ count++;
+ }
+ Assert.assertEquals(2, count);
+ rs = conn.createStatement().executeQuery("EXPLAIN " + sql);
+ String plan = QueryUtil.getExplainPlan(rs);
+ Assert.assertTrue(plan.contains("RANGE SCAN OVER " + tableName + "
[1,X'01'] - [1,X'02']"));
+
+ sql = "SELECT * FROM " + tableName + " WHERE id = 1 AND
SUBBINARY(VAR_BIN_COL, 2, 1) = X'01'";
+ rs = conn.createStatement().executeQuery("EXPLAIN " + sql);
+ plan = QueryUtil.getExplainPlan(rs);
+ Assert.assertTrue(plan.contains("RANGE SCAN OVER " + tableName + "
[1]"));
+ }
+
+ private void upsertRow(PreparedStatement stmt, int id, byte[] b1, byte[]
b2) throws SQLException {
+ stmt.setInt(1, id);
+ stmt.setBytes(2, b1);
+ stmt.setBytes(3, b2);
+ stmt.executeUpdate();
+ }
+
+ private void assertSubBinary(byte[] expected, byte[] actual, int start,
int length) {
+ for (int i = 0; i < length; i++) {
+ Assert.assertEquals(expected[start + i], actual[i]);
+ }
+ }
+}