diff --git a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBComplexQueryIT.java b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBComplexQueryIT.java index ac93e29302e2..55b421422497 100644 --- a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBComplexQueryIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBComplexQueryIT.java @@ -81,4 +81,32 @@ public void queryTest1() { retArray, DATABASE_NAME); } + + @Test + public void testTableLessQuery() { + String[] expectedHeader; + String[] retArray; + + expectedHeader = new String[] {"_col0", "_col1", "_col2", "_col3"}; + retArray = new String[] {"2,0,1,1,"}; + tableResultSetEqualTest("SELECT 1+1, 1-1, 1*1, 1/1", expectedHeader, retArray, DATABASE_NAME); + + expectedHeader = new String[] {"_col0", "_col1", "_col2"}; + retArray = new String[] {"0.841471,0.540302,1.557408,"}; + tableResultSetEqualTest( + "SELECT round(sin(1),6), round(cos(1),6), round(tan(1),6)", + expectedHeader, + retArray, + DATABASE_NAME); + + expectedHeader = new String[] {"_col0"}; + retArray = new String[] {"Hello world,"}; + tableResultSetEqualTest( + "SELECT FORMAT('Hello %s','world')", expectedHeader, retArray, DATABASE_NAME); + + // SELECT COUNT(*) without FROM returns 1 (implicit single-row semantics) + expectedHeader = new String[] {"_col0"}; + retArray = new String[] {"1,"}; + tableResultSetEqualTest("SELECT COUNT(*)", expectedHeader, retArray, DATABASE_NAME); + } } diff --git a/iotdb-core/calc-commons/src/main/java/org/apache/iotdb/calc/plan/planner/TableOperatorGenerator.java b/iotdb-core/calc-commons/src/main/java/org/apache/iotdb/calc/plan/planner/TableOperatorGenerator.java index 86edc71a8e03..cabe0e18babd 100644 --- a/iotdb-core/calc-commons/src/main/java/org/apache/iotdb/calc/plan/planner/TableOperatorGenerator.java +++ b/iotdb-core/calc-commons/src/main/java/org/apache/iotdb/calc/plan/planner/TableOperatorGenerator.java @@ -175,12 +175,14 @@ import org.apache.tsfile.common.conf.TSFileConfig; import org.apache.tsfile.common.conf.TSFileDescriptor; import org.apache.tsfile.enums.TSDataType; +import org.apache.tsfile.read.common.block.TsBlock; 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.block.column.RunLengthEncodedColumn; import org.apache.tsfile.read.common.type.Type; import org.apache.tsfile.utils.Binary; @@ -212,6 +214,7 @@ import static org.apache.iotdb.calc.execution.operator.source.relational.aggregation.AccumulatorFactory.createBuiltinAccumulator; import static org.apache.iotdb.calc.execution.operator.source.relational.aggregation.AccumulatorFactory.createGroupedAccumulator; import static org.apache.iotdb.calc.plan.planner.CommonOperatorUtils.IDENTITY_FILL; +import static org.apache.iotdb.calc.plan.planner.CommonOperatorUtils.TIME_COLUMN_TEMPLATE; import static org.apache.iotdb.calc.plan.planner.CommonOperatorUtils.UNKNOWN_DATATYPE; import static org.apache.iotdb.calc.plan.planner.CommonOperatorUtils.getLinearFill; import static org.apache.iotdb.calc.plan.planner.CommonOperatorUtils.getPreviousFill; @@ -2174,8 +2177,22 @@ public Operator visitValuesNode(ValuesNode node, C context) { context, node.getPlanNodeId(), MappingCollectOperator.class.getSimpleName()); // Currently we only support empty values operator - assert node.getRowCount() == 0; - return new ValuesOperator(operatorContext, ImmutableList.of()); + if (node.getRowCount() == 0) { + return new ValuesOperator(operatorContext, ImmutableList.of()); + } + + // No-FROM query (e.g. SELECT 1+1): produce rowCount rows with no value columns so that the + // upstream ProjectNode can evaluate expressions once per row. + if (node.getRowCount() == 1) { + TsBlock oneRowWithoutColumnsBlock = + new TsBlock( + node.getRowCount(), + new RunLengthEncodedColumn(TIME_COLUMN_TEMPLATE, node.getRowCount()), + new Column[0]); + return new ValuesOperator(operatorContext, ImmutableList.of(oneRowWithoutColumnsBlock)); + } else { + throw new IllegalArgumentException("Row count must be 0 or 1"); + } } @Override diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/QueryPlanner.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/QueryPlanner.java index 3fc66970fd32..957b0bc47924 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/QueryPlanner.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/QueryPlanner.java @@ -40,6 +40,7 @@ import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.ProjectNode; import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.SortNode; import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.ValueFillNode; +import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.ValuesNode; import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.WindowNode; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Cast; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.ComparisonExpression; @@ -61,12 +62,14 @@ import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.SortItem; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.VariableDefinition; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.WindowFrame; +import org.apache.iotdb.commons.queryengine.plan.relational.type.InternalTypeManager; import org.apache.iotdb.db.queryengine.common.MPPQueryContext; import org.apache.iotdb.db.queryengine.common.QueryId; import org.apache.iotdb.db.queryengine.plan.relational.analyzer.Analysis; import org.apache.iotdb.db.queryengine.plan.relational.analyzer.Analysis.GroupingSetAnalysis; import org.apache.iotdb.db.queryengine.plan.relational.analyzer.FieldId; import org.apache.iotdb.db.queryengine.plan.relational.analyzer.RelationType; +import org.apache.iotdb.db.queryengine.plan.relational.metadata.TableMetadataImpl; import org.apache.iotdb.db.queryengine.plan.relational.planner.ir.GapFillStartAndEndTimeExtractVisitor; import org.apache.iotdb.db.queryengine.plan.relational.planner.ir.PredicateWithUncorrelatedScalarSubqueryReconstructor; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Delete; @@ -788,7 +791,14 @@ private PlanBuilder planFrom(QuerySpecification node) { .process(node.getFrom().orElse(null), null); return newPlanBuilder(relationPlan, analysis); } else { - throw new SemanticException("From clause must not be empty"); + return new PlanBuilder( + new TranslationMap( + outerContext, + analysis.getImplicitFromScope(node), + analysis, + ImmutableList.of(), + new PlannerContext(new TableMetadataImpl(), new InternalTypeManager())), + new ValuesNode(queryIdAllocator.genPlanNodeId(), 1)); } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/UnaliasSymbolReferences.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/UnaliasSymbolReferences.java index 6dda2d17503d..b944715fb8fa 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/UnaliasSymbolReferences.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/UnaliasSymbolReferences.java @@ -53,6 +53,7 @@ import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.TopKRankingNode; import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.UnionNode; import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.ValueFillNode; +import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.ValuesNode; import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.WindowNode; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Expression; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.NullLiteral; @@ -510,6 +511,19 @@ public PlanAndMappings visitGroup(GroupNode node, UnaliasContext context) { mapping); } + @Override + public PlanAndMappings visitValuesNode(ValuesNode node, UnaliasContext context) { + Map mapping = new HashMap<>(context.getCorrelationMapping()); + SymbolMapper mapper = symbolMapper(mapping); + + List newOutputs = mapper.map(node.getOutputSymbols()); + Optional> newRows = + node.getRows().map(rows -> rows.stream().map(mapper::map).collect(toImmutableList())); + + return new PlanAndMappings( + new ValuesNode(node.getPlanNodeId(), newOutputs, node.getRowCount(), newRows), mapping); + } + @Override public PlanAndMappings visitFilter(FilterNode node, UnaliasContext context) { PlanAndMappings rewrittenSource = node.getChild().accept(this, context); diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/planner/node/ValuesNode.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/planner/node/ValuesNode.java index 473282d41f0b..3dd6071e4755 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/planner/node/ValuesNode.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/planner/node/ValuesNode.java @@ -205,6 +205,11 @@ public static ValuesNode deserialize(ByteBuffer byteBuffer) { planNodeId, outputSymbols, rowCount, flag ? Optional.of(rows) : Optional.empty()); } + @Override + public List getOutputSymbols() { + return outputSymbols; + } + public int getRowCount() { return rowCount; }