@@ -1729,15 +1729,103 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
17291729 case K_UNION: case K_EXCEPT: case K_INTERSECT: case K_MINUS:
17301730 return true;
17311731 case K_GROUP: case K_ORDER:
1732- return following == K_BY;
1732+ // not a column named group aliased by: SELECT a, group by FROM t
1733+ int afterBy = getToken(4).kind;
1734+ return following == K_BY && afterBy != K_FROM && afterBy != EOF
1735+ && afterBy != CLOSING_BRACKET && afterBy != ST_SEMICOLON
1736+ && !",".equals(getToken(4).image);
17331737 case K_LIMIT: case K_OFFSET:
1734- return following == S_LONG || following == OPENING_BRACKET
1735- || "?".equals(getToken(3).image);
1738+ return following == S_LONG || "?".equals(getToken(3).image);
17361739 default:
17371740 return false;
17381741 }
17391742 }
17401743
1744+ /**
1745+ * Whether the REPLACE at {@code offset} is DuckDB's unparenthesized {@code REPLACE expr AS name}:
1746+ * an AS follows at the top level before the end of the select item, so that a star aliased
1747+ * "replace" keeps parsing. With {@code offset} 0, the REPLACE has been consumed.
1748+ */
1749+ private boolean isUnparenthesizedReplaceAhead(int offset) {
1750+ Token token = getToken(offset);
1751+ if (offset > 0 && token.kind != K_REPLACE) {
1752+ return false;
1753+ }
1754+ int depth = 0;
1755+ while (true) {
1756+ token = nextWithItemToken(token);
1757+ if (token.kind == EOF || ";".equals(token.image)) {
1758+ return false;
1759+ }
1760+ if ("(".equals(token.image) || "[".equals(token.image) || "{".equals(token.image)) {
1761+ depth++;
1762+ } else if (")".equals(token.image) || "]".equals(token.image)
1763+ || "}".equals(token.image)) {
1764+ if (--depth < 0) {
1765+ return false;
1766+ }
1767+ } else if (depth == 0) {
1768+ if (token.kind == K_AS) {
1769+ return true;
1770+ }
1771+ if (token.kind == K_FROM || ",".equals(token.image)) {
1772+ return false;
1773+ }
1774+ }
1775+ }
1776+ }
1777+
1778+ private boolean isUnparenthesizedReplaceAhead() {
1779+ return isUnparenthesizedReplaceAhead(1);
1780+ }
1781+
1782+ /**
1783+ * Whether DuckDB's {@code RENAME (column AS name, ...)} or {@code RENAME column AS name}
1784+ * follows, every column being renamed with AS, so that a star aliased "rename" keeps parsing.
1785+ */
1786+ private boolean isRenameAhead() {
1787+ Token token = getToken(1);
1788+ if (token.kind != K_RENAME) {
1789+ return false;
1790+ }
1791+ token = nextWithItemToken(token);
1792+ boolean parenthesized = "(".equals(token.image);
1793+ if (parenthesized) {
1794+ token = nextWithItemToken(token);
1795+ }
1796+ while (true) {
1797+ // column [. column]* AS name
1798+ while (true) {
1799+ if (!isIdentifierishNameToken(token)) {
1800+ return false;
1801+ }
1802+ token = nextWithItemToken(token);
1803+ if (!".".equals(token.image)) {
1804+ break;
1805+ }
1806+ token = nextWithItemToken(token);
1807+ }
1808+ if (token.kind != K_AS) {
1809+ return false;
1810+ }
1811+ token = nextWithItemToken(token);
1812+ if (!isIdentifierishNameToken(token)) {
1813+ return false;
1814+ }
1815+ token = nextWithItemToken(token);
1816+ if (!parenthesized) {
1817+ return true;
1818+ }
1819+ if (")".equals(token.image)) {
1820+ return true;
1821+ }
1822+ if (!",".equals(token.image)) {
1823+ return false;
1824+ }
1825+ token = nextWithItemToken(token);
1826+ }
1827+ }
1828+
17411829 /** DuckDB's unparenthesized REPLACE and RENAME name their single item: * RENAME i AS x. */
17421830 private static void requireTransformerAlias(SelectItem<?> item) throws ParseException {
17431831 if (item.getAlias() == null) {
@@ -8799,7 +8887,8 @@ boolean AllColumnsTransformerAhead() #void:
87998887 | <K_EXCLUDE> ParenthesedColumnList() )
88008888 { return true; }
88018889 // DuckDB: * EXCLUDE i, * REPLACE expr AS i, * RENAME [(]i AS x[)]
8802- | LOOKAHEAD( <K_EXCLUDE> Column() | <K_REPLACE> | <K_RENAME> ) { return true; }
8890+ | LOOKAHEAD( <K_EXCLUDE> Column() ) { return true; }
8891+ | LOOKAHEAD({ isUnparenthesizedReplaceAhead() || isRenameAhead() }) { return true; }
88038892 | { return false; }
88048893}
88058894
@@ -8921,7 +9010,8 @@ ColumnsTransformer ColumnsTransformer():
89219010 { transformer = new ColumnsTransformer(ColumnsTransformer.ColumnsTransformerType.REPLACE)
89229011 .setStrict(strict); }
89239012 (
8924- LOOKAHEAD("(") "(" replaceItems = SelectItemsList() ")"
9013+ LOOKAHEAD({ "(".equals(getToken(1).image) && !isUnparenthesizedReplaceAhead(0) })
9014+ "(" replaceItems = SelectItemsList() ")"
89259015 |
89269016 item = SelectItem()
89279017 {
@@ -8966,6 +9056,7 @@ AllColumns AllColumns(boolean allowAdditions):
89669056 // BigData allows EXCEPT, DuckDB allows EXCLUDE and RENAME, ClickHouse allows APPLY;
89679057 // they may repeat and combine in any order
89689058 (
9059+ // AllColumnsTransformerAhead() tells DuckDB's REPLACE and RENAME from an alias of the star
89699060 LOOKAHEAD( <K_APPLY> "(" | <K_REPLACE> | <K_RENAME>
89709061 | <K_EXCEPT> [ <K_STRICT> ] ( ParenthesedColumnList() | <S_CHAR_LITERAL> )
89719062 | <K_EXCLUDE> ( ParenthesedColumnList() | Column() ) )
0 commit comments