multiple refactors
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java index 50312a4..a432bca 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java
@@ -48,14 +48,9 @@ import org.apache.iotdb.commons.queryengine.common.SqlDialect; import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId; import org.apache.iotdb.commons.queryengine.plan.planner.plan.parameter.InputLocation; -import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.BinaryLiteral; -import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.BooleanLiteral; -import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.DoubleLiteral; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Identifier; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Literal; -import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.LongLiteral; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.NullLiteral; -import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.StringLiteral; import org.apache.iotdb.commons.queryengine.plan.relational.sql.parser.ParsingException; import org.apache.iotdb.commons.utils.PathUtils; import org.apache.iotdb.db.audit.DNAuditLogger; @@ -143,6 +138,7 @@ import org.apache.iotdb.db.utils.QueryDataSetUtils; import org.apache.iotdb.db.utils.SchemaUtils; import org.apache.iotdb.db.utils.SetThreadName; +import org.apache.iotdb.db.utils.TypeServices; import org.apache.iotdb.rpc.RpcUtils; import org.apache.iotdb.rpc.TSStatusCode; import org.apache.iotdb.rpc.stmt.PreparedParameterSerde; @@ -216,6 +212,7 @@ import org.apache.tsfile.read.common.block.TsBlock; import org.apache.tsfile.read.common.block.TsBlockBuilder; import org.apache.tsfile.read.common.block.column.TsBlockSerde; +import org.apache.tsfile.read.common.type.Type; import org.apache.tsfile.read.filter.basic.Filter; import org.apache.tsfile.read.filter.factory.TimeFilterApi; import org.apache.tsfile.utils.Binary; @@ -699,34 +696,9 @@ return new Pair<>(new NullLiteral(), "NULL"); } - switch (param.type) { - case BOOLEAN: - String boolStr = (Boolean) param.value ? "true" : "false"; - return new Pair<>(new BooleanLiteral(boolStr), boolStr); - case INT32: - case INT64: - String numStr = String.valueOf(param.value); - return new Pair<>(new LongLiteral(numStr), numStr); - case FLOAT: - String floatStr = String.valueOf(param.value); - return new Pair<>(new DoubleLiteral((Float) param.value), floatStr); - case DOUBLE: - String doubleStr = String.valueOf(param.value); - return new Pair<>(new DoubleLiteral((Double) param.value), doubleStr); - case TEXT: - case STRING: - String strVal = (String) param.value; - // Escape single quotes for SQL - String escapedStr = "'" + strVal.replace("'", "''") + "'"; - return new Pair<>(new StringLiteral(strVal), escapedStr); - case BLOB: - byte[] bytes = (byte[]) param.value; - String hexStr = "X'" + PreparedParameterSerde.bytesToHex(bytes) + "'"; - return new Pair<>(new BinaryLiteral(bytes), hexStr); - default: - throw new IllegalArgumentException( - DataNodeMiscMessages.UNKNOWN_PARAMETER_TYPE + param.type); - } + return TypeServices.ValueConversion.PREPARED_PARAMETER_LITERAL_SERVICE + .call(Type.fromTsDataType(param.type)) + .apply(param.value); } }
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/SeriesScanUtil.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/SeriesScanUtil.java index 26dfcc6..dfb615b 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/SeriesScanUtil.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/SeriesScanUtil.java
@@ -41,6 +41,7 @@ import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource; import org.apache.iotdb.db.utils.CommonUtils; import org.apache.iotdb.db.utils.SchemaUtils; +import org.apache.iotdb.db.utils.TypeServices; import org.apache.iotdb.db.utils.datastructure.MemPointIterator; import org.apache.tsfile.block.column.Column; @@ -57,12 +58,6 @@ import org.apache.tsfile.read.common.block.TsBlock; import org.apache.tsfile.read.common.block.TsBlockBuilder; import org.apache.tsfile.read.common.block.TsBlockUtil; -import org.apache.tsfile.read.common.block.column.BinaryColumn; -import org.apache.tsfile.read.common.block.column.BooleanColumn; -import org.apache.tsfile.read.common.block.column.DoubleColumn; -import org.apache.tsfile.read.common.block.column.FloatColumn; -import org.apache.tsfile.read.common.block.column.IntColumn; -import org.apache.tsfile.read.common.block.column.LongColumn; import org.apache.tsfile.read.common.type.Type; import org.apache.tsfile.read.controller.IChunkLoader; import org.apache.tsfile.read.filter.basic.Filter; @@ -72,7 +67,6 @@ import org.apache.tsfile.read.reader.page.TablePageReader; import org.apache.tsfile.read.reader.series.PaginationController; import org.apache.tsfile.utils.Accountable; -import org.apache.tsfile.utils.Binary; import org.apache.tsfile.utils.RamUsageEstimator; import org.apache.tsfile.utils.TsPrimitiveType; import org.slf4j.Logger; @@ -80,7 +74,6 @@ import java.io.IOException; import java.io.Serializable; -import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; @@ -962,439 +955,11 @@ int positionCount = tsBlock.getPositionCount(); Column[] newValueColumns = new Column[length]; for (int i = 0; i < length; i++) { - TSDataType sourceType = valueColumns[i].getDataType(); TSDataType finalDataType = getTsDataTypeList().get(i); - switch (finalDataType) { - case BOOLEAN: - if (sourceType == TSDataType.BOOLEAN) { - newValueColumns[i] = valueColumns[i]; - } else { - newValueColumns[i] = - new BooleanColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new boolean[positionCount]); - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = true; - } - } - break; - case INT32: - if (sourceType == TSDataType.INT32) { - newValueColumns[i] = valueColumns[i]; - } else { - newValueColumns[i] = - new IntColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new int[positionCount], - TSDataType.INT32); - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = true; - } - } - break; - case INT64: - if (sourceType == TSDataType.INT64) { - newValueColumns[i] = valueColumns[i]; - } else if (sourceType == TSDataType.INT32) { - newValueColumns[i] = - new LongColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new long[positionCount]); - - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = valueColumns[i].isNull()[j]; - if (!valueColumns[i].isNull()[j]) { - newValueColumns[i].getLongs()[j] = - ((Number) valueColumns[i].getInts()[j]).longValue(); - } - } - } else if (sourceType == TSDataType.TIMESTAMP) { - newValueColumns[i] = valueColumns[i]; - } else { - newValueColumns[i] = - new LongColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new long[positionCount]); - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = true; - } - } - break; - case FLOAT: - if (sourceType == TSDataType.FLOAT) { - newValueColumns[i] = valueColumns[i]; - } else if (sourceType == TSDataType.INT32) { - newValueColumns[i] = - new FloatColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new float[positionCount]); - - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = valueColumns[i].isNull()[j]; - if (!valueColumns[i].isNull()[j]) { - newValueColumns[i].getFloats()[j] = - ((Number) valueColumns[i].getInts()[j]).floatValue(); - } - } - } else { - newValueColumns[i] = - new FloatColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new float[positionCount]); - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = true; - } - } - break; - case DOUBLE: - if (sourceType == TSDataType.DOUBLE) { - newValueColumns[i] = valueColumns[i]; - } else if (sourceType == TSDataType.INT32) { - newValueColumns[i] = - new DoubleColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new double[positionCount]); - - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = valueColumns[i].isNull()[j]; - if (!valueColumns[i].isNull()[j]) { - newValueColumns[i].getDoubles()[j] = - ((Number) valueColumns[i].getInts()[j]).doubleValue(); - } - } - } else if (sourceType == TSDataType.INT64) { - newValueColumns[i] = - new DoubleColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new double[positionCount]); - - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = valueColumns[i].isNull()[j]; - if (!valueColumns[i].isNull()[j]) { - newValueColumns[i].getDoubles()[j] = - ((Number) valueColumns[i].getLongs()[j]).doubleValue(); - } - } - } else if (sourceType == TSDataType.FLOAT) { - newValueColumns[i] = - new DoubleColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new double[positionCount]); - - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = valueColumns[i].isNull()[j]; - if (!valueColumns[i].isNull()[j]) { - newValueColumns[i].getDoubles()[j] = - ((Number) valueColumns[i].getFloats()[j]).doubleValue(); - } - } - } else if (sourceType == TSDataType.TIMESTAMP) { - newValueColumns[i] = - new DoubleColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new double[positionCount]); - - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = valueColumns[i].isNull()[j]; - if (!valueColumns[i].isNull()[j]) { - newValueColumns[i].getDoubles()[j] = - ((Number) valueColumns[i].getLongs()[j]).doubleValue(); - } - } - } else { - newValueColumns[i] = - new DoubleColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new double[positionCount]); - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = true; - } - } - break; - case TEXT: - if (SchemaUtils.isUsingSameColumn(sourceType, TSDataType.TEXT)) { - newValueColumns[i] = valueColumns[i]; - } else if (sourceType == TSDataType.INT32) { - newValueColumns[i] = - new BinaryColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new Binary[positionCount]); - - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = valueColumns[i].isNull()[j]; - if (!valueColumns[i].isNull()[j]) { - newValueColumns[i].getBinaries()[j] = - new Binary( - String.valueOf(valueColumns[i].getInts()[j]), StandardCharsets.UTF_8); - } - } - } else if (sourceType == TSDataType.DATE) { - newValueColumns[i] = - new BinaryColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new Binary[positionCount]); - - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = valueColumns[i].isNull()[j]; - if (!valueColumns[i].isNull()[j]) { - newValueColumns[i].getBinaries()[j] = - new Binary( - TSDataType.getDateStringValue(valueColumns[i].getInts()[j]), - StandardCharsets.UTF_8); - } - } - } else if (sourceType == TSDataType.INT64 || sourceType == TSDataType.TIMESTAMP) { - newValueColumns[i] = - new BinaryColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new Binary[positionCount]); - - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = valueColumns[i].isNull()[j]; - if (!valueColumns[i].isNull()[j]) { - newValueColumns[i].getBinaries()[j] = - new Binary( - String.valueOf(valueColumns[i].getLongs()[j]), StandardCharsets.UTF_8); - } - } - } else if (sourceType == TSDataType.FLOAT) { - newValueColumns[i] = - new BinaryColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new Binary[positionCount]); - - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = valueColumns[i].isNull()[j]; - if (!valueColumns[i].isNull()[j]) { - newValueColumns[i].getBinaries()[j] = - new Binary( - String.valueOf(valueColumns[i].getFloats()[j]), StandardCharsets.UTF_8); - } - } - } else if (sourceType == TSDataType.DOUBLE) { - newValueColumns[i] = - new BinaryColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new Binary[positionCount]); - - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = valueColumns[i].isNull()[j]; - if (!valueColumns[i].isNull()[j]) { - newValueColumns[i].getBinaries()[j] = - new Binary( - String.valueOf(valueColumns[i].getDoubles()[j]), StandardCharsets.UTF_8); - } - } - } else if (sourceType == TSDataType.BOOLEAN) { - newValueColumns[i] = - new BinaryColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new Binary[positionCount]); - - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = valueColumns[i].isNull()[j]; - if (!valueColumns[i].isNull()[j]) { - newValueColumns[i].getBinaries()[j] = - new Binary( - String.valueOf(valueColumns[i].getBooleans()[j]), StandardCharsets.UTF_8); - } - } - } else { - newValueColumns[i] = - new BinaryColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new Binary[positionCount]); - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = true; - } - } - break; - case TIMESTAMP: - if (SchemaUtils.isUsingSameColumn(sourceType, TSDataType.TIMESTAMP)) { - newValueColumns[i] = valueColumns[i]; - } else if (sourceType == TSDataType.INT32) { - newValueColumns[i] = - new LongColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new long[positionCount]); - - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = valueColumns[i].isNull()[j]; - if (!valueColumns[i].isNull()[j]) { - newValueColumns[i].getLongs()[j] = - ((Number) valueColumns[i].getInts()[j]).longValue(); - } - } - } else if (sourceType == TSDataType.INT64) { - newValueColumns[i] = valueColumns[i]; - } else { - newValueColumns[i] = - new LongColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new long[positionCount]); - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = true; - } - } - break; - case DATE: - if (SchemaUtils.isUsingSameColumn(sourceType, TSDataType.DATE)) { - newValueColumns[i] = valueColumns[i]; - } else { - newValueColumns[i] = - new IntColumn( - positionCount, Optional.of(new boolean[positionCount]), new int[positionCount]); - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = true; - } - } - break; - case BLOB: - if (SchemaUtils.isUsingSameColumn(sourceType, TSDataType.BLOB)) { - newValueColumns[i] = valueColumns[i]; - } else { - newValueColumns[i] = - new BinaryColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new Binary[positionCount]); - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = true; - } - } - break; - case STRING: - if (SchemaUtils.isUsingSameColumn(sourceType, TSDataType.STRING)) { - newValueColumns[i] = valueColumns[i]; - } else if (sourceType == TSDataType.INT32) { - newValueColumns[i] = - new BinaryColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new Binary[positionCount]); - - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = valueColumns[i].isNull()[j]; - if (!valueColumns[i].isNull()[j]) { - newValueColumns[i].getBinaries()[j] = - new Binary( - String.valueOf(valueColumns[i].getInts()[j]), StandardCharsets.UTF_8); - } - } - } else if (sourceType == TSDataType.DATE) { - newValueColumns[i] = - new BinaryColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new Binary[positionCount]); - - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = valueColumns[i].isNull()[j]; - if (!valueColumns[i].isNull()[j]) { - newValueColumns[i].getBinaries()[j] = - new Binary( - TSDataType.getDateStringValue(valueColumns[i].getInts()[j]), - StandardCharsets.UTF_8); - } - } - } else if (sourceType == TSDataType.INT64 || sourceType == TSDataType.TIMESTAMP) { - newValueColumns[i] = - new BinaryColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new Binary[positionCount]); - - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = valueColumns[i].isNull()[j]; - if (!valueColumns[i].isNull()[j]) { - newValueColumns[i].getBinaries()[j] = - new Binary( - String.valueOf(valueColumns[i].getLongs()[j]), StandardCharsets.UTF_8); - } - } - } else if (sourceType == TSDataType.FLOAT) { - newValueColumns[i] = - new BinaryColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new Binary[positionCount]); - - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = valueColumns[i].isNull()[j]; - if (!valueColumns[i].isNull()[j]) { - newValueColumns[i].getBinaries()[j] = - new Binary( - String.valueOf(valueColumns[i].getFloats()[j]), StandardCharsets.UTF_8); - } - } - } else if (sourceType == TSDataType.DOUBLE) { - newValueColumns[i] = - new BinaryColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new Binary[positionCount]); - - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = valueColumns[i].isNull()[j]; - if (!valueColumns[i].isNull()[j]) { - newValueColumns[i].getBinaries()[j] = - new Binary( - String.valueOf(valueColumns[i].getDoubles()[j]), StandardCharsets.UTF_8); - } - } - } else if (sourceType == TSDataType.BOOLEAN) { - newValueColumns[i] = - new BinaryColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new Binary[positionCount]); - - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = valueColumns[i].isNull()[j]; - if (!valueColumns[i].isNull()[j]) { - newValueColumns[i].getBinaries()[j] = - new Binary( - String.valueOf(valueColumns[i].getBooleans()[j]), StandardCharsets.UTF_8); - } - } - } else { - newValueColumns[i] = - new BinaryColumn( - positionCount, - Optional.of(new boolean[positionCount]), - new Binary[positionCount]); - for (int j = 0; j < valueColumns[i].getPositionCount(); j++) { - newValueColumns[i].isNull()[j] = true; - } - } - break; - case OBJECT: - newValueColumns[i] = valueColumns[i]; - case VECTOR: - case UNKNOWN: - default: - break; - } + newValueColumns[i] = + TypeServices.Transformation.ALTERED_DATA_TYPE_COLUMN_TRANSFORMER_SERVICE + .call(Type.fromTsDataType(finalDataType)) + .transform(valueColumns[i], positionCount); } tsBlock = new TsBlock(tsBlock.getTimeColumn(), newValueColumns);
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/TypeServices.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/TypeServices.java index 43e7d9e..c81d0d1 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/TypeServices.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/TypeServices.java
@@ -290,6 +290,41 @@ }; }; + // Schema evolution keeps physically compatible columns, widens supported values, and exposes + // incompatible historical values as nulls instead of interpreting their raw storage. + public static final TypeService<AlteredDataTypeColumnTransformer> + ALTERED_DATA_TYPE_COLUMN_TRANSFORMER_SERVICE = + type -> + switch (type.getTypeEnum()) { + case BOOLEAN, + INT32, + INT64, + FLOAT, + DOUBLE, + TEXT, + STRING, + TIMESTAMP, + DATE, + BLOB -> { + final TSDataType targetType = TSDataType.valueOf(type.getTypeEnum().name()); + yield (source, positionCount) -> { + final TSDataType sourceType = source.getDataType(); + // DATE shares INT32's physical column but was historically incompatible with + // a schema change to INT32, while the other shared-column pairs are reusable. + if (sourceType == targetType + || (targetType != TSDataType.INT32 + && SchemaUtils.isUsingSameColumn(sourceType, targetType))) { + return source; + } + return targetType.isCompatible(sourceType) + ? source.convertTo(targetType) + : type.createNullColumn(positionCount); + }; + } + case OBJECT -> (source, positionCount) -> source; + case ROW, UNKNOWN, VECTOR -> (source, positionCount) -> null; + }; + public static final TypeService<RoundTransformer> ROUND_TRANSFORMER_SERVICE = type -> switch (type.getTypeEnum()) { @@ -813,6 +848,11 @@ } @FunctionalInterface + public interface AlteredDataTypeColumnTransformer { + Column transform(Column source, int positionCount); + } + + @FunctionalInterface public interface RoundTransformer { void transform(RoundFunctionTransformer transformer, Column[] columns, ColumnBuilder builder) throws QueryProcessException, IOException; @@ -853,6 +893,7 @@ COLUMN_BUILDER_SERVICE.check(); VALUE_TO_DOUBLE_SERVICE.check(); TRANSFORM_COLUMN_VALUE_WRITER_SERVICE.check(); + ALTERED_DATA_TYPE_COLUMN_TRANSFORMER_SERVICE.check(); ROUND_TRANSFORMER_SERVICE.check(); DIFF_TRANSFORMER_SERVICE.check(); NEGATION_TRANSFORMER_SERVICE.check(); @@ -1634,10 +1675,47 @@ }; }; + public static final TypeService<Function<Object, Pair<Literal, String>>> + PREPARED_PARAMETER_LITERAL_SERVICE = + type -> + switch (type.getTypeEnum()) { + case BOOLEAN -> + value -> { + final String text = Boolean.toString((Boolean) value); + return new Pair<>(new BooleanLiteral(text), text); + }; + case INT32, INT64 -> + value -> { + final String text = String.valueOf(value); + return new Pair<>(new LongLiteral(text), text); + }; + case FLOAT -> + value -> new Pair<>(new DoubleLiteral((Float) value), String.valueOf(value)); + case DOUBLE -> + value -> new Pair<>(new DoubleLiteral((Double) value), String.valueOf(value)); + case TEXT, STRING -> + value -> { + final String text = (String) value; + return new Pair<>( + new StringLiteral(text), "'" + text.replace("'", "''") + "'"); + }; + case BLOB -> + value -> { + final BinaryLiteral literal = new BinaryLiteral((byte[]) value); + return new Pair<>(literal, "X'" + literal.toHexString() + "'"); + }; + case DATE, TIMESTAMP, OBJECT, ROW, UNKNOWN, VECTOR -> + value -> { + throw new IllegalArgumentException( + DataNodeMiscMessages.UNKNOWN_PARAMETER_TYPE + type.getTypeEnum()); + }; + }; + static { VALUE_PARSER_NO_EXCEPTION_SERVICE.check(); VALUE_PARSER_SERVICE.check(); AUTO_CAST_SERVICE.check(); + PREPARED_PARAMETER_LITERAL_SERVICE.check(); } private static NumberFormatException inconsistentValueException(
diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/TypeServicesTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/TypeServicesTest.java new file mode 100644 index 0000000..cd0b8a4 --- /dev/null +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/TypeServicesTest.java
@@ -0,0 +1,147 @@ +/* + * 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.utils; + +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.BinaryLiteral; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Literal; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.StringLiteral; + +import org.apache.tsfile.block.column.Column; +import org.apache.tsfile.enums.TSDataType; +import org.apache.tsfile.read.common.block.column.BinaryColumn; +import org.apache.tsfile.read.common.block.column.BooleanColumn; +import org.apache.tsfile.read.common.block.column.FloatColumn; +import org.apache.tsfile.read.common.block.column.IntColumn; +import org.apache.tsfile.read.common.block.column.LongColumn; +import org.apache.tsfile.read.common.type.Type; +import org.apache.tsfile.utils.Binary; +import org.apache.tsfile.utils.DateUtils; +import org.apache.tsfile.utils.Pair; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.util.Optional; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +public class TypeServicesTest { + + @Test + public void testAlteredDataTypeNumericConversion() { + final Column intSource = + new IntColumn( + 3, + Optional.of(new boolean[] {false, true, false}), + new int[] {1, 0, -2}, + TSDataType.INT32); + final Column longSource = new LongColumn(1, Optional.of(new boolean[] {false}), new long[] {3}); + final Column floatSource = + new FloatColumn(1, Optional.of(new boolean[] {false}), new float[] {4.5F}); + + final Column int64Result = transform(intSource, TSDataType.INT64); + final Column floatResult = transform(intSource, TSDataType.FLOAT); + final Column doubleResult = transform(intSource, TSDataType.DOUBLE); + + assertEquals(1L, int64Result.getLong(0)); + assertTrue(int64Result.isNull(1)); + assertEquals(-2L, int64Result.getLong(2)); + assertEquals(1.0F, floatResult.getFloat(0), 0.0F); + assertTrue(floatResult.isNull(1)); + assertEquals(-2.0F, floatResult.getFloat(2), 0.0F); + assertEquals(1.0, doubleResult.getDouble(0), 0.0); + assertTrue(doubleResult.isNull(1)); + assertEquals(-2.0, doubleResult.getDouble(2), 0.0); + assertEquals(3.0, transform(longSource, TSDataType.DOUBLE).getDouble(0), 0.0); + assertEquals(4.5, transform(floatSource, TSDataType.DOUBLE).getDouble(0), 0.0); + } + + @Test + public void testAlteredDataTypeTextConversion() { + final int date = DateUtils.parseDateExpressionToInt(LocalDate.of(2026, 8, 12)); + final Column dateColumn = + new IntColumn(1, Optional.of(new boolean[] {false}), new int[] {date}, TSDataType.DATE); + final Column booleanColumn = + new BooleanColumn(1, Optional.of(new boolean[] {false}), new boolean[] {true}); + + assertEquals("2026-08-12", transform(dateColumn, TSDataType.TEXT).getBinary(0).toString()); + assertEquals("true", transform(booleanColumn, TSDataType.STRING).getBinary(0).toString()); + } + + @Test + public void testAlteredDataTypeIncompatibleValuesBecomeNull() { + final Column source = + new BinaryColumn( + 2, + Optional.of(new boolean[] {false, false}), + new Binary[] { + new Binary("1", StandardCharsets.UTF_8), new Binary("2", StandardCharsets.UTF_8) + }); + + final Column result = transform(source, TSDataType.INT32); + + assertTrue(result.isNull(0)); + assertTrue(result.isNull(1)); + } + + @Test + public void testAlteredDataTypeCompatibleColumnIsReused() { + final Column intSource = + new IntColumn(1, Optional.of(new boolean[] {false}), new int[] {1}, TSDataType.INT32); + final Column dateSource = + new IntColumn(1, Optional.of(new boolean[] {false}), new int[] {1}, TSDataType.DATE); + + final Column sameTypeResult = transform(intSource, TSDataType.INT32); + final Column intToDateResult = transform(intSource, TSDataType.DATE); + final Column dateToIntResult = transform(dateSource, TSDataType.INT32); + + assertSame(intSource, sameTypeResult); + assertSame(intSource, intToDateResult); + assertFalse(sameTypeResult.isNull(0)); + assertTrue(dateToIntResult.isNull(0)); + } + + @Test + public void testPreparedParameterLiteralConversion() { + final Pair<Literal, String> stringResult = convertPreparedParameter(TSDataType.STRING, "it's"); + final Pair<Literal, String> blobResult = + convertPreparedParameter(TSDataType.BLOB, new byte[] {(byte) 0xAB, 0x01}); + + assertEquals("it's", ((StringLiteral) stringResult.left).getValue()); + assertEquals("'it''s'", stringResult.right); + assertEquals("AB01", ((BinaryLiteral) blobResult.left).toHexString()); + assertEquals("X'AB01'", blobResult.right); + } + + private static Column transform(final Column source, final TSDataType targetType) { + return TypeServices.Transformation.ALTERED_DATA_TYPE_COLUMN_TRANSFORMER_SERVICE + .call(Type.fromTsDataType(targetType)) + .transform(source, source.getPositionCount()); + } + + private static Pair<Literal, String> convertPreparedParameter( + final TSDataType type, final Object value) { + return TypeServices.ValueConversion.PREPARED_PARAMETER_LITERAL_SERVICE + .call(Type.fromTsDataType(type)) + .apply(value); + } +}