Skip to content

Commit 2e77bf4

Browse files
Add FROM-first query syntax support (#17410)
1 parent fad2869 commit 2e77bf4

4 files changed

Lines changed: 308 additions & 10 deletions

File tree

  • integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent
  • iotdb-core
    • datanode/src
      • main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser
      • test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer
    • relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.iotdb.relational.it.query.recent;
21+
22+
import org.apache.iotdb.it.env.EnvFactory;
23+
import org.apache.iotdb.it.framework.IoTDBTestRunner;
24+
import org.apache.iotdb.itbase.category.TableClusterIT;
25+
import org.apache.iotdb.itbase.category.TableLocalStandaloneIT;
26+
27+
import org.junit.AfterClass;
28+
import org.junit.BeforeClass;
29+
import org.junit.Test;
30+
import org.junit.experimental.categories.Category;
31+
import org.junit.runner.RunWith;
32+
33+
import static org.apache.iotdb.db.it.utils.TestUtils.prepareTableData;
34+
import static org.apache.iotdb.db.it.utils.TestUtils.tableResultSetEqualTest;
35+
36+
@RunWith(IoTDBTestRunner.class)
37+
@Category({TableLocalStandaloneIT.class, TableClusterIT.class})
38+
public class IoTDBFromFirstQueryIT {
39+
private static final String DATABASE_NAME = "test";
40+
41+
private static final String[] createSqls =
42+
new String[] {
43+
"CREATE DATABASE " + DATABASE_NAME,
44+
"USE " + DATABASE_NAME,
45+
"CREATE TABLE table1(device STRING TAG, region STRING TAG, temperature FLOAT FIELD, humidity DOUBLE FIELD)",
46+
"CREATE TABLE table2(device STRING TAG, location STRING TAG, pressure FLOAT FIELD)",
47+
"INSERT INTO table1(time,device,region,temperature,humidity) values(1,'d1','north',25.5,60.3)",
48+
"INSERT INTO table1(time,device,region,temperature,humidity) values(2,'d1','north',26.1,59.8)",
49+
"INSERT INTO table1(time,device,region,temperature,humidity) values(3,'d2','south',24.8,65.2)",
50+
"INSERT INTO table2(time,device,location,pressure) values(1,'d1','room1',1013.25)",
51+
"INSERT INTO table2(time,device,location,pressure) values(2,'d2','room2',1012.5)",
52+
"FLUSH"
53+
};
54+
55+
@BeforeClass
56+
public static void setUp() throws Exception {
57+
EnvFactory.getEnv().initClusterEnvironment();
58+
prepareTableData(createSqls);
59+
}
60+
61+
@AfterClass
62+
public static void tearDown() throws Exception {
63+
EnvFactory.getEnv().cleanClusterEnvironment();
64+
}
65+
66+
@Test
67+
public void testBasicFromFirstQuery() {
68+
String[] expectedHeader = new String[] {"time", "device", "region", "temperature", "humidity"};
69+
String[] retArray =
70+
new String[] {
71+
"1970-01-01T00:00:00.001Z,d1,north,25.5,60.3,",
72+
"1970-01-01T00:00:00.002Z,d1,north,26.1,59.8,",
73+
"1970-01-01T00:00:00.003Z,d2,south,24.8,65.2,"
74+
};
75+
76+
tableResultSetEqualTest(
77+
"FROM table1 SELECT * order by time", expectedHeader, retArray, DATABASE_NAME);
78+
}
79+
80+
@Test
81+
public void testFromFirstWithImplicitSelect() {
82+
String[] expectedHeader = new String[] {"time", "device", "region", "temperature", "humidity"};
83+
String[] retArray =
84+
new String[] {
85+
"1970-01-01T00:00:00.001Z,d1,north,25.5,60.3,",
86+
"1970-01-01T00:00:00.002Z,d1,north,26.1,59.8,",
87+
"1970-01-01T00:00:00.003Z,d2,south,24.8,65.2,"
88+
};
89+
90+
tableResultSetEqualTest("FROM table1 order by time", expectedHeader, retArray, DATABASE_NAME);
91+
}
92+
93+
@Test
94+
public void testFromFirstWithSimpleJoin() {
95+
String[] expectedHeader =
96+
new String[] {
97+
"time", "device", "region", "temperature", "humidity", "location", "pressure"
98+
};
99+
String[] retArray =
100+
new String[] {
101+
"1970-01-01T00:00:00.001Z,d1,north,25.5,60.3,room1,1013.25,",
102+
"1970-01-01T00:00:00.002Z,d1,north,26.1,59.8,null,null,",
103+
"1970-01-01T00:00:00.003Z,d2,south,24.8,65.2,null,null,"
104+
};
105+
106+
tableResultSetEqualTest(
107+
"FROM table1 t1 LEFT JOIN table2 t2 ON t1.device = t2.device AND t1.time = t2.time "
108+
+ "SELECT t1.time, t1.device, t1.region, t1.temperature, t1.humidity, t2.location, t2.pressure "
109+
+ "ORDER BY t1.time",
110+
expectedHeader,
111+
retArray,
112+
DATABASE_NAME);
113+
}
114+
115+
@Test
116+
public void testFromFirstWithWhereAndAggregate() {
117+
String[] expectedHeader = new String[] {"device", "avg_temp", "count_rows"};
118+
String[] retArray = new String[] {"d1,25.8,2,", "d2,24.8,1,"};
119+
120+
tableResultSetEqualTest(
121+
"FROM table1 SELECT device, ROUND(AVG(temperature), 1) as avg_temp, COUNT(*) as count_rows WHERE temperature > 24.0 GROUP BY device order by device",
122+
expectedHeader,
123+
retArray,
124+
DATABASE_NAME);
125+
}
126+
}

iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2438,31 +2438,79 @@ public Node visitRowCount(final RelationalSqlParser.RowCountContext ctx) {
24382438

24392439
@Override
24402440
public Node visitQuerySpecification(RelationalSqlParser.QuerySpecificationContext ctx) {
2441+
return buildQuerySpecification(
2442+
ctx,
2443+
ctx.selectItem(),
2444+
ctx.relation(),
2445+
ctx.SELECT(),
2446+
ctx.setQuantifier(),
2447+
ctx.where,
2448+
ctx.groupBy(),
2449+
ctx.having,
2450+
ctx.windowDefinition());
2451+
}
2452+
2453+
@Override
2454+
public Node visitFromFirstQuerySpecification(
2455+
RelationalSqlParser.FromFirstQuerySpecificationContext ctx) {
2456+
return buildQuerySpecification(
2457+
ctx,
2458+
ctx.selectItem(),
2459+
ctx.relation(),
2460+
ctx.SELECT(),
2461+
ctx.setQuantifier(),
2462+
ctx.where,
2463+
ctx.groupBy(),
2464+
ctx.having,
2465+
ctx.windowDefinition());
2466+
}
2467+
2468+
private Node buildQuerySpecification(
2469+
ParserRuleContext parserRuleContext,
2470+
List<RelationalSqlParser.SelectItemContext> selectItemContexts,
2471+
List<RelationalSqlParser.RelationContext> relationContexts,
2472+
TerminalNode selectNode,
2473+
RelationalSqlParser.SetQuantifierContext setQuantifier,
2474+
RelationalSqlParser.BooleanExpressionContext where,
2475+
RelationalSqlParser.GroupByContext groupBy,
2476+
RelationalSqlParser.BooleanExpressionContext having,
2477+
List<RelationalSqlParser.WindowDefinitionContext> windowDefinitions) {
2478+
24412479
Optional<Relation> from = Optional.empty();
2442-
List<SelectItem> selectItems = visit(ctx.selectItem(), SelectItem.class);
2480+
List<SelectItem> selectItems = visit(selectItemContexts, SelectItem.class);
24432481

2444-
List<Relation> relations = visit(ctx.relation(), Relation.class);
2482+
List<Relation> relations = visit(relationContexts, Relation.class);
24452483
if (!relations.isEmpty()) {
24462484
// synthesize implicit join nodes
24472485
Iterator<Relation> iterator = relations.iterator();
24482486
Relation relation = iterator.next();
24492487

24502488
while (iterator.hasNext()) {
2451-
relation = new Join(getLocation(ctx), Join.Type.IMPLICIT, relation, iterator.next());
2489+
relation =
2490+
new Join(getLocation(parserRuleContext), Join.Type.IMPLICIT, relation, iterator.next());
24522491
}
24532492

24542493
from = Optional.of(relation);
24552494
}
24562495

2496+
// If no SELECT items provided (FROM-first without SELECT clause), default to SELECT *
2497+
if (selectItems.isEmpty()) {
2498+
selectItems =
2499+
ImmutableList.of(new AllColumns(getLocation(parserRuleContext), ImmutableList.of()));
2500+
}
2501+
2502+
NodeLocation selectLocation =
2503+
selectNode != null ? getLocation(selectNode) : getLocation(parserRuleContext);
2504+
24572505
return new QuerySpecification(
2458-
getLocation(ctx),
2459-
new Select(getLocation(ctx.SELECT()), isDistinct(ctx.setQuantifier()), selectItems),
2506+
getLocation(parserRuleContext),
2507+
new Select(selectLocation, isDistinct(setQuantifier), selectItems),
24602508
from,
2461-
visitIfPresent(ctx.where, Expression.class),
2462-
visitIfPresent(ctx.groupBy(), GroupBy.class),
2463-
visitIfPresent(ctx.having, Expression.class),
2509+
visitIfPresent(where, Expression.class),
2510+
visitIfPresent(groupBy, GroupBy.class),
2511+
visitIfPresent(having, Expression.class),
24642512
Optional.empty(),
2465-
visit(ctx.windowDefinition(), WindowDefinition.class),
2513+
visit(windowDefinitions, WindowDefinition.class),
24662514
Optional.empty(),
24672515
Optional.empty(),
24682516
Optional.empty());

iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AnalyzerTest.java

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,6 +1252,120 @@ public void analyzeInsertRow() {
12521252
assertEquals(1, distributedQueryPlan.getInstances().size());
12531253
}
12541254

1255+
@Test
1256+
public void fromFirstQueryTest() throws OperatorNotFoundException {
1257+
final String sqlSelectFirst = "SELECT * FROM table1";
1258+
final String sqlFromFirst = "FROM table1 SELECT *";
1259+
1260+
final Analysis analysisSelectFirst = analyzeSQL(sqlSelectFirst, TEST_MATADATA, QUERY_CONTEXT);
1261+
final SymbolAllocator symbolAllocatorSelectFirst = new SymbolAllocator();
1262+
final LogicalQueryPlan logicalQueryPlanSelectFirst =
1263+
new TableLogicalPlanner(
1264+
QUERY_CONTEXT,
1265+
TEST_MATADATA,
1266+
SESSION_INFO,
1267+
symbolAllocatorSelectFirst,
1268+
DEFAULT_WARNING)
1269+
.plan(analysisSelectFirst);
1270+
final PlanNode rootNodeSelectFirst = logicalQueryPlanSelectFirst.getRootNode();
1271+
final DeviceTableScanNode deviceTableScanNodeSelectFirst =
1272+
(DeviceTableScanNode) ((OutputNode) rootNodeSelectFirst).getChild();
1273+
1274+
final Analysis analysisFromFirst = analyzeSQL(sqlFromFirst, TEST_MATADATA, QUERY_CONTEXT);
1275+
final SymbolAllocator symbolAllocatorFromFirst = new SymbolAllocator();
1276+
final LogicalQueryPlan logicalQueryPlanFromFirst =
1277+
new TableLogicalPlanner(
1278+
QUERY_CONTEXT,
1279+
TEST_MATADATA,
1280+
SESSION_INFO,
1281+
symbolAllocatorFromFirst,
1282+
DEFAULT_WARNING)
1283+
.plan(analysisFromFirst);
1284+
final PlanNode rootNodeFromFirst = logicalQueryPlanFromFirst.getRootNode();
1285+
final DeviceTableScanNode deviceTableScanNodeFromFirst =
1286+
(DeviceTableScanNode) ((OutputNode) rootNodeFromFirst).getChild();
1287+
1288+
assertEquals(
1289+
deviceTableScanNodeSelectFirst.getOutputColumnNames(),
1290+
deviceTableScanNodeFromFirst.getOutputColumnNames());
1291+
1292+
assertEquals(
1293+
deviceTableScanNodeSelectFirst.getQualifiedObjectName(),
1294+
deviceTableScanNodeFromFirst.getQualifiedObjectName());
1295+
}
1296+
1297+
@Test
1298+
public void fromFirstImplicitSelectTest() throws OperatorNotFoundException {
1299+
final String sqlFromFirst = "FROM table1";
1300+
final String sqlSelectFirst = "SELECT * FROM table1";
1301+
1302+
final Analysis analysisFromFirst = analyzeSQL(sqlFromFirst, TEST_MATADATA, QUERY_CONTEXT);
1303+
final SymbolAllocator symbolAllocatorFromFirst = new SymbolAllocator();
1304+
final LogicalQueryPlan logicalQueryPlanFromFirst =
1305+
new TableLogicalPlanner(
1306+
QUERY_CONTEXT,
1307+
TEST_MATADATA,
1308+
SESSION_INFO,
1309+
symbolAllocatorFromFirst,
1310+
DEFAULT_WARNING)
1311+
.plan(analysisFromFirst);
1312+
final PlanNode rootNodeFromFirst = logicalQueryPlanFromFirst.getRootNode();
1313+
final DeviceTableScanNode deviceTableScanNodeFromFirst =
1314+
(DeviceTableScanNode) ((OutputNode) rootNodeFromFirst).getChild();
1315+
1316+
final Analysis analysisSelectFirst = analyzeSQL(sqlSelectFirst, TEST_MATADATA, QUERY_CONTEXT);
1317+
final SymbolAllocator symbolAllocatorSelectFirst = new SymbolAllocator();
1318+
final LogicalQueryPlan logicalQueryPlanSelectFirst =
1319+
new TableLogicalPlanner(
1320+
QUERY_CONTEXT,
1321+
TEST_MATADATA,
1322+
SESSION_INFO,
1323+
symbolAllocatorSelectFirst,
1324+
DEFAULT_WARNING)
1325+
.plan(analysisSelectFirst);
1326+
final PlanNode rootNodeSelectFirst = logicalQueryPlanSelectFirst.getRootNode();
1327+
final DeviceTableScanNode deviceTableScanNodeSelectFirst =
1328+
(DeviceTableScanNode) ((OutputNode) rootNodeSelectFirst).getChild();
1329+
1330+
assertEquals(
1331+
deviceTableScanNodeSelectFirst.getOutputColumnNames(),
1332+
deviceTableScanNodeFromFirst.getOutputColumnNames());
1333+
1334+
assertEquals(
1335+
Arrays.asList("time", "tag1", "tag2", "tag3", "attr1", "attr2", "s1", "s2", "s3"),
1336+
deviceTableScanNodeFromFirst.getOutputColumnNames());
1337+
}
1338+
1339+
@Test
1340+
public void fromFirstWithFilterTest() throws OperatorNotFoundException {
1341+
final String sql = "FROM table1 SELECT tag1, s1 WHERE s1 > 1";
1342+
1343+
final Analysis analysis = analyzeSQL(sql, TEST_MATADATA, QUERY_CONTEXT);
1344+
final SymbolAllocator symbolAllocator = new SymbolAllocator();
1345+
final LogicalQueryPlan logicalQueryPlan =
1346+
new TableLogicalPlanner(
1347+
QUERY_CONTEXT, TEST_MATADATA, SESSION_INFO, symbolAllocator, DEFAULT_WARNING)
1348+
.plan(analysis);
1349+
1350+
final PlanNode rootNode = logicalQueryPlan.getRootNode();
1351+
assertTrue(rootNode instanceof OutputNode);
1352+
assertTrue(rootNode.getChildren().get(0) instanceof DeviceTableScanNode);
1353+
1354+
final DeviceTableScanNode deviceTableScanNode =
1355+
(DeviceTableScanNode) rootNode.getChildren().get(0);
1356+
1357+
assertEquals(Arrays.asList("tag1", "s1"), deviceTableScanNode.getOutputColumnNames());
1358+
1359+
assertNotNull(deviceTableScanNode.getPushDownPredicate());
1360+
assertEquals("(\"s1\" > 1)", deviceTableScanNode.getPushDownPredicate().toString());
1361+
1362+
assertEquals(
1363+
ImmutableSet.of("tag1", "s1"),
1364+
deviceTableScanNode.getAssignments().keySet().stream()
1365+
.map(Symbol::toString)
1366+
.collect(Collectors.toSet()));
1367+
}
1368+
12551369
public static Analysis analyzeSQL(String sql, Metadata metadata, final MPPQueryContext context) {
12561370
SqlParser sqlParser = new SqlParser();
12571371
Statement statement =

iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,7 @@ queryPrimary
10181018
| TABLE qualifiedName #table
10191019
| VALUES expression (',' expression)* #inlineTable
10201020
| '(' queryNoWith ')' #subquery
1021+
| fromFirstQuerySpecification #fromFirstQueryPrimary
10211022
;
10221023

10231024
sortItem
@@ -1033,6 +1034,15 @@ querySpecification
10331034
(WINDOW windowDefinition (',' windowDefinition)*)?
10341035
;
10351036

1037+
fromFirstQuerySpecification
1038+
: FROM relation (',' relation)*
1039+
(SELECT setQuantifier? selectItem (',' selectItem)*)?
1040+
(WHERE where=booleanExpression)?
1041+
(GROUP BY groupBy)?
1042+
(HAVING having=booleanExpression)?
1043+
(WINDOW windowDefinition (',' windowDefinition)*)?
1044+
;
1045+
10361046
groupBy
10371047
: setQuantifier? groupingElement (',' groupingElement)*
10381048
;
@@ -2036,4 +2046,4 @@ WS
20362046
// when splitting statements with DelimiterLexer
20372047
UNRECOGNIZED
20382048
: .
2039-
;
2049+
;

0 commit comments

Comments
 (0)