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
1 change: 1 addition & 0 deletions babel/src/main/codegen/config.fmpp
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,7 @@ data: {
includeIntervalWithoutQualifier: true
includeStarExclude: true
includeSelectBy: true
includeDistinctOn: true
}
}

Expand Down
29 changes: 29 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 @@ -521,6 +521,35 @@ private void checkSqlResult(String funLibrary, String query, String result) {
.returns(result);
}

/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-5406">[CALCITE-5406]
* Support the SELECT DISTINCT ON statement</a>. */
@Test void testDistinctOn() {
final SqlValidatorFixture v = Fixtures.forValidator()
.withParserConfig(c -> c.withParserFactory(SqlBabelParserImpl.FACTORY))
.withConformance(SqlConformanceEnum.BABEL);

// Basic DISTINCT ON
v.withSql("select distinct on (deptno) empno, ename from emp order by deptno, empno")
.ok();

// DISTINCT ON with multiple columns
v.withSql("select distinct on (deptno, job) empno, ename from emp order by deptno, job, empno")
.ok();

// DISTINCT ON with expression
v.withSql("select distinct on (deptno) empno, sal * 12 as annual_sal "
+ "from emp order by deptno, sal desc").ok();

// DISTINCT ON without ORDER BY should fail
v.withSql("^select distinct on (deptno) empno from emp^")
.fails("SELECT DISTINCT ON requires an ORDER BY clause");

// DISTINCT ON with ORDER BY mismatch should fail
v.withSql("select distinct on (deptno) empno from emp order by ^empno^")
.fails("SELECT DISTINCT ON expressions must match initial ORDER BY expressions");
}

