Skip to content

Commit 594fb87

Browse files
Support table-less SELECT queries without FROM clause (#17437)
1 parent 16d3669 commit 594fb87

5 files changed

Lines changed: 77 additions & 3 deletions

File tree

integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBComplexQueryIT.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,32 @@ public void queryTest1() {
8181
retArray,
8282
DATABASE_NAME);
8383
}
84+
85+
@Test
86+
public void testTableLessQuery() {
87+
String[] expectedHeader;
88+
String[] retArray;
89+
90+
expectedHeader = new String[] {"_col0", "_col1", "_col2", "_col3"};
91+
retArray = new String[] {"2,0,1,1,"};
92+
tableResultSetEqualTest("SELECT 1+1, 1-1, 1*1, 1/1", expectedHeader, retArray, DATABASE_NAME);
93+
94+
expectedHeader = new String[] {"_col0", "_col1", "_col2"};
95+
retArray = new String[] {"0.841471,0.540302,1.557408,"};
96+
tableResultSetEqualTest(
97+
"SELECT round(sin(1),6), round(cos(1),6), round(tan(1),6)",
98+
expectedHeader,
99+
retArray,
100+
DATABASE_NAME);
101+
102+
expectedHeader = new String[] {"_col0"};
103+
retArray = new String[] {"Hello world,"};
104+
tableResultSetEqualTest(
105+
"SELECT FORMAT('Hello %s','world')", expectedHeader, retArray, DATABASE_NAME);
106+
107+
// SELECT COUNT(*) without FROM returns 1 (implicit single-row semantics)
108+
expectedHeader = new String[] {"_col0"};
109+
retArray = new String[] {"1,"};
110+
tableResultSetEqualTest("SELECT COUNT(*)", expectedHeader, retArray, DATABASE_NAME);
111+
}
84112
}

iotdb-core/calc-commons/src/main/java/org/apache/iotdb/calc/plan/planner/TableOperatorGenerator.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,12 +176,14 @@
176176
import org.apache.tsfile.common.conf.TSFileConfig;
177177
import org.apache.tsfile.common.conf.TSFileDescriptor;
178178
import org.apache.tsfile.enums.TSDataType;
179+
import org.apache.tsfile.read.common.block.TsBlock;
179180
import org.apache.tsfile.read.common.block.column.BinaryColumn;
180181
import org.apache.tsfile.read.common.block.column.BooleanColumn;
181182
import org.apache.tsfile.read.common.block.column.DoubleColumn;
182183
import org.apache.tsfile.read.common.block.column.FloatColumn;
183184
import org.apache.tsfile.read.common.block.column.IntColumn;
184185
import org.apache.tsfile.read.common.block.column.LongColumn;
186+
import org.apache.tsfile.read.common.block.column.RunLengthEncodedColumn;
185187
import org.apache.tsfile.read.common.type.Type;
186188
import org.apache.tsfile.utils.Binary;
187189

@@ -213,6 +215,7 @@
213215
import static org.apache.iotdb.calc.execution.operator.source.relational.aggregation.AccumulatorFactory.createBuiltinAccumulator;
214216
import static org.apache.iotdb.calc.execution.operator.source.relational.aggregation.AccumulatorFactory.createGroupedAccumulator;
215217
import static org.apache.iotdb.calc.plan.planner.CommonOperatorUtils.IDENTITY_FILL;
218+
import static org.apache.iotdb.calc.plan.planner.CommonOperatorUtils.TIME_COLUMN_TEMPLATE;
216219
import static org.apache.iotdb.calc.plan.planner.CommonOperatorUtils.UNKNOWN_DATATYPE;
217220
import static org.apache.iotdb.calc.plan.planner.CommonOperatorUtils.getLinearFill;
218221
import static org.apache.iotdb.calc.plan.planner.CommonOperatorUtils.getPreviousFill;
@@ -2179,8 +2182,22 @@ public Operator visitValuesNode(ValuesNode node, C context) {
21792182
context, node.getPlanNodeId(), MappingCollectOperator.class.getSimpleName());
21802183

21812184
// Currently we only support empty values operator
2182-
assert node.getRowCount() == 0;
2183-
return new ValuesOperator(operatorContext, ImmutableList.of());
2185+
if (node.getRowCount() == 0) {
2186+
return new ValuesOperator(operatorContext, ImmutableList.of());
2187+
}
2188+
2189+
// No-FROM query (e.g. SELECT 1+1): produce rowCount rows with no value columns so that the
2190+
// upstream ProjectNode can evaluate expressions once per row.
2191+
if (node.getRowCount() == 1) {
2192+
TsBlock oneRowWithoutColumnsBlock =
2193+
new TsBlock(
2194+
node.getRowCount(),
2195+
new RunLengthEncodedColumn(TIME_COLUMN_TEMPLATE, node.getRowCount()),
2196+
new Column[0]);
2197+
return new ValuesOperator(operatorContext, ImmutableList.of(oneRowWithoutColumnsBlock));
2198+
} else {
2199+
throw new IllegalArgumentException("Row count must be 0 or 1");
2200+
}
21842201
}
21852202

