@@ -1675,15 +1675,103 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
16751675 case K_UNION: case K_EXCEPT: case K_INTERSECT: case K_MINUS:
16761676 return true;
16771677 case K_GROUP: case K_ORDER:
1678- return following == K_BY;
1678+ // not a column named group aliased by: SELECT a, group by FROM t
1679+ int afterBy = getToken(4).kind;
1680+ return following == K_BY && afterBy != K_FROM && afterBy != EOF
1681+ && afterBy != CLOSING_BRACKET && afterBy != ST_SEMICOLON
1682+ && !",".equals(getToken(4).image);
16791683 case K_LIMIT: case K_OFFSET:
1680- return following == S_LONG || following == OPENING_BRACKET
1681- || "?".equals(getToken(3).image);
1684+ return following == S_LONG || "?".equals(getToken(3).image);
16821685 default:
16831686 return false;
16841687 }
16851688 }
16861689
1690+ /**
1691+ * Whether the REPLACE at {@code offset} is DuckDB's unparenthesized {@code REPLACE expr AS name}:
1692+ * an AS follows at the top level before the end of the select item, so that a star aliased
1693+ * "replace" keeps parsing. With {@code offset} 0, the REPLACE has been consumed.
1694+ */
1695+ private boolean isUnparenthesizedReplaceAhead(int offset) {
1696+ Token token = getToken(offset);
1697+ if (offset > 0 && token.kind != K_REPLACE) {
1698+ return false;
1699+ }
1700+ int depth = 0;
1701+ while (true) {
1702+ token = nextWithItemToken(token);
1703+ if (token.kind == EOF || ";".equals(token.image)) {
1704+ return false;
1705+ }
1706+ if ("(".equals(token.image) || "[".equals(token.image) || "{".equals(token.image)) {
1707+ depth++;
1708+ } else if (")".equals(token.image) || "]".equals(token.image)
1709+ || "}".equals(token.image)) {
1710+ if (--depth < 0) {
1711+ return false;
1712+ }
1713+ } else if (depth == 0) {
1714+ if (token.kind == K_AS) {
1715+ return true;
1716+ }
1717+ if (token.kind == K_FROM || ",".equals(token.image)) {
1718+ return false;
1719+ }
1720+ }
1721+ }
1722+ }
1723+
1724+ private boolean isUnparenthesizedReplaceAhead() {
1725+ return isUnparenthesizedReplaceAhead(1);
1726+ }
1727+
1728+ /**
1729+ * Whether DuckDB's {@code RENAME (column AS name, ...)} or {@code RENAME column AS name}
1730+ * follows, every column being renamed with AS, so that a star aliased "rename" keeps parsing.
1731+ */
1732+ private boolean isRenameAhead() {
1733+ Token token = getToken(1);
1734+ if (token.kind != K_RENAME) {
1735+ return false;
1736+ }
1737+ token = nextWithItemToken(token);
1738+ boolean parenthesized = "(".equals(token.image);
1739+ if (parenthesized) {
1740+ token = nextWithItemToken(token);
1741+ }
1742+ while (true) {
1743+ // column [. column]* AS name
1744+ while (true) {
1745+ if (!isIdentifierishNameToken(token)) {
1746+ return false;
1747+ }
1748+ token = nextWithItemToken(token);
1749+ if (!".".equals(token.image)) {
1750+ break;
1751+ }
1752+ token = nextWithItemToken(token);
1753+ }
1754+ if (token.kind != K_AS) {
1755+ return false;
1756+ }
1757+ token = nextWithItemToken(token);
1758+ if (!isIdentifierishNameToken(token)) {
1759+ return false;
1760+ }
1761+ token = nextWithItemToken(token);
1762+ if (!parenthesized) {
1763+ return true;
1764+ }
1765+ if (")".equals(token.image)) {
1766+ return true;
1767+ }
1768+ if (!",".equals(token.image)) {
1769+ return false;
1770+ }
1771+ token = nextWithItemToken(token);
1772+ }
1773+ }
1774+
16871775 /** DuckDB's unparenthesized REPLACE and RENAME name their single item: * RENAME i AS x. */
16881776 private static void requireTransformerAlias(SelectItem<?> item) throws ParseException {
16891777 if (item.getAlias() == null) {
@@ -8667,7 +8755,8 @@ boolean AllColumnsTransformerAhead() #void:
86678755 | <K_EXCLUDE> ParenthesedColumnList() )
86688756 { return true; }
86698757 // DuckDB: * EXCLUDE i, * REPLACE expr AS i, * RENAME [(]i AS x[)]
8670- | LOOKAHEAD( <K_EXCLUDE> Column() | <K_REPLACE> | <K_RENAME> ) { return true; }
8758+ | LOOKAHEAD( <K_EXCLUDE> Column() ) { return true; }
8759+ | LOOKAHEAD({ isUnparenthesizedReplaceAhead() || isRenameAhead() }) { return true; }
86718760 | { return false; }
86728761}
86738762
@@ -8789,7 +8878,8 @@ ColumnsTransformer ColumnsTransformer():
87898878 { transformer = new ColumnsTransformer(ColumnsTransformer.ColumnsTransformerType.REPLACE)
87908879 .setStrict(strict); }
87918880 (
8792- LOOKAHEAD("(") "(" replaceItems = SelectItemsList() ")"
8881+ LOOKAHEAD({ "(".equals(getToken(1).image) && !isUnparenthesizedReplaceAhead(0) })
8882+ "(" replaceItems = SelectItemsList() ")"
87938883 |
87948884 item = SelectItem()
87958885 {
@@ -8834,6 +8924,7 @@ AllColumns AllColumns(boolean allowAdditions):
88348924 // BigData allows EXCEPT, DuckDB allows EXCLUDE and RENAME, ClickHouse allows APPLY;
88358925 // they may repeat and combine in any order
88368926 (
8927+ // AllColumnsTransformerAhead() tells DuckDB's REPLACE and RENAME from an alias of the star
88378928 LOOKAHEAD( <K_APPLY> "(" | <K_REPLACE> | <K_RENAME>
88388929 | <K_EXCEPT> [ <K_STRICT> ] ( ParenthesedColumnList() | <S_CHAR_LITERAL> )
88398930 | <K_EXCLUDE> ( ParenthesedColumnList() | Column() ) )
0 commit comments