This is an automated email from the ASF dual-hosted git repository. jackietien pushed a commit to branch ty/to_base64 in repository https://gitbox.apache.org/repos/asf/iotdb.git
commit 5d0063568c5e820a6a1c169073821c01258830c8 Author: JackieTien97 <[email protected]> AuthorDate: Sun Jul 20 16:56:21 2025 +0800 Add built-in scalar function to_base64 for the IoTDB table model, supporting STRING, TEXT, and BLOB types, with full integration, unit tests, and integration tests. --- .../scalar/IoTDBToBase64FunctionTableIT.java | 130 +++++++++ .../relational/ColumnTransformerBuilder.java | 8 + .../relational/metadata/TableMetadataImpl.java | 11 + .../unary/scalar/ToBase64ColumnTransformer.java | 58 ++++ .../scalar/ToBase64ColumnTransformerTest.java | 321 +++++++++++++++++++++ .../relational/TableBuiltinScalarFunction.java | 1 + 6 files changed, 529 insertions(+) diff --git a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/old/builtinfunction/scalar/IoTDBToBase64FunctionTableIT.java b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/old/builtinfunction/scalar/IoTDBToBase64FunctionTableIT.java new file mode 100644 index 00000000000..6a70aeec2e2 --- /dev/null +++ b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/old/builtinfunction/scalar/IoTDBToBase64FunctionTableIT.java @@ -0,0 +1,130 @@ +/* + * 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.iotdb.relational.it.query.old.builtinfunction.scalar; + +import org.apache.iotdb.it.env.EnvFactory; +import org.apache.iotdb.it.framework.IoTDBTestRunner; +import org.apache.iotdb.itbase.category.TableClusterIT; +import org.apache.iotdb.itbase.category.TableLocalStandaloneIT; +import org.apache.iotdb.itbase.env.BaseEnv; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; + +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.Statement; +import java.util.Base64; + +import static org.apache.iotdb.db.it.utils.TestUtils.tableAssertTestFail; +import static org.apache.iotdb.db.it.utils.TestUtils.tableResultSetEqualTest; + +@RunWith(IoTDBTestRunner.class) +@Category({TableLocalStandaloneIT.class, TableClusterIT.class}) +public class IoTDBToBase64FunctionTableIT { + + private static final String DATABASE_NAME = "db_base64"; + + private static final String[] SQLs = + new String[] { + "CREATE DATABASE " + DATABASE_NAME, + "USE " + DATABASE_NAME, + "CREATE TABLE t1(device_id STRING TAG, s1 STRING FIELD, s2 TEXT FIELD, s3 BLOB FIELD, s4 STRING FIELD)", + "INSERT INTO t1(time, device_id, s1, s2, s3, s4) VALUES (1, 'd1', 'iotdb', 'iotdb', X'0102030405', null)", + "INSERT INTO t1(time, device_id, s1, s2, s3, s4) VALUES (2, 'd2', '', '', X'', null)", + // Setup for unsupported types + "CREATE TABLE t2(device_id STRING TAG, i INT32 FIELD, b BOOLEAN FIELD, f FLOAT FIELD, d DOUBLE FIELD, dt DATE FIELD, ts TIMESTAMP FIELD)", + "INSERT INTO t2(time, device_id, i, b, f, d, dt, ts) VALUES (1, 'd1', 1, true, 1.1, 2.2, '2024-01-01', 123456789)" + }; + + @BeforeClass + public static void setUp() throws Exception { + EnvFactory.getEnv().getConfig().getCommonConfig().setTimestampPrecision("ns"); + EnvFactory.getEnv().initClusterEnvironment(); + insertData(); + } + + protected static void insertData() { + try (Connection connection = EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); + Statement statement = connection.createStatement()) { + for (String sql : SQLs) { + statement.execute(sql); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @AfterClass + public static void tearDown() throws Exception { + EnvFactory.getEnv().cleanClusterEnvironment(); + } + + @Test + public void testToBase64() { + String expectedStr = + Base64.getEncoder().encodeToString("iotdb".getBytes(StandardCharsets.UTF_8)); + String expectedBlob = Base64.getEncoder().encodeToString(new byte[] {1, 2, 3, 4, 5}); + String expectedEmpty = ""; + tableResultSetEqualTest( + "SELECT to_base64(s1), to_base64(s2), to_base64(s3), to_base64(s4) FROM t1 WHERE time = 1", + new String[] {"_col0", "_col1", "_col2", "_col3"}, + new String[] {expectedStr + "," + expectedStr + "," + expectedBlob + ",null,"}, + DATABASE_NAME); + + tableResultSetEqualTest( + "SELECT to_base64(s1), to_base64(s2), to_base64(s3), to_base64(s4) FROM t1 WHERE time = 2", + new String[] {"_col0", "_col1", "_col2", "_col3"}, + new String[] {expectedEmpty + "," + expectedEmpty + "," + expectedEmpty + ",null,"}, + DATABASE_NAME); + } + + @Test + public void testToBase64UnsupportedTypes() { + // Assert that to_base64 on unsupported types throws an error + tableAssertTestFail( + "SELECT to_base64(i) FROM t2", + "701: Scalar function to_base64 only accepts one argument and it must be STRING, TEXT or BLOB data type.", + DATABASE_NAME); + tableAssertTestFail( + "SELECT to_base64(b) FROM t2", + "701: Scalar function to_base64 only accepts one argument and it must be STRING, TEXT or BLOB data type.", + DATABASE_NAME); + tableAssertTestFail( + "SELECT to_base64(f) FROM t2", + "701: Scalar function to_base64 only accepts one argument and it must be STRING, TEXT or BLOB data type.", + DATABASE_NAME); + tableAssertTestFail( + "SELECT to_base64(d) FROM t2", + "701: Scalar function to_base64 only accepts one argument and it must be STRING, TEXT or BLOB data type.", + DATABASE_NAME); + tableAssertTestFail( + "SELECT to_base64(dt) FROM t2", + "701: Scalar function to_base64 only accepts one argument and it must be STRING, TEXT or BLOB data type.", + DATABASE_NAME); + tableAssertTestFail( + "SELECT to_base64(ts) FROM t2", + "701: Scalar function to_base64 only accepts one argument and it must be STRING, TEXT or BLOB data type.", + DATABASE_NAME); + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/relational/ColumnTransformerBuilder.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/relational/ColumnTransformerBuilder.java index 0e915ab258c..bdbfb2800ca 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/relational/ColumnTransformerBuilder.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/relational/ColumnTransformerBuilder.java @@ -1022,6 +1022,14 @@ public class ColumnTransformerBuilder Type returnType = columnTransformers.get(0).getType(); return AbstractGreatestLeastColumnTransformer.getLeastColumnTransformer( returnType, columnTransformers); + } else if (TableBuiltinScalarFunction.TO_BASE64 + .getFunctionName() + .equalsIgnoreCase(functionName)) { + ColumnTransformer first = this.process(children.get(0), context); + if (children.size() == 1) { + return new org.apache.iotdb.db.queryengine.transformation.dag.column.unary.scalar + .ToBase64ColumnTransformer(org.apache.tsfile.read.common.type.StringType.STRING, first); + } } else { // user defined function if (TableUDFUtils.isScalarFunction(functionName)) { diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableMetadataImpl.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableMetadataImpl.java index 8934de172e9..21cfe04892a 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableMetadataImpl.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableMetadataImpl.java @@ -570,6 +570,17 @@ public class TableMetadataImpl implements Metadata { + " must have at least two arguments, and all type must be the same."); } return argumentTypes.get(0); + } else if (TableBuiltinScalarFunction.TO_BASE64 + .getFunctionName() + .equalsIgnoreCase(functionName)) { + if (!(argumentTypes.size() == 1 + && (isCharType(argumentTypes.get(0)) || isBlobType(argumentTypes.get(0))))) { + throw new SemanticException( + "Scalar function " + + functionName.toLowerCase(Locale.ENGLISH) + + " only accepts one argument and it must be STRING, TEXT or BLOB data type."); + } + return STRING; } // builtin aggregation function diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/transformation/dag/column/unary/scalar/ToBase64ColumnTransformer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/transformation/dag/column/unary/scalar/ToBase64ColumnTransformer.java new file mode 100644 index 00000000000..2bc2c3c8ab1 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/transformation/dag/column/unary/scalar/ToBase64ColumnTransformer.java @@ -0,0 +1,58 @@ +/* + * 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.iotdb.db.queryengine.transformation.dag.column.unary.scalar; + +import org.apache.iotdb.db.queryengine.transformation.dag.column.unary.UnaryColumnTransformer; + +import org.apache.tsfile.block.column.Column; +import org.apache.tsfile.block.column.ColumnBuilder; +import org.apache.tsfile.utils.Binary; + +import java.util.Base64; + +public class ToBase64ColumnTransformer extends UnaryColumnTransformer { + + public ToBase64ColumnTransformer( + org.apache.tsfile.read.common.type.Type returnType, + org.apache.iotdb.db.queryengine.transformation.dag.column.ColumnTransformer + childColumnTransformer) { + super(returnType, childColumnTransformer); + } + + @Override + protected void doTransform(Column column, ColumnBuilder columnBuilder) { + doTransform(column, columnBuilder, null); + } + + @Override + protected void doTransform(Column column, ColumnBuilder columnBuilder, boolean[] selection) { + int positionCount = column.getPositionCount(); + for (int i = 0; i < positionCount; i++) { + if ((selection != null && !selection[i]) || column.isNull(i)) { + columnBuilder.appendNull(); + continue; + } + Binary value = column.getBinary(i); + byte[] bytes = value.getValues(); + byte[] encoded = Base64.getEncoder().encode(bytes); + columnBuilder.writeBinary(new Binary(encoded)); + } + } +} diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/transformation/dag/column/unary/scalar/ToBase64ColumnTransformerTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/transformation/dag/column/unary/scalar/ToBase64ColumnTransformerTest.java new file mode 100644 index 00000000000..3cf6ff88f1d --- /dev/null +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/transformation/dag/column/unary/scalar/ToBase64ColumnTransformerTest.java @@ -0,0 +1,321 @@ +/* + * 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.iotdb.db.queryengine.transformation.dag.column.unary.scalar; + +import org.apache.tsfile.block.column.Column; +import org.apache.tsfile.read.common.block.column.BinaryColumn; +import org.apache.tsfile.read.common.type.StringType; +import org.apache.tsfile.utils.Binary; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Optional; + +public class ToBase64ColumnTransformerTest { + + private Column mockColumn(Binary[] values, boolean[] valueIsNull) { + return new BinaryColumn( + values.length, valueIsNull == null ? Optional.empty() : Optional.of(valueIsNull), values); + } + + private org.apache.iotdb.db.queryengine.transformation.dag.column.ColumnTransformer + mockChildTransformer(Column column) { + org.apache.iotdb.db.queryengine.transformation.dag.column.ColumnTransformer mock = + Mockito.mock( + org.apache.iotdb.db.queryengine.transformation.dag.column.ColumnTransformer.class); + Mockito.when(mock.getColumn()).thenReturn(column); + Mockito.doNothing().when(mock).tryEvaluate(); + Mockito.doNothing().when(mock).evaluateWithSelection(Mockito.any()); + Mockito.doNothing().when(mock).clearCache(); + return mock; + } + + @Test + public void testStringType() { + String input = "iotdb"; + Binary[] values = new Binary[] {new Binary(input, StandardCharsets.UTF_8)}; + Column column = mockColumn(values, null); + org.apache.iotdb.db.queryengine.transformation.dag.column.ColumnTransformer child = + mockChildTransformer(column); + ToBase64ColumnTransformer transformer = new ToBase64ColumnTransformer(StringType.STRING, child); + transformer.addReferenceCount(); + transformer.evaluate(); + Column result = transformer.getColumn(); + String expected = Base64.getEncoder().encodeToString(input.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals(expected, result.getBinary(0).toString()); + } + + @Test + public void testStringTypeNull() { + boolean[] valueIsNull = new boolean[] {true}; + Binary[] values = new Binary[] {null}; + Column column = mockColumn(values, valueIsNull); + org.apache.iotdb.db.queryengine.transformation.dag.column.ColumnTransformer child = + mockChildTransformer(column); + ToBase64ColumnTransformer transformer = new ToBase64ColumnTransformer(StringType.STRING, child); + transformer.addReferenceCount(); + transformer.evaluate(); + Column result = transformer.getColumn(); + Assert.assertTrue(result.isNull(0)); + } + + @Test + public void testBlobType() { + byte[] input = new byte[] {1, 2, 3, 4, 5}; + Binary[] values = new Binary[] {new Binary(input)}; + Column column = mockColumn(values, null); + org.apache.iotdb.db.queryengine.transformation.dag.column.ColumnTransformer child = + mockChildTransformer(column); + ToBase64ColumnTransformer transformer = new ToBase64ColumnTransformer(StringType.STRING, child); + transformer.addReferenceCount(); + transformer.evaluate(); + Column result = transformer.getColumn(); + String expected = Base64.getEncoder().encodeToString(input); + Assert.assertEquals(expected, result.getBinary(0).toString()); + } + + @Test + public void testBlobTypeNull() { + boolean[] valueIsNull = new boolean[] {true}; + Binary[] values = new Binary[] {null}; + Column column = mockColumn(values, valueIsNull); + org.apache.iotdb.db.queryengine.transformation.dag.column.ColumnTransformer child = + mockChildTransformer(column); + ToBase64ColumnTransformer transformer = new ToBase64ColumnTransformer(StringType.STRING, child); + transformer.addReferenceCount(); + transformer.evaluate(); + Column result = transformer.getColumn(); + Assert.assertTrue(result.isNull(0)); + } + + @Test + public void testTextType() { + String input = "iotdb"; + Binary[] values = new Binary[] {new Binary(input, StandardCharsets.UTF_8)}; + Column column = mockColumn(values, null); + org.apache.iotdb.db.queryengine.transformation.dag.column.ColumnTransformer child = + mockChildTransformer(column); + ToBase64ColumnTransformer transformer = new ToBase64ColumnTransformer(StringType.STRING, child); + transformer.addReferenceCount(); + transformer.evaluate(); + Column result = transformer.getColumn(); + String expected = Base64.getEncoder().encodeToString(input.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals(expected, result.getBinary(0).toString()); + } + + @Test + public void testTextTypeNull() { + boolean[] valueIsNull = new boolean[] {true}; + Binary[] values = new Binary[] {null}; + Column column = mockColumn(values, valueIsNull); + org.apache.iotdb.db.queryengine.transformation.dag.column.ColumnTransformer child = + mockChildTransformer(column); + ToBase64ColumnTransformer transformer = new ToBase64ColumnTransformer(StringType.STRING, child); + transformer.addReferenceCount(); + transformer.evaluate(); + Column result = transformer.getColumn(); + Assert.assertTrue(result.isNull(0)); + } + + @Test + public void testStringTypeMultiRowsWithNull() { + String input1 = "iotdb"; + String input2 = "iotdb2"; + boolean[] valueIsNull = new boolean[] {false, true, false}; + Binary[] values = + new Binary[] { + new Binary(input1, StandardCharsets.UTF_8), + null, + new Binary(input2, StandardCharsets.UTF_8) + }; + Column column = mockColumn(values, valueIsNull); + org.apache.iotdb.db.queryengine.transformation.dag.column.ColumnTransformer child = + mockChildTransformer(column); + ToBase64ColumnTransformer transformer = new ToBase64ColumnTransformer(StringType.STRING, child); + transformer.addReferenceCount(); + transformer.evaluate(); + Column result = transformer.getColumn(); + String expected1 = Base64.getEncoder().encodeToString(input1.getBytes(StandardCharsets.UTF_8)); + String expected2 = Base64.getEncoder().encodeToString(input2.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals(expected1, result.getBinary(0).toString()); + Assert.assertTrue(result.isNull(1)); + Assert.assertEquals(expected2, result.getBinary(2).toString()); + } + + @Test + public void testBlobTypeMultiRowsWithNull() { + byte[] input1 = new byte[] {1, 2, 3}; + byte[] input2 = new byte[] {4, 5, 6}; + boolean[] valueIsNull = new boolean[] {false, true, false}; + Binary[] values = new Binary[] {new Binary(input1), null, new Binary(input2)}; + Column column = mockColumn(values, valueIsNull); + org.apache.iotdb.db.queryengine.transformation.dag.column.ColumnTransformer child = + mockChildTransformer(column); + ToBase64ColumnTransformer transformer = new ToBase64ColumnTransformer(StringType.STRING, child); + transformer.addReferenceCount(); + transformer.evaluate(); + Column result = transformer.getColumn(); + String expected1 = Base64.getEncoder().encodeToString(input1); + String expected2 = Base64.getEncoder().encodeToString(input2); + Assert.assertEquals(expected1, result.getBinary(0).toString()); + Assert.assertTrue(result.isNull(1)); + Assert.assertEquals(expected2, result.getBinary(2).toString()); + } + + @Test + public void testTextTypeMultiRowsWithNull() { + String input1 = "iotdb"; + String input2 = "iotdb2"; + boolean[] valueIsNull = new boolean[] {false, true, false}; + Binary[] values = + new Binary[] { + new Binary(input1, StandardCharsets.UTF_8), + null, + new Binary(input2, StandardCharsets.UTF_8) + }; + Column column = mockColumn(values, valueIsNull); + org.apache.iotdb.db.queryengine.transformation.dag.column.ColumnTransformer child = + mockChildTransformer(column); + ToBase64ColumnTransformer transformer = new ToBase64ColumnTransformer(StringType.STRING, child); + transformer.addReferenceCount(); + transformer.evaluate(); + Column result = transformer.getColumn(); + String expected1 = Base64.getEncoder().encodeToString(input1.getBytes(StandardCharsets.UTF_8)); + String expected2 = Base64.getEncoder().encodeToString(input2.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals(expected1, result.getBinary(0).toString()); + Assert.assertTrue(result.isNull(1)); + Assert.assertEquals(expected2, result.getBinary(2).toString()); + } + + @Test + public void testEvaluateWithSelection() { + String input1 = "iotdb"; + String input2 = "iotdb2"; + boolean[] valueIsNull = new boolean[] {false, false, false}; + Binary[] values = + new Binary[] { + new Binary(input1, StandardCharsets.UTF_8), + new Binary(input2, StandardCharsets.UTF_8), + new Binary(input1, StandardCharsets.UTF_8) + }; + Column column = mockColumn(values, valueIsNull); + org.apache.iotdb.db.queryengine.transformation.dag.column.ColumnTransformer child = + mockChildTransformer(column); + ToBase64ColumnTransformer transformer = new ToBase64ColumnTransformer(StringType.STRING, child); + transformer.addReferenceCount(); + + // Only select the first and third row + boolean[] selection = new boolean[] {true, false, true}; + transformer.evaluateWithSelection(selection); + Column result = transformer.getColumn(); + + String expected1 = Base64.getEncoder().encodeToString(input1.getBytes(StandardCharsets.UTF_8)); + String expected2 = Base64.getEncoder().encodeToString(input2.getBytes(StandardCharsets.UTF_8)); + // Row 0: selected, should be encoded + Assert.assertEquals(expected1, result.getBinary(0).toString()); + // Row 1: not selected, should be null + Assert.assertTrue(result.isNull(1)); + // Row 2: selected, should be encoded + Assert.assertEquals(expected1, result.getBinary(2).toString()); + } + + @Test + public void testStringTypeEvaluateWithSelection() { + String input1 = "iotdb"; + String input2 = "iotdb2"; + boolean[] valueIsNull = new boolean[] {false, false, false}; + Binary[] values = + new Binary[] { + new Binary(input1, StandardCharsets.UTF_8), + new Binary(input2, StandardCharsets.UTF_8), + new Binary(input1, StandardCharsets.UTF_8) + }; + Column column = mockColumn(values, valueIsNull); + org.apache.iotdb.db.queryengine.transformation.dag.column.ColumnTransformer child = + mockChildTransformer(column); + ToBase64ColumnTransformer transformer = new ToBase64ColumnTransformer(StringType.STRING, child); + transformer.addReferenceCount(); + + // Only select the first and third row + boolean[] selection = new boolean[] {true, false, true}; + transformer.evaluateWithSelection(selection); + Column result = transformer.getColumn(); + + String expected1 = Base64.getEncoder().encodeToString(input1.getBytes(StandardCharsets.UTF_8)); + String expected2 = Base64.getEncoder().encodeToString(input2.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals(expected1, result.getBinary(0).toString()); + Assert.assertTrue(result.isNull(1)); + Assert.assertEquals(expected1, result.getBinary(2).toString()); + } + + @Test + public void testBlobTypeEvaluateWithSelection() { + byte[] input1 = new byte[] {1, 2, 3}; + byte[] input2 = new byte[] {4, 5, 6}; + boolean[] valueIsNull = new boolean[] {false, false, false}; + Binary[] values = new Binary[] {new Binary(input1), new Binary(input2), new Binary(input1)}; + Column column = mockColumn(values, valueIsNull); + org.apache.iotdb.db.queryengine.transformation.dag.column.ColumnTransformer child = + mockChildTransformer(column); + ToBase64ColumnTransformer transformer = new ToBase64ColumnTransformer(StringType.STRING, child); + transformer.addReferenceCount(); + + boolean[] selection = new boolean[] {true, false, true}; + transformer.evaluateWithSelection(selection); + Column result = transformer.getColumn(); + + String expected1 = Base64.getEncoder().encodeToString(input1); + String expected2 = Base64.getEncoder().encodeToString(input2); + Assert.assertEquals(expected1, result.getBinary(0).toString()); + Assert.assertTrue(result.isNull(1)); + Assert.assertEquals(expected1, result.getBinary(2).toString()); + } + + @Test + public void testTextTypeEvaluateWithSelection() { + String input1 = "iotdb"; + String input2 = "iotdb2"; + boolean[] valueIsNull = new boolean[] {false, false, false}; + Binary[] values = + new Binary[] { + new Binary(input1, StandardCharsets.UTF_8), + new Binary(input2, StandardCharsets.UTF_8), + new Binary(input1, StandardCharsets.UTF_8) + }; + Column column = mockColumn(values, valueIsNull); + org.apache.iotdb.db.queryengine.transformation.dag.column.ColumnTransformer child = + mockChildTransformer(column); + ToBase64ColumnTransformer transformer = new ToBase64ColumnTransformer(StringType.STRING, child); + transformer.addReferenceCount(); + + boolean[] selection = new boolean[] {true, false, true}; + transformer.evaluateWithSelection(selection); + Column result = transformer.getColumn(); + + String expected1 = Base64.getEncoder().encodeToString(input1.getBytes(StandardCharsets.UTF_8)); + String expected2 = Base64.getEncoder().encodeToString(input2.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals(expected1, result.getBinary(0).toString()); + Assert.assertTrue(result.isNull(1)); + Assert.assertEquals(expected1, result.getBinary(2).toString()); + } +} diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/relational/TableBuiltinScalarFunction.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/relational/TableBuiltinScalarFunction.java index 6d5b1ef4721..985f7ab1b9e 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/relational/TableBuiltinScalarFunction.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/relational/TableBuiltinScalarFunction.java @@ -67,6 +67,7 @@ public enum TableBuiltinScalarFunction { FORMAT("format"), GREATEST("greatest"), LEAST("least"), + TO_BASE64("to_base64"), ; private final String functionName;
