This is an automated email from the ASF dual-hosted git repository.

JackieTien97 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 594fb879c4d Support table-less SELECT queries without FROM clause 
(#17437)
594fb879c4d is described below

commit 594fb879c4d21398449291ccdcbe0d0e0ec26631
Author: FearfulTomcat27 <[email protected]>
AuthorDate: Thu Jun 4 20:24:51 2026 +0800

    Support table-less SELECT queries without FROM clause (#17437)
---
 .../it/query/recent/IoTDBComplexQueryIT.java       | 28 ++++++++++++++++++++++
 .../calc/plan/planner/TableOperatorGenerator.java  | 21 ++++++++++++++--
 .../plan/relational/planner/QueryPlanner.java      | 12 +++++++++-
 .../optimizations/UnaliasSymbolReferences.java     | 14 +++++++++++
 .../plan/relational/planner/node/ValuesNode.java   |  5 ++++
 5 files changed, 77 insertions(+), 3 deletions(-)

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 ac93e29302e..55b42142249 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 class IoTDBComplexQueryIT {
         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 ed4fc101b1b..f65ba4dbc5d 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
@@ -176,12 +176,14 @@ import org.apache.tsfile.block.column.Column;
 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;
 
@@ -213,6 +215,7 @@ import static 
org.apache.iotdb.calc.execution.operator.source.relational.aggrega
 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;
@@ -2179,8 +2182,22 @@ public abstract class TableOperatorGenerator<
             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 10ea1c1e25a..0232406c4e5 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.Previou
 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,6 +62,7 @@ import 
org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.QuerySpecifi
 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.i18n.DataNodeQueryMessages;
 import org.apache.iotdb.db.queryengine.common.MPPQueryContext;
 import org.apache.iotdb.db.queryengine.common.QueryId;
@@ -68,6 +70,7 @@ 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;
@@ -792,7 +795,14 @@ public class QueryPlanner {
               .process(node.getFrom().orElse(null), null);
       return newPlanBuilder(relationPlan, analysis);
     } else {
-      throw new 
SemanticException(DataNodeQueryMessages.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 6dda2d17503..b944715fb8f 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.TopKNod
 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 class UnaliasSymbolReferences implements 
PlanOptimizer {
           mapping);
     }
 
+    @Override
+    public PlanAndMappings visitValuesNode(ValuesNode node, UnaliasContext 
context) {
+      Map<Symbol, Symbol> mapping = new 
HashMap<>(context.getCorrelationMapping());
+      SymbolMapper mapper = symbolMapper(mapping);
+
+      List<Symbol> newOutputs = mapper.map(node.getOutputSymbols());
+      Optional<List<Expression>> 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 473282d41f0..3dd6071e475 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 class ValuesNode extends SourceNode {
         planNodeId, outputSymbols, rowCount, flag ? Optional.of(rows) : 
Optional.empty());
   }
 
+  @Override
+  public List<Symbol> getOutputSymbols() {
+    return outputSymbols;
+  }
+
   public int getRowCount() {
     return rowCount;
   }

Reply via email to