|
17 | 17 | package org.apache.calcite.test; |
18 | 18 |
|
19 | 19 | import org.apache.calcite.config.CalciteConnectionProperty; |
| 20 | +import org.apache.calcite.rel.RelNode; |
| 21 | +import org.apache.calcite.rel.rel2sql.RelToSqlConverter; |
20 | 22 | import org.apache.calcite.rel.type.DelegatingTypeSystem; |
21 | 23 | import org.apache.calcite.rel.type.RelDataTypeField; |
22 | 24 | import org.apache.calcite.rel.type.TimeFrameSet; |
| 25 | +import org.apache.calcite.schema.SchemaPlus; |
| 26 | +import org.apache.calcite.sql.SqlDialect; |
| 27 | +import org.apache.calcite.sql.SqlNode; |
23 | 28 | import org.apache.calcite.sql.SqlOperatorTable; |
| 29 | +import org.apache.calcite.sql.dialect.CalciteSqlDialect; |
24 | 30 | import org.apache.calcite.sql.fun.SqlLibrary; |
25 | 31 | import org.apache.calcite.sql.fun.SqlLibraryOperatorTableFactory; |
| 32 | +import org.apache.calcite.sql.parser.SqlParser; |
26 | 33 | import org.apache.calcite.sql.parser.SqlParserFixture; |
27 | 34 | import org.apache.calcite.sql.parser.babel.SqlBabelParserImpl; |
28 | 35 | import org.apache.calcite.sql.validate.SqlConformanceEnum; |
| 36 | +import org.apache.calcite.tools.FrameworkConfig; |
| 37 | +import org.apache.calcite.tools.Frameworks; |
| 38 | +import org.apache.calcite.tools.Planner; |
| 39 | +import org.apache.calcite.tools.Programs; |
| 40 | +import org.apache.calcite.util.TestUtil; |
29 | 41 |
|
30 | 42 | import com.google.common.collect.ImmutableList; |
31 | 43 |
|
|
43 | 55 | import java.util.function.UnaryOperator; |
44 | 56 | import java.util.stream.Collectors; |
45 | 57 |
|
| 58 | +import static org.apache.calcite.test.Matchers.isLinux; |
| 59 | + |
46 | 60 | import static org.hamcrest.CoreMatchers.is; |
47 | 61 | import static org.hamcrest.MatcherAssert.assertThat; |
48 | 62 |
|
@@ -341,6 +355,133 @@ names, is( |
341 | 355 | .type("RecordType(VARCHAR(10) NOT NULL NAME) NOT NULL"); |
342 | 356 | } |
343 | 357 |
|
| 358 | + /** Test case for |
| 359 | + * <a href="https://issues.apache.org/jira/browse/CALCITE-5347">[CALCITE-5347] |
| 360 | + * Support parse "SELECT ... BY" in Babel parser</a>. */ |
| 361 | + @Test void testByClause() { |
| 362 | + final SqlValidatorFixture v = Fixtures.forValidator() |
| 363 | + .withParserConfig(c -> c.withParserFactory(SqlBabelParserImpl.FACTORY)) |
| 364 | + .withConformance(SqlConformanceEnum.BABEL); |
| 365 | + |
| 366 | + // Test basic BY clause: SELECT a BY b is sugar for SELECT b, a GROUP BY b ORDER BY b |
| 367 | + v.withSql("select ename, empno by deptno from emp").ok(); |
| 368 | + // Test BY clause with alias |
| 369 | + v.withSql("select ename, empno by deptno as dept from emp").ok(); |
| 370 | + // Test BY clause with DESC modifier |
| 371 | + v.withSql("select ename, empno by deptno DESC from emp").ok(); |
| 372 | + // Test BY clause with multiple columns |
| 373 | + v.withSql("select ename, empno by deptno, job from emp").ok(); |
| 374 | + // Test complex BY clause example from the feature proposal |
| 375 | + v.withSql("select e.ename, e.empno by d.name as dept DESC, e.job as title " |
| 376 | + + "from emp as e join dept as d on e.deptno = d.deptno where d.name = 'SALES'") |
| 377 | + .ok(); |
| 378 | + |
| 379 | + // Test SELECT BY cannot be used with GROUP BY |
| 380 | + v.withSql("select ename by deptno from emp ^group by empno^") |
| 381 | + .fails("SELECT BY cannot be used with GROUP BY"); |
| 382 | + // Test SELECT BY cannot be used with ORDER BY |
| 383 | + v.withSql("select ename by deptno from emp ^order by empno^") |
| 384 | + .fails("SELECT BY cannot be used with ORDER BY"); |
| 385 | + } |
| 386 | + |
| 387 | + /** Test case of |
| 388 | + * <a href="https://issues.apache.org/jira/browse/CALCITE-5347">[CALCITE-5347] |
| 389 | + * Add 'SELECT ... BY', a syntax extension that is shorthand for GROUP BY and ORDER BY</a>. */ |
| 390 | + @Test void testByClauseConversion() { |
| 391 | + // Test basic BY clause: SELECT a BY b is sugar for SELECT b, a GROUP BY b ORDER BY b |
| 392 | + final String sql = "select ename, empno by deptno from emp"; |
| 393 | + final String expected = "SELECT \"DEPTNO\"," |
| 394 | + + " ANY_VALUE(\"ENAME\") AS \"ENAME\"," |
| 395 | + + " ANY_VALUE(\"EMPNO\") AS \"EMPNO\"\n" |
| 396 | + + "FROM \"SCOTT\".\"EMP\"\n" |
| 397 | + + "GROUP BY \"DEPTNO\"\n" |
| 398 | + + "ORDER BY \"DEPTNO\""; |
| 399 | + checkSqlConversion(sql, expected); |
| 400 | + |
| 401 | + // Test BY clause with alias |
| 402 | + final String sql2 = "select ename, empno by deptno as dept from emp"; |
| 403 | + final String expected2 = "SELECT \"DEPTNO\" AS \"DEPT\"," |
| 404 | + + " ANY_VALUE(\"ENAME\") AS \"ENAME\"," |
| 405 | + + " ANY_VALUE(\"EMPNO\") AS \"EMPNO\"\n" |
| 406 | + + "FROM \"SCOTT\".\"EMP\"\n" |
| 407 | + + "GROUP BY \"DEPTNO\"\n" |
| 408 | + + "ORDER BY \"DEPTNO\""; |
| 409 | + checkSqlConversion(sql2, expected2); |
| 410 | + |
| 411 | + // Test BY clause with DESC modifier |
| 412 | + final String sql3 = "select ename, empno by deptno DESC from emp"; |
| 413 | + final String expected3 = "SELECT \"DEPTNO\"," |
| 414 | + + " ANY_VALUE(\"ENAME\") AS \"ENAME\"," |
| 415 | + + " ANY_VALUE(\"EMPNO\") AS \"EMPNO\"\n" |
| 416 | + + "FROM \"SCOTT\".\"EMP\"\n" |
| 417 | + + "GROUP BY \"DEPTNO\"\n" |
| 418 | + + "ORDER BY \"DEPTNO\" DESC"; |
| 419 | + checkSqlConversion(sql3, expected3); |
| 420 | + |
| 421 | + // Test BY clause with multiple columns |
| 422 | + final String sql4 = "select ename, empno by deptno, job from emp"; |
| 423 | + final String expected4 = "SELECT \"DEPTNO\", \"JOB\"," |
| 424 | + + " ANY_VALUE(\"ENAME\") AS \"ENAME\"," |
| 425 | + + " ANY_VALUE(\"EMPNO\") AS \"EMPNO\"\n" |
| 426 | + + "FROM \"SCOTT\".\"EMP\"\n" |
| 427 | + + "GROUP BY \"DEPTNO\", \"JOB\"\n" |
| 428 | + + "ORDER BY \"DEPTNO\", \"JOB\""; |
| 429 | + checkSqlConversion(sql4, expected4); |
| 430 | + |
| 431 | + // Test complex BY clause example from the feature proposal |
| 432 | + final String sql5 = "SELECT e.ename, e.empno BY d.dname AS dept DESC, e.job AS title\n" |
| 433 | + + "FROM emp AS e\n" |
| 434 | + + " JOIN dept AS d ON e.deptno = d.deptno\n" |
| 435 | + + "WHERE d.loc = 'CHICAGO'"; |
| 436 | + final String expected5 = "SELECT \"DEPT\".\"DNAME\" AS \"DEPT\"," |
| 437 | + + " \"EMP\".\"JOB\" AS \"TITLE\"," |
| 438 | + + " ANY_VALUE(\"EMP\".\"ENAME\") AS \"ENAME\"," |
| 439 | + + " ANY_VALUE(\"EMP\".\"EMPNO\") AS \"EMPNO\"\n" |
| 440 | + + "FROM \"SCOTT\".\"EMP\"\n" |
| 441 | + + "INNER JOIN \"SCOTT\".\"DEPT\" ON \"EMP\".\"DEPTNO\" = \"DEPT\".\"DEPTNO\"\n" |
| 442 | + + "WHERE \"DEPT\".\"LOC\" = 'CHICAGO'\n" |
| 443 | + + "GROUP BY \"DEPT\".\"DNAME\", \"EMP\".\"JOB\"\n" |
| 444 | + + "ORDER BY \"DEPT\".\"DNAME\" DESC, \"EMP\".\"JOB\""; |
| 445 | + checkSqlConversion(sql5, expected5); |
| 446 | + } |
| 447 | + |
| 448 | + private void checkSqlConversion(String sql, String expected) { |
| 449 | + try { |
| 450 | + final SqlParser.Config parserConfig = SqlParser.config() |
| 451 | + .withParserFactory(SqlBabelParserImpl.FACTORY); |
| 452 | + |
| 453 | + final SchemaPlus rootSchema = Frameworks.createRootSchema(true); |
| 454 | + final SchemaPlus defaultSchema = |
| 455 | + CalciteAssert.addSchema(rootSchema, CalciteAssert.SchemaSpec.JDBC_SCOTT); |
| 456 | + |
| 457 | + final FrameworkConfig config = Frameworks.newConfigBuilder() |
| 458 | + .parserConfig(parserConfig) |
| 459 | + .defaultSchema(defaultSchema) |
| 460 | + .programs(Programs.standard()) |
| 461 | + .build(); |
| 462 | + |
| 463 | + final Planner planner = Frameworks.getPlanner(config); |
| 464 | + final SqlNode parse = planner.parse(sql); |
| 465 | + final SqlNode validate = planner.validate(parse); |
| 466 | + RelNode rel = planner.rel(validate).project(); |
| 467 | + |
| 468 | + final SqlDialect dialect = CalciteSqlDialect.DEFAULT; |
| 469 | + final RelToSqlConverter converter = new RelToSqlConverter(dialect); |
| 470 | + final SqlNode sqlNode = converter.visitRoot(rel).asStatement(); |
| 471 | + final String actual = sqlNode.toSqlString(c -> |
| 472 | + c.withDialect(dialect) |
| 473 | + .withAlwaysUseParentheses(false) |
| 474 | + .withSelectListItemsOnSeparateLines(false) |
| 475 | + .withUpdateSetListNewline(false) |
| 476 | + .withIndentation(0)) |
| 477 | + .getSql(); |
| 478 | + |
| 479 | + assertThat(actual, isLinux(expected)); |
| 480 | + } catch (Exception e) { |
| 481 | + throw TestUtil.rethrow(e); |
| 482 | + } |
| 483 | + } |
| 484 | + |
344 | 485 | private void checkSqlResult(String funLibrary, String query, String result) { |
345 | 486 | CalciteAssert.that() |
346 | 487 | .with(CalciteConnectionProperty.PARSER_FACTORY, |
|
0 commit comments