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 ac93e29302e22..d1d60ee66674b 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,30 @@ 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[] {Math.sin(1) + "," + Math.cos(1) + "," + Math.tan(1) + ","}; + tableResultSetEqualTest( + "SELECT sin(1), cos(1), tan(1)", 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/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 c31a2061f49ec..534dc7e2f792f 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 @@ -274,12 +274,14 @@ import org.apache.tsfile.file.metadata.idcolumn.TwoLevelDBExtractor; import org.apache.tsfile.read.TimeValuePair; import org.apache.tsfile.read.common.TimeRange; +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.BinaryType; import org.apache.tsfile.read.common.type.BlobType; import org.apache.tsfile.read.common.type.BooleanType; @@ -4403,9 +4405,19 @@ public Operator visitValuesNode(ValuesNode node, LocalExecutionPlanContext conte 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. + TsBlock emptyRowBlock = + new TsBlock( + node.getRowCount(), + new RunLengthEncodedColumn( + AbstractTableScanOperator.TIME_COLUMN_TEMPLATE, node.getRowCount()), + new Column[0]); + return new ValuesOperator(operatorContext, ImmutableList.of(emptyRowBlock)); } @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 071a6a05da671..d7a7ed4c1f59e 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 @@ -29,6 +29,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.analyzer.FieldId; import org.apache.iotdb.db.queryengine.plan.relational.analyzer.NodeRef; 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.planner.node.AggregationNode; @@ -43,6 +44,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ProjectNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.SortNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ValueFillNode; +import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ValuesNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.WindowNode; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Cast; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ComparisonExpression; @@ -65,6 +67,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SortItem; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.VariableDefinition; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.WindowFrame; +import org.apache.iotdb.db.queryengine.plan.relational.type.InternalTypeManager; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -783,7 +786,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/node/ValuesNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/ValuesNode.java index cb5c28ea02aaf..2119bfc53dd4f 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/ValuesNode.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/ValuesNode.java @@ -204,6 +204,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; } 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 24adb746df4e1..65b619782a276 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 @@ -65,6 +65,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.planner.node.TreeDeviceViewScanNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.UnionNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ValueFillNode; +import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ValuesNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.WindowNode; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression; import org.apache.iotdb.db.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);