Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions babel/src/test/java/org/apache/calcite/test/BabelParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,24 @@ class BabelParserTest extends SqlParserTest {
sql(sql3).ok(expected3);
}

/** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-7532">
* [CALCITE-7532] Support the syntax SELECT * REPLACE(expr as column)</a>.
* */
@Test void testStarReplace() {
final String sql = "select * replace(empno + 1 as empno) from emp";
final String expected = "SELECT * REPLACE ((`EMPNO` + 1) AS `EMPNO`)\n"
+ "FROM `EMP`";
sql(sql).ok(expected);

final String sql2 = "select e.* replace(e.empno + 1 as e.empno, e.sal * 2 as e.sal)"
+ " from emp e join dept d on e.deptno = d.deptno";
final String expected2 = "SELECT `E`.* REPLACE ((`E`.`EMPNO` + 1) AS `E`.`EMPNO`,"
+ " (`E`.`SAL` * 2) AS `E`.`SAL`)\n"
+ "FROM `EMP` AS `E`\n"
+ "INNER JOIN `DEPT` AS `D` ON (`E`.`DEPTNO` = `D`.`DEPTNO`)";
sql(sql2).ok(expected2);
}

/** Tests that there are no reserved keywords. */
@Disabled
@Test void testKeywords() {
Expand Down
73 changes: 73 additions & 0 deletions babel/src/test/java/org/apache/calcite/test/BabelTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,79 @@ names, is(
.fails("SELECT \\* EXCLUDE/EXCEPT list cannot exclude all columns");
}

/** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-7532">
* [CALCITE-7532] Support the syntax SELECT * REPLACE(expr as column)</a>. */
@Test void testStarReplaceValidation() {
final SqlValidatorFixture fixture = Fixtures.forValidator()
.withParserConfig(p -> p.withParserFactory(SqlBabelParserImpl.FACTORY));

fixture.withSql("select * replace(empno + 1 as empno) from emp")
.type(type -> {
final List<String> names = type.getFieldList().stream()
.map(RelDataTypeField::getName)
.collect(Collectors.toList());
assertThat(
names, is(
ImmutableList.of("EMPNO", "ENAME", "JOB", "MGR",
"HIREDATE", "SAL", "COMM", "DEPTNO", "SLACKER")));
// Verify that EMPNO type is still INTEGER (or similar)
Comment thread
mihaibudiu marked this conversation as resolved.
assertThat(type.getFieldList().get(0).getType().getSqlTypeName().getName(),
is("INTEGER"));
});

fixture.withSql("select * replace(empno + 1 as empno, sal * 2 as sal) from emp")
.type(type -> {
final List<String> names = type.getFieldList().stream()
.map(RelDataTypeField::getName)
.collect(Collectors.toList());
assertThat(
names, is(
ImmutableList.of("EMPNO", "ENAME", "JOB", "MGR",
"HIREDATE", "SAL", "COMM", "DEPTNO", "SLACKER")));
});

// REPLACE with a completely different type
fixture.withSql("select * replace('fixed' as empno) from emp")
.type(type -> {
final List<String> names = type.getFieldList().stream()
.map(RelDataTypeField::getName)
.collect(Collectors.toList());
assertThat(
names, is(
ImmutableList.of("EMPNO", "ENAME", "JOB", "MGR",
"HIREDATE", "SAL", "COMM", "DEPTNO", "SLACKER")));
// EMPNO was INTEGER, now replaced by a CHAR literal
assertThat(type.getFieldList().get(0).getType().getSqlTypeName().getName(),
is("CHAR"));
});

// Same column replaced twice
fixture.withSql("select * replace(empno + 1 as empno, 'fixed' as ^empno^) from emp")
.fails("SELECT \\* REPLACE list contains duplicate column\\(s\\): EMPNO");

// Unknown column in REPLACE list
fixture.withSql("select * replace(empno + 1 as ^foo^) from emp")
.fails("SELECT \\* REPLACE list contains unknown column\\(s\\): FOO");

// Table-qualified star with REPLACE
fixture.withSql("select e.* replace(e.empno + 1 as e.empno)"
+ " from emp e join dept d on e.deptno = d.deptno")
.type(type -> {
final List<String> names = type.getFieldList().stream()
.map(RelDataTypeField::getName)
.collect(Collectors.toList());
assertThat(
names, is(
ImmutableList.of("EMPNO", "ENAME", "JOB", "MGR",
"HIREDATE", "SAL", "COMM", "DEPTNO", "SLACKER")));
});

// REPLACE with unknown qualified column
fixture.withSql("select e.* replace(e.empno + 1 as ^d.deptno^)"
Comment thread
mihaibudiu marked this conversation as resolved.
+ " from emp e join dept d on e.deptno = d.deptno")
.fails("SELECT \\* REPLACE list contains unknown column\\(s\\): DEPTNO");
}

/** Tests that DATEADD, DATEDIFF, DATEPART, DATE_PART allow custom time
* frames. */
@Test void testTimeFrames() {
Expand Down
46 changes: 46 additions & 0 deletions babel/src/test/resources/sql/select.iq
Original file line number Diff line number Diff line change
Expand Up @@ -286,4 +286,50 @@ select d1.* except(d1.dname) from dept d1 except(select d2.* except(d2.dname) fr

!ok

# SELECT * REPLACE(expr AS column)
select * replace(empno + 1 as empno) from emp where empno = 7369;
+-------+-------+-------+------+------------+--------+------+--------+
| EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO |
+-------+-------+-------+------+------------+--------+------+--------+
| 7370 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | | 20 |
+-------+-------+-------+------+------------+--------+------+--------+
(1 row)

!ok

select * replace(sal * 2 as sal, upper(ename) as ename) from emp where empno = 7369;
+-------+-------+-------+------+------------+---------+------+--------+
| EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO |
+-------+-------+-------+------+------------+---------+------+--------+
| 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 1600.00 | | 20 |
+-------+-------+-------+------+------------+---------+------+--------+
(1 row)

!ok

select e.* replace(e.empno + 1 as e.empno)
from emp e join dept d on e.deptno = d.deptno
where e.empno = 7782;
+-------+-------+---------+------+------------+---------+------+--------+
| EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO |
+-------+-------+---------+------+------------+---------+------+--------+
| 7783 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | | 10 |
+-------+-------+---------+------+------------+---------+------+--------+
(1 row)

!ok

select empno replace(empno + 1 as empno) from emp;
REPLACE clause must follow a STAR expression
!error

select * replace(empno + 1 as foo) from emp;
SELECT * REPLACE list contains unknown column(s): FOO
!error

select e.* replace(e.empno + 1 as d.deptno)
from emp e join dept d on e.deptno = d.deptno;
SELECT * REPLACE list contains unknown column(s): DEPTNO
!error

# End select.iq
52 changes: 52 additions & 0 deletions core/src/main/codegen/templates/Parser.jj
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ import org.apache.calcite.sql.SqlSelect;
import org.apache.calcite.sql.SqlByRewriter;
import org.apache.calcite.sql.SqlSelectKeyword;
import org.apache.calcite.sql.SqlStarExclude;
import org.apache.calcite.sql.SqlStarReplace;
import org.apache.calcite.sql.SqlSetOption;
import org.apache.calcite.sql.SqlSnapshot;
import org.apache.calcite.sql.SqlTableRef;
Expand Down Expand Up @@ -2017,6 +2018,7 @@ SqlNode SelectExpression() :
{
SqlNode e;
SqlNodeList excludeList;
SqlNodeList replaceList;
}
{
(
Expand All @@ -2027,6 +2029,7 @@ SqlNode SelectExpression() :
e = Expression(ExprContext.ACCEPT_SUB_QUERY)
)
(
<#if (parser.includeStarExclude!default.parser.includeStarExclude)>
excludeList = StarExcludeList() {
if (!(e instanceof SqlIdentifier)) {
throw SqlUtil.newContextException(excludeList.getParserPosition(),
Expand All @@ -2043,10 +2046,30 @@ SqlNode SelectExpression() :
return new SqlStarExclude(pos, sqlIdentifier, excludeList);
}
|
</#if>
<#if (parser.includeStarExclude!default.parser.includeStarExclude)>
replaceList = StarReplaceList() {
if (!(e instanceof SqlIdentifier)) {
throw SqlUtil.newContextException(replaceList.getParserPosition(),
RESOURCE.selectReplaceRequiresStar());
}
final SqlIdentifier sqlIdentifier = (SqlIdentifier) e;
if (!sqlIdentifier.isStar()) {
throw SqlUtil.newContextException(replaceList.getParserPosition(),
RESOURCE.selectReplaceRequiresStar());
}
final SqlParserPos pos = SqlParserPos.sum(
ImmutableList.of(sqlIdentifier.getParserPosition(),
replaceList.getParserPosition()));
return new SqlStarReplace(pos, sqlIdentifier, replaceList);
}
|
</#if>
{ return e; }
)
}

<#if (parser.includeStarExclude!default.parser.includeStarExclude)>
SqlNodeList StarExcludeList() :
{
final Span s;
Expand All @@ -2067,6 +2090,35 @@ SqlNodeList StarExcludeList() :
return new SqlNodeList(list, s.end(this));
}
}
</#if>

<#if (parser.includeStarExclude!default.parser.includeStarExclude)>
SqlNodeList StarReplaceList() :
{
final Span s;
final List<SqlNode> list = new ArrayList<SqlNode>();
SqlNode expr;
SqlIdentifier id;
}
{
<REPLACE> <LPAREN> { s = span(); }
expr = Expression(ExprContext.ACCEPT_SUB_QUERY) <AS> id = CompoundIdentifier() {
list.add(SqlStdOperatorTable.AS.createCall(
SqlParserPos.sum(ImmutableList.of(expr.getParserPosition(),
id.getParserPosition())), expr, id));
}
(
<COMMA> expr = Expression(ExprContext.ACCEPT_SUB_QUERY) <AS> id = CompoundIdentifier() {
list.add(SqlStdOperatorTable.AS.createCall(
SqlParserPos.sum(ImmutableList.of(expr.getParserPosition(),
id.getParserPosition())), expr, id));
}
)*
<RPAREN> {
return new SqlNodeList(list, s.end(this));
}
}
</#if>
<#else>
/**
* Parses one unaliased expression in a select list.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -810,12 +810,21 @@ ExInst<CalciteException> illegalArgumentForTableFunctionCall(String a0,
@BaseMessage("EXCLUDE/EXCEPT clause must follow a STAR expression")
ExInst<CalciteException> selectExcludeRequiresStar();

@BaseMessage("REPLACE clause must follow a STAR expression")
ExInst<CalciteException> selectReplaceRequiresStar();

@BaseMessage("SELECT * EXCLUDE/EXCEPT list contains unknown column(s): {0}")
ExInst<SqlValidatorException> selectStarExcludeListContainsUnknownColumns(String columns);

@BaseMessage("SELECT * EXCLUDE/EXCEPT list cannot exclude all columns")
ExInst<SqlValidatorException> selectStarExcludeCannotExcludeAllColumns();

@BaseMessage("SELECT * REPLACE list contains unknown column(s): {0}")
ExInst<SqlValidatorException> selectStarReplaceListContainsUnknownColumns(String columns);

@BaseMessage("SELECT * REPLACE list contains duplicate column(s): {0}")
ExInst<SqlValidatorException> selectStarReplaceListContainsDuplicateColumns(String columns);

@BaseMessage("Group function ''{0}'' can only appear in GROUP BY clause")
ExInst<SqlValidatorException> groupFunctionMustAppearInGroupByClause(String funcName);

Expand Down
84 changes: 84 additions & 0 deletions core/src/main/java/org/apache/calcite/sql/SqlStarReplace.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.calcite.sql;

import org.apache.calcite.sql.parser.SqlParserPos;

import com.google.common.collect.ImmutableList;

import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.List;

import static java.util.Objects.requireNonNull;

/**
* Represents {@code SELECT * REPLACE(expr AS column, ...)}.
*/
public class SqlStarReplace extends SqlCall {
public static final SqlOperator OPERATOR =
new SqlSpecialOperator("SELECT_STAR_REPLACE", SqlKind.OTHER) {
@SuppressWarnings("argument.type.incompatible")
@Override public SqlCall createCall(
@Nullable SqlLiteral functionQualifier,
SqlParserPos pos,
@Nullable SqlNode... operands) {
return new SqlStarReplace(
pos,
(SqlIdentifier) operands[0],
(SqlNodeList) operands[1]);
}
};

private final SqlIdentifier starIdentifier;
private final SqlNodeList replaceList;

public SqlStarReplace(SqlParserPos pos, SqlIdentifier starIdentifier,
SqlNodeList replaceList) {
super(pos);
this.starIdentifier = requireNonNull(starIdentifier, "starIdentifier");
this.replaceList = requireNonNull(replaceList, "replaceList");
}

public SqlIdentifier getStarIdentifier() {
return starIdentifier;
}

public SqlNodeList getReplaceList() {
return replaceList;
}

@Override public SqlOperator getOperator() {
return OPERATOR;
}

@Override public SqlKind getKind() {
return OPERATOR.getKind();
}

@Override public List<SqlNode> getOperandList() {
return ImmutableList.of(starIdentifier, replaceList);
}

@Override public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
starIdentifier.unparse(writer, leftPrec, rightPrec);
writer.sep("REPLACE");
final SqlWriter.Frame frame = writer.startList("(", ")");
replaceList.unparse(writer, 0, 0);
writer.endList(frame);
}
}
Loading
Loading