21862203
@Override

iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/QueryPlanner.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.ProjectNode;
4141
import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.SortNode;
4242
import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.ValueFillNode;
43+
import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.ValuesNode;
4344
import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.WindowNode;
4445
import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Cast;
4546
import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.ComparisonExpression;
@@ -61,13 +62,15 @@
6162
import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.SortItem;
6263
import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.VariableDefinition;
6364
import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.WindowFrame;
65+
import org.apache.iotdb.commons.queryengine.plan.relational.type.InternalTypeManager;
6466
import org.apache.iotdb.db.i18n.DataNodeQueryMessages;
6567
import org.apache.iotdb.db.queryengine.common.MPPQueryContext;
6668
import org.apache.iotdb.db.queryengine.common.QueryId;
6769
import org.apache.iotdb.db.queryengine.plan.relational.analyzer.Analysis;
6870
import org.apache.iotdb.db.queryengine.plan.relational.analyzer.Analysis.GroupingSetAnalysis;
6971
import org.apache.iotdb.db.queryengine.plan.relational.analyzer.FieldId;
7072
import org.apache.iotdb.db.queryengine.plan.relational.analyzer.RelationType;
73+
import org.apache.iotdb.db.queryengine.plan.relational.metadata.TableMetadataImpl;
7174
import org.apache.iotdb.db.queryengine.plan.relational.planner.ir.GapFillStartAndEndTimeExtractVisitor;
7275
import org.apache.iotdb.db.queryengine.plan.relational.planner.ir.PredicateWithUncorrelatedScalarSubqueryReconstructor;
7376
import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Delete;
@@ -792,7 +795,14 @@ private PlanBuilder planFrom(QuerySpecification node) {
792795
.process(node.getFrom().orElse(null), null);
793796
return newPlanBuilder(relationPlan, analysis);
794797
} else {
795-
throw new SemanticException(DataNodeQueryMessages.FROM_CLAUSE_MUST_NOT_BE_EMPTY);
798+
return new PlanBuilder(
799+
new TranslationMap(
800+
outerContext,
801+
analysis.getImplicitFromScope(node),
802+
analysis,
803+
ImmutableList.of(),
804+
new PlannerContext(new TableMetadataImpl(), new InternalTypeManager())),
805+
new ValuesNode(queryIdAllocator.genPlanNodeId(), 1));
796806
}
797807
}
798808

iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/UnaliasSymbolReferences.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.TopKRankingNode;
5454
import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.UnionNode;
5555
import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.ValueFillNode;
56+
import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.ValuesNode;
5657
import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.WindowNode;
5758
import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Expression;
5859
import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.NullLiteral;
@@ -510,6 +511,19 @@ public PlanAndMappings visitGroup(GroupNode node, UnaliasContext context) {
510511
mapping);
511512
}
512513

514+
@Override
515+
public PlanAndMappings visitValuesNode(ValuesNode node, UnaliasContext context) {
516+
Map<Symbol, Symbol> mapping = new HashMap<>(context.getCorrelationMapping());
517+
SymbolMapper mapper = symbolMapper(mapping);
518+
519+
List<Symbol> newOutputs = mapper.map(node.getOutputSymbols());
520+
Optional<List<Expression>> newRows =
521+
node.getRows().map(rows -> rows.stream().map(mapper::map).collect(toImmutableList()));
522+
523+
return new PlanAndMappings(
524+
new ValuesNode(node.getPlanNodeId(), newOutputs, node.getRowCount(), newRows), mapping);
525+
}
526+
513527
@Override
514528
public PlanAndMappings visitFilter(FilterNode node, UnaliasContext context) {
515529
PlanAndMappings rewrittenSource = node.getChild().accept(this, context);

iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/planner/node/ValuesNode.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,11 @@ public static ValuesNode deserialize(ByteBuffer byteBuffer) {
205205
planNodeId, outputSymbols, rowCount, flag ? Optional.of(rows) : Optional.empty());
206206
}
207207

208+
@Override
209+
public List<Symbol> getOutputSymbols() {
210+
return outputSymbols;
211+
}
212+
208213
public int getRowCount() {
209214
return rowCount;
210215
}

0 commit comments

Comments
 (0)