/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-7337">[CALCITE-7337]
* Add age function (enabled in PostgreSQL library)</a>. */
Expand Down
78 changes: 78 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,82 @@ select d1.* except(d1.dname) from dept d1 except(select d2.* except(d2.dname) fr

!ok

# [CALCITE-5406] Support the SELECT DISTINCT ON statement for PostgreSQL dialect

# Test basic DISTINCT ON
SELECT DISTINCT ON (deptno) empno, ename, deptno
FROM emp
ORDER BY deptno, empno;
+-------+-------+--------+
| EMPNO | ENAME | DEPTNO |
+-------+-------+--------+
| 7782 | CLARK | 10 |
| 7369 | SMITH | 20 |
| 7499 | ALLEN | 30 |
+-------+-------+--------+
(3 rows)

!ok

# Test DISTINCT ON with descending order
SELECT DISTINCT ON (deptno) empno, ename, sal, deptno
FROM emp
ORDER BY deptno, sal DESC, empno;
+-------+-------+---------+--------+
| EMPNO | ENAME | SAL | DEPTNO |
+-------+-------+---------+--------+
| 7839 | KING | 5000.00 | 10 |
| 7788 | SCOTT | 3000.00 | 20 |
| 7698 | BLAKE | 2850.00 | 30 |
+-------+-------+---------+--------+
(3 rows)

!ok

# Test DISTINCT ON with multiple columns
SELECT DISTINCT ON (deptno, job) empno, ename, deptno, job
FROM emp
ORDER BY deptno, job, empno;
+-------+--------+--------+-----------+
| EMPNO | ENAME | DEPTNO | JOB |
+-------+--------+--------+-----------+
| 7934 | MILLER | 10 | CLERK |
| 7782 | CLARK | 10 | MANAGER |
| 7839 | KING | 10 | PRESIDENT |
| 7788 | SCOTT | 20 | ANALYST |
| 7369 | SMITH | 20 | CLERK |
| 7566 | JONES | 20 | MANAGER |
| 7900 | JAMES | 30 | CLERK |
| 7698 | BLAKE | 30 | MANAGER |
| 7499 | ALLEN | 30 | SALESMAN |
+-------+--------+--------+-----------+
(9 rows)

!ok

# Test DISTINCT ON with expression
SELECT DISTINCT ON (deptno) empno, ename, sal * 12 AS annual_sal, deptno
FROM emp
ORDER BY deptno, sal DESC, empno;
+-------+-------+------------+--------+
| EMPNO | ENAME | ANNUAL_SAL | DEPTNO |
+-------+-------+------------+--------+
| 7788 | SCOTT | 36000.00 | 20 |
| 7839 | KING | 60000.00 | 10 |
| 7698 | BLAKE | 34200.00 | 30 |
+-------+-------+------------+--------+
(3 rows)

!ok

# Test DISTINCT ON unparse
SELECT DISTINCT ON (deptno) empno, ename, deptno
FROM emp
ORDER BY deptno, empno;

SELECT DISTINCT ON ("DEPTNO") "EMP"."EMPNO", "EMP"."ENAME", "EMP"."DEPTNO"
FROM "scott"."EMP" AS "EMP"
ORDER BY "DEPTNO", "EMPNO"
!explain-validated-on all

# End select.iq
5 changes: 3 additions & 2 deletions core/src/main/codegen/config.fmpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ data: {
# FMPP will use the declaration from default_config.fmpp.
parser: {
# Generated parser implementation package and class name.
package: "org.apache.calcite.sql.parser.impl",
class: "SqlParserImpl",
package: "org.apache.calcite.sql.parser.impl"
class: "SqlParserImpl"
includeDistinctOn: true

# List of files in @includes directory that have parser method
# implementations for parsing custom SQL statements, literals or types
Expand Down
1 change: 1 addition & 0 deletions core/src/main/codegen/default_config.fmpp
Original file line number Diff line number Diff line change
Expand Up @@ -461,4 +461,5 @@ parser: {
includeParsingStringLiteralAsArrayLiteral: false
includeIntervalWithoutQualifier: false
includeStarExclude: false
includeDistinctOn: false
}
31 changes: 30 additions & 1 deletion core/src/main/codegen/templates/Parser.jj
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,24 @@ List<SqlNode> FunctionParameterList(ExprContext exprContext) :
}
}

void AllOrDistinctOrDistinctOn(List<SqlLiteral> keywords, List<SqlNode> distinctOnList) :
{
final Span s;
final SqlNodeList distinctOn;
}
{
<ALL> { keywords.add(SqlSelectKeyword.ALL.symbol(getPos())); }
|
<DISTINCT> { s = span(); }
(
<ON> <LPAREN>
distinctOn = ExpressionCommaList(s, ExprContext.ACCEPT_SUB_QUERY)
<RPAREN>
{ distinctOnList.addAll(distinctOn.getList()); }
)?
{ keywords.add(SqlSelectKeyword.DISTINCT.symbol(s.end(this))); }
}

SqlLiteral AllOrDistinct() :
{
}
Expand Down Expand Up @@ -1371,6 +1389,7 @@ SqlSelect SqlSelect() :
final SqlNode qualify;
final SqlNodeList by;
final List<SqlNode> hints = new ArrayList<SqlNode>();
final List<SqlNode> distinctOnList = new ArrayList<SqlNode>();
final Span s;
}
{
Expand All @@ -1382,9 +1401,15 @@ SqlSelect SqlSelect() :
keywords.add(SqlSelectKeyword.STREAM.symbol(getPos()));
}
)?
<#if parser.includeDistinctOn!default.parser.includeDistinctOn>
(
AllOrDistinctOrDistinctOn(keywords, distinctOnList)
)?
<#else>
(
keyword = AllOrDistinct() { keywords.add(keyword); }
)?
</#if>
{
keywordList = new SqlNodeList(keywords, s.addAll(keywords).pos());
}
Expand Down Expand Up @@ -1413,10 +1438,14 @@ SqlSelect SqlSelect() :
}
)
{
final SqlNodeList distinctOn = distinctOnList.isEmpty()
? null
: new SqlNodeList(distinctOnList, Span.of(distinctOnList).pos());
final SqlSelect select = new SqlSelect(s.end(this), keywordList,
new SqlNodeList(selectList, Span.of(selectList).pos()),
fromClause, where, groupBy, having, windowDecls, qualify,
null, null, null, new SqlNodeList(hints, getPos()));
null, null, null, new SqlNodeList(hints, getPos()),
distinctOn);
SqlByRewriter.rewrite(select, by);
return select;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,15 @@ ExInst<SqlValidatorException> intervalFieldExceedsPrecision(Number a0,
@BaseMessage("QUALIFY expression ''{0}'' must contain a window function")
ExInst<SqlValidatorException> qualifyExpressionMustContainWindowFunction(String a0);

@BaseMessage("SELECT DISTINCT ON is not supported under the current SQL conformance level")
ExInst<SqlValidatorException> distinctOnNotAllowed();

@BaseMessage("SELECT DISTINCT ON requires an ORDER BY clause")
ExInst<SqlValidatorException> distinctOnRequiresOrderBy();

@BaseMessage("SELECT DISTINCT ON expressions must match initial ORDER BY expressions")
ExInst<SqlValidatorException> distinctOnOrderByMismatch();

@BaseMessage("ROW/RANGE not allowed with RANK, DENSE_RANK, ROW_NUMBER, PERCENTILE_CONT/DISC or LAG/LEAD functions")
ExInst<SqlValidatorException> rankWithFrame();

Expand Down
49 changes: 45 additions & 4 deletions core/src/main/java/org/apache/calcite/sql/SqlSelect.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
public static final int WHERE_OPERAND = 3;
public static final int HAVING_OPERAND = 5;
public static final int QUALIFY_OPERAND = 7;
public static final int DISTINCT_ON_OPERAND = 12;

SqlNodeList keywordList;
SqlNodeList selectList;
Expand All @@ -56,6 +57,7 @@
@Nullable SqlNode offset;
@Nullable SqlNode fetch;
@Nullable SqlNodeList hints;
@Nullable SqlNodeList distinctOn;
boolean hasByClause;

//~ Constructors -----------------------------------------------------------
Expand All @@ -72,7 +74,8 @@
@Nullable SqlNodeList orderBy,
@Nullable SqlNode offset,
@Nullable SqlNode fetch,
@Nullable SqlNodeList hints) {
@Nullable SqlNodeList hints,
@Nullable SqlNodeList distinctOn) {
super(pos);
this.keywordList = requireNonNull(keywordList != null
? keywordList : new SqlNodeList(pos));
Expand All @@ -88,10 +91,29 @@
this.offset = offset;
this.fetch = fetch;
this.hints = hints;
this.distinctOn = distinctOn;
this.hasByClause = false;
}

