Parses PIVOT clause with the following syntax: PIVOT ( agg_func(col) [AS alias], ... FOR pivot_col IN (val1 [AS alias1], val2 [AS alias2], ... | subquery) [FOR pivot_col2 IN (...)] [GROUP BY col1, col2, ...] ) [alias] Note: ELSE clause is not supported in PIVO
(GenericLexer lexer, IQueryModel model, SqlParserCallback sqlParserCallback)
| 3886 | * do not support ELSE in PIVOT. For such requirements, user can use subqueries instead. |
| 3887 | */ |
| 3888 | private CharSequence parsePivot(GenericLexer lexer, IQueryModel model, SqlParserCallback sqlParserCallback) throws SqlException { |
| 3889 | CharSequence tok; |
| 3890 | expectTok(lexer, '('); |
| 3891 | |
| 3892 | // Parse aggregate functions. |
| 3893 | FunctionFactoryCache functionFactoryCache = cairoEngine.getFunctionFactoryCache(); |
| 3894 | pivotAliasMap.clear(); |
| 3895 | do { |
| 3896 | model.addPivotGroupByColumn(parsePivotAggregateColumn(lexer, model, functionFactoryCache, sqlParserCallback)); |
| 3897 | tok = optTok(lexer); |
| 3898 | } while (tok != null && isNotForKeyword(tok) && isComma(tok)); |
| 3899 | |
| 3900 | ObjList<QueryColumn> pivotGroupByCols = model.getPivotGroupByColumns(); |
| 3901 | boolean hasNoAlias = false; |
| 3902 | for (int i = 0, n = pivotGroupByCols.size(); i < n; i++) { |
| 3903 | QueryColumn qc = pivotGroupByCols.getQuick(i); |
| 3904 | if (qc.getAlias() == null) { |
| 3905 | hasNoAlias = true; |
| 3906 | CharacterStoreEntry entry = characterStore.newEntry(); |
| 3907 | qc.getAst().toSink(entry); |
| 3908 | CharSequence alias = SqlUtil.createExprColumnAlias( |
| 3909 | characterStore, |
| 3910 | entry.toImmutable(), |
| 3911 | pivotAliasMap, |
| 3912 | aliasSequenceMap, |
| 3913 | configuration.getColumnAliasGeneratedMaxSize(), |
| 3914 | true |
| 3915 | ); |
| 3916 | pivotAliasMap.add(alias); |
| 3917 | qc.setAlias(alias, qc.getAst().position); |
| 3918 | } |
| 3919 | } |
| 3920 | model.setPivotGroupByColumnHasNoAlias(hasNoAlias && pivotGroupByCols.size() == 1); |
| 3921 | |
| 3922 | if (tok == null || isNotForKeyword(tok)) { |
| 3923 | throw SqlException.$(lexer.lastTokenPosition(), "expected FOR"); |
| 3924 | } |
| 3925 | |
| 3926 | // Parse FOR expressions (e.g., FOR region IN ('East', 'West') ELSE 'Other') |
| 3927 | // We parse "col IN (values)" separately (not as standard IN expression) because |
| 3928 | // PIVOT supports per-value aliases (e.g., 'value' AS alias) which regular IN doesn't. |
| 3929 | while (true) { |
| 3930 | ExpressionNode inColumnExpr; |
| 3931 | try { |
| 3932 | // Stop at top-level IN operator to handle values list with alias support |
| 3933 | expressionParser.setStopOnTopINOperator(true); |
| 3934 | inColumnExpr = expr(lexer, model, sqlParserCallback); |
| 3935 | } finally { |
| 3936 | expressionParser.setStopOnTopINOperator(false); |
| 3937 | } |
| 3938 | if (inColumnExpr == null) { |
| 3939 | throw SqlException.$(lexer.lastTokenPosition(), "expected IN expression"); |
| 3940 | } |
| 3941 | if (hasGroupByFunc(sqlNodeStack, functionFactoryCache, inColumnExpr)) { |
| 3942 | throw SqlException.$(inColumnExpr.position, "aggregate functions are not supported in PIVOT FOR expressions"); |
| 3943 | } |
| 3944 | |
| 3945 | expectTok(lexer, "in"); |
no test coverage detected