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}