/** deprecated, without {@code qualify}. */
/** Constructor without {@code distinctOn}; distinctOn defaults to null. */
public SqlSelect(SqlParserPos pos,

Check warning on line 99 in core/src/main/java/org/apache/calcite/sql/SqlSelect.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Constructor has 13 parameters, which is greater than 7 authorized.

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZ4XPewz4aSQ9tYgI4YT&open=AZ4XPewz4aSQ9tYgI4YT&pullRequest=4933
@Nullable SqlNodeList keywordList,
SqlNodeList selectList,
@Nullable SqlNode from,
@Nullable SqlNode where,
@Nullable SqlNodeList groupBy,
@Nullable SqlNode having,
@Nullable SqlNodeList windowDecls,
@Nullable SqlNode qualify,
@Nullable SqlNodeList orderBy,
@Nullable SqlNode offset,
@Nullable SqlNode fetch,
@Nullable SqlNodeList hints) {
this(pos, keywordList, selectList, from, where, groupBy, having,
windowDecls, qualify, orderBy, offset, fetch, hints, null);
}

/** deprecated, without {@code qualify} and {@code distinctOn}. */
@Deprecated // to be removed before 2.0
public SqlSelect(SqlParserPos pos,
@Nullable SqlNodeList keywordList,
Expand All @@ -106,7 +128,7 @@
@Nullable SqlNode fetch,
@Nullable SqlNodeList hints) {
this(pos, keywordList, selectList, from, where, groupBy, having,
windowDecls, null, orderBy, offset, fetch, hints);
windowDecls, null, orderBy, offset, fetch, hints, null);
}

