001package gudusoft.gsqlparser.lineage.v2.producer; 002 003import java.util.ArrayDeque; 004import java.util.ArrayList; 005import java.util.Arrays; 006import java.util.Collections; 007import java.util.Comparator; 008import java.util.Deque; 009import java.util.HashMap; 010import java.util.HashSet; 011import java.util.IdentityHashMap; 012import java.util.List; 013import java.util.Locale; 014import java.util.Map; 015import java.util.Set; 016 017import gudusoft.gsqlparser.EDbVendor; 018import gudusoft.gsqlparser.EExpressionType; 019import gudusoft.gsqlparser.EJoinType; 020import gudusoft.gsqlparser.ESetOperatorType; 021import gudusoft.gsqlparser.ETableSource; 022import gudusoft.gsqlparser.ETokenType; 023import gudusoft.gsqlparser.lineage.v2.shape.TDb2ReturnStmt; 024import gudusoft.gsqlparser.lineage.v2.shape.TMssqlReturn; 025import gudusoft.gsqlparser.lineage.v2.shape.TReturnStmt; 026import gudusoft.gsqlparser.lineage.v2.shape.TCreateFunctionStmt; 027import gudusoft.gsqlparser.lineage.v2.shape.TDoExecuteBlockStmt; 028import gudusoft.gsqlparser.lineage.v2.shape.TCommonBlock; 029import gudusoft.gsqlparser.lineage.v2.shape.TMssqlBlock; 030import gudusoft.gsqlparser.lineage.v2.shape.TStoredProcedureSqlStatement; 031import gudusoft.gsqlparser.lineage.v2.shape.ShapeBuilder; 032import gudusoft.gsqlparser.lineage.v2.shape.TCTE; 033import gudusoft.gsqlparser.lineage.v2.shape.TCaseExpression; 034import gudusoft.gsqlparser.lineage.v2.shape.TCreateMaterializedSqlStatement; 035import gudusoft.gsqlparser.lineage.v2.shape.TCreateTableOption; 036import gudusoft.gsqlparser.lineage.v2.shape.TCreateTableSqlStatement; 037import gudusoft.gsqlparser.lineage.v2.shape.TCreateViewSqlStatement; 038import gudusoft.gsqlparser.lineage.v2.shape.TCustomSqlStatement; 039import gudusoft.gsqlparser.lineage.v2.shape.TDeleteSqlStatement; 040import gudusoft.gsqlparser.lineage.v2.shape.TExpression; 041import gudusoft.gsqlparser.lineage.v2.shape.TExpressionList; 042import gudusoft.gsqlparser.lineage.v2.shape.TFunctionCall; 043import gudusoft.gsqlparser.lineage.v2.shape.TGroupBy; 044import gudusoft.gsqlparser.lineage.v2.shape.THierarchical; 045import gudusoft.gsqlparser.lineage.v2.shape.TInsertCondition; 046import gudusoft.gsqlparser.lineage.v2.shape.TInsertIntoValue; 047import gudusoft.gsqlparser.lineage.v2.shape.TInsertSqlStatement; 048import gudusoft.gsqlparser.lineage.v2.shape.TJoin; 049import gudusoft.gsqlparser.lineage.v2.shape.TJoinItem; 050import gudusoft.gsqlparser.lineage.v2.shape.TJoinList; 051import gudusoft.gsqlparser.lineage.v2.shape.TMergeSqlStatement; 052import gudusoft.gsqlparser.lineage.v2.shape.TMergeWhenClause; 053import gudusoft.gsqlparser.lineage.v2.shape.TMultiTarget; 054import gudusoft.gsqlparser.lineage.v2.shape.TObjectName; 055import gudusoft.gsqlparser.lineage.v2.shape.TOutputClause; 056import gudusoft.gsqlparser.lineage.v2.shape.TPTNodeList; 057import gudusoft.gsqlparser.lineage.v2.shape.TParseTreeNode; 058import gudusoft.gsqlparser.lineage.v2.shape.TParseTreeVisitor; 059import gudusoft.gsqlparser.lineage.v2.shape.TPivotClause; 060import gudusoft.gsqlparser.lineage.v2.shape.TPivotInClause; 061import gudusoft.gsqlparser.lineage.v2.shape.TReturningClause; 062import gudusoft.gsqlparser.lineage.v2.shape.TSelectSqlStatement; 063import gudusoft.gsqlparser.lineage.v2.shape.TSourceToken; 064import gudusoft.gsqlparser.lineage.v2.shape.TLateralView; 065import gudusoft.gsqlparser.lineage.v2.shape.TTable; 066import gudusoft.gsqlparser.lineage.v2.shape.TUnnestClause; 067import gudusoft.gsqlparser.lineage.v2.shape.TUpdateSqlStatement; 068import gudusoft.gsqlparser.lineage.v2.shape.TWhenClauseItem; 069import gudusoft.gsqlparser.lineage.v2.shape.TWindowDef; 070import gudusoft.gsqlparser.lineage.v2.shape.TWindowDefinition; 071import gudusoft.gsqlparser.lineage.v2.shape.TWindowSpecification; 072 073/** 074 * The {@code sqllr-path-1} paths of one statement ({@code lineage-spec/algorithms/path-v1.md}). 075 * 076 * <p>PATH-V1 anchors a reference at "the innermost step whose construct contains the first character of the 077 * reference" (Section 6.1), so this index records, for every step the statement emits, the text range of its 078 * construct and its path, and answers {@link #anchor} by the innermost range containing an offset. Steps are 079 * assigned top down from the parse tree with explicit work queues: the clauses of a query block and the steps of a 080 * data-changing statement have their parent fixed by Sections 3 and 4; expression steps ({@code FUNCTION_ARG}, 081 * {@code CASE_*}, {@code SUBQUERY}, {@code WINDOW}) take the innermost enclosing step of their clause as parent. 082 * GSP's {@code TParseTreeNode.parent} is not used: it is set in only a few places.</p> 083 * 084 * <p>Forms PATH-V1 does not cover (Section 2, item 3) are recorded as uncovered ranges; a reference inside one has 085 * no {@code sqllr-path-1} anchor ({@link Anchor#isCovered()} is false), and the producer publishes nothing anchored 086 * there (plan 06, decision 1). A column reference that lands directly on a statement, query block or FROM-item step 087 * sits in a clause or table modifier this index does not know (MODEL, MATCH_RECOGNIZE, SAMPLE, table hints, OUTPUT, 088 * time travel): {@link #anchorColumn} reports it uncovered rather than inventing a path.</p> 089 * 090 * @since 4.2.17 091 */ 092public final class PathIndex { 093 094 /** The anchor of an offset: a path, or the fact that it lies in an uncovered form or outside the statement. */ 095 public static final class Anchor { 096 private final String path; 097 private final String uncoveredForm; 098 099 Anchor(String path, String uncoveredForm) { 100 this.path = path; 101 this.uncoveredForm = uncoveredForm; 102 } 103 104 /** The {@code sqllr-path-1} path; {@code null} when not covered. */ 105 public String getPath() { 106 return path; 107 } 108 109 public boolean isCovered() { 110 return path != null; 111 } 112 113 /** The uncovered form that contains the offset, or {@code null}. */ 114 public String getUncoveredForm() { 115 return uncoveredForm; 116 } 117 118 @Override 119 public String toString() { 120 return path != null ? path : "UNCOVERED " + uncoveredForm; 121 } 122 } 123 124 /** One step's construct. */ 125 static final class Construct { 126 final String code; 127 int index; 128 final int start; 129 final int end; 130 Construct parent; 131 /** For expression steps: the clause step whose expressions are searched for the innermost parent. */ 132 final Construct scope; 133 final int order; 134 String path; 135 int depth = -1; 136 final List<Construct> textChildren = new ArrayList<Construct>(); 137 138 Construct(String code, int index, int start, int end, Construct parent, Construct scope, int order) { 139 this.code = code; 140 this.index = index; 141 this.start = start; 142 this.end = end; 143 this.parent = parent; 144 this.scope = scope; 145 this.order = order; 146 } 147 148 boolean contains(int offset) { 149 return start <= offset && offset < end; 150 } 151 152 boolean containsRange(Construct other) { 153 return start <= other.start && other.end <= end; 154 } 155 156 int length() { 157 return end - start; 158 } 159 } 160 161 private static final class Uncovered { 162 final String form; 163 final int start; 164 final int end; 165 166 Uncovered(String form, int start, int end) { 167 this.form = form; 168 this.start = start; 169 this.end = end; 170 } 171 } 172 173 /** Steps on which a column reference never lands in a covered form. */ 174 private static final Set<String> NOT_A_COLUMN_ANCHOR = new HashSet<String>(Arrays.asList("SELECT", "INSERT", 175 "UPDATE", "DELETE", "MERGE", "CREATE_TABLE", "CREATE_VIEW", "SUBQUERY", "CTE", "BRANCH", "SET_OP", "WITH", 176 "FROM", "JOIN", "TARGET", "USING", "LATERAL")); 177 178 /** Functions whose FIRST operand can be a bare date-part keyword (SQL Server, Snowflake, MySQL), not an operand. */ 179 private static final Set<String> FIRST_IS_DATE_PART = new HashSet<String>(Arrays.asList("DATEADD", "DATEDIFF", 180 "DATEDIFF_BIG", "DATEPART", "DATENAME", "DATETRUNC", "DATE_BUCKET", "DATE_PART", "DATE_TRUNC", 181 "TIMESTAMPADD", "TIMESTAMPDIFF", "TIMEADD", "TIMEDIFF", "TIME_SLICE")); 182 183 /** Functions whose LAST operand can be a bare date-part keyword (BigQuery), not an operand. */ 184 private static final Set<String> LAST_IS_DATE_PART = new HashSet<String>(Arrays.asList("DATE_DIFF", 185 "DATETIME_DIFF", "TIMESTAMP_DIFF", "TIME_DIFF", "DATE_TRUNC", "DATETIME_TRUNC", "TIMESTAMP_TRUNC", 186 "TIME_TRUNC", "LAST_DAY")); 187 188 private static final Set<String> DATE_PARTS = new HashSet<String>(Arrays.asList("YEAR", "YY", "YYYY", "YEARS", 189 "QUARTER", "QQ", "Q", "QUARTERS", "MONTH", "MM", "M", "MONTHS", "DAYOFYEAR", "DY", "Y", "DAY", "DD", "D", 190 "DAYS", "WEEK", "WK", "WW", "WEEKS", "WEEKDAY", "DW", "HOUR", "HH", "HOURS", "MINUTE", "MI", "N", "MINUTES", 191 "SECOND", "SS", "S", "SECONDS", "MILLISECOND", "MS", "MILLISECONDS", "MICROSECOND", "MCS", "MICROSECONDS", 192 "NANOSECOND", "NS", "NANOSECONDS", "ISO_WEEK", "ISOWK", "ISOWW", "ISOWEEK", "ISOYEAR", "TZOFFSET", "TZ", 193 "DAYOFWEEK", "DAYOFMONTH", "EPOCH", "DATE", "TIME", "DATETIME", "TIMESTAMP", "CENTURY", "DECADE", 194 "MILLENNIUM", "YEAROFWEEK", "YEAROFWEEKISO", "WEEKISO", "WEEKOFYEAR")); 195 196 private final String statementStep; 197 private final EDbVendor vendor; 198 private final List<Construct> constructs = new ArrayList<Construct>(); 199 private final List<Uncovered> uncovered = new ArrayList<Uncovered>(); 200 private final Map<TSelectSqlStatement, Construct> blocks = new IdentityHashMap<TSelectSqlStatement, Construct>(); 201 private final SourceText text; 202 private final List<Construct> roots = new ArrayList<Construct>(); 203 private final Deque<Object[]> work = new ArrayDeque<Object[]>(); 204 private Construct root; 205 private boolean statementUncovered; 206 private String statementUncoveredForm; 207 208 private PathIndex(SourceText text, String statementStep, EDbVendor vendor) { 209 this.text = text; 210 this.statementStep = statementStep; 211 this.vendor = vendor; 212 } 213 214 /** The statement step code of a statement, or {@code null} when PATH-V1 has none for it. */ 215 public static String statementStep(TCustomSqlStatement stmt) { 216 if (stmt instanceof TSelectSqlStatement) { 217 return "SELECT"; 218 } 219 if (stmt instanceof TInsertSqlStatement) { 220 return "INSERT"; 221 } 222 if (stmt instanceof TUpdateSqlStatement) { 223 return "UPDATE"; 224 } 225 if (stmt instanceof TDeleteSqlStatement) { 226 return "DELETE"; 227 } 228 if (stmt instanceof TMergeSqlStatement) { 229 return "MERGE"; 230 } 231 if (stmt instanceof TCreateTableSqlStatement) { 232 return "CREATE_TABLE"; 233 } 234 if (stmt instanceof TCreateViewSqlStatement || stmt instanceof TCreateMaterializedSqlStatement) { 235 return "CREATE_VIEW"; 236 } 237 if (isRoutineDefinition(stmt)) { 238 return "CREATE_ROUTINE"; // for the values it returns and the procedural parts of its body (Sections 3.5, 3.6) 239 } 240 if (isBlock(stmt)) { 241 return "BLOCK"; // for the procedural parts of its body (Section 3.6) 242 } 243 if (Procedural.isStandaloneAssignment(stmt)) { 244 return "ASSIGNMENT"; // SET @v = e, DECLARE @v INT = e outside every body (Section 3.6) 245 } 246 if (DocumentBuilder.isDynamicSite(stmt)) { 247 return "EXECUTE"; // dynamic execution, with its arguments (the text and the values it binds, slice 12) 248 } 249 if (stmt instanceof gudusoft.gsqlparser.lineage.v2.shape.TCallStatement 250 || stmt instanceof gudusoft.gsqlparser.lineage.v2.shape.TDb2CallStmt || isPlsqlCall(stmt)) { 251 return "CALL"; // a call of a routine, with its arguments (plan 09 slice 12) 252 } 253 if (isProcedureExecute(stmt)) { 254 return "EXECUTE"; // SQL Server EXEC of a named procedure, with its arguments (plan 09 slice 12) 255 } 256 return null; 257 } 258 259 /** 260 * A PL/SQL procedure call written as a statement ({@code p(a, b);}, {@code pkg.p(x => 1);}), as opposed to the 261 * DBMS_SQL.PARSE call that carries dynamic SQL text (a dynamic site). 262 */ 263 static boolean isPlsqlCall(TCustomSqlStatement stmt) { 264 return stmt instanceof gudusoft.gsqlparser.lineage.v2.shape.TBasicStmt 265 && ((gudusoft.gsqlparser.lineage.v2.shape.TBasicStmt) stmt).getExpr() != null 266 && ((gudusoft.gsqlparser.lineage.v2.shape.TBasicStmt) stmt).getExpr().getFunctionCall() != null 267 && !DocumentBuilder.isDynamicSite(stmt); 268 } 269 270 /** A SQL Server EXEC of a named procedure, as opposed to an EXEC of dynamic SQL text (a dynamic site). */ 271 static boolean isProcedureExecute(TCustomSqlStatement stmt) { 272 return stmt instanceof gudusoft.gsqlparser.lineage.v2.shape.TMssqlExecute 273 && ((gudusoft.gsqlparser.lineage.v2.shape.TMssqlExecute) stmt).getModuleName() != null 274 && !DocumentBuilder.isDynamicSite(stmt); 275 } 276 277 /** 278 * The arguments of a call statement, in text order (plan 09 slice 12): of a SQL Server EXEC, each parameter's value 279 * (a named one's value); of a CALL, each argument (a named one {@code p => v} as written, one operand). 280 */ 281 static List<TExpression> callArguments(TCustomSqlStatement stmt) { 282 List<TExpression> out = new ArrayList<TExpression>(); 283 if (stmt instanceof gudusoft.gsqlparser.lineage.v2.shape.TMssqlExecute) { 284 gudusoft.gsqlparser.lineage.v2.shape.TExecParameterList list = 285 ((gudusoft.gsqlparser.lineage.v2.shape.TMssqlExecute) stmt).getParameters(); 286 for (int i = 0; list != null && i < list.size(); i++) { 287 gudusoft.gsqlparser.lineage.v2.shape.TExecParameter x = list.getExecParameter(i); 288 out.add(x == null ? null : x.getParameterValue()); 289 } 290 } else if (stmt instanceof gudusoft.gsqlparser.lineage.v2.shape.TCallStatement) { 291 TExpressionList args = ((gudusoft.gsqlparser.lineage.v2.shape.TCallStatement) stmt).getArgs(); 292 for (int i = 0; args != null && i < args.size(); i++) { 293 out.add(args.getExpression(i)); 294 } 295 } else if (stmt instanceof gudusoft.gsqlparser.lineage.v2.shape.TDb2CallStmt) { 296 TExpressionList args = ((gudusoft.gsqlparser.lineage.v2.shape.TDb2CallStmt) stmt).getParameters(); 297 for (int i = 0; args != null && i < args.size(); i++) { 298 out.add(args.getExpression(i)); 299 } 300 } else if (stmt instanceof gudusoft.gsqlparser.lineage.v2.shape.TExecImmeStmt) { 301 // EXECUTE IMMEDIATE text USING a, b: the text, then the bound values 302 gudusoft.gsqlparser.lineage.v2.shape.TExecImmeStmt e = (gudusoft.gsqlparser.lineage.v2.shape.TExecImmeStmt) stmt; 303 out.add(e.getDynamicStringExpr()); 304 for (int i = 0; e.getBindArguments() != null && i < e.getBindArguments().size(); i++) { 305 gudusoft.gsqlparser.lineage.v2.shape.TBindArgument arg = e.getBindArguments().getBindArgument(i); 306 out.add(arg == null ? null : arg.getBindArgumentExpr()); 307 } 308 } else if (stmt instanceof gudusoft.gsqlparser.lineage.v2.shape.TBasicStmt 309 && ((gudusoft.gsqlparser.lineage.v2.shape.TBasicStmt) stmt).getExpr() != null 310 && ((gudusoft.gsqlparser.lineage.v2.shape.TBasicStmt) stmt).getExpr().getFunctionCall() != null) { 311 TExpressionList args = ((gudusoft.gsqlparser.lineage.v2.shape.TBasicStmt) stmt).getExpr().getFunctionCall() 312 .getArgs(); 313 for (int i = 0; args != null && i < args.size(); i++) { 314 out.add(args.getExpression(i)); 315 } 316 } 317 return out; 318 } 319 320 /** 321 * An anonymous block that runs where it stands (DECLARE ... BEGIN ... END, BEGIN ... END, PostgreSQL DO), or a 322 * control-flow statement outside every body (SQL Server IF and WHILE of a batch). 323 */ 324 static boolean isBlock(TCustomSqlStatement stmt) { 325 // a control-flow statement is a statement of the document only outside every body (plan 09 slice 8): there it 326 // runs where it stands, as a block does 327 return stmt instanceof TMssqlBlock || stmt instanceof TCommonBlock || stmt instanceof TDoExecuteBlockStmt 328 || Procedural.isControlBlock(stmt); 329 } 330 331 /** A routine definition (CREATE FUNCTION, CREATE PROCEDURE), not a block that runs where it stands. */ 332 static boolean isRoutineDefinition(TCustomSqlStatement stmt) { 333 return stmt instanceof TStoredProcedureSqlStatement && !(stmt instanceof TMssqlBlock 334 || stmt instanceof TCommonBlock || stmt instanceof TDoExecuteBlockStmt); 335 } 336 337 /** 338 * The values a routine definition returns, in the order of their RETURN[k] steps (path-v1.md, Section 3.5): the 339 * expression of a function whose body is one expression; otherwise each RETURN with a value of its own body in 340 * text order, through blocks and control flow but not into a nested routine definition, and not a RETURN of a 341 * query (RETURN QUERY, a RETURN whose value is a statement) or of nothing. Shared by the paths and the statement 342 * graph, so both read one rule. 343 */ 344 static List<TExpression> returnValues(TCustomSqlStatement routine) { 345 return returnsValue(routine) ? valuesReturned(routine) : new ArrayList<TExpression>(); 346 } 347 348 /** 349 * Whether a routine definition can return a value (Codex S7 #12): a function, and the procedures whose RETURN 350 * gives the caller a value: SQL Server and Sybase (the status), DB2 SQL PL (the status), Snowflake (RETURNS). The 351 * database rejects a RETURN with a value in an Oracle, MySQL or PostgreSQL procedure and in a trigger. 352 */ 353 static boolean returnsValue(TCustomSqlStatement routine) { 354 if (!isRoutineDefinition(routine)) { 355 return false; 356 } 357 // the parse's statement type names the definition (sstcreatefunction, sstmssqlcreateprocedure, ...) 358 String type = String.valueOf(routine.sqlstatementtype).toLowerCase(Locale.ROOT); 359 if (type.contains("trigger")) { 360 return false; 361 } 362 if (type.contains("function")) { 363 return true; 364 } 365 EDbVendor vendor = routine.dbvendor; 366 return type.contains("procedure") && (vendor == EDbVendor.dbvmssql || vendor == EDbVendor.dbvazuresql 367 || vendor == EDbVendor.dbvsybase || vendor == EDbVendor.dbvsybasease || vendor == EDbVendor.dbvdb2 368 || vendor == EDbVendor.dbvsnowflake); 369 } 370 371 /** The value a RETURN statement gives, or null: none, or a query (RETURN QUERY, RETURN (SELECT ...), RETURN SELECT). */ 372 static TExpression returnedValue(TCustomSqlStatement ret) { 373 TExpression value = rawReturnValue(ret); 374 return value == null || returnsQuery(ret) || returnsNext(ret) || queryExecute(ret) ? null : value; 375 } 376 377 /** PL/pgSQL {@code RETURN NEXT}, with or without an operand: one row of the set a function returns. */ 378 static boolean returnsNext(TCustomSqlStatement ret) { 379 if (!(ret instanceof TReturnStmt) || ret.getStartToken() == null) { 380 return false; 381 } 382 TSourceToken next = nextSolidToken(ret.getStartToken()); 383 return next != null && "NEXT".equalsIgnoreCase(next.getAstext()); // non-identifier-compare: keyword 384 } 385 386 /** 387 * PL/pgSQL {@code RETURN NEXT}, {@code RETURN QUERY} and {@code RETURN QUERY EXECUTE}: they add rows to the set the 388 * function returns and execution goes on after them, so they are no jump (Codex 13f #2). 389 */ 390 static boolean appendsRows(TCustomSqlStatement ret) { 391 return returnsNext(ret) || returnsRows(ret) || queryExecute(ret); 392 } 393 394 /** PL/pgSQL {@code RETURN QUERY EXECUTE}: the rows of dynamic SQL text. */ 395 static boolean queryExecute(TCustomSqlStatement ret) { 396 if (!(ret instanceof TReturnStmt) || ret.getStartToken() == null) { 397 return false; 398 } 399 TSourceToken next = nextSolidToken(ret.getStartToken()); 400 TSourceToken after = next == null ? null : nextSolidToken(next); 401 return next != null && after != null && "QUERY".equalsIgnoreCase(next.getAstext()) // non-identifier-compare: keyword 402 && "EXECUTE".equalsIgnoreCase(after.getAstext()); // non-identifier-compare: keyword 403 } 404 405 /** 406 * Whether a routine's {@code RETURN NEXT} is modelled (plan 09 slice 13f; path-v1 Section 3.5, fold-v1 Section 4.7, 407 * item 8): a PostgreSQL-family function that returns a set of rows ({@code RETURNS TABLE}, {@code RETURNS SETOF}). 408 */ 409 static boolean nextModelled(TCustomSqlStatement routine) { 410 if (!(routine instanceof TCreateFunctionStmt) || !DocumentBuilder.postgresFamily(routine.dbvendor)) { 411 return false; 412 } 413 TCreateFunctionStmt f = (TCreateFunctionStmt) routine; 414 if (f.getReturnsType() == gudusoft.gsqlparser.EFunctionReturnsType.frtInlineTableValue) { 415 return true; 416 } 417 return f.getReturnDataType() != null && DocumentBuilder.hasWord(f.getReturnDataType(), "SETOF"); 418 } 419 420 /** 421 * A RETURN of a table that path-v1 does not cover (Section 2, item 3), never a value: RETURN QUERY EXECUTE (Codex 422 * 13d #3: its command text was taken for the value returned), and RETURN NEXT of a routine that does not return a 423 * set of rows of the PostgreSQL family, which the database rejects; the form's words, or null. 424 */ 425 static String unmodelledRowReturn(TCustomSqlStatement routine, TCustomSqlStatement ret) { 426 if (queryExecute(ret)) { 427 return "RETURN QUERY EXECUTE"; 428 } 429 return returnsNext(ret) && !nextModelled(routine) ? "RETURN NEXT" : null; 430 } 431 432 /** 433 * The steps RETURN[k] of a routine in order (path-v1 Section 3.5): {the RETURN statement, or null for an expression 434 * body; its value, or null}: each RETURN with a value {@link #returnValues} lists, and each RETURN NEXT of a routine 435 * that models it ({@link #nextModelled}), with its operand or without one (plan 09 slice 13f), in text order. 436 */ 437 static List<Object[]> returnSteps(TCustomSqlStatement routine) { 438 List<Object[]> out = new ArrayList<Object[]>(); 439 if (!returnsValue(routine)) { 440 return out; 441 } 442 if (routine instanceof TCreateFunctionStmt && ((TCreateFunctionStmt) routine).getSqlExpression() != null) { 443 out.add(new Object[] {null, ((TCreateFunctionStmt) routine).getSqlExpression()}); 444 return out; 445 } 446 boolean next = nextModelled(routine); 447 for (TCustomSqlStatement ret : returnsOf(routine)) { 448 if (returnsNext(ret) || queryExecute(ret)) { 449 // RETURN NEXT (slice 13f); RETURN QUERY EXECUTE, whose command expression follows RETURN[k] and 450 // whose literal text is a derived unit with RETURN[k] its site (slice 13g) 451 if (next) { 452 out.add(new Object[] {ret, rawReturnValue(ret)}); 453 } 454 continue; 455 } 456 TExpression value = valueOf(routine, ret); 457 if (value != null) { 458 out.add(new Object[] {ret, value}); 459 } 460 } 461 return out; 462 } 463 464 /** The expression a RETURN gives, a query included (not DB2's RETURN of a subquery), or null. */ 465 private static TExpression rawReturnValue(TCustomSqlStatement ret) { 466 return ret instanceof TReturnStmt ? ((TReturnStmt) ret).getExpression() 467 : ret instanceof TMssqlReturn ? ((TMssqlReturn) ret).getReturnExpr() 468 : ret instanceof TDb2ReturnStmt && ((TDb2ReturnStmt) ret).getSubquery() == null 469 ? ((TDb2ReturnStmt) ret).getReturnExpr() : null; 470 } 471 472 /** 473 * PL/pgSQL {@code RETURN QUERY q} (not {@code RETURN QUERY EXECUTE}): the query's rows are rows of the set the 474 * function returns (plan 09 slice 8). 475 */ 476 static boolean returnsRows(TCustomSqlStatement ret) { 477 if (!(ret instanceof TReturnStmt) || ret.getStartToken() == null) { 478 return false; 479 } 480 TSourceToken next = nextSolidToken(ret.getStartToken()); 481 TSourceToken after = next == null ? null : nextSolidToken(next); 482 return next != null && "QUERY".equalsIgnoreCase(next.getAstext()) // non-identifier-compare: keyword 483 && (after == null || !"EXECUTE".equalsIgnoreCase(after.getAstext())); // non-identifier-compare: keyword 484 } 485 486 /** 487 * Whether a RETURN of a query is one the paths and the graph model (plan 09 slice 8; path-v1.md, Section 3.5): 488 * PL/pgSQL RETURN QUERY q, whose rows the function returns, and RETURN (SELECT ...) of a function that returns 489 * one value, a scalar subquery; not an inline table function's RETURN SELECT, RETURN QUERY EXECUTE or DB2's RETURN 490 * of a subquery. 491 */ 492 static boolean modelledQueryReturn(TCustomSqlStatement routine, TCustomSqlStatement ret) { 493 if (inlineTableQuery(routine, ret) != null) { 494 return true; // an inline table function's RETURN of its query (Section 3.5) 495 } 496 if (!returnsQuery(ret) || rawReturnValue(ret) == null) { 497 return false; 498 } 499 if (returnsRows(ret)) { 500 return true; 501 } 502 if (ret instanceof TReturnStmt && "QUERY".equalsIgnoreCase( // non-identifier-compare: keyword 503 String.valueOf(nextSolidToken(ret.getStartToken()) == null ? null 504 : nextSolidToken(ret.getStartToken()).getAstext()))) { 505 return false; // RETURN QUERY EXECUTE: dynamic SQL 506 } 507 return routine instanceof TCreateFunctionStmt && ((TCreateFunctionStmt) routine).getReturnsType() 508 != gudusoft.gsqlparser.EFunctionReturnsType.frtInlineTableValue; 509 } 510 511 /** 512 * A SQL Server inline table function: {@code RETURNS TABLE AS RETURN SELECT ...} or {@code RETURN (SELECT ...)}, 513 * no declared return table (plan 09 phase C; path-v1 Section 3.5). Its RETURN's query gives the rows the 514 * function returns, as PL/pgSQL's RETURN QUERY does. 515 */ 516 static boolean inlineTable(TCustomSqlStatement routine) { 517 if (!(routine instanceof TCreateFunctionStmt) || (routine.dbvendor != EDbVendor.dbvmssql 518 && routine.dbvendor != EDbVendor.dbvazuresql)) { 519 return false; 520 } 521 TCreateFunctionStmt f = (TCreateFunctionStmt) routine; 522 return f.getReturnsType() == gudusoft.gsqlparser.EFunctionReturnsType.frtInlineTableValue 523 && (f.getReturnTableDefinitions() == null || f.getReturnTableDefinitions().size() == 0); 524 } 525 526 /** The query an inline table function's RETURN gives (in parentheses or not), or null. */ 527 static TSelectSqlStatement inlineTableQuery(TCustomSqlStatement routine, TCustomSqlStatement ret) { 528 if (!(ret instanceof TMssqlReturn) || !inlineTable(routine)) { 529 return null; 530 } 531 TExpression value = ((TMssqlReturn) ret).getReturnExpr(); 532 if (value != null) { 533 return queryOf(value); 534 } 535 return ret.getStatements() != null && ret.getStatements().size() == 1 536 && ret.getStatements().get(0) instanceof TSelectSqlStatement 537 ? (TSelectSqlStatement) ret.getStatements().get(0) : null; 538 } 539 540 /** 541 * The RETURN of an inline table function written without parentheses ({@code RETURN SELECT ...}), whose query is 542 * no expression: {RETURN, query}, or null. Its step is RETURN[0] with the query as SUBQUERY[0] below it. 543 */ 544 static Object[] inlineBareReturn(TCustomSqlStatement routine) { 545 if (!inlineTable(routine)) { 546 return null; 547 } 548 for (TCustomSqlStatement ret : returnsOf(routine)) { 549 if (((TMssqlReturn) ret).getReturnExpr() == null && inlineTableQuery(routine, ret) != null) { 550 return new Object[] {ret, inlineTableQuery(routine, ret)}; 551 } 552 } 553 return null; 554 } 555 556 /** The RETURN statements of a routine's own body, in text order (whatever they return). */ 557 static List<TCustomSqlStatement> returnStatementsOf(TCustomSqlStatement routine) { 558 return returnsOf(routine); 559 } 560 561 /** Whether a RETURN of the routine returns rows: PL/pgSQL RETURN QUERY, or an inline table function's query. */ 562 static boolean returnsRows(TCustomSqlStatement routine, TCustomSqlStatement ret) { 563 return returnsRows(ret) || inlineTableQuery(routine, ret) != null; 564 } 565 566 /** The query an expression is as a whole (through parentheses), or null. */ 567 static TSelectSqlStatement queryOf(TExpression value) { 568 TExpression e = value; 569 while (e != null && e.getExpressionType() == EExpressionType.parenthesis_t && e.getLeftOperand() != null) { 570 e = e.getLeftOperand(); 571 } 572 return e != null && e.getExpressionType() == EExpressionType.subquery_t ? e.getSubQuery() : null; 573 } 574 575 /** The RETURN statement of each value {@link #returnValues} lists, in the same order (null for an expression body). */ 576 static List<TCustomSqlStatement> returnStatements(TCustomSqlStatement routine) { 577 List<TCustomSqlStatement> out = new ArrayList<TCustomSqlStatement>(); 578 if (!returnsValue(routine)) { 579 return out; 580 } 581 if (routine instanceof TCreateFunctionStmt && ((TCreateFunctionStmt) routine).getSqlExpression() != null) { 582 out.add(null); 583 return out; 584 } 585 for (TCustomSqlStatement ret : returnsOf(routine)) { 586 if (valueOf(routine, ret) != null) { 587 out.add(ret); 588 } 589 } 590 return out; 591 } 592 593 /** The value one RETURN of the routine's own body gives in RETURN[k] (Section 3.5), or null. */ 594 private static TExpression valueOf(TCustomSqlStatement routine, TCustomSqlStatement ret) { 595 return returnsQuery(ret) ? (modelledQueryReturn(routine, ret) ? rawReturnValue(ret) : null) 596 : returnedValue(ret); 597 } 598 599 /** 600 * Whether a RETURN returns a query (Codex S7 #4): RETURN QUERY, DB2 RETURN of a subquery, or a value that is a 601 * query as a whole (an inline table function's RETURN SELECT, RETURN (SELECT ...)). A scalar expression with a 602 * subquery inside it (COALESCE((SELECT ...), @p)) returns a value: its other inputs are read. 603 */ 604 static boolean returnsQuery(TCustomSqlStatement ret) { 605 if (ret instanceof TDb2ReturnStmt && ((TDb2ReturnStmt) ret).getSubquery() != null) { 606 return true; 607 } 608 TExpression value = ret instanceof TReturnStmt ? ((TReturnStmt) ret).getExpression() 609 : ret instanceof TMssqlReturn ? ((TMssqlReturn) ret).getReturnExpr() : null; 610 if (value == null) { 611 return ret.getStatements() != null && ret.getStatements().size() > 0; 612 } 613 while (value.getExpressionType() == EExpressionType.parenthesis_t && value.getLeftOperand() != null) { 614 value = value.getLeftOperand(); 615 } 616 return value.getExpressionType() == EExpressionType.subquery_t; 617 } 618 619 /** The values of the RETURNs of a routine's own body (or its expression body), whatever the routine may return. */ 620 static List<TExpression> valuesReturned(TCustomSqlStatement routine) { 621 List<TExpression> out = new ArrayList<TExpression>(); 622 if (!isRoutineDefinition(routine)) { 623 return out; 624 } 625 if (routine instanceof TCreateFunctionStmt && ((TCreateFunctionStmt) routine).getSqlExpression() != null) { 626 out.add(((TCreateFunctionStmt) routine).getSqlExpression()); 627 return out; 628 } 629 for (TCustomSqlStatement ret : returnsOf(routine)) { 630 TExpression value = valueOf(routine, ret); 631 if (value != null) { 632 out.add(value); 633 } 634 } 635 return out; 636 } 637 638 /** The RETURN statements of a routine's own body in text order, through blocks and control flow. */ 639 private static List<TCustomSqlStatement> returnsOf(TCustomSqlStatement routine) { 640 List<TCustomSqlStatement> found = new ArrayList<TCustomSqlStatement>(); 641 Deque<TCustomSqlStatement> pending = new ArrayDeque<TCustomSqlStatement>(); 642 pending.push(routine); 643 while (!pending.isEmpty()) { 644 TCustomSqlStatement container = pending.pop(); 645 for (int i = 0; container.getStatements() != null && i < container.getStatements().size(); i++) { 646 TCustomSqlStatement child = container.getStatements().get(i); 647 if (child == null || isRoutineDefinition(child)) { 648 continue; // a nested routine's RETURNs are its own 649 } 650 if (child instanceof TReturnStmt || child instanceof TMssqlReturn || child instanceof TDb2ReturnStmt) { 651 found.add(child); 652 continue; 653 } 654 pending.push(child); 655 } 656 } 657 Collections.sort(found, new Comparator<TCustomSqlStatement>() { 658 @Override 659 public int compare(TCustomSqlStatement a, TCustomSqlStatement b) { 660 return Integer.compare(a.getStartToken() == null ? 0 : a.getStartToken().posinlist, 661 b.getStartToken() == null ? 0 : b.getStartToken().posinlist); 662 } 663 }); 664 return found; 665 } 666 667 /** 668 * The paths of one statement. 669 * 670 * @param stmt the statement 671 * @param text the text of the source unit the statement's tokens are positioned in 672 * @param step the statement step to use when {@link #statementStep} has none ({@code COPY}, {@code CALL}, 673 * {@code EXECUTE}), or {@code null}: the statement then has no covered anchor at all 674 */ 675 public static PathIndex build(TCustomSqlStatement stmt, SourceText text, String step) { 676 String code = statementStep(stmt); 677 PathIndex index = new PathIndex(text, code != null ? code : step, stmt.dbvendor); 678 index.build(stmt, code != null); 679 return index; 680 } 681 682 /** 683 * The same for a statement of dynamic SQL text: the analysis moves its tokens' lines and columns to the site, so 684 * positions come from the tokens' offsets into the text. 685 */ 686 public static PathIndex buildByOffset(TCustomSqlStatement stmt, SourceText text) { 687 String code = statementStep(stmt); 688 PathIndex index = new PathIndex(text, code, stmt.dbvendor); 689 index.byOffset = true; 690 index.build(stmt, code != null); 691 return index; 692 } 693 694 /** Positions from token offsets (a statement of dynamic SQL text) instead of lines and columns. */ 695 private boolean byOffset; 696 697 private int tokenOffset(TSourceToken token) { 698 if (byOffset) { 699 return token.offset < 0 ? -1 : (int) token.offset; 700 } 701 return text.lexerOffset(token.lineNo, token.columnNo); 702 } 703 704 /** {@link #build(TCustomSqlStatement, SourceText, String)} with no fallback statement step. */ 705 public static PathIndex build(TCustomSqlStatement stmt, SourceText text) { 706 return build(stmt, text, null); 707 } 708 709 /** 710 * The paths of a statement a caller holds as a live parse tree (a parse with no analysis): the tree is projected 711 * first (plan 09, slice 1b), and the live overloads of {@link #anchorColumn} and {@link #blockPath} take its nodes. 712 */ 713 public static PathIndex build(gudusoft.gsqlparser.TCustomSqlStatement stmt, SourceText text) { 714 ShapeBuilder projection = 715 ShapeBuilder.projectKeepingLive(stmt); 716 PathIndex index = build((TCustomSqlStatement) projection.shape().root(), text, null); 717 index.projection = projection; 718 return index; 719 } 720 721 /** The projection of a live tree {@link #build(gudusoft.gsqlparser.TCustomSqlStatement, SourceText)} took. */ 722 private ShapeBuilder projection; 723 724 private <T> T mirrorOf(Object live, Class<T> type) { 725 if (projection == null) { 726 throw new IllegalStateException("the index was built from a projection, not from a live tree"); 727 } 728 return type.cast(projection.mirrorOf(live)); 729 } 730 731 /** The statement step path, such as {@code INSERT[0]}, or {@code null} when the statement has none. */ 732 public String getStatementPath() { 733 return statementStep == null ? null : statementStep + "[0]"; 734 } 735 736 /** The path of a query block (the {@code QUERY_BLOCK} scope path), or {@code null} when not indexed. */ 737 public String blockPath(TSelectSqlStatement block) { 738 Construct c = blocks.get(block); 739 return c == null ? null : c.path; 740 } 741 742 /** {@link #blockPath(TSelectSqlStatement)} of a live query block of the tree the index was built from. */ 743 public String blockPath(gudusoft.gsqlparser.stmt.TSelectSqlStatement block) { 744 return blockPath(mirrorOf(block, TSelectSqlStatement.class)); 745 } 746 747 /** Every indexed query block and its path, in no particular order. */ 748 public Map<TSelectSqlStatement, String> blockPaths() { 749 Map<TSelectSqlStatement, String> out = new IdentityHashMap<TSelectSqlStatement, String>(); 750 for (Map.Entry<TSelectSqlStatement, Construct> e : blocks.entrySet()) { 751 out.put(e.getKey(), e.getValue().path); 752 } 753 return out; 754 } 755 756 /** The innermost query block path that is a prefix of (or equal to) a path, or {@code null}. */ 757 public String enclosingBlockPath(String path) { 758 if (path == null) { 759 return null; 760 } 761 String best = null; 762 for (Construct c : blocks.values()) { 763 String p = c.path; 764 if ((path.equals(p) || path.startsWith(p + "/")) && (best == null || p.length() > best.length())) { 765 best = p; 766 } 767 } 768 return best; 769 } 770 771 /** Whether every part of the statement is in a covered form. */ 772 public boolean isFullyCovered() { 773 return !statementUncovered && uncovered.isEmpty(); 774 } 775 776 /** The anchor of the reference whose first character is at the UTF-16 offset. */ 777 public Anchor anchor(int offset) { 778 if (statementUncovered) { 779 return new Anchor(null, statementUncoveredForm); 780 } 781 for (Uncovered u : uncovered) { 782 if (u.start <= offset && offset < u.end) { 783 return new Anchor(null, u.form); 784 } 785 } 786 Construct best = null; 787 List<Construct> level = roots; 788 while (true) { 789 Construct hit = find(level, offset); 790 if (hit == null) { 791 break; 792 } 793 best = hit; 794 level = hit.textChildren; 795 } 796 if (best == null) { 797 return new Anchor(null, "OUTSIDE_STATEMENT"); 798 } 799 return new Anchor(best.path, null); 800 } 801 802 /** The child of a sorted, disjoint level that contains the offset, or null. */ 803 private static Construct find(List<Construct> level, int offset) { 804 int low = 0; 805 int high = level.size() - 1; 806 int candidate = -1; 807 while (low <= high) { 808 int mid = (low + high) >>> 1; 809 if (level.get(mid).start <= offset) { 810 candidate = mid; 811 low = mid + 1; 812 } else { 813 high = mid - 1; 814 } 815 } 816 for (int i = candidate; i >= 0 && i >= candidate - 2; i--) { 817 if (level.get(i).contains(offset)) { 818 return level.get(i); 819 } 820 } 821 return null; 822 } 823 824 /** The anchor of the first character of a token. */ 825 public Anchor anchor(TSourceToken token) { 826 int offset = token == null ? -1 : tokenOffset(token); 827 return offset < 0 ? new Anchor(null, "UNPOSITIONED") : anchor(offset); 828 } 829 830 /** The anchor of the first token of a node. */ 831 public Anchor anchor(TParseTreeNode node) { 832 return anchor(node == null ? null : node.getStartToken()); 833 } 834 835 /** {@link #anchorColumn(TObjectName)} of a live column reference of the tree the index was built from. */ 836 public Anchor anchorColumn(gudusoft.gsqlparser.nodes.TObjectName column) { 837 return anchorColumn(mirrorOf(column, TObjectName.class)); 838 } 839 840 /** 841 * The anchor of a column reference: as {@link #anchor(TParseTreeNode)}, but a column that lands directly on a 842 * statement, query block, WITH, FROM, JOIN, TARGET or USING step is in a construct this index does not model 843 * and is reported uncovered. 844 */ 845 public Anchor anchorColumn(TObjectName column) { 846 Anchor a = anchor(column); 847 if (!a.isCovered()) { 848 return a; 849 } 850 String last = a.getPath().substring(a.getPath().lastIndexOf('/') + 1); 851 String code = last.substring(0, last.indexOf('[')); 852 if (NOT_A_COLUMN_ANCHOR.contains(code)) { 853 return new Anchor(null, "UNMODELLED CLAUSE AT " + a.getPath()); 854 } 855 return a; 856 } 857 858 // --------------------------------------------------------------------------------------------- 859 // Building 860 // --------------------------------------------------------------------------------------------- 861 862 private int start(TParseTreeNode node) { 863 if (node == null || node.getStartToken() == null) { 864 return -1; 865 } 866 TSourceToken t = node.getStartToken(); 867 return tokenOffset(t); 868 } 869 870 private int end(TParseTreeNode node) { 871 if (node == null || node.getEndToken() == null) { 872 return -1; 873 } 874 TSourceToken t = node.getEndToken(); 875 int offset = tokenOffset(t); 876 return offset < 0 ? -1 : offset + (t.getAstext() == null ? 0 : t.getAstext().length()); 877 } 878 879 private int offsetOf(TSourceToken token) { 880 return token == null ? -1 : tokenOffset(token); 881 } 882 883 private static TSourceToken previousSolid(TSourceToken token) { 884 TSourceToken t = token == null ? null : token.getPrevTokenInChain(); 885 while (t != null && (t.tokentype == ETokenType.ttwhitespace || t.tokentype == ETokenType.ttreturn 886 || t.tokentype == ETokenType.ttsimplecomment || t.tokentype == ETokenType.ttbracketedcomment)) { 887 t = t.getPrevTokenInChain(); 888 } 889 return t; 890 } 891 892 /** The offset of the keyword just before a node when it is one of the given words, or the node's start. */ 893 private int keywordStart(TParseTreeNode node, String... keywords) { 894 int s = start(node); 895 TSourceToken t = node == null ? null : previousSolid(node.getStartToken()); 896 for (int hops = 0; hops < 3 && t != null; hops++) { 897 for (String keyword : keywords) { 898 if (keyword.equalsIgnoreCase(t.getAstext())) { // non-identifier-compare: SQL keyword text 899 int o = offsetOf(t); 900 if (o >= 0 && o < s) { 901 s = o; 902 } 903 return s; 904 } 905 } 906 t = previousSolid(t); 907 } 908 return s; 909 } 910 911 private Construct add(String code, int index, int start, int end, Construct parent) { 912 if (start < 0 || end < start) { 913 return null; 914 } 915 Construct c = new Construct(code, index, start, end, parent, null, constructs.size()); 916 constructs.add(c); 917 return c; 918 } 919 920 private Construct add(String code, int index, TParseTreeNode node, Construct parent) { 921 return add(code, index, start(node), end(node), parent); 922 } 923 924 /** An expression step whose parent is the innermost step of {@code scope} that contains it. */ 925 private Construct addExpression(String code, int index, int start, int end, Construct scope) { 926 if (start < 0 || end < start) { 927 return null; 928 } 929 Construct c = new Construct(code, index, start, end, null, scope, constructs.size()); 930 constructs.add(c); 931 return c; 932 } 933 934 private void markUncovered(String form, TParseTreeNode node) { 935 markUncovered(form, start(node), end(node)); 936 } 937 938 private void markUncovered(String form, int s, int e) { 939 if (s >= 0 && e >= s) { 940 uncovered.add(new Uncovered(form, s, e)); 941 } 942 } 943 944 private void build(TCustomSqlStatement stmt, boolean covered) { 945 if (statementStep == null) { 946 statementUncovered = true; 947 statementUncoveredForm = "STATEMENT " + stmt.sqlstatementtype; 948 return; 949 } 950 root = add(statementStep, 0, stmt, null); 951 if (root == null) { 952 statementUncovered = true; 953 statementUncoveredForm = "UNPOSITIONED"; 954 return; 955 } 956 if (!covered) { 957 // COPY, CALL, EXECUTE: the statement step only; their arguments are not covered 958 uncovered.add(new Uncovered("ARGUMENTS OF " + statementStep, root.start, root.end)); 959 finish(); 960 return; 961 } 962 dmlCtes = coveredDmlCtes(stmt); 963 try { 964 if (stmt instanceof TSelectSqlStatement) { 965 queryExpression((TSelectSqlStatement) stmt, root); 966 } else if (stmt instanceof TInsertSqlStatement) { 967 insert((TInsertSqlStatement) stmt); 968 } else if (stmt instanceof TUpdateSqlStatement) { 969 update((TUpdateSqlStatement) stmt); 970 } else if (stmt instanceof TDeleteSqlStatement) { 971 delete((TDeleteSqlStatement) stmt); 972 } else if (stmt instanceof TMergeSqlStatement) { 973 merge((TMergeSqlStatement) stmt); 974 } else if (stmt instanceof TCreateTableSqlStatement) { 975 createTable((TCreateTableSqlStatement) stmt); 976 } else if (stmt instanceof TCreateViewSqlStatement) { 977 TCreateViewSqlStatement view = (TCreateViewSqlStatement) stmt; 978 createView(view, view.getViewName(), view.getViewAliasClause(), view.getSubquery()); 979 } else if (stmt instanceof TCreateMaterializedSqlStatement) { 980 TCreateMaterializedSqlStatement view = (TCreateMaterializedSqlStatement) stmt; 981 createView(view, view.getViewName(), view.getViewAliasClause(), view.getSubquery()); 982 } else if (isRoutineDefinition(stmt) || isBlock(stmt)) { 983 List<Object[]> steps = returnSteps(stmt); 984 for (int k = 0; k < steps.size(); k++) { 985 TExpression value = (TExpression) steps.get(k)[1]; 986 if (value != null) { 987 expressions(add("RETURN", k, value, root), value); 988 } else { 989 add("RETURN", k, (TCustomSqlStatement) steps.get(k)[0], root); // RETURN NEXT without an operand 990 } 991 } 992 Object[] bare = inlineBareReturn(stmt); 993 if (bare != null) { 994 // RETURN SELECT ... of an inline table function: RETURN[0], its query SUBQUERY[0] below it 995 Construct ret = add("RETURN", 0, (TCustomSqlStatement) bare[0], root); 996 Construct sub = ret == null ? null : add("SUBQUERY", 0, (TSelectSqlStatement) bare[1], ret); 997 if (sub != null) { 998 work.push(new Object[] {bare[1], sub}); 999 } 1000 } 1001 procedural(Procedural.of(stmt)); 1002 } else if ("ASSIGNMENT".equals(statementStep)) { 1003 procedural(Procedural.standalone(stmt)); 1004 } else if ("CALL".equals(statementStep) || "EXECUTE".equals(statementStep)) { 1005 callArgumentSteps(stmt, root); 1006 } 1007 drain(); 1008 returning(stmt); 1009 } catch (RuntimeException e) { 1010 // a parse tree this index cannot walk (GSP leaves some children null): nothing is covered 1011 statementUncovered = true; 1012 statementUncoveredForm = "UNTRAVERSABLE PARSE TREE (" + e.getClass().getSimpleName() + ")"; 1013 return; 1014 } 1015 finish(); 1016 markUnmodelledIdentifiers(stmt); 1017 } 1018 1019 /** 1020 * The procedural parts of a routine's or a block's own body, or of a standalone assignment (Section 3.6): ASSIGN[k] 1021 * with its value's expression steps, or with the query of a SQL Server SELECT @v = ... as SUBQUERY[0]; CONDITION[k] 1022 * with its expression steps; CURSOR[c] introducing the cursor's query; FETCH[f] with nothing below it. 1023 */ 1024 private void procedural(Procedural parts) { 1025 for (int k = 0; k < parts.assigns.size(); k++) { 1026 Procedural.Assign a = parts.assigns.get(k); 1027 int end = a.last == null ? -1 : offsetOf(a.last); 1028 Construct c = add("ASSIGN", k, offsetOf(a.first), 1029 end < 0 ? -1 : end + (a.last.getAstext() == null ? 0 : a.last.getAstext().length()), root); 1030 if (c == null) { 1031 continue; 1032 } 1033 if (a.query != null) { 1034 Construct query = add("SUBQUERY", 0, a.query, c); 1035 if (query != null) { 1036 work.push(new Object[] {a.query, query}); 1037 } 1038 } else { 1039 expressions(c, a.value); 1040 } 1041 } 1042 for (int k = 0; k < parts.conditions.size(); k++) { 1043 Procedural.Condition c = parts.conditions.get(k); 1044 expressions(add("CONDITION", k, c.expr, root), c.expr); 1045 } 1046 for (int k = 0; k < parts.cursors.size(); k++) { 1047 Procedural.Cursor c = parts.cursors.get(k); 1048 Construct cursor = add("CURSOR", k, c.query, root); 1049 if (cursor != null) { 1050 work.push(new Object[] {c.query, cursor}); 1051 } 1052 } 1053 for (int k = 0; k < parts.fetches.size(); k++) { 1054 add("FETCH", k, parts.fetches.get(k).node, root); 1055 } 1056 } 1057 1058 private void drain() { 1059 while (!work.isEmpty()) { 1060 Object[] item = work.pop(); 1061 queryExpression((TSelectSqlStatement) item[0], (Construct) item[1]); 1062 } 1063 } 1064 1065 private void with(TCustomSqlStatement stmt, Construct parent) { 1066 if (stmt.getCteList() == null || stmt.getCteList().size() == 0) { 1067 return; 1068 } 1069 Construct with = add("WITH", 0, stmt.getCteList(), parent); 1070 if (with == null) { 1071 return; 1072 } 1073 for (int c = 0; c < stmt.getCteList().size(); c++) { 1074 TCTE cte = stmt.getCteList().getCTE(c); 1075 Construct cc = add("CTE", c, cte, with); 1076 if (cc == null) { 1077 continue; 1078 } 1079 if (cte.getColumnList() != null) { 1080 add("COLUMN_LIST", 0, cte.getColumnList(), cc); 1081 } 1082 if (cte.getSubquery() != null) { 1083 work.push(new Object[] {cte.getSubquery(), cc}); 1084 } else if (coveredHere(cte)) { 1085 dataChangingBody(cte, cc); 1086 } else { 1087 markUncovered("CTE BODY", cte); 1088 } 1089 } 1090 } 1091 1092 /** 1093 * The data-changing statement a CTE's body is (plan 09 slice 13e; path-v1 Section 3.2, item 6): its step, INSERT[0], 1094 * UPDATE[0] or DELETE[0] directly below CTE[c], then its own clauses below that step, as the statement step's of a 1095 * statement of its own would be; its RETURNING is RETURNING[0] below it. 1096 */ 1097 private void dataChangingBody(TCTE cte, Construct cc) { 1098 TCustomSqlStatement body = dataChanging(cte); 1099 String step = body instanceof TInsertSqlStatement ? "INSERT" : body instanceof TUpdateSqlStatement ? "UPDATE" 1100 : body instanceof TMergeSqlStatement ? "MERGE" : "DELETE"; 1101 int bodyStart = start(body); 1102 int bodyEnd = end(body); 1103 if (bodyStart >= 0 && bodyEnd < 0 && cte.getEndToken() != null) { 1104 // the parse gives no end to a body such as DELETE FROM t (Codex 13e #1): it ends at the CTE's ")" 1105 bodyEnd = offsetOf(cte.getEndToken()); 1106 } 1107 Construct dml = bodyStart < 0 || bodyEnd < bodyStart ? null : add(step, 0, bodyStart, bodyEnd, cc); 1108 if (dml == null) { 1109 markUncovered("CTE BODY", cte); 1110 return; 1111 } 1112 Construct savedRoot = root; 1113 TParseTreeNode savedOwn = ownReturning; 1114 root = dml; 1115 ownReturning = null; 1116 try { 1117 if (body instanceof TInsertSqlStatement) { 1118 insert((TInsertSqlStatement) body); 1119 } else if (body instanceof TUpdateSqlStatement) { 1120 update((TUpdateSqlStatement) body); 1121 } else if (body instanceof TMergeSqlStatement) { 1122 merge((TMergeSqlStatement) body); 1123 } else { 1124 delete((TDeleteSqlStatement) body); 1125 } 1126 if (ownReturning != null) { 1127 ownedReturnings.add(Integer.valueOf(start(ownReturning))); 1128 } 1129 dmlCteSteps.put(cte, dml); 1130 } finally { 1131 root = savedRoot; 1132 ownReturning = savedOwn; 1133 } 1134 } 1135 1136 /** The covered data-changing CTEs of the statement ({@link #coveredDmlCtes}), in WITH order. */ 1137 private List<TCTE> dmlCtes = java.util.Collections.<TCTE>emptyList(); 1138 1139 /** Their statement steps, CTE[c]/UPDATE[0] and the like, once indexed (paths are set by finish()). */ 1140 private final Map<TCTE, Construct> dmlCteSteps = new java.util.IdentityHashMap<TCTE, Construct>(); 1141 1142 private boolean coveredHere(TCTE cte) { 1143 for (TCTE c : dmlCtes) { 1144 if (c == cte) { 1145 return true; 1146 } 1147 } 1148 return false; 1149 } 1150 1151 /** The starts of the RETURNING clauses covered besides the statement's own: the covered CTE's. */ 1152 private final java.util.Set<Integer> ownedReturnings = new java.util.HashSet<Integer>(); 1153 1154 /** The path of the step of a covered data-changing CTE's statement (CTE[c]/UPDATE[0]), or null. */ 1155 public String getDmlCtePath(TCTE cte) { 1156 Construct step = dmlCteSteps.get(cte); 1157 return step == null ? null : step.path; 1158 } 1159 1160 /** The INSERT, UPDATE, DELETE or MERGE (PostgreSQL 17, slice 13i) a CTE's body is, or null (a query). */ 1161 static TCustomSqlStatement dataChanging(TCTE cte) { 1162 TCustomSqlStatement body = cte == null || cte.getSubquery() != null ? null : cte.getPreparableStmt(); 1163 return body instanceof TInsertSqlStatement || body instanceof TUpdateSqlStatement 1164 || body instanceof TDeleteSqlStatement || body instanceof TMergeSqlStatement ? body : null; 1165 } 1166 1167 /** 1168 * The data-changing CTEs the paths and the graph cover (plan 09 slices 13e, 13h and 13i, owner rulings 2026-10-17 1169 * and 2026-10-18; fold-v1 Section 4.8, item 7): the PostgreSQL family's 1170 * {@code WITH d AS (INSERT/UPDATE/DELETE ... [RETURNING ...])}, every one of the WITH of a query, INSERT, UPDATE, 1171 * DELETE or MERGE, in WITH order, each with its own model (an INSERT inside an INSERT included). A target is 1172 * always a table and a body never reads its own CTE: the parse binds them so (TCustomSqlStatement.unbindCteTarget, 1173 * TCTE.unbindSelfReferences, measured on PostgreSQL 17); a parse that still binds one to a CTE is not covered, 1174 * said, never read as the CTE. One rule for the paths and the graph. 1175 */ 1176 static List<TCTE> coveredDmlCtes(TCustomSqlStatement stmt) { 1177 if (stmt == null || !dmlCteDialect(stmt.dbvendor)) { 1178 return Collections.<TCTE>emptyList(); 1179 } 1180 if (!(stmt instanceof TSelectSqlStatement || stmt instanceof TInsertSqlStatement 1181 || stmt instanceof TUpdateSqlStatement || stmt instanceof TDeleteSqlStatement 1182 || stmt instanceof TMergeSqlStatement) || stmt.getCteList() == null 1183 || (stmt instanceof TInsertSqlStatement && ((TInsertSqlStatement) stmt).isMultiTableInsert())) { 1184 return Collections.<TCTE>emptyList(); 1185 } 1186 List<TCTE> out = new ArrayList<TCTE>(); 1187 for (int c = 0; c < stmt.getCteList().size(); c++) { 1188 TCTE cte = stmt.getCteList().getCTE(c); 1189 TCustomSqlStatement body = dataChanging(cte); 1190 if (body == null || boundToCte(body.getTargetTable()) || boundToCte(stmt.getTargetTable())) { 1191 continue; 1192 } 1193 boolean self = false; 1194 for (int i = 0; body.tables != null && i < body.tables.size(); i++) { 1195 TTable t = body.tables.getTable(i); 1196 self |= t != null && t.isCTEName() && t.getCTE() == cte; 1197 } 1198 if (!self) { 1199 out.add(cte); 1200 } 1201 } 1202 return out; 1203 } 1204 1205 /** 1206 * The dialects whose data-changing CTEs are covered: PostgreSQL, Greenplum and EDB (GaussDB parses them too but its 1207 * rules are not verified: not covered, said). 1208 */ 1209 static boolean dmlCteDialect(EDbVendor v) { 1210 return v == EDbVendor.dbvpostgresql || v == EDbVendor.dbvgreenplum || v == EDbVendor.dbvedb; 1211 } 1212 1213 /** Whether the parse bound a target to a CTE (it does not for the PostgreSQL family: a guard, never a guess). */ 1214 private static boolean boundToCte(TTable target) { 1215 return target != null && target.isCTEName(); 1216 } 1217 1218 private void queryExpression(TSelectSqlStatement s, Construct in) { 1219 blocks.put(s, in); 1220 with(s, in); 1221 if (s.getValueClause() != null) { 1222 // a VALUES list used as a query (plan 09 slice 11): its rows are VALUES[r] below the step that introduces 1223 // the query expression 1224 if (s.getValueClause().getRows() == null || s.getValueClause().getRows().isEmpty()) { 1225 markUncovered("VALUES AS A QUERY", s); 1226 } else { 1227 valuesRows(s.getValueClause().getRows(), in); 1228 } 1229 return; 1230 } 1231 if (isSetOperation(s)) { 1232 if (mixesPrecedence(s)) { 1233 // GSP groups A UNION B INTERSECT C left to right; the SQL standard (and these dialects) bind 1234 // INTERSECT tighter, so the parse tree's branches are not the query's: no path is published 1235 markUncovered("SET OPERATION PRECEDENCE", s); 1236 return; 1237 } 1238 int[] range = setRange(s); 1239 Construct setOp = add("SET_OP", 0, range[0], range[1], in); 1240 if (setOp == null) { 1241 return; 1242 } 1243 List<TSelectSqlStatement> operands = operands(s); 1244 for (int b = 0; b < operands.size(); b++) { 1245 TSelectSqlStatement operand = operands.get(b); 1246 Construct branch = add("BRANCH", b, operand, setOp); 1247 if (branch != null) { 1248 work.push(new Object[] {operand, branch}); 1249 } 1250 } 1251 if (s.getOrderbyClause() != null) { 1252 expressions(add("ORDER_BY", 0, s.getOrderbyClause(), setOp), s.getOrderbyClause()); 1253 } 1254 limits(s, setOp); 1255 return; 1256 } 1257 block(s, in); 1258 } 1259 1260 private static boolean isSetOperation(TSelectSqlStatement s) { 1261 return s.getSetOperatorType() != null && s.getSetOperatorType() != ESetOperatorType.none 1262 && s.getLeftStmt() != null && s.getRightStmt() != null; 1263 } 1264 1265 /** 1266 * Whether an unparenthesized chain mixes INTERSECT with UNION/EXCEPT/MINUS in a dialect that binds INTERSECT 1267 * tighter (all six portable dialects but Oracle, where the set operators have equal precedence). 1268 */ 1269 private boolean mixesPrecedence(TSelectSqlStatement s) { 1270 if (vendor == EDbVendor.dbvoracle) { 1271 return false; 1272 } 1273 boolean intersect = false; 1274 boolean other = false; 1275 Deque<TSelectSqlStatement> stack = new ArrayDeque<TSelectSqlStatement>(); 1276 stack.push(s); 1277 while (!stack.isEmpty()) { 1278 TSelectSqlStatement x = stack.pop(); 1279 if (!isSetOperation(x) || (x != s && x.getParenthesisCount() > 0)) { 1280 continue; 1281 } 1282 if (x.getSetOperatorType() == ESetOperatorType.intersect) { 1283 intersect = true; 1284 } else { 1285 other = true; 1286 } 1287 stack.push(x.getLeftStmt()); 1288 stack.push(x.getRightStmt()); 1289 } 1290 return intersect && other; 1291 } 1292 1293 /** From the first operand to the end of the set operation, excluding a WITH clause written before it. */ 1294 private int[] setRange(TSelectSqlStatement s) { 1295 TSelectSqlStatement first = s; 1296 while (isSetOperation(first)) { 1297 first = first.getLeftStmt(); 1298 } 1299 return new int[] {start(first), end(s)}; 1300 } 1301 1302 /** 1303 * The operands of one {@code SET_OP}: a chain of one operator with the same {@code ALL}/{@code DISTINCT} form and 1304 * no parentheses is flattened, in text order (Section 3.2, item 3). 1305 */ 1306 private List<TSelectSqlStatement> operands(TSelectSqlStatement s) { 1307 List<TSelectSqlStatement> out = new ArrayList<TSelectSqlStatement>(); 1308 Deque<TSelectSqlStatement> stack = new ArrayDeque<TSelectSqlStatement>(); 1309 stack.push(s.getRightStmt()); 1310 TSelectSqlStatement left = s.getLeftStmt(); 1311 while (isSetOperation(left) && left.getSetOperatorType() == s.getSetOperatorType() && left.isAll() == s.isAll() 1312 && left.getParenthesisCount() == 0 && (left.getCteList() == null || left.getCteList().size() == 0) 1313 && left.getOrderbyClause() == null) { 1314 stack.push(left.getRightStmt()); 1315 left = left.getLeftStmt(); 1316 } 1317 out.add(left); 1318 while (!stack.isEmpty()) { 1319 out.add(stack.pop()); 1320 } 1321 return out; 1322 } 1323 1324 private void block(TSelectSqlStatement s, Construct in) { 1325 if (s.getResultColumnList() != null) { 1326 expressions(add("SELECT_LIST", 0, s.getResultColumnList(), in), s.getResultColumnList()); 1327 } 1328 if (s.getJoins() != null && s.getJoins().size() > 0) { 1329 fromClause("FROM", s.getJoins(), keywordOffset(s.getJoins(), "FROM"), in); 1330 } 1331 if (s.getWhereClause() != null) { 1332 expressions(add("WHERE", 0, s.getWhereClause(), in), s.getWhereClause()); 1333 } 1334 TGroupBy groupBy = s.getGroupByClause(); 1335 if (groupBy != null) { 1336 if (groupBy.getItems() != null && groupBy.getItems().size() > 0) { 1337 int st = groupBy.getGROUP() != null ? offsetOf(groupBy.getGROUP()) : start(groupBy.getItems()); 1338 expressions(add("GROUP_BY", 0, st, end(groupBy.getItems()), in), groupBy.getItems()); 1339 } 1340 if (groupBy.getHavingClause() != null) { 1341 int st = groupBy.getHAVING() != null ? offsetOf(groupBy.getHAVING()) : start(groupBy.getHavingClause()); 1342 expressions(add("HAVING", 0, st, end(groupBy.getHavingClause()), in), groupBy.getHavingClause()); 1343 } 1344 } 1345 if (s.getQualifyClause() != null) { 1346 expressions(add("QUALIFY", 0, s.getQualifyClause(), in), s.getQualifyClause()); 1347 } 1348 if (s.getOrderbyClause() != null) { 1349 expressions(add("ORDER_BY", 0, s.getOrderbyClause(), in), s.getOrderbyClause()); 1350 } 1351 limits(s, in); 1352 if (s.getWindowClause() != null) { 1353 if (s.getWindowClause().getWindowDefs() != null) { 1354 for (int w = 0; w < s.getWindowClause().getWindowDefs().size(); w++) { 1355 TWindowDef definition = (TWindowDef) s.getWindowClause().getWindowDefs().getElement(w); 1356 windowParts(add("WINDOW", w, definition, in), definition.getPartitionClause(), 1357 definition.getOrderBy()); 1358 } 1359 } else if (s.getWindowClause().getWindowDefinitions() != null) { 1360 for (int w = 0; w < s.getWindowClause().getWindowDefinitions().size(); w++) { 1361 TWindowDefinition definition = 1362 (TWindowDefinition) s.getWindowClause() 1363 .getWindowDefinitions().getElement(w); 1364 Construct window = add("WINDOW", w, definition, in); 1365 TWindowSpecification spec = definition.getWindowSpecification(); 1366 if (spec != null) { 1367 windowParts(window, spec.getPartitionClause(), spec.getOrderBy()); 1368 } 1369 } 1370 } 1371 } 1372 THierarchical hierarchical = s.getHierarchicalClause(); 1373 if (hierarchical != null) { 1374 markUncovered("CONNECT BY", hierarchical); 1375 } 1376 } 1377 1378 /** The offset of a keyword written just before the node (such as FROM before the first table). */ 1379 private int keywordOffset(TParseTreeNode node, String keyword) { 1380 TParseTreeNode first = node; 1381 if (node instanceof TJoinList && ((TJoinList) node).size() > 0) { 1382 first = ((TJoinList) node).getJoin(0); 1383 if (first.getStartToken() == null) { 1384 first = ((TJoinList) node).getJoin(0).getTable(); 1385 } 1386 } 1387 return keywordStart(first, keyword) < start(first) ? keywordStart(first, keyword) : -1; 1388 } 1389 1390 /** TOP, LIMIT, OFFSET and FETCH are one LIMIT step; each fragment is a construct with that path. */ 1391 private void limits(TSelectSqlStatement s, Construct in) { 1392 TParseTreeNode[] parts = {s.getTopClause(), s.getLimitClause(), s.getOffsetClause(), s.getFetchFirstClause()}; 1393 for (TParseTreeNode part : parts) { 1394 if (part != null) { 1395 expressions(add("LIMIT", 0, part, in), part); 1396 } 1397 } 1398 } 1399 1400 /** {@code FROM}, {@code USING} below DELETE/MERGE, or the target list of UPDATE: joins and derived tables. */ 1401 private Construct fromClause(String code, TJoinList joins, int keywordAt, Construct in) { 1402 int[] range = joinListRange(joins); 1403 if (range == null) { 1404 return null; 1405 } 1406 Construct from = add(code, 0, keywordAt >= 0 && keywordAt < range[0] ? keywordAt : range[0], range[1], in); 1407 if (from == null) { 1408 return null; 1409 } 1410 joinsAndTables(joins, from); 1411 return from; 1412 } 1413 1414 private int[] joinListRange(TJoinList joins) { 1415 int st = -1; 1416 int en = -1; 1417 for (int i = 0; i < joins.size(); i++) { 1418 TJoin join = joins.getJoin(i); 1419 TParseTreeNode[] parts = {join, join.getTable()}; 1420 for (TParseTreeNode part : parts) { 1421 int ps = start(part); 1422 int pe = end(part); 1423 if (ps >= 0 && pe >= ps) { 1424 st = st < 0 ? ps : Math.min(st, ps); 1425 en = Math.max(en, pe); 1426 } 1427 } 1428 if (join.getJoinItems() != null) { 1429 for (int k = 0; k < join.getJoinItems().size(); k++) { 1430 TJoinItem item = join.getJoinItems().getJoinItem(k); 1431 int ps = start(item); 1432 int pe = Math.max(end(item), end(item.getOnCondition())); 1433 if (ps >= 0 && pe >= ps) { 1434 st = st < 0 ? ps : Math.min(st, ps); 1435 en = Math.max(en, pe); 1436 } 1437 } 1438 } 1439 } 1440 return st < 0 ? null : new int[] {st, en}; 1441 } 1442 1443 private void joinsAndTables(TParseTreeNode node, Construct from) { 1444 final List<TJoinItem> joinItems = new ArrayList<TJoinItem>(); 1445 final List<TTable> tables = new ArrayList<TTable>(); 1446 final List<TSelectSqlStatement> selects = new ArrayList<TSelectSqlStatement>(); 1447 final List<TPivotClause> pivots = new ArrayList<TPivotClause>(); 1448 node.acceptChildren(new TParseTreeVisitor() { 1449 @Override 1450 public void preVisit(TJoinItem item) { 1451 joinItems.add(item); 1452 } 1453 1454 @Override 1455 public void preVisit(TTable table) { 1456 tables.add(table); 1457 } 1458 1459 @Override 1460 public void preVisit(TSelectSqlStatement stmt) { 1461 selects.add(stmt); 1462 } 1463 1464 @Override 1465 public void preVisit(TPivotClause pivot) { 1466 pivots.add(pivot); 1467 } 1468 }); 1469 List<TSelectSqlStatement> outer = outermost(selects); 1470 // JOIN[j]: the join items outside every derived table, in text order of their JOIN keyword; an APPLY is no 1471 // JOIN keyword (its item is a lateral item of the clause, LATERAL[l] below FROM[0], plan 09 slice 11) 1472 List<TJoinItem> items = new ArrayList<TJoinItem>(); 1473 final List<TTable> applied = new ArrayList<TTable>(); 1474 for (TJoinItem item : joinItems) { 1475 if (containedInAny(item, outer)) { 1476 continue; 1477 } 1478 if (item.getJoinType() == EJoinType.crossapply || item.getJoinType() == EJoinType.outerapply) { 1479 if (item.getTable() != null) { 1480 applied.add(item.getTable()); 1481 } 1482 continue; 1483 } 1484 items.add(item); 1485 } 1486 Collections.sort(items, new Comparator<TJoinItem>() { 1487 @Override 1488 public int compare(TJoinItem a, TJoinItem b) { 1489 return Integer.compare(start(a), start(b)); 1490 } 1491 }); 1492 List<Construct> joinConstructs = new ArrayList<Construct>(); 1493 for (int j = 0; j < items.size(); j++) { 1494 TJoinItem item = items.get(j); 1495 int en = Math.max(end(item), end(item.getOnCondition())); 1496 if (item.getUsingColumns() != null) { 1497 en = Math.max(en, end(item.getUsingColumns())); 1498 } 1499 Construct join = add("JOIN", j, keywordStart(item, "JOIN"), en, from); 1500 if (join == null) { 1501 continue; 1502 } 1503 joinConstructs.add(join); 1504 if (item.getOnCondition() != null) { 1505 expressions(add("ON", 0, keywordStart(item.getOnCondition(), "ON"), end(item.getOnCondition()), join), 1506 item.getOnCondition()); 1507 } 1508 if (item.getUsingColumns() != null) { 1509 add("USING", 0, keywordStart(item.getUsingColumns(), "USING"), end(item.getUsingColumns()), join); 1510 } 1511 } 1512 final List<TPivotClause> covered = new ArrayList<TPivotClause>(); 1513 for (TTable table : tables) { 1514 if (table.getSubquery() == null ? containedInAny(table, outer) : !outer.contains(table.getSubquery())) { 1515 continue; 1516 } 1517 List<TPivotClause> operators = pivotClauses(table); 1518 if (!operators.isEmpty()) { 1519 // PATH-V1 PIVOT[p] (plan 09 slice 10): the operators below the step of the item they apply to, in text 1520 // order; the item itself (a table name, or a derived table, SUBQUERY[k]) is reached as an item of its own 1521 Construct parent = from; 1522 for (Construct join : joinConstructs) { 1523 if (join.contains(start(table)) && (parent == from || join.length() < parent.length())) { 1524 parent = join; 1525 } 1526 } 1527 for (TPivotClause pivot : operators) { 1528 Construct op = add("PIVOT", -1, keywordStart(pivot, "PIVOT", "UNPIVOT"), end(pivot), parent); 1529 if (op == null) { 1530 continue; 1531 } 1532 covered.add(pivot); 1533 if (pivot.getAggregation_function_list() != null) { 1534 expressions(op, pivot.getAggregation_function_list()); 1535 } else if (pivot.getAggregation_function() != null) { 1536 expressions(op, pivot.getAggregation_function()); 1537 } 1538 TPivotInClause in = pivot.getPivotInClause(); 1539 if (in != null && in.getSubQuery() != null) { 1540 Construct sub = add("SUBQUERY", -1, in.getSubQuery(), op); 1541 if (sub != null) { 1542 work.push(new Object[] {in.getSubQuery(), sub}); 1543 } 1544 } 1545 } 1546 continue; 1547 } 1548 if (lateralItem(table, applied, from, joinConstructs)) { 1549 continue; 1550 } 1551 String form = uncoveredTableForm(table); 1552 if (form != null) { 1553 markUncovered(form, table); 1554 continue; 1555 } 1556 TSourceToken pattern = matchRecognizeToken(table); 1557 if (pattern != null) { 1558 // path-v1 Section 2, item 3: the clause (its partition, measures, pattern and definitions) is not 1559 // covered; the item's rows are its table's (doc 10, Section 2.1), its columns are not (StatementGraph) 1560 markUncovered("MATCH_RECOGNIZE", offsetOf(pattern), end(table)); 1561 } 1562 if (table.getSubquery() != null) { 1563 Construct parent = from; 1564 for (Construct join : joinConstructs) { 1565 if (join.contains(start(table)) && (parent == from || join.length() < parent.length())) { 1566 parent = join; 1567 } 1568 } 1569 Construct sub = add("SUBQUERY", -1, table.getSubquery(), parent); 1570 if (sub != null) { 1571 work.push(new Object[] {table.getSubquery(), sub}); 1572 } 1573 } 1574 } 1575 for (TPivotClause pivot : pivots) { 1576 if (!containedInAny(pivot, outer) && !covered.contains(pivot)) { 1577 markUncovered("PIVOT", pivot); 1578 } 1579 } 1580 } 1581 1582 /** 1583 * PATH-V1 {@code LATERAL[l]} (plan 09 slice 11): a lateral item of a FROM clause, below the step of the item 1584 * (FROM[0], or the JOIN[j] that introduces it), numbered in text order among the lateral items below that step: a 1585 * derived table written after LATERAL or the right side of an APPLY (its query is SUBQUERY[0] below it), and a call 1586 * in FROM (a table function, TABLE(...), UNNEST, a Hive-family LATERAL VIEW), whose operands are FUNCTION_ARG[k] 1587 * below it (Section 5, item 1: TABLE(f(x)) is f's call as FUNCTION_ARG[0]). A VALUES list used as a FROM item is 1588 * a derived table, SUBQUERY[k], whose rows are VALUES[r]. Its Hive LATERAL VIEWs follow a table name. Returns 1589 * whether the item was one of these. 1590 */ 1591 private boolean lateralItem(TTable table, List<TTable> applied, Construct from, List<Construct> joinConstructs) { 1592 Construct parent = from; 1593 // an APPLY's item is directly below FROM[0] even inside a parenthesized join tree (APPLY is no JOIN keyword; 1594 // Codex slice 11 #10) 1595 for (Construct join : applied.contains(table) ? Collections.<Construct>emptyList() : joinConstructs) { 1596 if (join.contains(start(table)) && (parent == from || join.length() < parent.length())) { 1597 parent = join; 1598 } 1599 } 1600 if (table.getTableType() == ETableSource.objectname && table.getLateralViewList() != null) { 1601 for (TLateralView view : table.getLateralViewList()) { 1602 Construct lateral = add("LATERAL", -1, view, from); 1603 if (lateral != null && view.getUdtf() != null) { 1604 tableCall(lateral, view.getUdtf()); 1605 } 1606 } 1607 return false; // the table itself is an item of its own 1608 } 1609 ETableSource type = table.getTableType(); 1610 boolean query = table.getSubquery() != null && (applied.contains(table) || "LATERAL".equals(lateralKeyword(table))); 1611 if (query) { 1612 int st = keywordStart(table, "LATERAL"); 1613 Construct lateral = add("LATERAL", -1, Math.min(st >= 0 ? st : start(table), start(table.getSubquery())), 1614 Math.max(end(table), end(table.getSubquery())), parent); 1615 Construct sub = lateral == null ? null : add("SUBQUERY", 0, table.getSubquery(), lateral); 1616 if (sub != null) { 1617 work.push(new Object[] {table.getSubquery(), sub}); 1618 } 1619 return true; 1620 } 1621 if (type == ETableSource.function || type == ETableSource.unnest || type == ETableSource.tableExpr) { 1622 int st = keywordStart(table, "LATERAL"); 1623 Construct lateral = add("LATERAL", -1, st >= 0 ? st : start(table), end(table), parent); 1624 if (lateral == null) { 1625 return true; 1626 } 1627 if (type == ETableSource.function && table.getFuncCall() != null) { 1628 tableCall(lateral, table.getFuncCall()); 1629 } else if (type == ETableSource.unnest && table.getUnnestClause() != null) { 1630 // UNNEST(a, b) is a call: its arrays are its operands (an expression, or the column of the legacy 1631 // UNNEST(t.arr), in the order TableFunctionLayout reads them) 1632 List<TParseTreeNode> arrays = unnestArrays(table.getUnnestClause()); 1633 for (int k = 0; k < arrays.size(); k++) { 1634 addExpression("FUNCTION_ARG", k, start(arrays.get(k)), end(arrays.get(k)), lateral); 1635 expressions(lateral, arrays.get(k)); 1636 } 1637 } else if (type == ETableSource.tableExpr && table.getTableExpr() != null) { 1638 // Oracle TABLE(collection): a call whose one operand is the collection 1639 addExpression("FUNCTION_ARG", 0, start(table.getTableExpr()), end(table.getTableExpr()), lateral); 1640 expressions(lateral, table.getTableExpr()); 1641 } 1642 return true; 1643 } 1644 if (type == ETableSource.rowList || (type == ETableSource.subquery && table.getSubquery() == null 1645 && table.getValueClause() != null)) { 1646 Construct sub = add("SUBQUERY", -1, table, parent); 1647 if (sub != null) { 1648 valuesRows(valueRows(table), sub); 1649 } 1650 return true; 1651 } 1652 return false; 1653 } 1654 1655 /** 1656 * The operands of a call in FROM, FUNCTION_ARG[k] below its LATERAL[l] (Section 5, item 1): each argument as 1657 * written, a named argument ({@code input => x}) whole. Read from the call's arguments: a vendor's own call class 1658 * (Snowflake's FLATTEN) is not a call the expression visitor lists. 1659 */ 1660 private void tableCall(Construct lateral, TFunctionCall call) { 1661 TExpressionList args = call.getArgs(); 1662 for (int k = 0; args != null && k < args.size(); k++) { 1663 TExpression arg = args.getExpression(k); 1664 if (arg != null && start(arg) >= 0) { 1665 addExpression("FUNCTION_ARG", k, start(arg), end(arg), lateral); 1666 expressions(lateral, arg); 1667 } 1668 } 1669 } 1670 1671 /** The arrays of an UNNEST, as {@code TableFunctionLayout.unnestArrays} reads them. */ 1672 private static List<TParseTreeNode> unnestArrays(TUnnestClause clause) { 1673 List<TParseTreeNode> arrays = new ArrayList<TParseTreeNode>(); 1674 if (clause.getExprList() != null && clause.getExprList().size() > 0) { 1675 for (int i = 0; i < clause.getExprList().size(); i++) { 1676 arrays.add(clause.getExprList().getExpression(i)); 1677 } 1678 } else if (clause.getArrayExpr() != null) { 1679 arrays.add(clause.getArrayExpr()); 1680 } else if (clause.getColumns() != null) { 1681 for (int i = 0; i < clause.getColumns().size(); i++) { 1682 arrays.add(clause.getColumns().getObjectName(i)); 1683 } 1684 } 1685 return arrays; 1686 } 1687 1688 /** "LATERAL" when that keyword stands before a derived table (or between its start and its query), else null. */ 1689 private String lateralKeyword(TTable table) { 1690 TSourceToken before = previousSolid(table.getStartToken()); 1691 if (before != null && "LATERAL".equalsIgnoreCase(before.getAstext())) { // non-identifier-compare: keyword 1692 return "LATERAL"; 1693 } 1694 if (table.getSubquery() != null && table.getStartToken() != null) { 1695 int subStart = start(table.getSubquery()); 1696 for (TSourceToken t = table.getStartToken(); t != null && offsetOf(t) >= 0 && offsetOf(t) < subStart; 1697 t = t.getNextTokenInChain()) { 1698 if ("LATERAL".equalsIgnoreCase(t.getAstext())) { // non-identifier-compare: keyword 1699 return "LATERAL"; 1700 } 1701 } 1702 } 1703 return null; 1704 } 1705 1706 /** The rows of a VALUES list used as a FROM item: its value clause's, or its row list's. */ 1707 private List<TParseTreeNode> valueRows(TTable table) { 1708 List<TParseTreeNode> rows = new ArrayList<TParseTreeNode>(); 1709 if (table.getValueClause() != null && table.getValueClause().getRows() != null 1710 && !table.getValueClause().getRows().isEmpty()) { 1711 rows.addAll(table.getValueClause().getRows()); 1712 } else if (table.getRowList() != null) { 1713 for (int r = 0; r < table.getRowList().size(); r++) { 1714 rows.add(table.getRowList().getMultiTarget(r)); 1715 } 1716 } 1717 return rows; 1718 } 1719 1720 /** VALUES[r] (plan 09 slice 11): the r-th row of a VALUES list used as a query, directly below its step. */ 1721 private void valuesRows(List<? extends TParseTreeNode> rows, Construct in) { 1722 for (int r = 0; r < rows.size(); r++) { 1723 TParseTreeNode row = rows.get(r); 1724 Construct c = add("VALUES", r, row, in); 1725 if (c != null) { 1726 expressions(c, row); 1727 } 1728 } 1729 } 1730 1731 /** 1732 * The one PIVOT or UNPIVOT operator a FROM item applies to its table or derived table (plan 09 slice 10), or null: 1733 * an item with no operator, with several chained operators, or a Teradata TD_UNPIVOT table operator. 1734 */ 1735 static TPivotClause singlePivot(TTable table) { 1736 List<TPivotClause> clauses = pivotClauses(table); 1737 return clauses.size() == 1 ? clauses.get(0) : null; 1738 } 1739 1740 /** 1741 * The PIVOT and UNPIVOT operators a FROM item applies to its table or derived table, in text order (a chain has 1742 * several), or none: an item with no operator, or a Teradata TD_UNPIVOT table operator. 1743 */ 1744 static List<TPivotClause> pivotClauses(TTable table) { 1745 List<TPivotClause> out = new ArrayList<TPivotClause>(); 1746 if (table == null || table.getTableType() != ETableSource.pivoted_table || table.getPivotedTable() == null 1747 || table.getPivotedTable().getTableSource() == null) { 1748 return out; 1749 } 1750 TPTNodeList clauses = table.getPivotedTable().getPivotClauseList(); 1751 for (int i = 0; clauses != null && i < clauses.size(); i++) { 1752 if (!(clauses.getElement(i) instanceof TPivotClause)) { 1753 return new ArrayList<TPivotClause>(); 1754 } 1755 out.add((TPivotClause) clauses.getElement(i)); 1756 } 1757 return out; 1758 } 1759 1760 /** 1761 * The PATH-V1 form a table source is not covered by, or null for a table name or a derived table (lateral items 1762 * and VALUES lists are steps of their own: {@link #lateralItem}). 1763 */ 1764 private String uncoveredTableForm(TTable table) { 1765 ETableSource type = table.getTableType(); 1766 if (table.getPivotClause() != null || type == ETableSource.pivoted_table || type == ETableSource.td_unpivot) { 1767 return "PIVOT"; 1768 } 1769 if (type == ETableSource.subquery || table.getSubquery() != null || type == ETableSource.objectname 1770 || type == ETableSource.join || type == ETableSource.lateralView) { 1771 // a LATERAL VIEW's table: its step is the view's own, made with the table it follows 1772 return null; 1773 } 1774 return "TABLE " + type; 1775 } 1776 1777 /** 1778 * Whether a FROM item carries a MATCH_RECOGNIZE clause. GSP keeps no node for it: the item's tokens run over the 1779 * clause, whose result has the partition columns and the measures, never the table's other columns. 1780 */ 1781 static boolean matchRecognize(TTable item) { 1782 return item != null && item.getTableType() != ETableSource.function && matchRecognizeToken(item) != null; 1783 } 1784 1785 /** 1786 * Why a column read through a MATCH_RECOGNIZE item is not the table's column of that name, or null when it is 1787 * (plan 09 slice 13j). The result has the PARTITION BY columns and the measures; ALL ROWS PER MATCH keeps every 1788 * other column of the table too. A measure takes its name from its alias, so a name a measure carries is never 1789 * the table's column, and with ONE ROW PER MATCH a name no PARTITION BY item carries is none of the result's. 1790 * The clause has no node: its tokens are read at depth one of its parentheses. 1791 */ 1792 static String matchRecognizeColumn(TTable item, EDbVendor vendor, String column) { 1793 TSourceToken keyword = matchRecognizeToken(item); 1794 String over = item.getTableName() == null ? "the table" : item.getTableName().toString(); 1795 String unknown = "may name a measure of MATCH_RECOGNIZE over " + over + ", whose columns are not published yet"; 1796 if (keyword == null) { 1797 return unknown; 1798 } 1799 List<TSourceToken> solid = new ArrayList<TSourceToken>(); 1800 for (int i = keyword.posinlist + 1; i < keyword.container.size(); i++) { 1801 TSourceToken t = keyword.container.get(i); 1802 if (!blank(t)) { 1803 solid.add(t); 1804 } 1805 if (t == item.getEndToken()) { 1806 break; 1807 } 1808 } 1809 List<TSourceToken> partition = new ArrayList<TSourceToken>(); 1810 List<TSourceToken> measures = new ArrayList<TSourceToken>(); 1811 boolean allRows = false; 1812 boolean closed = false; 1813 String clause = ""; 1814 TSourceToken last = null; // the last solid token of the measure being read: its alias 1815 int depth = 0; 1816 for (int i = 0; i < solid.size() && !closed; i++) { 1817 TSourceToken t = solid.get(i); 1818 String text = t.getAstext(); 1819 String k = text.toUpperCase(Locale.ROOT); 1820 boolean word = t.tokentype != ETokenType.ttdqstring && t.tokentype != ETokenType.ttsqstring; 1821 if ("(".equals(text)) { 1822 depth++; 1823 if (depth > 1 && "MEASURES".equals(clause)) { 1824 last = t; 1825 } 1826 } else if (")".equals(text)) { 1827 depth--; 1828 closed = depth == 0; 1829 if (!closed && "MEASURES".equals(clause)) { 1830 last = t; 1831 } 1832 } else if (depth == 1 && word && !"DEFINE".equals(clause) && ("PARTITION".equals(k) || "ORDER".equals(k) 1833 || "MEASURES".equals(k) || "ONE".equals(k) || "ALL".equals(k) || "AFTER".equals(k) 1834 || "PATTERN".equals(k) || "SUBSET".equals(k) || "DEFINE".equals(k))) { 1835 if ("MEASURES".equals(clause)) { 1836 if (last == null) { 1837 return unknown; 1838 } 1839 measures.add(last); 1840 } 1841 last = null; 1842 clause = k; 1843 allRows |= "ALL".equals(k) && i + 1 < solid.size() 1844 && "ROWS".equals(solid.get(i + 1).getAstext().toUpperCase(Locale.ROOT)); 1845 } else if ("MEASURES".equals(clause)) { 1846 if (depth == 1 && ",".equals(text)) { 1847 if (last == null) { 1848 return unknown; 1849 } 1850 measures.add(last); 1851 last = null; 1852 } else { 1853 last = t; 1854 } 1855 } else if (depth == 1 && "PARTITION".equals(clause) && !"BY".equals(k) && !",".equals(text)) { 1856 if (".".equals(text)) { 1857 if (!partition.isEmpty()) { 1858 partition.remove(partition.size() - 1); // a qualifier: the column is the part after it 1859 } 1860 } else { 1861 partition.add(t); 1862 } 1863 } 1864 } 1865 if (!closed) { 1866 return unknown; 1867 } 1868 if ("MEASURES".equals(clause)) { 1869 if (last == null) { 1870 return unknown; 1871 } 1872 measures.add(last); 1873 } 1874 for (TSourceToken alias : measures) { 1875 if (alias.tokentype != ETokenType.ttidentifier && alias.tokentype != ETokenType.ttdqstring 1876 && alias.tokentype != ETokenType.ttkeyword) { 1877 return unknown; // a measure that does not end in a name: its alias is not read 1878 } 1879 if (gudusoft.gsqlparser.util.SQLUtil.sameName(vendor, 1880 gudusoft.gsqlparser.sqlenv.ESQLDataObjectType.dotColumn, alias.getAstext(), column)) { 1881 return "names a measure of MATCH_RECOGNIZE over " + over + ", whose value is not published yet"; 1882 } 1883 } 1884 if (allRows) { 1885 return null; 1886 } 1887 for (TSourceToken name : partition) { 1888 if (gudusoft.gsqlparser.util.SQLUtil.sameName(vendor, 1889 gudusoft.gsqlparser.sqlenv.ESQLDataObjectType.dotColumn, name.getAstext(), column)) { 1890 return null; 1891 } 1892 } 1893 return "is no PARTITION BY column or measure of MATCH_RECOGNIZE over " + over + " (ONE ROW PER MATCH): the " 1894 + "result has no such column"; 1895 } 1896 1897 private static boolean blank(TSourceToken t) { 1898 return t.tokentype == ETokenType.ttwhitespace || t.tokentype == ETokenType.ttreturn 1899 || t.tokentype == ETokenType.ttsimplecomment || t.tokentype == ETokenType.ttbracketedcomment; 1900 } 1901 1902 /** Whether the tokens of a node name MATCH_RECOGNIZE anywhere (a cheap test before {@link #matchRecognize}). */ 1903 static boolean mentionsMatchRecognize(TParseTreeNode node) { 1904 TSourceToken start = node == null ? null : node.getStartToken(); 1905 TSourceToken end = node == null ? null : node.getEndToken(); 1906 if (start == null || end == null || start.container == null || end.container != start.container) { 1907 return true; // no range to scan: ask item by item 1908 } 1909 for (int i = start.posinlist; i <= end.posinlist && i < start.container.size(); i++) { 1910 if ("MATCH_RECOGNIZE".equalsIgnoreCase(start.container.get(i).getAstext())) { // non-identifier-compare: keyword 1911 return true; 1912 } 1913 } 1914 return false; 1915 } 1916 1917 /** The MATCH_RECOGNIZE keyword of a FROM item followed by its parenthesis, or null. */ 1918 private static TSourceToken matchRecognizeToken(TTable item) { 1919 TSourceToken start = item == null ? null : item.getStartToken(); 1920 TSourceToken end = item == null ? null : item.getEndToken(); 1921 if (start == null || end == null || start.container == null || end.container != start.container) { 1922 return null; 1923 } 1924 int depth = 0; 1925 for (int i = start.posinlist; i <= end.posinlist && i < start.container.size(); i++) { 1926 TSourceToken t = start.container.get(i); 1927 if ("(".equals(t.getAstext())) { 1928 depth++; 1929 } else if (")".equals(t.getAstext())) { 1930 depth--; 1931 } 1932 // the item's own clause, never one of a query or a call nested in it (Codex 13j #2) 1933 if (depth != 0 || !"MATCH_RECOGNIZE".equalsIgnoreCase(t.getAstext())) { // non-identifier-compare: keyword 1934 continue; 1935 } 1936 for (int j = i + 1; j <= end.posinlist && j < start.container.size(); j++) { 1937 TSourceToken n = start.container.get(j); 1938 if (!blank(n)) { 1939 if ("(".equals(n.getAstext())) { 1940 return t; 1941 } 1942 break; 1943 } 1944 } 1945 } 1946 return null; 1947 } 1948 1949 private List<TSelectSqlStatement> outermost(List<TSelectSqlStatement> selects) { 1950 List<int[]> ranges = new ArrayList<int[]>(); 1951 for (TSelectSqlStatement s : selects) { 1952 ranges.add(new int[] {start(s), end(s)}); 1953 } 1954 List<TSelectSqlStatement> out = new ArrayList<TSelectSqlStatement>(); 1955 for (int i = 0; i < selects.size(); i++) { 1956 boolean inner = false; 1957 for (int j = 0; j < selects.size() && !inner; j++) { 1958 if (i != j && ranges.get(j)[0] <= ranges.get(i)[0] && ranges.get(i)[1] <= ranges.get(j)[1] 1959 && (ranges.get(j)[0] < ranges.get(i)[0] || ranges.get(i)[1] < ranges.get(j)[1] || j < i)) { 1960 inner = true; 1961 } 1962 } 1963 if (!inner) { 1964 out.add(selects.get(i)); 1965 } 1966 } 1967 return out; 1968 } 1969 1970 /** Whether the whole node lies within one of the statements (a join item can start at a derived table's paren). */ 1971 private boolean containedInAny(TParseTreeNode node, List<TSelectSqlStatement> selects) { 1972 int ns = start(node); 1973 int ne = Math.max(end(node), node instanceof TJoinItem ? end(((TJoinItem) node).getOnCondition()) : -1); 1974 for (TSelectSqlStatement s : selects) { 1975 if (start(s) <= ns && ne <= end(s)) { 1976 return true; 1977 } 1978 } 1979 return false; 1980 } 1981 1982 private boolean insideAny(int offset, List<TSelectSqlStatement> selects) { 1983 for (TSelectSqlStatement s : selects) { 1984 if (start(s) <= offset && offset < end(s)) { 1985 return true; 1986 } 1987 } 1988 return false; 1989 } 1990 1991 /** 1992 * The expression steps of one clause (Section 5): calls and their operands, CASE parts, subqueries and OVER 1993 * clauses. Nodes inside a subquery are left to that subquery's own query expression. 1994 */ 1995 private void expressions(final Construct clause, TParseTreeNode node) { 1996 if (clause == null || node == null) { 1997 return; 1998 } 1999 final List<TFunctionCall> calls = new ArrayList<TFunctionCall>(); 2000 final List<TCaseExpression> cases = new ArrayList<TCaseExpression>(); 2001 final List<TExpression> exprs = new ArrayList<TExpression>(); 2002 final List<TSelectSqlStatement> selects = new ArrayList<TSelectSqlStatement>(); 2003 final List<TParseTreeNode> notCovered = new ArrayList<TParseTreeNode>(); 2004 node.acceptChildren(new TParseTreeVisitor() { 2005 @Override 2006 public void preVisit(TFunctionCall call) { 2007 calls.add(call); 2008 } 2009 2010 @Override 2011 public void preVisit(TCaseExpression expr) { 2012 cases.add(expr); 2013 } 2014 2015 @Override 2016 public void preVisit(TExpression expr) { 2017 exprs.add(expr); 2018 } 2019 2020 @Override 2021 public void preVisit(TSelectSqlStatement stmt) { 2022 selects.add(stmt); 2023 } 2024 2025 @Override 2026 public void preVisit(TPivotClause pivot) { 2027 notCovered.add(pivot); 2028 } 2029 2030 @Override 2031 public void preVisit(TReturningClause returning) { 2032 notCovered.add(returning); 2033 } 2034 2035 @Override 2036 public void preVisit(TOutputClause output) { 2037 notCovered.add(output); 2038 } 2039 }); 2040 if (node instanceof TExpression) { 2041 TExpression e = (TExpression) node; 2042 if (exprs.isEmpty() || exprs.get(0) != e) { 2043 exprs.add(0, e); // TExpression.acceptChildren visits the node itself first: never list it twice 2044 } 2045 if (e.getFunctionCall() != null && !calls.contains(e.getFunctionCall())) { 2046 calls.add(0, e.getFunctionCall()); 2047 } 2048 if (e.getCaseExpression() != null && !cases.contains(e.getCaseExpression())) { 2049 cases.add(0, e.getCaseExpression()); 2050 } 2051 if (e.getSubQuery() != null && !selects.contains(e.getSubQuery())) { 2052 selects.add(0, e.getSubQuery()); 2053 } 2054 } 2055 List<TSelectSqlStatement> outer = outermost(selects); 2056 for (TSelectSqlStatement sub : outer) { 2057 Construct c = addExpression("SUBQUERY", -1, start(sub), end(sub), clause); 2058 if (c != null) { 2059 work.push(new Object[] {sub, c}); 2060 } 2061 } 2062 for (TParseTreeNode n : notCovered) { 2063 if (!insideAny(start(n), outer)) { 2064 markUncovered(n instanceof TPivotClause ? "PIVOT" : n instanceof TOutputClause ? "OUTPUT" : "RETURNING", 2065 n); 2066 } 2067 } 2068 for (TExpression e : exprs) { 2069 if (e.getExpressionType() == EExpressionType.lambda_t && !insideAny(start(e), outer)) { 2070 markUncovered("LAMBDA", e); 2071 } 2072 } 2073 for (TCaseExpression k : cases) { 2074 if (insideAny(start(k), outer) || k.getWhenClauseItemList() == null) { 2075 continue; 2076 } 2077 for (int i = 0; i < k.getWhenClauseItemList().size(); i++) { 2078 TWhenClauseItem when = k.getWhenClauseItemList().getWhenClauseItem(i); 2079 if (when.getComparison_expr() != null) { 2080 addExpression("CASE_WHEN", i, start(when.getComparison_expr()), end(when.getComparison_expr()), 2081 clause); 2082 } 2083 if (when.getReturn_expr() != null) { 2084 addExpression("CASE_THEN", i, start(when.getReturn_expr()), end(when.getReturn_expr()), clause); 2085 } 2086 } 2087 if (k.getElse_expr() != null) { 2088 addExpression("CASE_ELSE", 0, start(k.getElse_expr()), end(k.getElse_expr()), clause); 2089 } 2090 } 2091 ExpressionTree tree = new ExpressionTree(exprs, outer); 2092 for (TFunctionCall call : calls) { 2093 if (insideAny(start(call), outer)) { 2094 continue; 2095 } 2096 List<int[]> excluded = new ArrayList<int[]>(); 2097 TWindowDef over = call.getWindowDef(); 2098 if (over != null && start(over) >= 0) { 2099 int[] window = {start(over), end(over)}; 2100 excluded.add(window); 2101 if (over.getWithinGroup() != null && !over.isIncludingOverClause()) { 2102 // SQL Server keeps WITHIN GROUP in the window definition 2103 markUncovered("WITHIN GROUP", window[0], window[1]); 2104 } else if (over.getName() == null || over.getPartitionClause() != null || over.getOrderBy() != null) { 2105 Construct w = addExpression("WINDOW", 0, window[0], window[1], clause); 2106 windowParts(w, over.getPartitionClause(), over.getOrderBy()); 2107 } 2108 } 2109 if (call.getFilterClause() != null) { 2110 markUncovered("AGGREGATE FILTER", call.getFilterClause()); 2111 excluded.add(new int[] {start(call.getFilterClause()), end(call.getFilterClause())}); 2112 } 2113 if (call.getWithinGroup() != null) { 2114 markUncovered("WITHIN GROUP", call.getWithinGroup()); 2115 excluded.add(new int[] {start(call.getWithinGroup()), end(call.getWithinGroup())}); 2116 } 2117 if (call.getTrimArgument() != null) { 2118 int k = 0; 2119 TExpression[] trimOperands = {call.getTrimArgument().getTrimCharacter(), 2120 call.getTrimArgument().getStringExpression()}; 2121 for (TExpression operand : trimOperands) { 2122 if (operand != null && start(operand) >= 0) { 2123 addExpression("FUNCTION_ARG", k++, start(operand), end(operand), clause); 2124 } 2125 } 2126 // the visitor does not walk the trim character: its calls, CASEs and subqueries get their steps here 2127 if (call.getTrimArgument().getTrimCharacter() != null) { 2128 expressions(clause, call.getTrimArgument().getTrimCharacter()); 2129 } 2130 continue; 2131 } 2132 List<TExpression> operands = tree.operandsOf(call, excluded); 2133 for (int k = 0; k < operands.size(); k++) { 2134 addExpression("FUNCTION_ARG", k, start(operands.get(k)), end(operands.get(k)), clause); 2135 } 2136 } 2137 } 2138 2139 /** 2140 * The TExpression nodes of one clause as a containment forest (their text ranges nest), so the operands of a 2141 * call are the children of its own expression node: no scan of every expression per call. 2142 */ 2143 private final class ExpressionTree { 2144 private final Map<TExpression, List<TExpression>> children = new IdentityHashMap<TExpression, List<TExpression>>(); 2145 private final Map<Long, TExpression> byRange = new HashMap<Long, TExpression>(); 2146 2147 private final List<TExpression> all; 2148 2149 ExpressionTree(List<TExpression> exprs, List<TSelectSqlStatement> outer) { 2150 List<TExpression> list = new ArrayList<TExpression>(); 2151 final Map<TExpression, Integer> order = new IdentityHashMap<TExpression, Integer>(); 2152 for (TExpression e : exprs) { 2153 // one node per object: a node listed twice would be its own child, and operandsOf would never end 2154 if (start(e) >= 0 && end(e) >= start(e) && !insideAny(start(e), outer) && !order.containsKey(e)) { 2155 order.put(e, Integer.valueOf(list.size())); 2156 list.add(e); 2157 } 2158 } 2159 Collections.sort(list, new Comparator<TExpression>() { 2160 @Override 2161 public int compare(TExpression a, TExpression b) { 2162 int c = Integer.compare(start(a), start(b)); 2163 if (c != 0) { 2164 return c; 2165 } 2166 c = Integer.compare(end(b), end(a)); 2167 return c != 0 ? c : order.get(a).compareTo(order.get(b)); 2168 } 2169 }); 2170 this.all = list; 2171 Deque<TExpression> stack = new ArrayDeque<TExpression>(); 2172 for (TExpression e : list) { 2173 while (!stack.isEmpty() && !(start(stack.peek()) <= start(e) && end(e) <= end(stack.peek()))) { 2174 stack.pop(); 2175 } 2176 if (!stack.isEmpty()) { 2177 List<TExpression> kids = children.get(stack.peek()); 2178 if (kids == null) { 2179 kids = new ArrayList<TExpression>(); 2180 children.put(stack.peek(), kids); 2181 } 2182 kids.add(e); 2183 } 2184 stack.push(e); 2185 Long key = Long.valueOf(((long) start(e) << 32) | (end(e) & 0xffffffffL)); 2186 if (!byRange.containsKey(key) && e.getExpressionType() == EExpressionType.function_t) { 2187 byRange.put(key, e); 2188 } 2189 } 2190 } 2191 2192 /** 2193 * The outermost expressions strictly inside a range, for a call whose own expression node is not in the 2194 * tree (its range differs from the call's): a scan of the clause, used only then. 2195 */ 2196 private List<TExpression> maximalWithin(int from, int to) { 2197 List<TExpression> out = new ArrayList<TExpression>(); 2198 int lastEnd = -1; 2199 for (TExpression e : all) { // sorted by start, then end descending 2200 int es = start(e); 2201 int ee = end(e); 2202 if (es < from || ee > to || (es == from && ee == to) || es < lastEnd) { 2203 continue; 2204 } 2205 out.add(e); 2206 lastEnd = ee; 2207 } 2208 return out; 2209 } 2210 2211 /** The expression operands of a call, in text order (Section 5, item 1). */ 2212 List<TExpression> operandsOf(TFunctionCall call, List<int[]> excluded) { 2213 List<TExpression> out = new ArrayList<TExpression>(); 2214 int callStart = start(call); 2215 int callEnd = end(call); 2216 int nameEnd = end(call.getFunctionName()); 2217 TExpression self = byRange.get(Long.valueOf(((long) callStart << 32) | (callEnd & 0xffffffffL))); 2218 List<TExpression> candidates = self != null ? children.get(self) : null; 2219 if (candidates == null) { 2220 candidates = maximalWithin(callStart, callEnd); 2221 } 2222 // a child with the same range as the call is the call itself again (a wrapper): use its children 2223 while (candidates.size() == 1 && start(candidates.get(0)) == callStart && end(candidates.get(0)) == callEnd) { 2224 List<TExpression> next = children.get(candidates.get(0)); 2225 if (next == null) { 2226 return out; 2227 } 2228 candidates = next; 2229 } 2230 String name = functionName(call); 2231 for (TExpression e : candidates) { 2232 int es = start(e); 2233 if (es < nameEnd || end(e) > callEnd || insideAnyRange(es, excluded) || isWildcard(e) 2234 || isKeywordOnly(e)) { 2235 continue; 2236 } 2237 out.add(e); 2238 } 2239 if (!out.isEmpty() && FIRST_IS_DATE_PART.contains(name) && isDatePart(out.get(0))) { 2240 out.remove(0); 2241 } else if (!out.isEmpty() && LAST_IS_DATE_PART.contains(name) && isDatePart(out.get(out.size() - 1))) { 2242 out.remove(out.size() - 1); 2243 } 2244 return out; 2245 } 2246 } 2247 2248 private static String functionName(TFunctionCall call) { 2249 if (call.getFunctionName() == null) { 2250 return ""; 2251 } 2252 String name = call.getFunctionName().getObjectString(); 2253 if (name == null || name.isEmpty()) { 2254 name = call.getFunctionName().toString(); 2255 } 2256 return name.toUpperCase(Locale.ROOT); // non-identifier-compare: built-in function names 2257 } 2258 2259 private static boolean insideAnyRange(int offset, List<int[]> ranges) { 2260 for (int[] r : ranges) { 2261 if (r[0] <= offset && offset < r[1]) { 2262 return true; 2263 } 2264 } 2265 return false; 2266 } 2267 2268 /** The {@code *} of {@code COUNT(*)}: a wildcard, decided by the parse (its part token is an asterisk). */ 2269 private static boolean isWildcard(TExpression e) { 2270 if (e.getStartToken() != null && e.getStartToken() == e.getEndToken() 2271 && e.getStartToken().tokentype == ETokenType.ttasterisk) { 2272 return true; 2273 } 2274 TObjectName name = e.getObjectOperand(); 2275 return e.getExpressionType() == EExpressionType.simple_object_name_t && name != null 2276 && name.getPartToken() != null && name.getPartToken().tokentype == ETokenType.ttasterisk; 2277 } 2278 2279 /** A date-part name such as {@code day} in {@code DATEADD(day, n, d)}: an unqualified name, not an operand. */ 2280 private static boolean isDatePart(TExpression e) { 2281 if (e.getExpressionType() != EExpressionType.simple_object_name_t || e.getObjectOperand() == null) { 2282 return false; 2283 } 2284 TObjectName name = e.getObjectOperand(); 2285 if (name.getObjectToken() != null || e.getStartToken() != e.getEndToken()) { 2286 return false; 2287 } 2288 String text = e.getStartToken().getAstext(); 2289 return text != null && DATE_PARTS.contains(text.toUpperCase(Locale.ROOT)); // non-identifier-compare: keyword 2290 } 2291 2292 /** A date-part or similar keyword that the grammar wraps in an expression is not an operand. */ 2293 private static boolean isKeywordOnly(TExpression e) { 2294 TSourceToken st = e.getStartToken(); 2295 TSourceToken et = e.getEndToken(); 2296 if (st == null || st != et) { 2297 return false; 2298 } 2299 return st.tokentype == ETokenType.ttkeyword 2300 && e.getExpressionType() != EExpressionType.simple_object_name_t 2301 && e.getExpressionType() != EExpressionType.simple_constant_t; 2302 } 2303 2304 private void windowParts(Construct window, TParseTreeNode partition, TParseTreeNode orderBy) { 2305 if (window == null) { 2306 return; 2307 } 2308 if (partition != null) { 2309 Construct p = add("PARTITION_BY", 0, partition, window); 2310 if (p != null && window.scope == null) { 2311 expressions(p, partition); // a named window: its own clause scope 2312 } 2313 } 2314 if (orderBy != null) { 2315 Construct o = add("ORDER_BY", 0, orderBy, window); 2316 if (o != null && window.scope == null) { 2317 expressions(o, orderBy); 2318 } 2319 } 2320 } 2321 2322 private void insert(TInsertSqlStatement stmt) { 2323 with(stmt, root); 2324 if (stmt.isMultiTableInsert()) { 2325 // INSERT ALL / INSERT FIRST: sqllr-path-1 has no step for an INTO clause or its WHEN condition (Section 2, 2326 // item 3); the parse's target table is one of the INTO targets, never TARGET[0] (plan 09 slice 5) 2327 uncoveredInto(stmt.getInsertIntoValues()); 2328 TPTNodeList conditions = stmt.getInsertConditions(); 2329 for (int i = 0; conditions != null && i < conditions.size(); i++) { 2330 Object condition = conditions.getElement(i); 2331 if (condition instanceof TInsertCondition) { 2332 markUncovered("MULTI-TABLE INSERT WHEN", (TInsertCondition) condition); 2333 } 2334 } 2335 uncoveredInto(stmt.getElseIntoValues()); 2336 if (stmt.getSubQuery() != null) { 2337 Construct select = add("SELECT", 0, stmt.getSubQuery(), root); 2338 if (select != null) { 2339 work.push(new Object[] {stmt.getSubQuery(), select}); 2340 } 2341 } 2342 return; 2343 } 2344 if (stmt.getTargetTable() != null) { 2345 add("TARGET", 0, stmt.getTargetTable(), root); 2346 } 2347 if (stmt.getColumnList() != null && stmt.getColumnList().size() > 0) { 2348 add("COLUMN_LIST", 0, stmt.getColumnList(), root); 2349 } 2350 if (stmt.getSubQuery() != null) { 2351 Construct select = add("SELECT", 0, stmt.getSubQuery(), root); 2352 if (select != null) { 2353 work.push(new Object[] {stmt.getSubQuery(), select}); 2354 } 2355 } 2356 if (stmt.getValues() != null) { 2357 for (int r = 0; r < stmt.getValues().size(); r++) { 2358 TMultiTarget row = stmt.getValues().getMultiTarget(r); 2359 expressions(add("VALUES", r, row, root), row); 2360 } 2361 } 2362 if (stmt.getExecuteStmt() != null && isProcedureExecute(stmt.getExecuteStmt())) { 2363 // INSERT ... EXEC p args: the call is EXECUTE[0] below INSERT[0], its arguments below it (slice 12) 2364 Construct call = add("EXECUTE", 0, stmt.getExecuteStmt(), root); 2365 if (call != null) { 2366 callArgumentSteps(stmt.getExecuteStmt(), call); 2367 } 2368 } 2369 returnedRows(stmt); 2370 } 2371 2372 /** The arguments of a call as FUNCTION_ARG[k] below its step, in text order, with their expression steps. */ 2373 private void callArgumentSteps(TCustomSqlStatement call, Construct parent) { 2374 List<TExpression> args = callArguments(call); 2375 for (int k = 0; k < args.size(); k++) { 2376 if (args.get(k) != null) { 2377 expressions(add("FUNCTION_ARG", k, args.get(k), parent), args.get(k)); 2378 } 2379 } 2380 } 2381 2382 /** The RETURNING or OUTPUT clause RETURNING[0] covers (returnedRows), or null. */ 2383 private TParseTreeNode ownReturning; 2384 2385 /** 2386 * The rows an INSERT, UPDATE or DELETE returns (PATH-V1 Section 3.4, plan 09 Section 10 Q6): RETURNING[0] below 2387 * the statement step over its RETURNING (PostgreSQL, SQLite, Oracle's RETURNING ... INTO) or OUTPUT (SQL Server) 2388 * clause, with the items' expression steps below it; for OUTPUT ... INTO h, the table as TARGET[0] and its column 2389 * list as COLUMN_LIST[0] below RETURNING[0]. Oracle's INTO variables are names at RETURNING[0] itself. A statement 2390 * the parse gives both clauses is left uncovered. 2391 */ 2392 private void returnedRows(TCustomSqlStatement stmt) { 2393 TOutputClause output = stmt.getOutputClause(); 2394 TReturningClause returning = stmt.getReturningClause(); 2395 if (output != null && returning != null) { 2396 return; 2397 } 2398 if (output != null) { 2399 Construct c = add("RETURNING", 0, output, root); 2400 if (c == null) { 2401 return; 2402 } 2403 ownReturning = output; 2404 if (output.getSelectItemList() != null) { 2405 expressions(c, output.getSelectItemList()); 2406 } 2407 if (output.getIntoTable() != null) { 2408 add("TARGET", 0, output.getIntoTable(), c); 2409 } 2410 if (output.getIntoColumnList() != null && output.getIntoColumnList().size() > 0) { 2411 add("COLUMN_LIST", 0, output.getIntoColumnList(), c); 2412 } 2413 } else if (returning != null) { 2414 Construct c = add("RETURNING", 0, returning, root); 2415 if (c == null) { 2416 return; 2417 } 2418 ownReturning = returning; 2419 if (returning.getResultExprList() != null) { 2420 expressions(c, returning.getResultExprList()); 2421 } 2422 if (returning.getColumnValueList() != null) { 2423 expressions(c, returning.getColumnValueList()); 2424 } 2425 if (returning.getVariableList() != null) { 2426 expressions(c, returning.getVariableList()); 2427 } 2428 } 2429 } 2430 2431 /** 2432 * Every RETURNING and OUTPUT clause of the statement but the one RETURNING[0] covers is not covered: a MERGE's, a 2433 * nested data-changing statement's (PostgreSQL WITH d AS (DELETE ... RETURNING ...)). 2434 */ 2435 private void returning(TCustomSqlStatement stmt) { 2436 final int own = ownReturning == null ? -1 : start(ownReturning); 2437 stmt.acceptChildren(new TParseTreeVisitor() { 2438 @Override 2439 public void preVisit(TReturningClause returning) { 2440 if (start(returning) != own && !ownedReturnings.contains(Integer.valueOf(start(returning)))) { 2441 markUncovered("RETURNING", returning); 2442 } 2443 } 2444 2445 @Override 2446 public void preVisit(TOutputClause output) { 2447 if (start(output) != own) { 2448 markUncovered("OUTPUT", output); 2449 } 2450 } 2451 }); 2452 if (stmt.getOutputClause() != null && start(stmt.getOutputClause()) != own) { 2453 markUncovered("OUTPUT", stmt.getOutputClause()); 2454 } 2455 } 2456 2457 private void update(TUpdateSqlStatement stmt) { 2458 with(stmt, root); 2459 int setAt = offsetOf(statementKeywordAfter(stmt, "SET", -1)); 2460 int fromAt = offsetOf(statementKeywordAfter(stmt, "FROM", setAt)); 2461 TJoinList joins = stmt.getJoins(); 2462 int[] joinRange = joins == null || joins.size() == 0 ? null : joinListRange(joins); 2463 if (joinRange != null && fromAt >= 0 && joinRange[0] > fromAt) { 2464 // UPDATE t SET ... FROM u: GSP keeps the FROM list in getJoins() 2465 fromClause("FROM", joins, fromAt, root); 2466 if (stmt.getTargetTable() != null) { 2467 dmlTarget(stmt.getTargetTable()); 2468 } 2469 } else if (joinRange != null && (setAt < 0 || joinRange[0] < setAt)) { 2470 // UPDATE t1, t2 SET / UPDATE t1 JOIN t2 ON ... SET: the target list 2471 fromClause("TARGET", joins, -1, root); 2472 } else if (stmt.getTargetTable() != null) { 2473 dmlTarget(stmt.getTargetTable()); 2474 } 2475 if (stmt.getReferenceJoins() != null && stmt.getReferenceJoins().size() > 0) { 2476 fromClause("FROM", stmt.getReferenceJoins(), fromAt, root); 2477 } 2478 if (stmt.getResultColumnList() != null) { 2479 int st = setAt >= 0 ? setAt : start(stmt.getResultColumnList()); 2480 expressions(add("SET", 0, st, end(stmt.getResultColumnList()), root), stmt.getResultColumnList()); 2481 } 2482 if (stmt.getWhereClause() != null) { 2483 expressions(add("WHERE", 0, stmt.getWhereClause(), root), stmt.getWhereClause()); 2484 } 2485 dmlRowLimit(stmt, stmt.getOrderByClause(), stmt.getLimitClause()); 2486 returnedRows(stmt); 2487 } 2488 2489 /** 2490 * The ORDER BY and the row limit (LIMIT, TOP) that choose the rows an UPDATE or DELETE changes (MySQL {@code ... 2491 * ORDER BY b LIMIT 10}, SQL Server {@code TOP (@n)}): ORDER_BY[0] and LIMIT[0] below the statement step, one LIMIT 2492 * step for every form of the limit (PATH-V1 Section 3.4, plan 09 slice 9-rest). 2493 */ 2494 private void dmlRowLimit(TCustomSqlStatement stmt, gudusoft.gsqlparser.lineage.v2.shape.TOrderBy orderBy, 2495 TParseTreeNode limit) { 2496 if (orderBy != null) { 2497 expressions(add("ORDER_BY", 0, orderBy, root), orderBy); 2498 } 2499 for (TParseTreeNode part : new TParseTreeNode[] {stmt.getTopClause(), limit}) { 2500 if (part != null) { 2501 expressions(add("LIMIT", 0, part, root), part); 2502 } 2503 } 2504 } 2505 2506 /** 2507 * The token a DELETE writes its target with: the first word after DELETE that is not FROM, ONLY, a TOP clause or 2508 * a MySQL modifier ({@code DELETE a FROM t a}: {@code a}; {@code DELETE FROM d.t}: {@code d}). Shared by the paths 2509 * (TARGET[0]) and the statement graph (the anchor of the DELETE's effect), so both read one rule. 2510 */ 2511 static TSourceToken deleteTarget(TDeleteSqlStatement stmt) { 2512 TSourceToken t = stmt.getStartToken(); 2513 TSourceToken last = stmt.getEndToken(); 2514 boolean afterDelete = false; 2515 boolean afterOnly = false; 2516 int depth = 0; 2517 int targetDepth = 0; 2518 while (t != null) { 2519 String s = t.getAstext(); 2520 if ("(".equals(s)) { 2521 if (afterDelete && afterOnly && depth == 0) { 2522 targetDepth = 1; // PostgreSQL DELETE FROM ONLY (t): the target is inside the parentheses 2523 } 2524 depth++; 2525 } else if (")".equals(s)) { 2526 depth--; 2527 } else if (depth == targetDepth && t.tokentype != gudusoft.gsqlparser.ETokenType.ttwhitespace 2528 && t.tokentype != gudusoft.gsqlparser.ETokenType.ttreturn 2529 && t.tokentype != gudusoft.gsqlparser.ETokenType.ttsimplecomment 2530 && t.tokentype != gudusoft.gsqlparser.ETokenType.ttbracketedcomment) { 2531 if (!afterDelete) { 2532 // Teradata abbreviates DELETE as DEL (Codex slice 9 #9) 2533 afterDelete = "DELETE".equalsIgnoreCase(s) // non-identifier-compare: SQL keyword text 2534 || "DEL".equalsIgnoreCase(s); // non-identifier-compare: SQL keyword text 2535 } else if (t.tokentype == gudusoft.gsqlparser.ETokenType.ttidentifier 2536 || !isDeleteModifier(s)) { 2537 return t; 2538 } else { 2539 afterOnly = "ONLY".equalsIgnoreCase(s); // non-identifier-compare: SQL keyword text 2540 } 2541 } 2542 if (t == last) { 2543 break; 2544 } 2545 t = t.getNextTokenInChain(); 2546 } 2547 return null; 2548 } 2549 2550 private static boolean isDeleteModifier(String s) { 2551 for (String m : new String[] {"FROM", "ONLY", "TOP", "PERCENT", "LOW_PRIORITY", "QUICK", "IGNORE"}) { 2552 if (m.equalsIgnoreCase(s)) { // non-identifier-compare: SQL keyword text 2553 return true; 2554 } 2555 } 2556 return false; 2557 } 2558 2559 /** The first statement-level keyword token after a UTF-16 offset (or anywhere when the offset is negative). */ 2560 private TSourceToken statementKeywordAfter(TCustomSqlStatement stmt, String keyword, int after) { 2561 TSourceToken t = stmt.getStartToken(); 2562 TSourceToken last = stmt.getEndToken(); 2563 int depth = 0; 2564 while (t != null) { 2565 String s = t.getAstext(); 2566 if ("(".equals(s)) { 2567 depth++; 2568 } else if (")".equals(s)) { 2569 depth--; 2570 } else if (depth == 0 && keyword.equalsIgnoreCase(s) && offsetOf(t) > after) { // non-identifier-compare: SQL keyword text 2571 return t; 2572 } 2573 if (t == last) { 2574 break; 2575 } 2576 t = t.getNextTokenInChain(); 2577 } 2578 return null; 2579 } 2580 2581 /** 2582 * TARGET[0] of an UPDATE, DELETE or MERGE, and below it SUBQUERY[0] when the target is a query (an inline view: 2583 * Oracle UPDATE (SELECT ...) v, DELETE FROM (SELECT ...), MERGE INTO (SELECT ...) d; PATH-V1 Section 3.4, slice 6). 2584 */ 2585 private Construct dmlTarget(TTable target) { 2586 Construct c = add("TARGET", 0, target, root); 2587 if (c != null && target.getSubquery() != null) { 2588 Construct sub = add("SUBQUERY", 0, target.getSubquery(), c); 2589 if (sub != null) { 2590 work.push(new Object[] {target.getSubquery(), sub}); 2591 } 2592 } 2593 return c; 2594 } 2595 2596 private void uncoveredInto(TPTNodeList list) { 2597 for (int i = 0; list != null && i < list.size(); i++) { 2598 Object into = list.getElement(i); 2599 if (into instanceof TInsertIntoValue) { 2600 markUncovered("MULTI-TABLE INSERT INTO", (TInsertIntoValue) into); 2601 } 2602 } 2603 } 2604 2605 private void delete(TDeleteSqlStatement stmt) { 2606 with(stmt, root); 2607 TJoinList list = stmt.getReferenceJoins() != null && stmt.getReferenceJoins().size() > 0 2608 ? stmt.getReferenceJoins() : stmt.getJoins(); 2609 int[] range = list == null || list.size() == 0 ? null : joinListRange(list); 2610 int targetStart = start(stmt.getTargetTable()); 2611 if (range != null && targetStart >= range[0] && targetStart < range[1]) { 2612 // SQL Server, Teradata DELETE a FROM t a JOIN ...: the parse's target is the FROM item the written target 2613 // names, so TARGET[0] is the name written after DELETE and the FROM list is the FROM clause (slice 9) 2614 TSourceToken written = deleteTarget(stmt); 2615 int writtenAt = offsetOf(written); 2616 if (written != null && writtenAt < range[0]) { 2617 add("TARGET", 0, writtenAt, writtenAt + written.getAstext().length(), root); 2618 } 2619 TSourceToken from = null; 2620 for (TSourceToken t = statementKeywordAfter(stmt, "FROM", -1); t != null && offsetOf(t) < range[0]; 2621 t = statementKeywordAfter(stmt, "FROM", offsetOf(t))) { 2622 from = t; // the last FROM before the list 2623 } 2624 fromClause("FROM", list, offsetOf(from), root); 2625 if (stmt.getWhereClause() != null) { 2626 expressions(add("WHERE", 0, stmt.getWhereClause(), root), stmt.getWhereClause()); 2627 } 2628 dmlRowLimit(stmt, stmt.getOrderByClause(), stmt.getLimitClause()); 2629 returnedRows(stmt); 2630 return; 2631 } 2632 int targetEnd = end(stmt.getTargetTable()); 2633 int[] targetList = list == stmt.getReferenceJoins() && stmt.getJoins() != null && stmt.getJoins().size() > 1 2634 ? joinListRange(stmt.getJoins()) : null; 2635 if (targetList != null && range != null && targetList[1] <= range[0]) { 2636 // MySQL DELETE t1, t2 FROM ... / DELETE FROM t1, t2 USING ...: the list of tables whose rows are deleted 2637 // is TARGET[0], as an UPDATE's table list is (PATH-V1 Section 3.4, plan 09 slice 9-rest) 2638 add("TARGET", 0, targetList[0], targetList[1], root); 2639 targetEnd = targetList[1]; 2640 } else if (stmt.getTargetTable() != null) { 2641 dmlTarget(stmt.getTargetTable()); 2642 } 2643 if (range != null && range[0] >= targetEnd) { 2644 int usingAt = offsetOf(statementKeywordAfter(stmt, "USING", targetEnd - 1)); 2645 int fromAt = offsetOf(statementKeywordAfter(stmt, "FROM", targetEnd - 1)); 2646 boolean using = usingAt >= 0 && usingAt < range[0] && (fromAt < 0 || usingAt > fromAt || fromAt > range[0]); 2647 fromClause(using ? "USING" : "FROM", list, using ? usingAt : fromAt, root); 2648 } 2649 if (stmt.getWhereClause() != null) { 2650 expressions(add("WHERE", 0, stmt.getWhereClause(), root), stmt.getWhereClause()); 2651 } 2652 dmlRowLimit(stmt, stmt.getOrderByClause(), stmt.getLimitClause()); 2653 returnedRows(stmt); 2654 } 2655 2656 private void merge(TMergeSqlStatement stmt) { 2657 with(stmt, root); 2658 Construct target = stmt.getTargetTable() == null ? null : dmlTarget(stmt.getTargetTable()); 2659 TTable using = stmt.getUsingTable(); 2660 if (using != null) { 2661 String form = uncoveredTableForm(using); 2662 if (target != null && start(using) == target.start && end(using) == target.end) { 2663 markUncovered("MERGE SOURCE (range equals the target)", using); 2664 } else if (form != null) { 2665 markUncovered(form, using); 2666 } else { 2667 Construct u = add("USING", 0, keywordStart(using, "USING"), end(using), root); 2668 if (u != null && using.getSubquery() != null) { 2669 Construct sub = add("SUBQUERY", 0, using.getSubquery(), u); 2670 if (sub != null) { 2671 work.push(new Object[] {using.getSubquery(), sub}); 2672 } 2673 } 2674 } 2675 } 2676 if (stmt.getCondition() != null) { 2677 expressions(add("ON", 0, stmt.getCondition(), root), stmt.getCondition()); 2678 } 2679 if (stmt.getWhenClauses() != null) { 2680 for (int w = 0; w < stmt.getWhenClauses().size(); w++) { 2681 TMergeWhenClause when = (TMergeWhenClause) stmt.getWhenClauses().getElement(w); 2682 Construct wc = add("WHEN", w, when, root); 2683 if (wc == null) { 2684 continue; 2685 } 2686 if (when.getCondition() != null) { 2687 expressions(wc, when.getCondition()); 2688 } 2689 // Oracle writes a WHEN clause's condition after its action (UPDATE SET ... WHERE c, DELETE WHERE c, 2690 // INSERT ... VALUES (...) WHERE c): conditions of the WHEN clause, directly below it (slice 6) 2691 if (when.getUpdateClause() != null && when.getUpdateClause().getUpdateWhereClause() != null) { 2692 expressions(wc, when.getUpdateClause().getUpdateWhereClause()); 2693 } 2694 if (when.getUpdateClause() != null && when.getUpdateClause().getDeleteWhereClause() != null) { 2695 expressions(wc, when.getUpdateClause().getDeleteWhereClause()); 2696 } 2697 if (when.getInsertClause() != null && when.getInsertClause().getInsertWhereClause() != null) { 2698 expressions(wc, when.getInsertClause().getInsertWhereClause()); 2699 } 2700 if (when.getUpdateClause() != null && when.getUpdateClause().getUpdateColumnList() != null) { 2701 expressions(add("SET", 0, when.getUpdateClause().getUpdateColumnList(), wc), 2702 when.getUpdateClause().getUpdateColumnList()); 2703 } 2704 if (when.getInsertClause() != null) { 2705 if (when.getInsertClause().getColumnList() != null) { 2706 add("COLUMN_LIST", 0, when.getInsertClause().getColumnList(), wc); 2707 } 2708 if (when.getInsertClause().getValuelist() != null) { 2709 expressions(add("VALUES", 0, when.getInsertClause().getValuelist(), wc), 2710 when.getInsertClause().getValuelist()); 2711 } 2712 } 2713 } 2714 } 2715 if (stmt.getReturningClause() != null) { 2716 markUncovered("RETURNING", stmt.getReturningClause()); 2717 } 2718 } 2719 2720 private void createTable(TCreateTableSqlStatement stmt) { 2721 with(stmt, root); 2722 if (stmt.getTargetTable() != null) { 2723 add("TARGET", 0, stmt.getTargetTable(), root); 2724 } 2725 if (stmt.getSubQuery() == null) { 2726 List<Source> sources = createSources(stmt); 2727 for (int s = 0; s < sources.size(); s++) { 2728 Source source = sources.get(s); 2729 TSourceToken last = source.last; 2730 int end = offsetOf(last); 2731 add("SOURCE", s, offsetOf(source.first), end < 0 ? -1 2732 : end + (last.getAstext() == null ? 0 : last.getAstext().length()), root); 2733 } 2734 return; 2735 } 2736 if (stmt.getColumnList() != null && stmt.getColumnList().size() > 0 2737 && start(stmt.getColumnList()) < start(stmt.getSubQuery())) { 2738 add("COLUMN_LIST", 0, stmt.getColumnList(), root); 2739 } 2740 Construct select = add("SELECT", 0, stmt.getSubQuery(), root); 2741 if (select != null) { 2742 work.push(new Object[] {stmt.getSubQuery(), select}); 2743 } 2744 } 2745 2746 /** 2747 * One source a CREATE TABLE without a query copies or reads (PATH-V1 {@code SOURCE[s]}, plan 09 slice 2): the 2748 * relation of a CLONE ({@link #name}), or a storage location literal ({@link #literal} as written). 2749 */ 2750 static final class Source { 2751 final TSourceToken first; 2752 final TSourceToken last; 2753 final TObjectName name; 2754 final String literal; 2755 2756 Source(TSourceToken first, TSourceToken last, TObjectName name, String literal) { 2757 this.first = first; 2758 this.last = last; 2759 this.name = name; 2760 this.literal = literal; 2761 } 2762 } 2763 2764 /** 2765 * The sources of a CREATE TABLE without a query, in text order: the CLONE source; the LOCATION or 2766 * external_location literal; each URI literal of BigQuery {@code uris} (found among the option's string tokens, 2767 * as the parse keeps the list as text). The producer reads its sources in this order (StatementGraph), so the 2768 * index of {@code SOURCE[s]} and the source a statement reads agree. 2769 */ 2770 static List<Source> createSources(TCreateTableSqlStatement stmt) { 2771 List<Source> out = new ArrayList<Source>(); 2772 if (stmt.getSubQuery() != null) { 2773 return out; 2774 } 2775 TObjectName clone = stmt.getCloneSourceTable(); 2776 if (clone != null && clone.getStartToken() != null) { 2777 out.add(new Source(clone.getStartToken(), clone.getEndToken(), clone, null)); 2778 } 2779 TObjectName location = stmt.getTableLocation(); 2780 if (location != null && location.getStartToken() != null) { 2781 TSourceToken t = location.getStartToken(); 2782 out.add(new Source(t, t, null, t.getAstext())); 2783 } 2784 for (int i = 0; stmt.getTableOptions() != null && i < stmt.getTableOptions().size(); i++) { 2785 TCreateTableOption option = stmt.getTableOptions().get(i); 2786 if (option.getCreateTableOptionType() != gudusoft.gsqlparser.ECreateTableOption.etoBigQueryExternal 2787 || option.getUris() == null || option.getStartToken() == null || option.getEndToken() == null) { 2788 continue; 2789 } 2790 List<String> uris = option.getUris(); 2791 TSourceToken t = option.getStartToken(); 2792 int k = 0; 2793 while (t != null && k < uris.size()) { 2794 if ((t.tokentype == ETokenType.ttsqstring || t.tokentype == ETokenType.ttdqstring) 2795 && uris.get(k).equals(t.getAstext())) { // token text: the parse keeps each URI as written 2796 out.add(new Source(t, t, null, t.getAstext())); 2797 k++; 2798 } 2799 if (t == option.getEndToken()) { 2800 break; 2801 } 2802 t = t.getNextTokenInChain(); 2803 } 2804 } 2805 TSourceToken path = optionsPath(stmt); 2806 if (path != null) { 2807 out.add(new Source(path, path, null, path.getAstext())); 2808 } 2809 Collections.sort(out, new java.util.Comparator<Source>() { 2810 @Override 2811 public int compare(Source a, Source b) { 2812 return Integer.compare(a.first.posinlist, b.first.posinlist); 2813 } 2814 }); 2815 return out; 2816 } 2817 2818 /** 2819 * The literal of the {@code path} option of {@code OPTIONS (...)} (Spark SQL, Databricks 2820 * {@code CREATE TABLE ... USING <format> OPTIONS (path '...')}): where the table's files are. The parse keeps the 2821 * options as text, so they are read from the tokens before the statement's query, if it has one. 2822 */ 2823 static TSourceToken optionsPath(TCreateTableSqlStatement stmt) { 2824 TSourceToken end = stmt.getSubQuery() != null && stmt.getSubQuery().getStartToken() != null 2825 ? stmt.getSubQuery().getStartToken() : stmt.getEndToken(); 2826 for (TSourceToken t = stmt.getStartToken(); t != null; t = t.getNextTokenInChain()) { 2827 TSourceToken open = "OPTIONS".equalsIgnoreCase(t.getAstext()) ? nextSolidToken(t) : null; // non-identifier-compare: keyword 2828 if (open != null && "(".equals(open.getAstext())) { 2829 int depth = 0; 2830 for (TSourceToken u = open; u != null && u != end; u = u.getNextTokenInChain()) { 2831 if ("(".equals(u.getAstext())) { 2832 depth++; 2833 } else if (")".equals(u.getAstext())) { 2834 if (--depth == 0) { 2835 break; 2836 } 2837 } else if (depth == 1 && isPathKey(u)) { 2838 TSourceToken v = nextSolidToken(u); 2839 if (v != null && "=".equals(v.getAstext())) { 2840 v = nextSolidToken(v); 2841 } 2842 if (v != null && (v.tokentype == ETokenType.ttsqstring || v.tokentype == ETokenType.ttdqstring)) { 2843 return v; 2844 } 2845 } 2846 } 2847 } 2848 if (t == end) { 2849 break; 2850 } 2851 } 2852 return null; 2853 } 2854 2855 /** An option key {@code path}, written as a word or as a string ({@code 'path'}). */ 2856 private static boolean isPathKey(TSourceToken t) { 2857 String text = t.getAstext(); 2858 if (text == null) { 2859 return false; 2860 } 2861 if (text.length() == 6 && (text.charAt(0) == '\'' || text.charAt(0) == '"') && text.charAt(5) == text.charAt(0)) { 2862 text = text.substring(1, 5); 2863 } 2864 return "PATH".equalsIgnoreCase(text); // non-identifier-compare: an option key, not an object name 2865 } 2866 2867 private static TSourceToken nextSolidToken(TSourceToken token) { 2868 TSourceToken t = token == null ? null : token.getNextTokenInChain(); 2869 while (t != null && (t.tokentype == ETokenType.ttwhitespace || t.tokentype == ETokenType.ttreturn 2870 || t.tokentype == ETokenType.ttsimplecomment || t.tokentype == ETokenType.ttbracketedcomment)) { 2871 t = t.getNextTokenInChain(); 2872 } 2873 return t; 2874 } 2875 2876 private void createView(TCustomSqlStatement stmt, TObjectName name, TParseTreeNode columns, 2877 TSelectSqlStatement query) { 2878 with(stmt, root); 2879 if (name != null) { 2880 add("TARGET", 0, name, root); 2881 } 2882 if (columns != null) { 2883 add("COLUMN_LIST", 0, columns, root); 2884 } 2885 if (query != null) { 2886 Construct select = add("SELECT", 0, query, root); 2887 if (select != null) { 2888 work.push(new Object[] {query, select}); 2889 } 2890 } 2891 } 2892 2893 // --------------------------------------------------------------------------------------------- 2894 // Finishing: parents, indexes, paths, lookup tree 2895 // --------------------------------------------------------------------------------------------- 2896 2897 /** Resolve expression parents, number the counted steps, compute every path and the lookup tree. */ 2898 private void finish() { 2899 resolveExpressionParents(); 2900 assignPaths(); 2901 buildLookup(); 2902 } 2903 2904 /** Steps that hold clauses, never a name: an identifier landing on one is in a clause this index does not know. */ 2905 private static final Set<String> BLOCK_STEPS = new HashSet<String>(Arrays.asList("SELECT", "INSERT", "UPDATE", 2906 "DELETE", "MERGE", "CREATE_TABLE", "CREATE_VIEW", "SUBQUERY", "CTE", "BRANCH", "SET_OP", "WITH")); 2907 2908 /** 2909 * Marks every identifier token of the statement that lands directly on a statement or query block step as 2910 * uncovered: it belongs to a clause GSP parses but this index does not model (Oracle MODEL, FOR UPDATE OF, 2911 * BigQuery FOR SYSTEM_TIME, table options), so no path of PATH-V1 describes it. 2912 */ 2913 private void markUnmodelledIdentifiers(TCustomSqlStatement stmt) { 2914 TSourceToken last = stmt.getEndToken(); 2915 for (TSourceToken t = stmt.getStartToken(); t != null; t = t.getNextTokenInChain()) { 2916 ETokenType type = t.tokentype; 2917 if (type == ETokenType.ttidentifier || type == ETokenType.ttdqstring || type == ETokenType.ttdbstring 2918 || type == ETokenType.ttbrstring) { 2919 int o = offsetOf(t); 2920 if (o >= 0) { 2921 Construct innermost = innermost(o); 2922 if (innermost != null && BLOCK_STEPS.contains(innermost.code)) { 2923 markUncovered("UNMODELLED CLAUSE", o, o + t.getAstext().length()); 2924 } 2925 } 2926 } 2927 if (t == last) { 2928 break; 2929 } 2930 } 2931 } 2932 2933 private Construct innermost(int offset) { 2934 Construct best = null; 2935 List<Construct> level = roots; 2936 while (true) { 2937 Construct hit = find(level, offset); 2938 if (hit == null) { 2939 return best; 2940 } 2941 best = hit; 2942 level = hit.textChildren; 2943 } 2944 } 2945 2946 /** 2947 * Each expression step's parent is the innermost step of its clause that contains it; a stack sweep over the 2948 * clause's steps sorted by (start, end descending, rank) finds it in n log n. 2949 */ 2950 private void resolveExpressionParents() { 2951 Map<Construct, List<Construct>> byClause = new IdentityHashMap<Construct, List<Construct>>(); 2952 for (Construct c : constructs) { 2953 Construct clause = c.scope != null ? c.scope 2954 : c.parent != null && "WINDOW".equals(c.parent.code) && c.parent.scope != null ? c.parent.scope : null; 2955 if (clause == null) { 2956 continue; 2957 } 2958 List<Construct> list = byClause.get(clause); 2959 if (list == null) { 2960 list = new ArrayList<Construct>(); 2961 byClause.put(clause, list); 2962 } 2963 list.add(c); 2964 } 2965 for (Map.Entry<Construct, List<Construct>> entry : byClause.entrySet()) { 2966 Construct clause = entry.getKey(); 2967 List<Construct> list = entry.getValue(); 2968 Collections.sort(list, new Comparator<Construct>() { 2969 @Override 2970 public int compare(Construct a, Construct b) { 2971 int c = Integer.compare(a.start, b.start); 2972 if (c != 0) { 2973 return c; 2974 } 2975 c = Integer.compare(b.end, a.end); 2976 if (c != 0) { 2977 return c; 2978 } 2979 c = Integer.compare(rank(a), rank(b)); 2980 return c != 0 ? c : Integer.compare(a.order, b.order); 2981 } 2982 }); 2983 Deque<Construct> stack = new ArrayDeque<Construct>(); 2984 for (Construct c : list) { 2985 while (!stack.isEmpty() && !stack.peek().containsRange(c)) { 2986 stack.pop(); 2987 } 2988 if (c.parent == null) { 2989 c.parent = stack.isEmpty() ? clause : stack.peek(); 2990 } 2991 stack.push(c); 2992 } 2993 } 2994 } 2995 2996 /** Nesting rank for equal ranges: an operand step encloses a subquery written as the whole operand. */ 2997 private static int rank(Construct c) { 2998 return "SUBQUERY".equals(c.code) ? 2 : 1; 2999 } 3000 3001 private int depth(Construct c) { 3002 if (c.depth >= 0) { 3003 return c.depth; 3004 } 3005 int d = 0; 3006 for (Construct p = c.parent; p != null; p = p.parent) { 3007 d++; 3008 if (d > constructs.size()) { 3009 throw new IllegalStateException("cyclic construct parents"); 3010 } 3011 } 3012 c.depth = d; 3013 return d; 3014 } 3015 3016 /** 3017 * Paths level by level; {@code SUBQUERY[k]} and {@code PIVOT[p]} count the subqueries and the operators directly 3018 * below the same step, in text order, 3019 * where "the same step" is its path (TOP, LIMIT, OFFSET and FETCH fragments are one {@code LIMIT[0]} step). 3020 */ 3021 private void assignPaths() { 3022 List<Construct> ordered = new ArrayList<Construct>(constructs); 3023 final Map<Construct, Integer> depths = new IdentityHashMap<Construct, Integer>(); 3024 for (Construct c : ordered) { 3025 depths.put(c, Integer.valueOf(depth(c))); 3026 } 3027 Collections.sort(ordered, new Comparator<Construct>() { 3028 @Override 3029 public int compare(Construct a, Construct b) { 3030 int c = depths.get(a).compareTo(depths.get(b)); 3031 return c != 0 ? c : Integer.compare(a.start, b.start); 3032 } 3033 }); 3034 int i = 0; 3035 while (i < ordered.size()) { 3036 int d = depths.get(ordered.get(i)).intValue(); 3037 int j = i; 3038 while (j < ordered.size() && depths.get(ordered.get(j)).intValue() == d) { 3039 j++; 3040 } 3041 Map<String, Integer> counters = new HashMap<String, Integer>(); 3042 for (int k = i; k < j; k++) { 3043 Construct c = ordered.get(k); // ordered by start within the level 3044 if (("SUBQUERY".equals(c.code) || "PIVOT".equals(c.code) || "LATERAL".equals(c.code)) && c.index < 0) { 3045 // PIVOT[p] counts the operators below the same step, in text order (a chain: PIVOT[0], PIVOT[1]) 3046 String key = c.code + "|" + (c.parent == null ? "" : c.parent.path); 3047 Integer n = counters.get(key); 3048 c.index = n == null ? 0 : n.intValue(); 3049 counters.put(key, Integer.valueOf(c.index + 1)); 3050 } 3051 c.path = (c.parent == null ? "" : c.parent.path + "/") + c.code + "[" + Math.max(c.index, 0) + "]"; 3052 } 3053 i = j; 3054 } 3055 } 3056 3057 /** The containment forest of construct ranges, for {@link #anchor} by binary search. */ 3058 private void buildLookup() { 3059 List<Construct> sorted = new ArrayList<Construct>(constructs); 3060 Collections.sort(sorted, new Comparator<Construct>() { 3061 @Override 3062 public int compare(Construct a, Construct b) { 3063 int c = Integer.compare(a.start, b.start); 3064 if (c != 0) { 3065 return c; 3066 } 3067 c = Integer.compare(b.end, a.end); 3068 return c != 0 ? c : Integer.compare(depth(a), depth(b)); 3069 } 3070 }); 3071 Deque<Construct> stack = new ArrayDeque<Construct>(); 3072 for (Construct c : sorted) { 3073 if (c.end <= c.start) { 3074 continue; // an empty range contains no offset 3075 } 3076 while (!stack.isEmpty() && !stack.peek().containsRange(c)) { 3077 stack.pop(); 3078 } 3079 if (stack.isEmpty()) { 3080 roots.add(c); 3081 } else { 3082 stack.peek().textChildren.add(c); 3083 } 3084 stack.push(c); 3085 } 3086 } 3087 3088 /** The constructs, for tests: path, start, end. */ 3089 List<String> describe() { 3090 List<String> out = new ArrayList<String>(); 3091 for (Construct c : constructs) { 3092 out.add(c.path + " " + c.start + ".." + c.end); 3093 } 3094 for (Uncovered u : uncovered) { 3095 out.add("UNCOVERED " + u.form + " " + u.start + ".." + u.end); 3096 } 3097 return out; 3098 } 3099}