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;

Reply via email to