001package gudusoft.gsqlparser.nodes;
002
003import gudusoft.gsqlparser.*;
004import gudusoft.gsqlparser.stmt.TDeleteSqlStatement;
005import gudusoft.gsqlparser.stmt.TDescribeStmt;
006import gudusoft.gsqlparser.stmt.TInsertSqlStatement;
007import gudusoft.gsqlparser.stmt.TSelectSqlStatement;
008import gudusoft.gsqlparser.stmt.TUpdateSqlStatement;
009import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType;
010import gudusoft.gsqlparser.util.SQLUtil;
011
012import java.util.ArrayList;
013
014/**
015 * A common table expression permits defining a result table with a table-name that can be specified as a table name in any FROM clause of the fullselect that follows.
016 *<p> Multiple common table expressions can be specified following the single WITH keyword.
017 *<p> Each common table expression specified can also be referenced by name in the FROM clause of subsequent common table expressions.
018 *<p> Syntax:
019 * <blockquote><pre>
020 * table-name [column-name [,...n]] AS (fullselect)</pre>
021 * </blockquote>
022*/
023public class TCTE extends TTable{
024
025    private boolean recursive = false;
026
027    public void setRecursive(boolean recursive) {
028        this.recursive = recursive;
029    }
030
031    public boolean isRecursive() {
032        return recursive;
033    }
034
035    public void initAttributesFromColumnList(){
036        if (this.getColumnList() != null){
037            int i = 0;
038            if ((this.getSubquery() != null)&&(this.getSubquery().getResultColumnList() != null)){
039                if (this.getSubquery().getResultColumnList().size() != this.getColumnList().size()){
040                    if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
041                        TBaseType.log(String.format("CTE: <%s> columns list size is not the same as select list in subQuery, maybe due to list -> * ", this.getTableName().toString())
042                                ,TLog.ERROR,this.getTableName());
043                    }
044                }
045            }
046            for(TObjectName column: this.getColumnList()){
047                if ((this.getSubquery() != null)&&(this.getSubquery().getResultColumnList(true) != null)){
048                    TResultColumnList resultColumnListFromSubquery = this.getSubquery().getResultColumnList(true);
049                    //  关联 subQuery 中 select list
050                       // relationAttributes.add(new TAttributeNode( this.getTableName().toString()+"." + column.toString() ,this,this.getSubquery().getResultColumnList().getResultColumn(i) ));
051                    // Fix: When subquery has SELECT *, all CTE columns should reference the single star column
052                    TResultColumn resultColumn = null;
053                    if (resultColumnListFromSubquery.size() == 1 &&
054                            resultColumnListFromSubquery.getResultColumn(0).toString().endsWith("*")) {
055                        // All CTE columns map to the single star column
056                        resultColumn = resultColumnListFromSubquery.getResultColumn(0);
057                    } else if (i < resultColumnListFromSubquery.size()) {
058                        // Normal mapping by position
059                        resultColumn = resultColumnListFromSubquery.getResultColumn(i);
060                    }
061                    TAttributeNode.addNodeToList(new TAttributeNode( this.getTableName().toString()+"." + column.toString() ,this,resultColumn ),getAttributes());
062
063                }else{
064                        //relationAttributes.add(new TAttributeNode( this.getTableName().toString()+"." + column.toString() ,this));
065                    TAttributeNode.addNodeToList(new TAttributeNode( this.getTableName().toString()+"." + column.toString() ,this),getAttributes());
066                }
067                i++;
068            }
069            //
070            if ((this.getSubquery() != null)&&(this.getSubquery().isCombinedQuery())&&(this.getSubquery().getLeftStmt().getResultColumnList() != null)){
071                TSelectSqlStatement left = subquery.getLeftStmt();
072
073                String prefix = this.getTableName().toString()+".";
074                addNewAttributeFromSubQuery(left.getResultColumnList(),getAttributes(),prefix,this.getColumnList());
075                while (left.isCombinedQuery()){
076                    left = left.getLeftStmt();
077                    addNewAttributeFromSubQuery(left.getResultColumnList(),getAttributes(),prefix,this.getColumnList());
078                }
079            }
080        }
081
082        if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
083            TBaseType.log(String.format("Prepare attributes for CTE: <%s> via column list",this.getTableName().toString()),TLog.DEBUG,this.getTableName());
084            for(TAttributeNode node:getAttributes()){
085                TBaseType.log(String.format("\tAttribute: <%s>, Select list column: <%s>",node.getName(),node.getSubLevelResultColumn()!=null?node.getSubLevelResultColumn().toString():"N/A" ),TLog.DEBUG);
086            }
087        }
088
089    }
090    public void initAttributesFromSubQuery(){
091        if (this.getSubquery() == null) return;
092        super.initAttributesFromSubquery(this.getSubquery(),this.getTableName().toString()+".");
093
094        if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
095            TBaseType.log(String.format("Prepare attributes (num = %d) for CTE: <%s> via subQuery",getAttributes().size() ,this.getTableName().toString()),TLog.DEBUG,this.getTableName());
096        }
097        int c = 0;
098        for(TAttributeNode node:getAttributes()){
099            if (node.getSqlColumn() != null){
100                if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
101                    TBaseType.log(String.format("\tAttribute: <%s>, SQL column: <%s>",node.getName(), node.getSqlColumn()!=null?node.getSqlColumn().toString():"N/A" ),TLog.DEBUG);
102                }
103            }else{
104                if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
105                    TBaseType.log(String.format("\tAttribute: <%s>, Select list column: <%s>",node.getName(), node.getSubLevelResultColumn()!=null?node.getSubLevelResultColumn().toString():"N/A" ),TLog.DEBUG);
106                }
107            }
108            if (node.getAccompaniedAttributeNodes().size() >0 ){
109                if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
110                    TBaseType.log(String.format("\t\tAccompanied nodes: %d",node.getAccompaniedAttributeNodes().size()),TLog.DEBUG);
111                }
112                for(TAttributeNode n:node.getAccompaniedAttributeNodes()){
113                    if (n.getSqlColumn() != null){
114                        if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
115                            TBaseType.log(String.format("\t\tAttribute: <%s>, SQL column: <%s>",n.getName(), n.getSqlColumn()!=null?n.getSqlColumn().toString():"N/A" ),TLog.DEBUG);
116                        }
117                    }else{
118                        if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
119                            TBaseType.log(String.format("\t\tAttribute: <%s>, Select list column: <%s>",n.getName(), n.getSubLevelResultColumn()!=null?n.getSubLevelResultColumn().toString():"N/A" ),TLog.DEBUG);
120                        }
121                    }
122                }
123            }
124
125            c++;
126            if (c > TLog.OUTPUT_ATTRIBUTES_MAX){
127                if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
128                    TBaseType.log(String.format("\t...skipped after output %d attributes",c-1),TLog.DEBUG,this.getTableName().getStartToken());
129                }
130
131                break;
132            }
133        }
134    }
135
136    public String getRelationName(){
137        return this.getTableName().toString();
138    }
139
140//    public ArrayList<TAttributeNode> getAttributes(){
141//        return relationAttributes;
142//    }
143
144    private TSelectSqlNode selectNode;
145    private TSelectSqlStatement subquery;
146
147    private  TInsertSqlNode insertSqlNode;
148    private TInsertSqlStatement insertStmt;
149
150    private  TUpdateSqlNode updateSqlNode;
151    private TUpdateSqlStatement updateStmt;
152
153    private TDeleteSqlNode deleteSqlNode;
154    private TDeleteSqlStatement deleteStmt;
155
156    // Databricks: DESCRIBE HISTORY (and other DESCRIBE forms) as a CTE body,
157    // e.g. WITH h AS (DESCRIBE HISTORY t) SELECT ... (Mantis 4641)
158    private TDescribeSqlNode describeSqlNode;
159    private TDescribeStmt describeStmt;
160
161    private TCustomSqlStatement preparableStmt;
162
163    public  boolean searchColumnInResultSet(TCustomSqlStatement pSql,TTable pTable,TObjectName pColumn,boolean pMustIn){
164      boolean lcResult = false;
165        if (getColumnList() != null){
166            for (int i=0;i<getColumnList().size();i++){
167                lcResult = SQLUtil.sameName(this.dbvendor, ESQLDataObjectType.dotColumn, getColumnList().getObjectName(i).toString(), pColumn.getColumnNameOnly());
168                //pColumn.setSourceColumn(getColumnList().getObjectName(i));
169                if (lcResult) break;
170            }
171            return lcResult;
172        }
173
174        TCustomSqlStatement lcStmt = pSql;
175        while (lcStmt != null){
176            if (lcStmt == getSubquery()){
177                pTable.getLinkedColumns().addObjectName(pColumn);
178                pColumn.setSourceTable(pTable);
179                lcResult = true;
180                break;
181            }
182            lcStmt = lcStmt.getParentStmt();
183        }
184
185        if (lcResult) return true;
186
187        if (getSubquery() == null) return false;
188        lcResult = getSubquery().searchColumnInResultSet(pColumn,pMustIn);
189
190        return lcResult;
191    }
192
193    /**
194     * preparable statement can be:
195     * <p> {@link #getSubquery()}
196     * <p> or{@link #getUpdateStmt()}
197     * <p> or{@link #getInsertStmt()}
198     * <p> or{@link #getDeleteStmt()}
199     * @return
200     */
201    public TCustomSqlStatement getPreparableStmt() {
202        preparableStmt = null;
203        if (subquery!=null){
204            preparableStmt = subquery;
205        }else if (updateStmt != null){
206            preparableStmt = updateStmt;
207        }else if (deleteStmt != null){
208            preparableStmt = deleteStmt;
209        }else if (insertStmt != null){
210            preparableStmt = insertStmt;
211        }else if (describeStmt != null){
212            preparableStmt = describeStmt;
213        }
214
215        return preparableStmt;
216    }
217
218    public TUpdateSqlStatement getUpdateStmt() {
219        return updateStmt;
220    }
221
222    public TInsertSqlStatement getInsertStmt() {
223
224        return insertStmt;
225    }
226
227    public TDeleteSqlStatement getDeleteStmt() {
228
229        return deleteStmt;
230    }
231
232    /**
233     * DESCRIBE statement used as the body of this CTE (Databricks
234     * DESCRIBE HISTORY inside WITH), or null.
235     */
236    public TDescribeStmt getDescribeStmt() {
237
238        return describeStmt;
239    }
240
241    /**
242     * @return table name of this common table expression.
243     */
244    public TObjectName getTableName() {
245        return tableName;
246    }
247
248    private TObjectName tableName;
249
250    public void setColumnList(TObjectNameList columnList) {
251        this.columnList = columnList;
252    }
253
254    /**
255     * fullselect of this common table expression.
256     * @return
257     */
258    public TSelectSqlStatement getSubquery() {
259        return subquery;
260    }
261
262    /**
263     * @return List of column name of this common table expression.
264
265     */
266    public TObjectNameList getColumnList() {
267        return columnList;
268    }
269
270    private TObjectNameList columnList = null;
271
272    /**
273     * ClickHouse: the columned-CTE grammar ({@code WITH t(c1, c2) AS (subquery)})
274     * parses the parenthesised column list as general expressions so the same
275     * {@code name '('} prefix can also serve function-call expression-CTEs
276     * ({@code WITH plus(1,2) AS x}) without a reduce/reduce conflict. Convert
277     * the expressions back to a column-name list. All-or-nothing: if any
278     * element is not a plain column reference (native ClickHouse rejects such
279     * a list), NO column list is set — compacting the valid entries would
280     * silently shift every later column onto the wrong subquery position.
281     */
282    public void setColumnListFromExpressions(TExpressionList exprs){
283        TObjectNameList cols = null;
284        for(int i=0; i<exprs.size(); i++){
285            TObjectName name = exprs.getExpression(i).getObjectOperand();
286            if (name == null) return;
287            if (cols == null) cols = new TObjectNameList();
288            cols.addObjectName(name);
289        }
290        if (cols != null) setColumnList(cols);
291    }
292
293    public void init(Object arg1,Object arg2)
294    {
295       tableName = (TObjectName)arg1;
296       //tableName.parseTablename();
297       //tableName.setObjectType(TObjectName.ttobjTableCTE);
298       tableName.setDbObjectType(EDbObjectType.cte);
299
300       if (arg2 instanceof TSelectSqlNode){
301       selectNode = (TSelectSqlNode)arg2;
302       }else if (arg2 instanceof TInsertSqlNode){
303           insertSqlNode = (TInsertSqlNode)arg2;
304       }else if (arg2 instanceof TUpdateSqlNode){
305           updateSqlNode = (TUpdateSqlNode)arg2;
306       }else if (arg2 instanceof TDeleteSqlNode){
307           deleteSqlNode = (TDeleteSqlNode)arg2;
308       }else if (arg2 instanceof TDescribeSqlNode){
309           describeSqlNode = (TDescribeSqlNode)arg2;
310       }else if (arg2 instanceof TExpression){
311           // ClickHouse expression-CTE: WITH <expr> AS <ident>
312           expression = (TExpression)arg2;
313       }
314    }
315
316    /**
317     * ClickHouse, the body of an expression-CTE ({@code WITH <expr> AS <ident>}) —
318     * a literal, lambda, function call or parenthesised expression that the
319     * identifier names. Null for subquery CTEs.
320     *
321     * @return the expression named by this CTE, null for subquery CTEs.
322     */
323    public TExpression getExpression() {
324        return expression;
325    }
326
327    private TExpression expression = null;
328
329    /**
330     * ClickHouse, set when the CTE is declared {@code AS MATERIALIZED (subquery)}.
331     *
332     * @return the MATERIALIZED keyword token, null otherwise.
333     */
334    public TSourceToken getMaterializedToken() {
335        return materializedToken;
336    }
337
338    public void setMaterializedToken(TSourceToken materializedToken) {
339        this.materializedToken = materializedToken;
340    }
341
342    private TSourceToken materializedToken = null;
343
344    public void incParenthesisCount() {
345        if (selectNode != null){
346            selectNode.incParenthesisCount();
347        }
348    }
349
350    public void doParse(TCustomSqlStatement psql, ESqlClause plocation){
351
352        if (selectNode != null){
353            //long t = System.currentTimeMillis();
354            subquery = new TSelectSqlStatement(psql.dbvendor);
355            subquery.rootNode = selectNode;
356            subquery.setQueryOfCTE(true);
357            subquery.setCteIncludeThisStmt(this);
358            subquery.doParseStatement(psql);
359            for(TTable t:subquery.getRelations()){
360                if (t.isCTEName() && t.getCTE() == this){
361                   // System.out.println("found cte name in the same level:"+ t.getCTE().toString());
362                    t.setCTEName(false);
363                    t.setCTE(null);
364                }
365            }
366//            if (columnList == null){
367//                columnList = new TObjectNameList();
368//                for(TResultColumn resultColumn:subquery.getResultColumnList()){
369//                    columnList.addObjectName(new TObjectName());
370//
371//                }
372//            }
373            //System.out.println("Time Escaped: " + (System.currentTimeMillis() - t)+", sql size:"+ this.toString().length() );
374        }else if (insertSqlNode != null){
375            insertStmt = new TInsertSqlStatement(psql.dbvendor);
376            insertStmt.rootNode = insertSqlNode;
377            insertStmt.setCteIncludeThisStmt(this);
378            insertStmt.doParseStatement(psql);
379        }else if (deleteSqlNode != null){
380            deleteStmt = new TDeleteSqlStatement(psql.dbvendor);
381            deleteStmt.rootNode = deleteSqlNode;
382            deleteStmt.setCteIncludeThisStmt(this);
383            deleteStmt.doParseStatement(psql);
384        }else if (updateSqlNode != null){
385            updateStmt = new TUpdateSqlStatement(psql.dbvendor);
386            updateStmt.rootNode = updateSqlNode;
387            updateStmt.setCteIncludeThisStmt(this);
388            updateStmt.doParseStatement(psql);
389        }else if (describeSqlNode != null){
390            describeStmt = new TDescribeStmt(psql.dbvendor);
391            describeStmt.rootNode = describeSqlNode;
392            describeStmt.doParseStatement(psql);
393        }
394    }
395
396    public void accept(TParseTreeVisitor v){
397        v.preVisit(this);
398        v.postVisit(this);
399    }
400
401    public void acceptChildren(TParseTreeVisitor v){
402        v.preVisit(this);
403        if (subquery != null){
404            subquery.acceptChildren(v);
405        }
406        if (expression != null){
407            // acceptChildren, not accept: TExpression.accept only visits the
408            // wrapper node, so resolver2's scope builder would never collect
409            // the definition's column references.
410            expression.acceptChildren(v);
411        }
412        v.postVisit(this);
413    }
414
415    public void setSubquery(TSelectSqlStatement subquery) {
416        this.subquery = subquery;
417    }
418
419    public void setInsertStmt(TInsertSqlStatement insertStmt) {
420        this.insertStmt = insertStmt;
421    }
422
423    public void setUpdateStmt(TUpdateSqlStatement updateStmt) {
424        this.updateStmt = updateStmt;
425    }
426
427    public void setDeleteStmt(TDeleteSqlStatement deleteStmt) {
428        this.deleteStmt = deleteStmt;
429    }
430
431    public void setPreparableStmt(TCustomSqlStatement preparableStmt) {
432        this.preparableStmt = preparableStmt;
433    }
434
435    public void setTableName(TObjectName tableName) {
436        this.tableName = tableName;
437    }
438}