//~ Methods ----------------------------------------------------------------
Expand All @@ -122,7 +144,8 @@
@SuppressWarnings("nullness")
@Override public List<SqlNode> getOperandList() {
return ImmutableNullableList.of(keywordList, selectList, from, where,
groupBy, having, windowDecls, qualify, orderBy, offset, fetch, hints);
groupBy, having, windowDecls, qualify, orderBy, offset, fetch, hints,
distinctOn);
}

@Override public void setOperand(int i, @Nullable SqlNode operand) {
Expand Down Expand Up @@ -160,6 +183,12 @@
case 10:
fetch = operand;
break;
case 11:
hints = (SqlNodeList) operand;
break;
case 12:
distinctOn = (SqlNodeList) operand;
break;
default:
throw new AssertionError(i);
}
Expand Down Expand Up @@ -327,4 +356,16 @@
public boolean isKeywordPresent(SqlSelectKeyword targetKeyWord) {
return getModifierNode(targetKeyWord) != null;
}

public boolean isDistinctOn() {
return distinctOn != null && !distinctOn.isEmpty();
}

public @Nullable SqlNodeList getDistinctOn() {
return distinctOn;
}

public void setDistinctOn(@Nullable SqlNodeList distinctOn) {
this.distinctOn = distinctOn;
}
}
26 changes: 20 additions & 6 deletions core/src/main/java/org/apache/calcite/sql/SqlSelectOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,19 @@
* <p>Operands are:
*
* <ul>
* <li>0: distinct ({@link SqlLiteral})</li>
* <li>0: keywordList ({@link SqlNodeList})</li>
* <li>1: selectClause ({@link SqlNodeList})</li>
* <li>2: fromClause ({@link SqlCall} to "join" operator)</li>
* <li>3: whereClause ({@link SqlNode})</li>
* <li>4: havingClause ({@link SqlNode})</li>
* <li>5: groupClause ({@link SqlNode})</li>
* <li>4: groupClause ({@link SqlNodeList})</li>
* <li>5: havingClause ({@link SqlNode})</li>
* <li>6: windowClause ({@link SqlNodeList})</li>
* <li>7: orderClause ({@link SqlNode})</li>
* <li>7: qualifyClause ({@link SqlNode})</li>
* <li>8: orderClause ({@link SqlNodeList})</li>
* <li>9: offsetClause ({@link SqlNode})</li>
* <li>10: fetchClause ({@link SqlNode})</li>
* <li>11: hints ({@link SqlNodeList})</li>
* <li>12: distinctOn ({@link SqlNodeList})</li>
* </ul>
*/
public class SqlSelectOperator extends SqlOperator {
Expand Down Expand Up @@ -80,7 +85,8 @@ private SqlSelectOperator() {
(SqlNodeList) operands[8],
operands[9],
operands[10],
(SqlNodeList) operands[11]);
(SqlNodeList) operands[11],
operands.length > 12 ? (SqlNodeList) operands[12] : null);
}

/**
Expand Down Expand Up @@ -116,7 +122,8 @@ public SqlSelect createCall(
orderBy,
offset,
fetch,
hints);
hints,
null);
}

@Override public <R> void acceptCall(
Expand Down Expand Up @@ -152,6 +159,13 @@ public SqlSelect createCall(
final SqlNode keyword = select.keywordList.get(i);
keyword.unparse(writer, 0, 0);
}
if (select.isDistinctOn()) {
writer.keyword("ON");
final SqlWriter.Frame frame =
writer.startList("(", ")");
castNonNull(select.distinctOn).unparse(writer, 0, 0);
writer.endList(frame);
}
writer.topN(select.fetch, select.offset);
final SqlNodeList selectClause = select.selectList;
writer.list(SqlWriter.FrameTypeEnum.SELECT_LIST, SqlWriter.COMMA,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,8 @@ public abstract class SqlAbstractConformance implements SqlConformance {
@Override public boolean supportsUnsignedTypes() {
return SqlConformanceEnum.DEFAULT.supportsUnsignedTypes();
}

@Override public boolean isDistinctOnAllowed() {
return SqlConformanceEnum.DEFAULT.isDistinctOnAllowed();
}
}
Loading
Loading