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