This is an automated email from the ASF dual-hosted git repository.
jackietien pushed a commit to branch UserDefinedTime
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/UserDefinedTime by this push:
new 0aa1d9c3677 support that the user could specify the name of time
column(columnCategory is time)(#17048)
0aa1d9c3677 is described below
commit 0aa1d9c3677517db18f726ac446ef58324ec87e3
Author: alpass163 <[email protected]>
AuthorDate: Tue Jan 20 18:09:14 2026 +0800
support that the user could specify the name of time column(columnCategory
is time)(#17048)
---
.../confignode/persistence/schema/ConfigMTree.java | 12 +--
.../TableInsertTabletStatementGenerator.java | 10 +--
.../operator/process/TableIntoOperator.java | 7 +-
.../execution/config/TableConfigTaskVisitor.java | 76 +++++++++--------
.../plan/planner/TableOperatorGenerator.java | 63 +++++++++++++-
.../relational/analyzer/ExpressionAnalyzer.java | 97 ++++++++++++++++++++++
.../relational/analyzer/StatementAnalyzer.java | 24 ++++--
.../fetcher/TableHeaderSchemaValidator.java | 7 +-
.../plan/relational/sql/parser/AstBuilder.java | 80 +++++-------------
.../commons/schema/table/InformationSchema.java | 19 -----
.../schema/table/TsFileTableSchemaUtil.java | 9 +-
.../apache/iotdb/commons/schema/table/TsTable.java | 81 ++++++++++--------
.../schema/table/column/TsTableColumnCategory.java | 4 +
pom.xml | 2 +-
14 files changed, 310 insertions(+), 181 deletions(-)
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTree.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTree.java
index 2725053b843..6b47773813e 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTree.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTree.java
@@ -35,7 +35,6 @@ import
org.apache.iotdb.commons.schema.node.utils.IMNodeIterator;
import org.apache.iotdb.commons.schema.table.TableNodeStatus;
import org.apache.iotdb.commons.schema.table.TreeViewSchema;
import org.apache.iotdb.commons.schema.table.TsTable;
-import org.apache.iotdb.commons.schema.table.column.TimeColumnSchema;
import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory;
import org.apache.iotdb.commons.schema.table.column.TsTableColumnSchema;
import org.apache.iotdb.commons.utils.MetadataUtils;
@@ -98,7 +97,6 @@ import static
org.apache.iotdb.commons.schema.SchemaConstant.INTERNAL_MNODE_TYPE
import static org.apache.iotdb.commons.schema.SchemaConstant.NON_TEMPLATE;
import static org.apache.iotdb.commons.schema.SchemaConstant.ROOT;
import static org.apache.iotdb.commons.schema.SchemaConstant.TABLE_MNODE_TYPE;
-import static org.apache.iotdb.commons.schema.table.TsTable.TIME_COLUMN_NAME;
// Since the ConfigMTree is all stored in memory, thus it is not restricted to
manage MNode through
// MTreeStore.
@@ -803,20 +801,14 @@ public class ConfigMTree {
throws MetadataException {
final TsTable table = getTable(database, tableName);
- final TsTableColumnSchema columnSchema =
- !columnName.equals(TIME_COLUMN_NAME) || Objects.isNull(comment)
- ? table.getColumnSchema(columnName)
- : new TimeColumnSchema(TIME_COLUMN_NAME, TSDataType.TIMESTAMP);
+ final TsTableColumnSchema columnSchema = table.getColumnSchema(columnName);
+
if (Objects.isNull(columnSchema)) {
throw new ColumnNotExistsException(
PathUtils.unQualifyDatabaseName(database.getFullPath()), tableName,
columnName);
}
if (Objects.nonNull(comment)) {
columnSchema.getProps().put(TsTable.COMMENT_KEY, comment);
- if (columnName.equals("time")) {
- // Replace the original time column
- table.addColumnSchema(columnSchema);
- }
} else {
columnSchema.getProps().remove(TsTable.COMMENT_KEY);
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/process/TableInsertTabletStatementGenerator.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/process/TableInsertTabletStatementGenerator.java
index a8987d652fc..aed6cd3a4c7 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/process/TableInsertTabletStatementGenerator.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/process/TableInsertTabletStatementGenerator.java
@@ -35,8 +35,6 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
-import static org.apache.iotdb.commons.schema.table.TsTable.TIME_COLUMN_NAME;
-
public class TableInsertTabletStatementGenerator extends
InsertTabletStatementGenerator {
private static final long INSTANCE_SIZE =
RamUsageEstimator.shallowSizeOfInstance(TableInsertTabletStatementGenerator.class);
@@ -54,13 +52,14 @@ public class TableInsertTabletStatementGenerator extends
InsertTabletStatementGe
List<TSDataType> inputColumnTypes,
List<TsTableColumnCategory> tsTableColumnCategories,
boolean isAligned,
- int rowLimit) {
+ int rowLimit,
+ String timeColumnName) {
super(
targetTable,
measurementToDataTypeMap.keySet().toArray(new String[0]),
measurementToDataTypeMap.values().toArray(new TSDataType[0]),
measurementToInputLocationMap.entrySet().stream()
- .filter(entry ->
!entry.getKey().equalsIgnoreCase(TIME_COLUMN_NAME))
+ .filter(entry -> !entry.getKey().equalsIgnoreCase(timeColumnName))
.map(Map.Entry::getValue)
.toArray(InputLocation[]::new),
inputColumnTypes.stream().map(TypeFactory::getType).toArray(Type[]::new),
@@ -69,8 +68,7 @@ public class TableInsertTabletStatementGenerator extends
InsertTabletStatementGe
this.databaseName = databaseName;
this.writtenCounter = new AtomicLong(0);
this.columnCategories = tsTableColumnCategories.toArray(new
TsTableColumnCategory[0]);
- this.timeColumnIndex =
-
measurementToInputLocationMap.get(TIME_COLUMN_NAME).getValueColumnIndex();
+ this.timeColumnIndex =
measurementToInputLocationMap.get(timeColumnName).getValueColumnIndex();
this.reset();
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/process/TableIntoOperator.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/process/TableIntoOperator.java
index 40b1781b69d..bf6f9c5bc94 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/process/TableIntoOperator.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/process/TableIntoOperator.java
@@ -28,6 +28,7 @@ import
org.apache.iotdb.db.queryengine.execution.MemoryEstimationHelper;
import org.apache.iotdb.db.queryengine.execution.operator.Operator;
import org.apache.iotdb.db.queryengine.execution.operator.OperatorContext;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.parameter.InputLocation;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.ColumnSchema;
import
org.apache.iotdb.db.queryengine.plan.statement.crud.InsertTabletStatement;
import com.google.common.util.concurrent.Futures;
@@ -67,7 +68,8 @@ public class TableIntoOperator extends AbstractIntoOperator {
Map<String, TSDataType> measurementToDataTypeMap,
boolean isAligned,
ExecutorService intoOperationExecutor,
- long statementSizePerLine) {
+ long statementSizePerLine,
+ ColumnSchema timeColumnOfTargetTable) {
super(operatorContext, child, inputColumnTypes, intoOperationExecutor,
statementSizePerLine);
this.maxReturnSize = MAX_RETURN_SIZE;
insertTabletStatementGenerator =
@@ -79,7 +81,8 @@ public class TableIntoOperator extends AbstractIntoOperator {
inputColumnTypes,
inputColumnCategories,
isAligned,
- maxRowNumberInStatement);
+ maxRowNumberInStatement,
+ timeColumnOfTargetTable.getName());
}
@Override
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java
index 008cb7946fc..e65991600b1 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java
@@ -580,8 +580,22 @@ public class TableConfigTaskVisitor extends
AstVisitor<IConfigTask, MPPQueryCont
table.addProp(TsTable.COMMENT_KEY, node.getComment());
}
+ // check if the time column has been specified
+ long timeColumnCount =
+ node.getElements().stream()
+ .filter(
+ columnDefinition ->
+ columnDefinition.getColumnCategory() ==
TsTableColumnCategory.TIME)
+ .count();
+ if (timeColumnCount > 1) {
+ throw new SemanticException("A table cannot have more than one time
column");
+ }
+ if (timeColumnCount == 0) {
+ // append the time column with default name "time" if user do not
specify the time column
+ table.addColumnSchema(new TimeColumnSchema(TIME_COLUMN_NAME,
TSDataType.TIMESTAMP));
+ }
+
// TODO: Place the check at statement analyzer
- boolean hasTimeColumn = false;
final Set<String> sourceNameSet = new HashSet<>();
boolean hasObject = false;
for (final ColumnDefinition columnDefinition : node.getElements()) {
@@ -590,15 +604,18 @@ public class TableConfigTaskVisitor extends
AstVisitor<IConfigTask, MPPQueryCont
final TSDataType dataType = getDataType(columnDefinition.getType());
hasObject |= dataType == TSDataType.OBJECT;
final String comment = columnDefinition.getComment();
- if (checkTimeColumnIdempotent(category, columnName, dataType, comment,
table)
- && !hasTimeColumn) {
- hasTimeColumn = true;
- continue;
- }
+
if (table.getColumnSchema(columnName) != null) {
throw new SemanticException(
String.format("Columns in table shall not share the same name
%s.", columnName));
}
+
+ // allow the user create time column
+ if (category == TsTableColumnCategory.TIME) {
+ validateAndGenerateTimeColumn(columnName, dataType, comment, table);
+ continue;
+ }
+
final TsTableColumnSchema schema =
TableHeaderSchemaValidator.generateColumnSchema(
category,
@@ -627,6 +644,25 @@ public class TableConfigTaskVisitor extends
AstVisitor<IConfigTask, MPPQueryCont
return new Pair<>(database, table);
}
+ private void validateAndGenerateTimeColumn(
+ final String columnName,
+ final TSDataType dataType,
+ final String comment,
+ final TsTable table) {
+
+ if (dataType == TSDataType.TIMESTAMP) {
+ final TsTableColumnSchema timeColumnSchema =
+ new TimeColumnSchema(columnName, TSDataType.TIMESTAMP);
+ if (Objects.nonNull(comment)) {
+ timeColumnSchema.getProps().put(TsTable.COMMENT_KEY, comment);
+ }
+ table.addColumnSchema(timeColumnSchema);
+
+ } else {
+ throw new SemanticException("The time column's type shall be
'timestamp'.");
+ }
+ }
+
@Override
protected IConfigTask visitAlterColumnDataType(
AlterColumnDataType node, MPPQueryContext context) {
@@ -647,34 +683,6 @@ public class TableConfigTaskVisitor extends
AstVisitor<IConfigTask, MPPQueryCont
node.isView());
}
- private boolean checkTimeColumnIdempotent(
- final TsTableColumnCategory category,
- final String columnName,
- final TSDataType dataType,
- final String comment,
- final TsTable table) {
- if (category == TsTableColumnCategory.TIME ||
columnName.equals(TIME_COLUMN_NAME)) {
- if (category == TsTableColumnCategory.TIME
- && columnName.equals(TIME_COLUMN_NAME)
- && dataType == TSDataType.TIMESTAMP) {
- if (Objects.nonNull(comment)) {
- final TsTableColumnSchema columnSchema =
- new TimeColumnSchema(TIME_COLUMN_NAME, TSDataType.TIMESTAMP);
- columnSchema.getProps().put(TsTable.COMMENT_KEY, comment);
- table.addColumnSchema(columnSchema);
- }
- return true;
- } else if (dataType == TSDataType.TIMESTAMP) {
- throw new SemanticException(
- "The time column category shall be bounded with column name
'time'.");
- } else {
- throw new SemanticException("The time column's type shall be
'timestamp'.");
- }
- }
-
- return false;
- }
-
@Override
protected IConfigTask visitRenameTable(final RenameTable node, final
MPPQueryContext context) {
context.setQueryType(QueryType.WRITE);
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/TableOperatorGenerator.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/TableOperatorGenerator.java
index 407b3154b1f..cdc7c8cc658 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/TableOperatorGenerator.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/TableOperatorGenerator.java
@@ -234,6 +234,7 @@ import
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression;
import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FunctionCall;
import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Literal;
import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.LongLiteral;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SymbolReference;
import
org.apache.iotdb.db.queryengine.plan.relational.type.InternalTypeManager;
import org.apache.iotdb.db.queryengine.plan.statement.component.Ordering;
import
org.apache.iotdb.db.queryengine.transformation.dag.column.ColumnTransformer;
@@ -3803,13 +3804,18 @@ public class TableOperatorGenerator extends
PlanVisitor<Operator, LocalExecution
int index = map.get(originInputColumnNames.get(i));
inputColumns.set(index, originColumns.get(i));
}
-
+ ColumnSchema timeColumnOfTargetTable = null;
for (int i = 0; i < inputColumns.size(); i++) {
String columnName = inputColumns.get(i).getName();
inputLocationMap.put(columnName, new InputLocation(0, i));
TsTableColumnCategory columnCategory =
inputColumns.get(i).getColumnCategory();
if (columnCategory == TIME) {
+ if (timeColumnOfTargetTable == null) {
+ timeColumnOfTargetTable = inputColumns.get(i);
+ } else {
+ throw new SemanticException("Multiple columns with TIME category
found");
+ }
continue;
}
@@ -3818,6 +3824,9 @@ public class TableOperatorGenerator extends
PlanVisitor<Operator, LocalExecution
inputColumnTypes.add(columnType);
inputColumnCategories.add(columnCategory);
}
+ if (timeColumnOfTargetTable == null) {
+ throw new SemanticException("Missing TIME category column");
+ }
long statementSizePerLine =
OperatorGeneratorUtil.calculateStatementSizePerLine(inputColumnTypes);
@@ -3833,7 +3842,8 @@ public class TableOperatorGenerator extends
PlanVisitor<Operator, LocalExecution
tsDataTypeMap,
true,
FragmentInstanceManager.getInstance().getIntoOperationExecutor(),
- statementSizePerLine);
+ statementSizePerLine,
+ timeColumnOfTargetTable);
}
private boolean[] checkStatisticAndScanOrder(
@@ -3878,6 +3888,22 @@ public class TableOperatorGenerator extends
PlanVisitor<Operator, LocalExecution
break;
}
+ // first and last, the second argument has to be the time column
+ if (FIRST_AGGREGATION.equals(funcName) ||
LAST_AGGREGATION.equals(funcName)) {
+ if (!isTimeColumn(aggregation.getArguments().get(1),
timeColumnName)) {
+ canUseStatistic = false;
+ break;
+ }
+ }
+
+ // first_by and last_by, the second argument has to be the time
column
+ if (FIRST_BY_AGGREGATION.equals(funcName) ||
LAST_BY_AGGREGATION.equals(funcName)) {
+ if (!isTimeColumn(aggregation.getArguments().get(2),
timeColumnName)) {
+ canUseStatistic = false;
+ break;
+ }
+ }
+
// only last_by(time, x) or last_by(x,time) can use statistic
if ((LAST_BY_AGGREGATION.equals(funcName) ||
FIRST_BY_AGGREGATION.equals(funcName))
&& !isTimeColumn(aggregation.getArguments().get(0),
timeColumnName)
@@ -3923,6 +3949,12 @@ public class TableOperatorGenerator extends
PlanVisitor<Operator, LocalExecution
return OptimizeType.NOOP;
}
+ // if the timeColumnName is null, the param of function is just a
timestamp column other than
+ // the time column
+ if (timeColumnName == null ||
!checkOrderColumnIsTime(node.getAggregations(), timeColumnName)) {
+ return OptimizeType.NOOP;
+ }
+
if (canUseLastRowOptimize(aggregators)) {
return OptimizeType.LAST_ROW;
}
@@ -3934,6 +3966,33 @@ public class TableOperatorGenerator extends
PlanVisitor<Operator, LocalExecution
return OptimizeType.NOOP;
}
+ /**
+ * Checks if the ordering column in aggregations matches the time column.
only check for
+ * FIRST/LAST/FIRST_BY/LAST_BY
+ */
+ private boolean checkOrderColumnIsTime(
+ Map<Symbol, AggregationNode.Aggregation> aggregations, String
timeColumnName) {
+
+ for (Map.Entry<Symbol, AggregationNode.Aggregation> entry :
aggregations.entrySet()) {
+ String functionName =
+
entry.getValue().getResolvedFunction().getSignature().getName().toLowerCase();
+ List<Expression> arguments = entry.getValue().getArguments();
+ Expression lastParam =
entry.getValue().getArguments().get(arguments.size() - 1);
+
+ switch (functionName) {
+ case FIRST_AGGREGATION:
+ case LAST_AGGREGATION:
+ case FIRST_BY_AGGREGATION:
+ case LAST_BY_AGGREGATION:
+ if (!((SymbolReference)
lastParam).getName().equalsIgnoreCase(timeColumnName)) {
+ return false;
+ }
+ break;
+ }
+ }
+ return true;
+ }
+
private boolean canUseLastRowOptimize(List<TableAggregator> aggregators) {
for (TableAggregator aggregator : aggregators) {
if (aggregator.getAccumulator() instanceof LastDescAccumulator) {
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
index 663c92ad83e..32d6d944609 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
@@ -19,6 +19,7 @@
package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
+import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory;
import org.apache.iotdb.db.exception.sql.SemanticException;
import org.apache.iotdb.db.queryengine.common.MPPQueryContext;
import org.apache.iotdb.db.queryengine.common.SessionInfo;
@@ -154,12 +155,17 @@ import static
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.WindowFram
import static
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.WindowFrame.Type.ROWS;
import static
org.apache.iotdb.db.queryengine.plan.relational.type.TypeSignatureTranslator.toTypeSignature;
import static
org.apache.iotdb.db.queryengine.plan.relational.utils.NodeUtils.getSortItemsFromOrderBy;
+import static org.apache.iotdb.db.utils.constant.SqlConstant.FIRST_AGGREGATION;
+import static
org.apache.iotdb.db.utils.constant.SqlConstant.FIRST_BY_AGGREGATION;
+import static org.apache.iotdb.db.utils.constant.SqlConstant.LAST_AGGREGATION;
+import static
org.apache.iotdb.db.utils.constant.SqlConstant.LAST_BY_AGGREGATION;
import static org.apache.tsfile.read.common.type.BlobType.BLOB;
import static org.apache.tsfile.read.common.type.BooleanType.BOOLEAN;
import static org.apache.tsfile.read.common.type.DoubleType.DOUBLE;
import static org.apache.tsfile.read.common.type.IntType.INT32;
import static org.apache.tsfile.read.common.type.LongType.INT64;
import static org.apache.tsfile.read.common.type.StringType.STRING;
+import static org.apache.tsfile.read.common.type.TimestampType.TIMESTAMP;
import static org.apache.tsfile.read.common.type.UnknownType.UNKNOWN;
public class ExpressionAnalyzer {
@@ -1051,6 +1057,38 @@ public class ExpressionAnalyzer {
throw new SemanticException("DISTINCT is not supported for
non-aggregation functions");
}
+ int argumentsNum = node.getArguments().size();
+ RelationType relationType =
context.getContext().getScope().getRelationType();
+ // Syntactic sugar: first(s1) => first(s1,time), first_by(s1,s2) =>
first_by(s1,s2,time)
+ // So do last and last_by.
+ switch (functionName.toLowerCase()) {
+ case FIRST_AGGREGATION:
+ case LAST_AGGREGATION:
+ if (argumentsNum == 1) {
+ addTimeArgument(node.getArguments(),
getActualTimeFieldName(relationType));
+ } else if (argumentsNum == 2) {
+ if (!checkArgumentIsTimestamp(
+ node.getArguments().get(1), (List<Field>)
relationType.getVisibleFields())) {
+ throw new SemanticException(
+ String.format(
+ "The second argument of %s function must be actual time
name", functionName));
+ }
+ }
+ break;
+ case FIRST_BY_AGGREGATION:
+ case LAST_BY_AGGREGATION:
+ if (argumentsNum == 2) {
+ addTimeArgument(node.getArguments(),
getActualTimeFieldName(relationType));
+ } else if (argumentsNum == 3) {
+ if (!checkArgumentIsTimestamp(
+ node.getArguments().get(2), (List<Field>)
relationType.getVisibleFields())) {
+ throw new SemanticException(
+ String.format(
+ "The third argument of %s function must be actual time
name", functionName));
+ }
+ }
+ }
+
List<Type> argumentTypes = getCallArgumentTypes(node.getArguments(),
context);
if (node.getArguments().size() > 127) {
@@ -1132,6 +1170,65 @@ public class ExpressionAnalyzer {
return argumentTypesBuilder.build();
}
+ private void addTimeArgument(List<Expression> arguments, String
actualTimeField) {
+
+ if (arguments.get(0) instanceof DereferenceExpression) {
+ arguments.add(
+ new DereferenceExpression(
+ ((DereferenceExpression) arguments.get(0)).getBase(),
+ new Identifier(actualTimeField.toLowerCase(Locale.ENGLISH))));
+ } else {
+ arguments.add(new
Identifier(actualTimeField.toLowerCase(Locale.ENGLISH)));
+ }
+ }
+
+ private boolean checkArgumentIsTimestamp(Expression argument, List<Field>
visibleFields) {
+
+ String argumentName =
+ (argument instanceof DereferenceExpression)
+ ? ((DereferenceExpression) argument)
+ .getField()
+ .orElseThrow(() -> new SemanticException("the input field do
not exists"))
+ .toString()
+ : argument.toString();
+
+ for (Field field : visibleFields) {
+ if (field
+ .getName()
+ .orElseThrow(() -> new SemanticException("the field in table do
not hava the name"))
+ .equalsIgnoreCase(argumentName)) {
+ return field.getType() == TIMESTAMP;
+ }
+ }
+ // should never reach here
+ throw new SemanticException("the input argument do not exists");
+ }
+
+ /** Retrieves the effective time column name from the relation's visible
fields. */
+ private String getActualTimeFieldName(RelationType relation) {
+
+ // Priority 1: Try to find a column explicitly marked as TIME category
+ Optional<String> timeColumn =
+ relation.getVisibleFields().stream()
+ .filter(field -> field.getColumnCategory() ==
TsTableColumnCategory.TIME)
+ .findFirst()
+ .flatMap(Field::getName);
+
+ if (timeColumn.isPresent()) {
+ return timeColumn.get();
+ }
+
+ // Priority 2: Fallback to the first TIMESTAMP column (e.g., for system
schema compatibility)
+ return relation.getVisibleFields().stream()
+ .filter(field -> field.getType() == TIMESTAMP)
+ .findFirst()
+ .flatMap(Field::getName)
+ .orElseThrow(
+ () ->
+ new SemanticException(
+ "Missing valid time column. The table must contain
either a column with the TIME category or at least one TIMESTAMP column."));
+ }
+
private Type analyzeMatchNumber(
FunctionCall node, StackableAstVisitorContext<Context> context) {
if (!node.getArguments().isEmpty()) {
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
index e9df62193c2..ae15c70100b 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
@@ -269,7 +269,6 @@ import static java.util.Collections.emptyList;
import static java.util.Locale.ENGLISH;
import static java.util.Objects.requireNonNull;
import static
org.apache.iotdb.commons.schema.table.TsTable.TABLE_ALLOWED_PROPERTIES;
-import static org.apache.iotdb.commons.schema.table.TsTable.TIME_COLUMN_NAME;
import static
org.apache.iotdb.commons.udf.builtin.relational.TableBuiltinScalarFunction.DATE_BIN;
import static
org.apache.iotdb.db.queryengine.execution.warnings.StandardWarningCode.REDUNDANT_ORDER_BY;
import static
org.apache.iotdb.db.queryengine.plan.relational.analyzer.AggregationAnalyzer.verifyOrderByAggregations;
@@ -684,10 +683,23 @@ public class StatementAnalyzer {
.collect(toImmutableList());
analysis.registerTable(insert.getTable(), tableSchema, targetTable);
- LinkedHashSet<String> tableColumns =
- columns.stream()
- .map(ColumnSchema::getName)
- .collect(Collectors.toCollection(LinkedHashSet::new));
+ LinkedHashSet<String> tableColumns = new LinkedHashSet<>();
+ String actualTimeColumnName = null;
+ for (ColumnSchema column : columns) {
+ tableColumns.add(column.getName().toLowerCase(ENGLISH));
+
+ if (column.getColumnCategory() == TsTableColumnCategory.TIME) {
+ if (actualTimeColumnName != null) {
+ throw new SemanticException(
+ "Multiple columns found with TIME category in table schema");
+ }
+ actualTimeColumnName = column.getName();
+ }
+ }
+ if (actualTimeColumnName == null) {
+ throw new SemanticException("Target table schema misses a TIME
category column");
+ }
+
LinkedHashSet<String> insertColumns;
if (insert.getColumns().isPresent()) {
insertColumns =
@@ -712,7 +724,7 @@ public class StatementAnalyzer {
}
// insert columns should contain time
- if (!insertColumns.contains(TIME_COLUMN_NAME)) {
+ if (!insertColumns.contains(actualTimeColumnName)) {
throw new SemanticException("time column can not be null");
}
// insert columns should contain at least one field column
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/TableHeaderSchemaValidator.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/TableHeaderSchemaValidator.java
index 99da3d005f7..2d629cb26ee 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/TableHeaderSchemaValidator.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/TableHeaderSchemaValidator.java
@@ -28,6 +28,7 @@ import org.apache.iotdb.commons.schema.table.TsTable;
import org.apache.iotdb.commons.schema.table.column.AttributeColumnSchema;
import org.apache.iotdb.commons.schema.table.column.FieldColumnSchema;
import org.apache.iotdb.commons.schema.table.column.TagColumnSchema;
+import org.apache.iotdb.commons.schema.table.column.TimeColumnSchema;
import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory;
import org.apache.iotdb.commons.schema.table.column.TsTableColumnSchema;
import org.apache.iotdb.db.auth.AuthorityChecker;
@@ -718,8 +719,10 @@ public class TableHeaderSchemaValidator {
schema = new AttributeColumnSchema(columnName, dataType);
break;
case TIME:
- throw new SemanticException(
- "Create table or add column statement shall not specify column
category TIME");
+ // throw new SemanticException("Add column statement shall not specify
column category
+ // TIME");
+ schema = new TimeColumnSchema(columnName, dataType);
+ break;
case FIELD:
schema =
dataType != TSDataType.UNKNOWN
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
index 5d529818a75..1ad212cf560 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
@@ -34,7 +34,6 @@ import org.apache.iotdb.commons.utils.CommonDateTimeUtils;
import org.apache.iotdb.db.exception.query.QueryProcessException;
import org.apache.iotdb.db.exception.sql.SemanticException;
import org.apache.iotdb.db.protocol.session.IClientSession;
-import org.apache.iotdb.db.queryengine.plan.expression.leaf.TimestampOperand;
import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AddColumn;
import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AliasedRelation;
import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AllColumns;
@@ -291,7 +290,6 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -305,7 +303,6 @@ import static java.util.Locale.ENGLISH;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
import static
org.apache.iotdb.commons.schema.column.ColumnHeaderConstant.DATA_NODE_ID_TABLE_MODEL;
-import static org.apache.iotdb.commons.schema.table.TsTable.TIME_COLUMN_NAME;
import static
org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory.ATTRIBUTE;
import static
org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory.FIELD;
import static
org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory.TAG;
@@ -338,10 +335,6 @@ import static
org.apache.iotdb.db.utils.TimestampPrecisionUtils.currPrecision;
import static
org.apache.iotdb.db.utils.constant.SqlConstant.APPROX_COUNT_DISTINCT;
import static
org.apache.iotdb.db.utils.constant.SqlConstant.APPROX_MOST_FREQUENT;
import static org.apache.iotdb.db.utils.constant.SqlConstant.APPROX_PERCENTILE;
-import static org.apache.iotdb.db.utils.constant.SqlConstant.FIRST_AGGREGATION;
-import static
org.apache.iotdb.db.utils.constant.SqlConstant.FIRST_BY_AGGREGATION;
-import static org.apache.iotdb.db.utils.constant.SqlConstant.LAST_AGGREGATION;
-import static
org.apache.iotdb.db.utils.constant.SqlConstant.LAST_BY_AGGREGATION;
public class AstBuilder extends RelationalSqlBaseVisitor<Node> {
@@ -855,15 +848,28 @@ public class AstBuilder extends
RelationalSqlBaseVisitor<Node> {
final List<String> columnNames =
identifiers.stream().map(Identifier::getValue).collect(toList());
int timeColumnIndex = -1;
- for (int i = 0; i < columnNames.size(); i++) {
- if (TIME_COLUMN_NAME.equalsIgnoreCase(columnNames.get(i))) {
- if (timeColumnIndex == -1) {
- timeColumnIndex = i;
- } else {
- throw new SemanticException("One row should only have one time
value");
+
+ // retrieve the table schema to identify the actual time column
+ TsTable table = DataNodeTableCache.getInstance().getTable(databaseName,
tableName);
+ List<TsTableColumnSchema> timeColumnCandidates =
+ table.getColumnList().stream()
+ .filter(col -> col.getColumnCategory() == TIME)
+ .collect(toList());
+ if (timeColumnCandidates.size() != 1) {
+ throw new SemanticException("the table should only have one column found
with TIME category");
+ } else {
+ // locate the time column index in the input identifiers if time column
exists in the schema
+ for (int i = 0; i < columnNames.size(); i++) {
+ if
(timeColumnCandidates.get(0).getColumnName().equalsIgnoreCase(columnNames.get(i)))
{
+ if (timeColumnIndex == -1) {
+ timeColumnIndex = i;
+ } else {
+ throw new SemanticException("One row should only have one time
value");
+ }
}
}
}
+
if (timeColumnIndex != -1) {
columnNames.remove(timeColumnIndex);
}
@@ -3417,29 +3423,7 @@ public class AstBuilder extends
RelationalSqlBaseVisitor<Node> {
new DereferenceExpression(getLocation(ctx.label), (Identifier)
visit(ctx.label)));
}
- // Syntactic sugar: first(s1) => first(s1,time), first_by(s1,s2) =>
first_by(s1,s2,time)
- // So do last and last_by.
- if (name.toString().equalsIgnoreCase(FIRST_AGGREGATION)
- || name.toString().equalsIgnoreCase(LAST_AGGREGATION)) {
- if (arguments.size() == 1) {
- appendTimeArgument(arguments);
- } else if (arguments.size() == 2) {
- check(
- checkArgumentIsTime(arguments.get(1)),
- "The second argument of 'first' or 'last' function must be 'time'",
- ctx);
- }
- } else if (name.toString().equalsIgnoreCase(FIRST_BY_AGGREGATION)
- || name.toString().equalsIgnoreCase(LAST_BY_AGGREGATION)) {
- if (arguments.size() == 2) {
- appendTimeArgument(arguments);
- } else if (arguments.size() == 3) {
- check(
- checkArgumentIsTime(arguments.get(2)),
- "The third argument of 'first_by' or 'last_by' function must be
'time'",
- ctx);
- }
- } else if (name.toString().equalsIgnoreCase(APPROX_COUNT_DISTINCT)) {
+ if (name.toString().equalsIgnoreCase(APPROX_COUNT_DISTINCT)) {
if (arguments.size() == 2
&& !(arguments.get(1) instanceof DoubleLiteral
|| arguments.get(1) instanceof LongLiteral
@@ -3467,30 +3451,6 @@ public class AstBuilder extends
RelationalSqlBaseVisitor<Node> {
return new FunctionCall(getLocation(ctx), name, window, nulls, distinct,
mode, arguments);
}
- private void appendTimeArgument(List<Expression> arguments) {
- if (arguments.get(0) instanceof DereferenceExpression) {
- arguments.add(
- new DereferenceExpression(
- ((DereferenceExpression) arguments.get(0)).getBase(),
- new Identifier(
-
TimestampOperand.TIMESTAMP_EXPRESSION_STRING.toLowerCase(Locale.ENGLISH))));
- } else {
- arguments.add(
- new
Identifier(TimestampOperand.TIMESTAMP_EXPRESSION_STRING.toLowerCase(Locale.ENGLISH)));
- }
- }
-
- private boolean checkArgumentIsTime(Expression argument) {
- if (argument instanceof DereferenceExpression) {
- return ((DereferenceExpression) argument)
- .getField()
- .get()
- .toString()
- .equalsIgnoreCase(TimestampOperand.TIMESTAMP_EXPRESSION_STRING);
- }
- return
argument.toString().equalsIgnoreCase(TimestampOperand.TIMESTAMP_EXPRESSION_STRING);
- }
-
@Override
public Node visitDateBinGapFill(RelationalSqlParser.DateBinGapFillContext
ctx) {
TimeDuration timeDuration =
DateTimeUtils.constructTimeDuration(ctx.timeDuration().getText());
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/InformationSchema.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/InformationSchema.java
index 3c15b2eaa28..ac6c8ebab24 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/InformationSchema.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/InformationSchema.java
@@ -69,7 +69,6 @@ public class InformationSchema {
new AttributeColumnSchema(ColumnHeaderConstant.STATEMENT_TABLE_MODEL,
TSDataType.STRING));
queriesTable.addColumnSchema(
new AttributeColumnSchema(ColumnHeaderConstant.USER_TABLE_MODEL,
TSDataType.STRING));
- queriesTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
schemaTables.put(QUERIES, queriesTable);
final TsTable databaseTable = new TsTable(DATABASES);
@@ -94,7 +93,6 @@ public class InformationSchema {
databaseTable.addColumnSchema(
new AttributeColumnSchema(
ColumnHeaderConstant.DATA_REGION_GROUP_NUM_TABLE_MODEL,
TSDataType.INT32));
- databaseTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
schemaTables.put(DATABASES, databaseTable);
final TsTable tableTable = new TsTable(TABLES);
@@ -114,7 +112,6 @@ public class InformationSchema {
ColumnHeaderConstant.COMMENT.toLowerCase(Locale.ENGLISH),
TSDataType.STRING));
tableTable.addColumnSchema(
new AttributeColumnSchema(ColumnHeaderConstant.TABLE_TYPE_TABLE_MODEL,
TSDataType.STRING));
- tableTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
schemaTables.put(TABLES, tableTable);
final TsTable columnTable = new TsTable(COLUMNS);
@@ -137,7 +134,6 @@ public class InformationSchema {
columnTable.addColumnSchema(
new AttributeColumnSchema(
ColumnHeaderConstant.COMMENT.toLowerCase(Locale.ENGLISH),
TSDataType.STRING));
- columnTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
schemaTables.put(COLUMNS, columnTable);
final TsTable regionTable = new TsTable(REGIONS);
@@ -179,7 +175,6 @@ public class InformationSchema {
regionTable.addColumnSchema(
new AttributeColumnSchema(
ColumnHeaderConstant.COMPRESSION_RATIO_TABLE_MODEL,
TSDataType.DOUBLE));
- regionTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
schemaTables.put(REGIONS, regionTable);
final TsTable pipeTable = new TsTable(PIPES);
@@ -208,7 +203,6 @@ public class InformationSchema {
pipeTable.addColumnSchema(
new AttributeColumnSchema(
ColumnHeaderConstant.ESTIMATED_REMAINING_SECONDS_TABLE_MODEL,
TSDataType.DOUBLE));
- pipeTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
schemaTables.put(PIPES, pipeTable);
final TsTable pipePluginTable = new TsTable(PIPE_PLUGINS);
@@ -220,7 +214,6 @@ public class InformationSchema {
new AttributeColumnSchema(ColumnHeaderConstant.CLASS_NAME_TABLE_MODEL,
TSDataType.STRING));
pipePluginTable.addColumnSchema(
new AttributeColumnSchema(ColumnHeaderConstant.PLUGIN_JAR_TABLE_MODEL,
TSDataType.STRING));
- pipePluginTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
schemaTables.put(PIPE_PLUGINS, pipePluginTable);
final TsTable topicTable = new TsTable(TOPICS);
@@ -229,7 +222,6 @@ public class InformationSchema {
topicTable.addColumnSchema(
new AttributeColumnSchema(
ColumnHeaderConstant.TOPIC_CONFIGS_TABLE_MODEL,
TSDataType.STRING));
- topicTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
schemaTables.put(TOPICS, topicTable);
final TsTable subscriptionTable = new TsTable(SUBSCRIPTIONS);
@@ -241,7 +233,6 @@ public class InformationSchema {
subscriptionTable.addColumnSchema(
new AttributeColumnSchema(
ColumnHeaderConstant.SUBSCRIBED_CONSUMERS_TABLE_MODEL,
TSDataType.STRING));
- subscriptionTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
schemaTables.put(SUBSCRIPTIONS, subscriptionTable);
final TsTable viewTable = new TsTable(VIEWS);
@@ -253,7 +244,6 @@ public class InformationSchema {
viewTable.addColumnSchema(
new AttributeColumnSchema(
ColumnHeaderConstant.VIEW_DEFINITION_TABLE_MODEL,
TSDataType.STRING));
- viewTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
schemaTables.put(VIEWS, viewTable);
final TsTable functionTable = new TsTable(FUNCTIONS);
@@ -268,7 +258,6 @@ public class InformationSchema {
functionTable.addColumnSchema(
new AttributeColumnSchema(
ColumnHeaderConstant.STATE.toLowerCase(Locale.ENGLISH),
TSDataType.STRING));
- functionTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
schemaTables.put(FUNCTIONS, functionTable);
final TsTable configurationsTable = new TsTable(CONFIGURATIONS);
@@ -278,7 +267,6 @@ public class InformationSchema {
configurationsTable.addColumnSchema(
new AttributeColumnSchema(
ColumnHeaderConstant.VALUE.toLowerCase(Locale.ENGLISH),
TSDataType.STRING));
- configurationsTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
schemaTables.put(CONFIGURATIONS, configurationsTable);
final TsTable keywordsTable = new TsTable(KEYWORDS);
@@ -286,7 +274,6 @@ public class InformationSchema {
new TagColumnSchema(ColumnHeaderConstant.WORD, TSDataType.STRING));
keywordsTable.addColumnSchema(
new AttributeColumnSchema(ColumnHeaderConstant.RESERVED,
TSDataType.INT32));
- keywordsTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
schemaTables.put(KEYWORDS, keywordsTable);
final TsTable nodesTable = new TsTable(NODES);
@@ -308,7 +295,6 @@ public class InformationSchema {
ColumnHeaderConstant.VERSION.toLowerCase(Locale.ENGLISH),
TSDataType.STRING));
nodesTable.addColumnSchema(
new AttributeColumnSchema(ColumnHeaderConstant.BUILD_INFO_TABLE_MODEL,
TSDataType.STRING));
- nodesTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
schemaTables.put(NODES, nodesTable);
final TsTable configNodesTable = new TsTable(CONFIG_NODES);
@@ -320,7 +306,6 @@ public class InformationSchema {
configNodesTable.addColumnSchema(
new AttributeColumnSchema(
ColumnHeaderConstant.ROLE.toLowerCase(Locale.ENGLISH),
TSDataType.STRING));
- configNodesTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
schemaTables.put(CONFIG_NODES, configNodesTable);
final TsTable dataNodesTable = new TsTable(DATA_NODES);
@@ -344,7 +329,6 @@ public class InformationSchema {
dataNodesTable.addColumnSchema(
new AttributeColumnSchema(
ColumnHeaderConstant.SCHEMA_CONSENSUS_PORT_TABLE_MODEL,
TSDataType.INT32));
- dataNodesTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
schemaTables.put(DATA_NODES, dataNodesTable);
final TsTable connectionsTable = new TsTable(CONNECTIONS);
@@ -360,7 +344,6 @@ public class InformationSchema {
new AttributeColumnSchema(ColumnHeaderConstant.LAST_ACTIVE_TIME,
TSDataType.TIMESTAMP));
connectionsTable.addColumnSchema(
new AttributeColumnSchema(ColumnHeaderConstant.CLIENT_IP,
TSDataType.STRING));
- connectionsTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
schemaTables.put(CONNECTIONS, connectionsTable);
final TsTable currentQueriesTable = new TsTable(CURRENT_QUERIES);
@@ -383,7 +366,6 @@ public class InformationSchema {
new AttributeColumnSchema(ColumnHeaderConstant.USER_TABLE_MODEL,
TSDataType.STRING));
currentQueriesTable.addColumnSchema(
new AttributeColumnSchema(ColumnHeaderConstant.CLIENT_IP,
TSDataType.STRING));
- currentQueriesTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
schemaTables.put(CURRENT_QUERIES, currentQueriesTable);
final TsTable queriesCostsHistogramTable = new
TsTable(QUERIES_COSTS_HISTOGRAM);
@@ -393,7 +375,6 @@ public class InformationSchema {
new AttributeColumnSchema(ColumnHeaderConstant.NUMS,
TSDataType.INT32));
queriesCostsHistogramTable.addColumnSchema(
new AttributeColumnSchema(ColumnHeaderConstant.DATANODE_ID,
TSDataType.INT32));
- queriesCostsHistogramTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
schemaTables.put(QUERIES_COSTS_HISTOGRAM, queriesCostsHistogramTable);
final TsTable servicesTable = new TsTable(SERVICES);
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsFileTableSchemaUtil.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsFileTableSchemaUtil.java
index 9a5abffbbcc..7a74f4be651 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsFileTableSchemaUtil.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsFileTableSchemaUtil.java
@@ -43,9 +43,8 @@ public class TsFileTableSchemaUtil {
/** Column category filter for efficient parsing */
public enum ColumnCategoryFilter {
- /** Include TAG and FIELD only (exclude TIME and ATTRIBUTE) - for TsFile
writing */
- NO_ATTRIBUTE(
- cat -> cat != TsTableColumnCategory.TIME && cat !=
TsTableColumnCategory.ATTRIBUTE);
+ /** Include TAG, time, and FIELD only (exclude ATTRIBUTE) - for TsFile
writing */
+ NO_ATTRIBUTE(cat -> cat != TsTableColumnCategory.ATTRIBUTE);
private final java.util.function.Predicate<TsTableColumnCategory>
predicate;
@@ -160,8 +159,8 @@ public class TsFileTableSchemaUtil {
for (final TsTableColumnSchema columnSchema : tsTableColumnSchemas) {
final TsTableColumnCategory category = columnSchema.getColumnCategory();
- // Skip TIME and ATTRIBUTE columns (only include TAG and FIELD)
- if (category == TsTableColumnCategory.TIME || category ==
TsTableColumnCategory.ATTRIBUTE) {
+ // Skip ATTRIBUTE columns (only include TIME, TAG and FIELD)
+ if (category == TsTableColumnCategory.ATTRIBUTE) {
continue;
}
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java
index 1b5a9d4ac34..61b00d060bd 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java
@@ -32,7 +32,6 @@ import
org.apache.iotdb.commons.schema.table.column.TsTableColumnSchemaUtil;
import org.apache.iotdb.commons.utils.CommonDateTimeUtils;
import com.google.common.collect.ImmutableList;
-import org.apache.tsfile.enums.TSDataType;
import org.apache.tsfile.utils.Pair;
import org.apache.tsfile.utils.ReadWriteIOUtils;
@@ -64,16 +63,13 @@ public class TsTable {
public static final String TIME_COLUMN_NAME = "time";
public static final String COMMENT_KEY = "__comment";
- private static final TimeColumnSchema TIME_COLUMN_SCHEMA =
- new TimeColumnSchema(TIME_COLUMN_NAME, TSDataType.TIMESTAMP);
-
public static final String TTL_PROPERTY = "ttl";
public static final Set<String> TABLE_ALLOWED_PROPERTIES =
Collections.singleton(TTL_PROPERTY);
private static final String OBJECT_STRING_ERROR =
"When there are object fields, the %s %s shall not be '.', '..' or
contain './', '.\\'.";
protected String tableName;
- private final Map<String, TsTableColumnSchema> columnSchemaMap = new
LinkedHashMap<>();
+ private Map<String, TsTableColumnSchema> columnSchemaMap = new
LinkedHashMap<>();
private final Map<String, Integer> tagColumnIndexMap = new HashMap<>();
private final Map<String, Integer> idColumnIndexMap = new HashMap<>();
@@ -101,7 +97,6 @@ public class TsTable {
public TsTable(final String tableName) {
this.tableName = tableName;
- columnSchemaMap.put(TIME_COLUMN_NAME, TIME_COLUMN_SCHEMA);
}
// This interface is used by InformationSchema table, so time column is not
necessary
@@ -217,42 +212,60 @@ public class TsTable {
});
}
+ /**
+ * Renames a column in the table schema while strictly preserving its
original ordinal position.
+ */
public void renameColumnSchema(final String oldName, final String newName) {
executeWrite(
() -> {
- // Ensures idempotency
- if (columnSchemaMap.containsKey(oldName)) {
- final TsTableColumnSchema schema = columnSchemaMap.remove(oldName);
- final Map<String, String> oldProps = schema.getProps();
- oldProps.computeIfAbsent(TreeViewSchema.ORIGINAL_NAME, k ->
schema.getColumnName());
- switch (schema.getColumnCategory()) {
- case TAG:
- columnSchemaMap.put(
- newName, new TagColumnSchema(newName,
schema.getDataType(), oldProps));
- break;
- case FIELD:
- columnSchemaMap.put(
- newName,
- new FieldColumnSchema(
- newName,
- schema.getDataType(),
- ((FieldColumnSchema) schema).getEncoding(),
- ((FieldColumnSchema) schema).getCompressor(),
- oldProps));
- break;
- case ATTRIBUTE:
- columnSchemaMap.put(
- newName, new AttributeColumnSchema(newName,
schema.getDataType(), oldProps));
- break;
- case TIME:
- default:
- // Do nothing
- columnSchemaMap.put(oldName, schema);
+ if (!columnSchemaMap.containsKey(oldName)) {
+ return;
+ }
+
+ // Capture the current strict order of current columns
+ List<Map.Entry<String, TsTableColumnSchema>> snapshotOfColumns =
+ new ArrayList<>(columnSchemaMap.entrySet());
+ columnSchemaMap.clear();
+
+ // Re-insert all entries in their original sequence, substituting
the renamed column at
+ // its exact original index
+ for (Map.Entry<String, TsTableColumnSchema> entry :
snapshotOfColumns) {
+ String currentKey = entry.getKey();
+ TsTableColumnSchema currentColumnSchema = entry.getValue();
+
+ if (currentKey.equals(oldName)) {
+ TsTableColumnSchema newSchema =
createRenamedSchema(currentColumnSchema, newName);
+ columnSchemaMap.put(newName, newSchema);
+ } else {
+ columnSchemaMap.put(currentKey, currentColumnSchema);
}
}
});
}
+ private TsTableColumnSchema createRenamedSchema(TsTableColumnSchema
oldSchema, String newName) {
+ Map<String, String> oldProps = oldSchema.getProps();
+ oldProps.computeIfAbsent(TreeViewSchema.ORIGINAL_NAME, k ->
oldSchema.getColumnName());
+
+ switch (oldSchema.getColumnCategory()) {
+ case TAG:
+ return new TagColumnSchema(newName, oldSchema.getDataType(), oldProps);
+ case FIELD:
+ return new FieldColumnSchema(
+ newName,
+ oldSchema.getDataType(),
+ ((FieldColumnSchema) oldSchema).getEncoding(),
+ ((FieldColumnSchema) oldSchema).getCompressor(),
+ oldProps);
+ case ATTRIBUTE:
+ return new AttributeColumnSchema(newName, oldSchema.getDataType(),
oldProps);
+ case TIME:
+ return new TimeColumnSchema(newName, oldSchema.getDataType(),
oldProps);
+ default:
+ return oldSchema;
+ }
+ }
+
public void removeColumnSchema(final String columnName) {
executeWrite(
() -> {
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/column/TsTableColumnCategory.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/column/TsTableColumnCategory.java
index ef620f3673a..4b171a89c5c 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/column/TsTableColumnCategory.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/column/TsTableColumnCategory.java
@@ -82,6 +82,8 @@ public enum TsTableColumnCategory {
return ColumnCategory.ATTRIBUTE;
case FIELD:
return ColumnCategory.FIELD;
+ case TIME:
+ return ColumnCategory.TIME;
default:
throw new IllegalArgumentException("Unsupported column type in TsFile:
" + this);
}
@@ -95,6 +97,8 @@ public enum TsTableColumnCategory {
return TAG;
case ATTRIBUTE:
return ATTRIBUTE;
+ case TIME:
+ return TIME;
default:
throw new IllegalArgumentException("Unknown column type: " +
columnType);
}
diff --git a/pom.xml b/pom.xml
index 6696f783d80..395b100d4e4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -173,7 +173,7 @@
<thrift.version>0.14.1</thrift.version>
<xz.version>1.9</xz.version>
<zstd-jni.version>1.5.6-3</zstd-jni.version>
- <tsfile.version>2.2.1-260115-SNAPSHOT</tsfile.version>
+ <tsfile.version>2.2.1-260120-SNAPSHOT</tsfile.version>
</properties>
<!--
if we claim dependencies in dependencyManagement, then we do not claim