001package gudusoft.gsqlparser.ir.semantic; 002 003import gudusoft.gsqlparser.ir.semantic.joinanalysis.JoinAnalysisFacts; 004import gudusoft.gsqlparser.ir.semantic.joinanalysis.JoinGraph; 005 006import java.util.ArrayList; 007import java.util.Collections; 008import java.util.List; 009 010/** 011 * Semantic shape of one SQL statement. Currently covers SELECT. 012 * 013 * <p><b>API status:</b> read-only traversal of analyzer-produced instances, 014 * including {@link #getJoinAnalysisFacts()}, {@link #getJoinGraph()}, and 015 * {@link #getJoinColumnRefs()}, is part of Join Analysis Consumption Profile 016 * v1. Public constructors, copying methods, and degrade factories are 017 * producer-oriented and are not included in that consumption profile. 018 * 019 * <p>{@link #name} is non-null when this statement is the body of a named 020 * CTE or a FROM-clause subquery. For top-level outer SELECTs it is null. 021 * 022 * <p>{@link #filterColumnRefs}, {@link #joinColumnRefs}, 023 * {@link #groupByColumnRefs}, {@link #havingColumnRefs}, and 024 * {@link #orderByColumnRefs} are flat lists of column references that 025 * appear in the WHERE, JOIN predicate (ON / USING), GROUP BY, HAVING, 026 * and ORDER BY clauses respectively. For {@code JOIN ... USING (k)} 027 * (slice 64) {@code joinColumnRefs} contains one ref per 028 * (relation, key) pair on both sides — left side first via 029 * catalog-aware narrowing, then the right side. The IR deliberately 030 * does <i>not</i> model structured 031 * {@code Filter}, {@code Join}, or {@code GroupBy} nodes with predicate 032 * trees yet; later slices will add them. Listing the affected columns 033 * is enough to answer the roadmap's questions about 034 * filter/join/grouping/having/ordering influence. 035 * 036 * <p>{@link #groupingElements} (slice 128) is the structured companion to 037 * the flat {@link #groupByColumnRefs}: one {@link GroupingElement} per 038 * top-level {@code GROUP BY} item, preserving the {@code SIMPLE} / 039 * {@code ROLLUP} / {@code CUBE} / {@code GROUPING SETS} structure the flat 040 * list discards. See {@link #getGroupingElements()}. 041 * 042 * <p>{@link #orderByColumnRefs} only ever contains references to physical 043 * (base or in-statement) columns. Ordinal references ({@code ORDER BY 1}) 044 * and bare-constant sort keys are rejected by the builder — emitting 045 * {@code []} for them would lose the dependency information silently. 046 * 047 * <p>Slice 9 (single-SELECT) rejects projection-alias references like 048 * {@code SELECT id AS x ... ORDER BY x}. Slice 21 (set-op outer) 049 * <i>accepts</i> alias references positionally against branch[0]'s 050 * outputs — the alias IS the set-op output schema. The two paths 051 * diverge intentionally; see 052 * {@code SemanticIRBuilder.buildOrderByColumnRefs} (slice 9) versus 053 * {@code SemanticIRBuilder.buildSetOpOuterOrderByColumnRefs} (slice 21). 054 */ 055public final class StatementGraph { 056 057 private final String name; 058 private final String kind; 059 private final List<RelationSource> relations; 060 private final List<OutputColumn> outputColumns; 061 private final List<OutputColumn> returningColumns; 062 private final List<ColumnRef> filterColumnRefs; 063 private final List<ColumnRef> joinColumnRefs; 064 private final List<ColumnRef> groupByColumnRefs; 065 private final List<ColumnRef> havingColumnRefs; 066 private final List<ColumnRef> orderByColumnRefs; 067 private final List<ColumnRef> distinctOnColumnRefs; 068 private final List<ColumnRef> qualifyColumnRefs; 069 private final List<GroupingElement> groupingElements; 070 private final List<ColumnRef> pivotColumnRefs; 071 private final boolean distinct; 072 private final SetOperator setOperator; 073 private final RowLimit rowLimit; 074 private final TargetRelation target; 075 /** 076 * Join-analysis facts (slice 167, GAP 1/2/4): the single optional 077 * carrier holding the structured {@link JoinGraph}, the WHERE filter 078 * predicates, and the query-block scope. Never null — defaults to 079 * {@link JoinAnalysisFacts#EMPTY} for every legacy constructor. 080 */ 081 private final JoinAnalysisFacts joinAnalysisFacts; 082 /** 083 * Optional block-level source span (slice 179, R5) covering this 084 * statement's own text. Null when not set (e.g. DML / set-op paths that 085 * do not thread the parse node). Read by {@code attachQueryBlockScopes} 086 * to populate {@link QueryBlockScope#getSourceSpan()}. 087 */ 088 private final SourceSpan sourceSpan; 089 /** 090 * Non-null only on a degrade placeholder (see {@link #KIND_UNANALYZED}): 091 * the ERROR diagnostic explaining why this block could not be analyzed. 092 */ 093 private final Diagnostic unanalyzedReason; 094 095 /** 096 * {@link #getKind() Kind} of a placeholder block emitted in place of a 097 * nested block the builder could not analyze, when the caller opted in 098 * via {@code SemanticIRBuildOptions.withDegradeUnsupportedNestedBlocks(true)}. 099 * 100 * <p>A placeholder carries no relations, no output columns and no clause 101 * refs — <em>not</em> because the block had none, but because it was 102 * never analyzed. Consumers that aggregate over 103 * {@code SemanticProgram.getStatements()} MUST treat this kind as 104 * "unknown", never as "empty": reading a placeholder as an ordinary 105 * SELECT with zero joins turns a known gap into a silent false negative. 106 * {@link #getUnanalyzedReason()} carries the cause and its source span. 107 */ 108 public static final String KIND_UNANALYZED = "UNANALYZED"; 109 110 /** 111 * Build the degrade placeholder for a nested block that could not be 112 * analyzed. {@code name} is the synthetic block name the analyzed body 113 * would have carried, so the block keeps its identity and its position 114 * in {@code SemanticProgram.getStatements()}. 115 * 116 * @param name synthetic block name (may be null) 117 * @param reason non-null diagnostic explaining the rejection 118 * @param span optional span of the skipped block 119 */ 120 public static StatementGraph unanalyzed(String name, Diagnostic reason, SourceSpan span) { 121 if (reason == null) { 122 throw new IllegalArgumentException("reason must be non-null"); 123 } 124 return new StatementGraph(name, KIND_UNANALYZED, 125 Collections.<RelationSource>emptyList(), 126 Collections.<OutputColumn>emptyList(), 127 Collections.<OutputColumn>emptyList(), 128 Collections.<ColumnRef>emptyList(), 129 Collections.<ColumnRef>emptyList(), 130 Collections.<ColumnRef>emptyList(), 131 Collections.<ColumnRef>emptyList(), 132 Collections.<ColumnRef>emptyList(), 133 Collections.<ColumnRef>emptyList(), 134 Collections.<ColumnRef>emptyList(), 135 Collections.<GroupingElement>emptyList(), 136 Collections.<ColumnRef>emptyList(), 137 /*distinct=*/ false, /*setOperator=*/ null, /*rowLimit=*/ null, 138 /*target=*/ null, JoinAnalysisFacts.EMPTY, span, reason); 139 } 140 141 /** 142 * Slice 129 primary constructor — adds the optional 143 * {@code pivotColumnRefs} slot, the columns CONSUMED by a {@code PIVOT} 144 * operator (the {@code FOR} / pivot column(s) followed by the 145 * aggregation-function argument column(s)). The slot is always non-null 146 * (use {@link Collections#emptyList()} when absent); non-empty only on a 147 * SELECT whose FROM source is a {@code PIVOT}. All other slots are 148 * unchanged. See {@link #getPivotColumnRefs()}. 149 */ 150 public StatementGraph(String name, 151 String kind, 152 List<RelationSource> relations, 153 List<OutputColumn> outputColumns, 154 List<OutputColumn> returningColumns, 155 List<ColumnRef> filterColumnRefs, 156 List<ColumnRef> joinColumnRefs, 157 List<ColumnRef> groupByColumnRefs, 158 List<ColumnRef> havingColumnRefs, 159 List<ColumnRef> orderByColumnRefs, 160 List<ColumnRef> distinctOnColumnRefs, 161 List<ColumnRef> qualifyColumnRefs, 162 List<GroupingElement> groupingElements, 163 List<ColumnRef> pivotColumnRefs, 164 boolean distinct, 165 SetOperator setOperator, 166 RowLimit rowLimit, 167 TargetRelation target) { 168 this(name, kind, relations, outputColumns, returningColumns, 169 filterColumnRefs, joinColumnRefs, groupByColumnRefs, 170 havingColumnRefs, orderByColumnRefs, distinctOnColumnRefs, 171 qualifyColumnRefs, groupingElements, pivotColumnRefs, 172 distinct, setOperator, rowLimit, target, 173 JoinAnalysisFacts.EMPTY); 174 } 175 176 /** 177 * Slice 167 primary constructor — adds the single optional 178 * {@link JoinAnalysisFacts} carrier (GAP 1/2/4). Every prior 179 * constructor delegates here with {@link JoinAnalysisFacts#EMPTY}, so 180 * the additive slot does not grow the constructor surface per GAP. A 181 * null {@code joinAnalysisFacts} coalesces to {@code EMPTY}. 182 */ 183 public StatementGraph(String name, 184 String kind, 185 List<RelationSource> relations, 186 List<OutputColumn> outputColumns, 187 List<OutputColumn> returningColumns, 188 List<ColumnRef> filterColumnRefs, 189 List<ColumnRef> joinColumnRefs, 190 List<ColumnRef> groupByColumnRefs, 191 List<ColumnRef> havingColumnRefs, 192 List<ColumnRef> orderByColumnRefs, 193 List<ColumnRef> distinctOnColumnRefs, 194 List<ColumnRef> qualifyColumnRefs, 195 List<GroupingElement> groupingElements, 196 List<ColumnRef> pivotColumnRefs, 197 boolean distinct, 198 SetOperator setOperator, 199 RowLimit rowLimit, 200 TargetRelation target, 201 JoinAnalysisFacts joinAnalysisFacts) { 202 this(name, kind, relations, outputColumns, returningColumns, filterColumnRefs, 203 joinColumnRefs, groupByColumnRefs, havingColumnRefs, orderByColumnRefs, 204 distinctOnColumnRefs, qualifyColumnRefs, groupingElements, pivotColumnRefs, 205 distinct, setOperator, rowLimit, target, joinAnalysisFacts, /*sourceSpan=*/ null); 206 } 207 208 /** 209 * Slice 179 (R5) primary constructor — adds the optional block-level 210 * {@link SourceSpan} covering this statement's own source text. Every 211 * prior constructor delegates here with a null span (additive). Set at 212 * the SELECT construction site (where the parse node is in scope) and 213 * read by {@code attachQueryBlockScopes} so each {@link QueryBlockScope} 214 * carries an honest span. 215 */ 216 public StatementGraph(String name, 217 String kind, 218 List<RelationSource> relations, 219 List<OutputColumn> outputColumns, 220 List<OutputColumn> returningColumns, 221 List<ColumnRef> filterColumnRefs, 222 List<ColumnRef> joinColumnRefs, 223 List<ColumnRef> groupByColumnRefs, 224 List<ColumnRef> havingColumnRefs, 225 List<ColumnRef> orderByColumnRefs, 226 List<ColumnRef> distinctOnColumnRefs, 227 List<ColumnRef> qualifyColumnRefs, 228 List<GroupingElement> groupingElements, 229 List<ColumnRef> pivotColumnRefs, 230 boolean distinct, 231 SetOperator setOperator, 232 RowLimit rowLimit, 233 TargetRelation target, 234 JoinAnalysisFacts joinAnalysisFacts, 235 SourceSpan sourceSpan) { 236 this(name, kind, relations, outputColumns, returningColumns, filterColumnRefs, 237 joinColumnRefs, groupByColumnRefs, havingColumnRefs, orderByColumnRefs, 238 distinctOnColumnRefs, qualifyColumnRefs, groupingElements, pivotColumnRefs, 239 distinct, setOperator, rowLimit, target, joinAnalysisFacts, sourceSpan, 240 /*unanalyzedReason=*/ null); 241 } 242 243 /** 244 * Full constructor — adds the optional {@code unanalyzedReason} slot 245 * that marks a degrade placeholder. Private: placeholders are built 246 * through {@link #unanalyzed(String, Diagnostic, SourceSpan)}, and every 247 * public constructor delegates here with a null reason, so an ordinary 248 * block can never be mistaken for a placeholder. 249 */ 250 private StatementGraph(String name, 251 String kind, 252 List<RelationSource> relations, 253 List<OutputColumn> outputColumns, 254 List<OutputColumn> returningColumns, 255 List<ColumnRef> filterColumnRefs, 256 List<ColumnRef> joinColumnRefs, 257 List<ColumnRef> groupByColumnRefs, 258 List<ColumnRef> havingColumnRefs, 259 List<ColumnRef> orderByColumnRefs, 260 List<ColumnRef> distinctOnColumnRefs, 261 List<ColumnRef> qualifyColumnRefs, 262 List<GroupingElement> groupingElements, 263 List<ColumnRef> pivotColumnRefs, 264 boolean distinct, 265 SetOperator setOperator, 266 RowLimit rowLimit, 267 TargetRelation target, 268 JoinAnalysisFacts joinAnalysisFacts, 269 SourceSpan sourceSpan, 270 Diagnostic unanalyzedReason) { 271 if (kind == null || kind.isEmpty()) { 272 throw new IllegalArgumentException("kind must be non-empty"); 273 } 274 // KIND_UNANALYZED and a non-null reason imply each other. Every public 275 // constructor delegates here with a null reason, so a hand-built 276 // statement cannot claim the reserved kind without its diagnostic — 277 // that would make isUnanalyzed() false and silently walk past every 278 // consumer safeguard (schema v4, projector / logical-builder refusal), 279 // turning "unknown" into "empty". Placeholders are built only through 280 // {@link #unanalyzed(String, Diagnostic, SourceSpan)}. 281 if (KIND_UNANALYZED.equals(kind) != (unanalyzedReason != null)) { 282 throw new IllegalArgumentException(KIND_UNANALYZED.equals(kind) 283 ? "kind " + KIND_UNANALYZED + " is reserved for degrade " 284 + "placeholders; use StatementGraph.unanalyzed(...)" 285 : "unanalyzedReason requires kind " + KIND_UNANALYZED); 286 } 287 if (relations == null || outputColumns == null 288 || returningColumns == null 289 || filterColumnRefs == null || joinColumnRefs == null 290 || groupByColumnRefs == null || havingColumnRefs == null 291 || orderByColumnRefs == null || distinctOnColumnRefs == null 292 || qualifyColumnRefs == null || groupingElements == null 293 || pivotColumnRefs == null) { 294 throw new IllegalArgumentException( 295 "relations/outputColumns/returningColumns/filterColumnRefs/joinColumnRefs/" 296 + "groupByColumnRefs/havingColumnRefs/orderByColumnRefs/" 297 + "distinctOnColumnRefs/qualifyColumnRefs/groupingElements/" 298 + "pivotColumnRefs must not be null"); 299 } 300 this.name = (name != null && name.isEmpty()) ? null : name; 301 this.kind = kind; 302 this.relations = Collections.unmodifiableList(new ArrayList<>(relations)); 303 this.outputColumns = Collections.unmodifiableList(new ArrayList<>(outputColumns)); 304 this.returningColumns = Collections.unmodifiableList(new ArrayList<>(returningColumns)); 305 this.filterColumnRefs = Collections.unmodifiableList(new ArrayList<>(filterColumnRefs)); 306 this.joinColumnRefs = Collections.unmodifiableList(new ArrayList<>(joinColumnRefs)); 307 this.groupByColumnRefs = Collections.unmodifiableList(new ArrayList<>(groupByColumnRefs)); 308 this.havingColumnRefs = Collections.unmodifiableList(new ArrayList<>(havingColumnRefs)); 309 this.orderByColumnRefs = Collections.unmodifiableList(new ArrayList<>(orderByColumnRefs)); 310 this.distinctOnColumnRefs = Collections.unmodifiableList(new ArrayList<>(distinctOnColumnRefs)); 311 this.qualifyColumnRefs = Collections.unmodifiableList(new ArrayList<>(qualifyColumnRefs)); 312 this.groupingElements = Collections.unmodifiableList(new ArrayList<>(groupingElements)); 313 this.pivotColumnRefs = Collections.unmodifiableList(new ArrayList<>(pivotColumnRefs)); 314 this.distinct = distinct; 315 this.setOperator = setOperator; 316 this.rowLimit = rowLimit; 317 this.target = target; 318 this.joinAnalysisFacts = 319 joinAnalysisFacts == null ? JoinAnalysisFacts.EMPTY : joinAnalysisFacts; 320 this.sourceSpan = sourceSpan; 321 this.unanalyzedReason = unanalyzedReason; 322 } 323 324 /** 325 * Return a copy of this statement with its {@link JoinAnalysisFacts} 326 * replaced. Used by the builder (slices 167/168/169/170) to attach 327 * join/predicate/scope facts after the flat graph is built, without 328 * mutating this immutable value. Preserves the block {@link #sourceSpan}. 329 */ 330 public StatementGraph withJoinAnalysisFacts(JoinAnalysisFacts facts) { 331 return new StatementGraph(name, kind, relations, outputColumns, returningColumns, 332 filterColumnRefs, joinColumnRefs, groupByColumnRefs, havingColumnRefs, 333 orderByColumnRefs, distinctOnColumnRefs, qualifyColumnRefs, groupingElements, 334 pivotColumnRefs, distinct, setOperator, rowLimit, target, facts, sourceSpan, 335 unanalyzedReason); 336 } 337 338 /** 339 * Return a copy of this statement with its block-level 340 * {@link #sourceSpan} replaced (slice 179, R5). Preserves the 341 * {@link JoinAnalysisFacts}. 342 */ 343 public StatementGraph withSourceSpan(SourceSpan newSourceSpan) { 344 return new StatementGraph(name, kind, relations, outputColumns, returningColumns, 345 filterColumnRefs, joinColumnRefs, groupByColumnRefs, havingColumnRefs, 346 orderByColumnRefs, distinctOnColumnRefs, qualifyColumnRefs, groupingElements, 347 pivotColumnRefs, distinct, setOperator, rowLimit, target, 348 joinAnalysisFacts, newSourceSpan, unanalyzedReason); 349 } 350 351 /** 352 * Slice 128 primary constructor preserved — adds the optional 353 * {@code groupingElements} slot, the structured per-top-level-element 354 * view of the {@code GROUP BY} ({@code SIMPLE} / {@code ROLLUP} / 355 * {@code CUBE} / {@code GROUPING SETS}; see {@link GroupingElement}). 356 * Delegates to the slice-129 primary with empty {@code pivotColumnRefs}. 357 */ 358 public StatementGraph(String name, 359 String kind, 360 List<RelationSource> relations, 361 List<OutputColumn> outputColumns, 362 List<OutputColumn> returningColumns, 363 List<ColumnRef> filterColumnRefs, 364 List<ColumnRef> joinColumnRefs, 365 List<ColumnRef> groupByColumnRefs, 366 List<ColumnRef> havingColumnRefs, 367 List<ColumnRef> orderByColumnRefs, 368 List<ColumnRef> distinctOnColumnRefs, 369 List<ColumnRef> qualifyColumnRefs, 370 List<GroupingElement> groupingElements, 371 boolean distinct, 372 SetOperator setOperator, 373 RowLimit rowLimit, 374 TargetRelation target) { 375 this(name, kind, relations, outputColumns, returningColumns, 376 filterColumnRefs, joinColumnRefs, groupByColumnRefs, 377 havingColumnRefs, orderByColumnRefs, 378 distinctOnColumnRefs, qualifyColumnRefs, groupingElements, 379 Collections.<ColumnRef>emptyList(), 380 distinct, setOperator, rowLimit, target); 381 } 382 383 /** 384 * Slice 125 primary constructor preserved — adds the optional 385 * {@code qualifyColumnRefs} slot for the {@code QUALIFY} clause 386 * (Snowflake / BigQuery / Teradata). Delegates to the slice-128 387 * primary constructor with empty {@code groupingElements}. 388 */ 389 public StatementGraph(String name, 390 String kind, 391 List<RelationSource> relations, 392 List<OutputColumn> outputColumns, 393 List<OutputColumn> returningColumns, 394 List<ColumnRef> filterColumnRefs, 395 List<ColumnRef> joinColumnRefs, 396 List<ColumnRef> groupByColumnRefs, 397 List<ColumnRef> havingColumnRefs, 398 List<ColumnRef> orderByColumnRefs, 399 List<ColumnRef> distinctOnColumnRefs, 400 List<ColumnRef> qualifyColumnRefs, 401 boolean distinct, 402 SetOperator setOperator, 403 RowLimit rowLimit, 404 TargetRelation target) { 405 this(name, kind, relations, outputColumns, returningColumns, 406 filterColumnRefs, joinColumnRefs, groupByColumnRefs, 407 havingColumnRefs, orderByColumnRefs, 408 distinctOnColumnRefs, qualifyColumnRefs, 409 Collections.<GroupingElement>emptyList(), 410 distinct, setOperator, rowLimit, target); 411 } 412 413 /** 414 * Slice 85 constructor preserved so production code that predates 415 * slice 125 keeps compiling unchanged. Delegates to the slice-125 416 * primary constructor with empty {@code qualifyColumnRefs}. QUALIFY 417 * is a SELECT-only clause, so DML / predicate-body / set-op-outer 418 * call sites correctly default to empty. 419 */ 420 public StatementGraph(String name, 421 String kind, 422 List<RelationSource> relations, 423 List<OutputColumn> outputColumns, 424 List<OutputColumn> returningColumns, 425 List<ColumnRef> filterColumnRefs, 426 List<ColumnRef> joinColumnRefs, 427 List<ColumnRef> groupByColumnRefs, 428 List<ColumnRef> havingColumnRefs, 429 List<ColumnRef> orderByColumnRefs, 430 List<ColumnRef> distinctOnColumnRefs, 431 boolean distinct, 432 SetOperator setOperator, 433 RowLimit rowLimit, 434 TargetRelation target) { 435 this(name, kind, relations, outputColumns, returningColumns, 436 filterColumnRefs, joinColumnRefs, groupByColumnRefs, 437 havingColumnRefs, orderByColumnRefs, 438 distinctOnColumnRefs, 439 Collections.<ColumnRef>emptyList(), 440 distinct, setOperator, rowLimit, target); 441 } 442 443 /** 444 * Slice 78 constructor preserved so production code that predates 445 * slice 85 keeps compiling unchanged. Delegates to the slice-85 446 * constructor with empty {@code returningColumns}. 447 */ 448 public StatementGraph(String name, 449 String kind, 450 List<RelationSource> relations, 451 List<OutputColumn> outputColumns, 452 List<ColumnRef> filterColumnRefs, 453 List<ColumnRef> joinColumnRefs, 454 List<ColumnRef> groupByColumnRefs, 455 List<ColumnRef> havingColumnRefs, 456 List<ColumnRef> orderByColumnRefs, 457 List<ColumnRef> distinctOnColumnRefs, 458 boolean distinct, 459 SetOperator setOperator, 460 RowLimit rowLimit, 461 TargetRelation target) { 462 this(name, kind, relations, outputColumns, 463 Collections.<OutputColumn>emptyList(), 464 filterColumnRefs, joinColumnRefs, groupByColumnRefs, 465 havingColumnRefs, orderByColumnRefs, 466 distinctOnColumnRefs, 467 distinct, setOperator, rowLimit, target); 468 } 469 470 /** 471 * Slice 73 constructor preserved so SELECT-kind production code that 472 * predates slice 78 keeps compiling unchanged. Delegates to the 473 * slice-78 constructor with {@code target=null}. 474 */ 475 public StatementGraph(String name, 476 String kind, 477 List<RelationSource> relations, 478 List<OutputColumn> outputColumns, 479 List<ColumnRef> filterColumnRefs, 480 List<ColumnRef> joinColumnRefs, 481 List<ColumnRef> groupByColumnRefs, 482 List<ColumnRef> havingColumnRefs, 483 List<ColumnRef> orderByColumnRefs, 484 List<ColumnRef> distinctOnColumnRefs, 485 boolean distinct, 486 SetOperator setOperator, 487 RowLimit rowLimit) { 488 this(name, kind, relations, outputColumns, 489 filterColumnRefs, joinColumnRefs, groupByColumnRefs, 490 havingColumnRefs, orderByColumnRefs, 491 distinctOnColumnRefs, 492 distinct, setOperator, rowLimit, /*target=*/ null); 493 } 494 495 /** 496 * Slice 125 SELECT constructor — the slice-73 SELECT shape plus the 497 * {@code qualifyColumnRefs} slot. Used by the shared SELECT builder 498 * ({@code SemanticIRBuilder.buildSelectStatementImpl}) so a SELECT 499 * carrying a QUALIFY clause can pass its resolved filter refs. 500 * Delegates to the slice-125 primary constructor with empty 501 * {@code returningColumns} and {@code target=null}. The parameter 502 * list differs from the slice-78 14-arg constructor by type 503 * (a {@code List} {@code qualifyColumnRefs} at position 11 vs a 504 * {@code boolean distinct} there), so overload resolution is 505 * unambiguous. 506 */ 507 public StatementGraph(String name, 508 String kind, 509 List<RelationSource> relations, 510 List<OutputColumn> outputColumns, 511 List<ColumnRef> filterColumnRefs, 512 List<ColumnRef> joinColumnRefs, 513 List<ColumnRef> groupByColumnRefs, 514 List<ColumnRef> havingColumnRefs, 515 List<ColumnRef> orderByColumnRefs, 516 List<ColumnRef> distinctOnColumnRefs, 517 List<ColumnRef> qualifyColumnRefs, 518 boolean distinct, 519 SetOperator setOperator, 520 RowLimit rowLimit) { 521 this(name, kind, relations, outputColumns, 522 Collections.<OutputColumn>emptyList(), 523 filterColumnRefs, joinColumnRefs, groupByColumnRefs, 524 havingColumnRefs, orderByColumnRefs, 525 distinctOnColumnRefs, 526 qualifyColumnRefs, 527 distinct, setOperator, rowLimit, /*target=*/ null); 528 } 529 530 /** 531 * Slice 128 SELECT constructor — the slice-125 SELECT shape plus the 532 * {@code groupingElements} slot, the structured GROUP BY view. Used by 533 * the shared SELECT builder ({@code SemanticIRBuilder}) so a SELECT 534 * with a GROUP BY can pass its structured grouping elements. Delegates 535 * to the slice-128 primary with empty {@code returningColumns} and 536 * {@code target=null}. The parameter list differs from the slice-125 537 * 14-arg SELECT constructor by the extra {@code List groupingElements} 538 * arg, so overload resolution is unambiguous. 539 */ 540 public StatementGraph(String name, 541 String kind, 542 List<RelationSource> relations, 543 List<OutputColumn> outputColumns, 544 List<ColumnRef> filterColumnRefs, 545 List<ColumnRef> joinColumnRefs, 546 List<ColumnRef> groupByColumnRefs, 547 List<ColumnRef> havingColumnRefs, 548 List<ColumnRef> orderByColumnRefs, 549 List<ColumnRef> distinctOnColumnRefs, 550 List<ColumnRef> qualifyColumnRefs, 551 List<GroupingElement> groupingElements, 552 boolean distinct, 553 SetOperator setOperator, 554 RowLimit rowLimit) { 555 this(name, kind, relations, outputColumns, 556 Collections.<OutputColumn>emptyList(), 557 filterColumnRefs, joinColumnRefs, groupByColumnRefs, 558 havingColumnRefs, orderByColumnRefs, 559 distinctOnColumnRefs, 560 qualifyColumnRefs, 561 groupingElements, 562 distinct, setOperator, rowLimit, /*target=*/ null); 563 } 564 565 /** 566 * Slice 129 PIVOT SELECT constructor — a {@code SELECT} whose FROM 567 * source is a {@code PIVOT}. Carries {@code relations} (the underlying 568 * pivot source), {@code outputColumns} (one per projected column), and 569 * {@code pivotColumnRefs} (the consumed FOR + aggregation-arg columns). 570 * Every other slot defaults empty / null: a slice-129 PIVOT skeleton has 571 * no filter / join / group-by / having / order-by / distinct-on / 572 * qualify / grouping / returning refs, is not {@code DISTINCT}, is not a 573 * set-op, has no row-limit, and writes no target. The five-arg shape is 574 * unambiguous against every other constructor. 575 */ 576 public StatementGraph(String name, 577 String kind, 578 List<RelationSource> relations, 579 List<OutputColumn> outputColumns, 580 List<ColumnRef> pivotColumnRefs) { 581 this(name, kind, relations, outputColumns, 582 Collections.<OutputColumn>emptyList(), 583 Collections.<ColumnRef>emptyList(), 584 Collections.<ColumnRef>emptyList(), 585 Collections.<ColumnRef>emptyList(), 586 Collections.<ColumnRef>emptyList(), 587 Collections.<ColumnRef>emptyList(), 588 Collections.<ColumnRef>emptyList(), 589 Collections.<ColumnRef>emptyList(), 590 Collections.<GroupingElement>emptyList(), 591 pivotColumnRefs, 592 /*distinct=*/ false, /*setOperator=*/ null, 593 /*rowLimit=*/ null, /*target=*/ null); 594 } 595 596 /** 597 * Slice 156 PIVOT SELECT constructor — the slice-129 PIVOT shape plus a 598 * {@code filterColumnRefs} slot for an admitted passthrough-only 599 * {@code WHERE} clause over a PIVOT (every WHERE ref resolves to a provable 600 * passthrough source column). All other clause slots default empty / null, 601 * exactly as the five-arg PIVOT constructor — so an admitted PIVOT without a 602 * WHERE (empty {@code filterColumnRefs}) is byte-identical to the five-arg 603 * form. The six-arg shape (two {@code List<ColumnRef>} trailing args) is 604 * unambiguous against every other constructor by arity. 605 */ 606 public StatementGraph(String name, 607 String kind, 608 List<RelationSource> relations, 609 List<OutputColumn> outputColumns, 610 List<ColumnRef> filterColumnRefs, 611 List<ColumnRef> pivotColumnRefs) { 612 this(name, kind, relations, outputColumns, 613 Collections.<OutputColumn>emptyList(), 614 filterColumnRefs, 615 Collections.<ColumnRef>emptyList(), 616 Collections.<ColumnRef>emptyList(), 617 Collections.<ColumnRef>emptyList(), 618 Collections.<ColumnRef>emptyList(), 619 Collections.<ColumnRef>emptyList(), 620 Collections.<ColumnRef>emptyList(), 621 Collections.<GroupingElement>emptyList(), 622 pivotColumnRefs, 623 /*distinct=*/ false, /*setOperator=*/ null, 624 /*rowLimit=*/ null, /*target=*/ null); 625 } 626 627 /** 628 * Slice 158 PIVOT SELECT constructor — the slice-156 PIVOT shape plus a 629 * {@code groupByColumnRefs} slot for an admitted passthrough-only 630 * {@code GROUP BY} clause over a PIVOT/UNPIVOT (every GROUP BY ref resolves 631 * to a provable passthrough source column). All other clause slots default 632 * empty / null, exactly as the six-arg PIVOT constructor — so an admitted 633 * PIVOT without a GROUP BY (empty {@code groupByColumnRefs}) is byte-identical 634 * to the six-arg form. The seven-arg shape (three {@code List<ColumnRef>} 635 * trailing args) is unambiguous against every other constructor by arity. 636 */ 637 public StatementGraph(String name, 638 String kind, 639 List<RelationSource> relations, 640 List<OutputColumn> outputColumns, 641 List<ColumnRef> filterColumnRefs, 642 List<ColumnRef> groupByColumnRefs, 643 List<ColumnRef> pivotColumnRefs) { 644 this(name, kind, relations, outputColumns, 645 Collections.<OutputColumn>emptyList(), 646 filterColumnRefs, 647 Collections.<ColumnRef>emptyList(), 648 groupByColumnRefs, 649 Collections.<ColumnRef>emptyList(), 650 Collections.<ColumnRef>emptyList(), 651 Collections.<ColumnRef>emptyList(), 652 Collections.<ColumnRef>emptyList(), 653 Collections.<GroupingElement>emptyList(), 654 pivotColumnRefs, 655 /*distinct=*/ false, /*setOperator=*/ null, 656 /*rowLimit=*/ null, /*target=*/ null); 657 } 658 659 /** 660 * Slice 159 PIVOT SELECT constructor — the slice-158 PIVOT shape plus a 661 * {@code qualifyColumnRefs} slot for an admitted passthrough-only 662 * {@code QUALIFY} clause over a PIVOT/UNPIVOT (every QUALIFY ref resolves to a 663 * provable passthrough source column). All other clause slots default empty / 664 * null, exactly as the seven-arg PIVOT constructor — so an admitted PIVOT 665 * without a QUALIFY (empty {@code qualifyColumnRefs}) is byte-identical to the 666 * seven-arg form. The eight-arg shape (four {@code List<ColumnRef>} trailing 667 * args) is unambiguous against every other constructor by arity. 668 */ 669 public StatementGraph(String name, 670 String kind, 671 List<RelationSource> relations, 672 List<OutputColumn> outputColumns, 673 List<ColumnRef> filterColumnRefs, 674 List<ColumnRef> groupByColumnRefs, 675 List<ColumnRef> qualifyColumnRefs, 676 List<ColumnRef> pivotColumnRefs) { 677 this(name, kind, relations, outputColumns, 678 Collections.<OutputColumn>emptyList(), 679 filterColumnRefs, 680 Collections.<ColumnRef>emptyList(), 681 groupByColumnRefs, 682 Collections.<ColumnRef>emptyList(), 683 Collections.<ColumnRef>emptyList(), 684 Collections.<ColumnRef>emptyList(), 685 qualifyColumnRefs, 686 Collections.<GroupingElement>emptyList(), 687 pivotColumnRefs, 688 /*distinct=*/ false, /*setOperator=*/ null, 689 /*rowLimit=*/ null, /*target=*/ null); 690 } 691 692 /** 693 * Slice 160 PIVOT SELECT constructor — the slice-159 PIVOT shape plus a 694 * {@code havingColumnRefs} slot for an admitted passthrough-only 695 * {@code HAVING} clause over a PIVOT/UNPIVOT (every HAVING ref resolves to a 696 * provable passthrough source column). All other clause slots default empty / 697 * null, exactly as the eight-arg PIVOT constructor — so an admitted PIVOT 698 * without a HAVING (empty {@code havingColumnRefs}) is byte-identical to the 699 * eight-arg form. The nine-arg shape (five {@code List<ColumnRef>} trailing 700 * args) is unambiguous against every other constructor by arity. The trailing 701 * args follow the natural SQL clause order: filter (WHERE), groupBy, having, 702 * qualify, then the consumed pivot refs. 703 */ 704 public StatementGraph(String name, 705 String kind, 706 List<RelationSource> relations, 707 List<OutputColumn> outputColumns, 708 List<ColumnRef> filterColumnRefs, 709 List<ColumnRef> groupByColumnRefs, 710 List<ColumnRef> havingColumnRefs, 711 List<ColumnRef> qualifyColumnRefs, 712 List<ColumnRef> pivotColumnRefs) { 713 this(name, kind, relations, outputColumns, 714 Collections.<OutputColumn>emptyList(), 715 filterColumnRefs, 716 Collections.<ColumnRef>emptyList(), 717 groupByColumnRefs, 718 havingColumnRefs, 719 Collections.<ColumnRef>emptyList(), 720 Collections.<ColumnRef>emptyList(), 721 qualifyColumnRefs, 722 Collections.<GroupingElement>emptyList(), 723 pivotColumnRefs, 724 /*distinct=*/ false, /*setOperator=*/ null, 725 /*rowLimit=*/ null, /*target=*/ null); 726 } 727 728 /** 729 * Slice 161 PIVOT SELECT constructor — the slice-160 PIVOT shape plus an 730 * {@code orderByColumnRefs} slot for a passthrough-only {@code ORDER BY} 731 * clause over a PIVOT/UNPIVOT (every sort key resolves to a provable 732 * passthrough source column). UNLIKE the WHERE/GROUP BY/HAVING/QUALIFY 733 * slots, an ORDER BY over a pivot was already admitted (slice 142, 734 * lineage-neutral), so this slot only refines the lineage and is empty when 735 * no passthrough sort key can be proven. All other clause slots default 736 * empty / null, exactly as the nine-arg PIVOT constructor — so an admitted 737 * PIVOT without a passthrough ORDER BY (empty {@code orderByColumnRefs}) is 738 * byte-identical to the nine-arg form. The ten-arg shape (six 739 * {@code List<ColumnRef>} trailing args) is unambiguous against every other 740 * constructor by arity. The trailing args follow the natural SQL clause 741 * order: filter (WHERE), groupBy, having, qualify, orderBy, then the 742 * consumed pivot refs. 743 */ 744 public StatementGraph(String name, 745 String kind, 746 List<RelationSource> relations, 747 List<OutputColumn> outputColumns, 748 List<ColumnRef> filterColumnRefs, 749 List<ColumnRef> groupByColumnRefs, 750 List<ColumnRef> havingColumnRefs, 751 List<ColumnRef> qualifyColumnRefs, 752 List<ColumnRef> orderByColumnRefs, 753 List<ColumnRef> pivotColumnRefs) { 754 this(name, kind, relations, outputColumns, 755 Collections.<OutputColumn>emptyList(), 756 filterColumnRefs, 757 Collections.<ColumnRef>emptyList(), 758 groupByColumnRefs, 759 havingColumnRefs, 760 orderByColumnRefs, 761 Collections.<ColumnRef>emptyList(), 762 qualifyColumnRefs, 763 Collections.<GroupingElement>emptyList(), 764 pivotColumnRefs, 765 /*distinct=*/ false, /*setOperator=*/ null, 766 /*rowLimit=*/ null, /*target=*/ null); 767 } 768 769 /** 770 * Pre-slice-73 constructor preserved so hand-built test fixtures 771 * (e.g. {@code SemanticIRProjectorBodyIndexesTest}) continue to 772 * compile without touching every call site. Delegates to the 773 * slice-73 constructor with an empty {@code distinctOnColumnRefs} 774 * list. New production code should call the slice-78 primary 775 * constructor directly. 776 */ 777 public StatementGraph(String name, 778 String kind, 779 List<RelationSource> relations, 780 List<OutputColumn> outputColumns, 781 List<ColumnRef> filterColumnRefs, 782 List<ColumnRef> joinColumnRefs, 783 List<ColumnRef> groupByColumnRefs, 784 List<ColumnRef> havingColumnRefs, 785 List<ColumnRef> orderByColumnRefs, 786 boolean distinct, 787 SetOperator setOperator, 788 RowLimit rowLimit) { 789 this(name, kind, relations, outputColumns, 790 filterColumnRefs, joinColumnRefs, groupByColumnRefs, 791 havingColumnRefs, orderByColumnRefs, 792 Collections.<ColumnRef>emptyList(), 793 distinct, setOperator, rowLimit); 794 } 795 796 /** Nullable: name for a CTE body or FROM-subquery alias, else null. */ 797 public String getName() { 798 return name; 799 } 800 801 public String getKind() { 802 return kind; 803 } 804 805 /** 806 * @return the diagnostic explaining why this block was not analyzed, or 807 * {@code null} for an ordinary (fully analyzed) block. Non-null 808 * exactly when {@link #getKind()} is 809 * {@value #KIND_UNANALYZED}; see 810 * {@code SemanticIRBuildOptions.withDegradeUnsupportedNestedBlocks}. 811 */ 812 public Diagnostic getUnanalyzedReason() { 813 return unanalyzedReason; 814 } 815 816 /** 817 * @return {@code true} when this entry is a degrade placeholder standing 818 * in for a nested block the builder could not analyze. Its empty 819 * relation / column lists mean "not analyzed", never "none 820 * present". 821 */ 822 public boolean isUnanalyzed() { 823 return unanalyzedReason != null; 824 } 825 826 public List<RelationSource> getRelations() { 827 return relations; 828 } 829 830 public List<OutputColumn> getOutputColumns() { 831 return outputColumns; 832 } 833 834 /** 835 * Slice 85 — RETURNING / OUTPUT projection columns for INSERT / UPDATE / 836 * DELETE statements. Empty list on every SELECT-kind statement (CTE 837 * body / FROM-subquery / scalar / set-op branch / outer), on every 838 * DML statement that did not supply a RETURNING (PG / Oracle) or 839 * OUTPUT (SQL Server) clause, and on CTAS / CREATE VIEW statements. 840 * 841 * <p>For PG / Oracle RETURNING, each entry's 842 * {@link OutputColumn#getName()} is the explicit alias when present, 843 * else the verbatim bare column spelling. 844 * {@link OutputColumn#getSources()} lists the underlying column refs; 845 * the {@code relationAlias} resolves through the same provider used 846 * for SET RHS / WHERE / JOIN ON, so a joined-UPDATE with 847 * {@code RETURNING t.a, s.x} produces refs against both target and 848 * FROM-side relations. 849 * 850 * <p>For SQL Server OUTPUT pseudo-table refs (INSERTED.col, 851 * DELETED.col), the {@code relationAlias} is preserved as the 852 * uppercase pseudo-table name ({@code "INSERTED"} or 853 * {@code "DELETED"}) so consumers can distinguish post-write from 854 * pre-write row state. Lineage edges still flow to 855 * {@link LineageRef#tableColumn(String, String)} pointing at the 856 * physical target table column — both INSERTED and DELETED ultimately 857 * reference the same physical column; only the temporal phase differs. 858 */ 859 public List<OutputColumn> getReturningColumns() { 860 return returningColumns; 861 } 862 863 public List<ColumnRef> getFilterColumnRefs() { 864 return filterColumnRefs; 865 } 866 867 public List<ColumnRef> getJoinColumnRefs() { 868 return joinColumnRefs; 869 } 870 871 public List<ColumnRef> getGroupByColumnRefs() { 872 return groupByColumnRefs; 873 } 874 875 /** 876 * Column references that appear in the {@code HAVING} clause's 877 * predicate. The list is per-statement and per-clause: a HAVING 878 * predicate that names {@code d.id} contributes one entry; a HAVING 879 * predicate inside an aggregate ({@code HAVING SUM(salary) > 1000}) 880 * contributes the underlying column ({@code salary}) — the same 881 * convention used for projection-side aggregate arguments 882 * (slice 6 OutputColumn.sources). 883 * 884 * <p>Subqueries in HAVING (scalar, EXISTS, IN-SELECT, ANY/ALL/SOME) 885 * and window functions in HAVING are rejected by the builder rather 886 * than silently captured, because the visitor would descend into 887 * inner scopes and leak refs (mirrors the slice-9 ORDER BY guards). 888 * 889 * <p>HAVING is row-influence semantically (it filters out groups), 890 * but it deliberately does <i>not</i> contribute to the canonical 891 * lineage model (slice 7 / {@code CanonicalLineageEdge}). The 892 * canonical model is a parity contract between IR and dlineage, and 893 * dlineage exposes no per-clause HAVING field — it folds HAVING refs 894 * into aggregate-function fdr/fdd edges. Including HAVING-derived 895 * canonical edges only on the IR side would manufacture 896 * divergence-by-design. The {@code havingColumnRefs} field remains 897 * useful for downstream consumers (SQL Guard, lineage explainers) 898 * that don't depend on the dlineage parity contract. 899 */ 900 public List<ColumnRef> getHavingColumnRefs() { 901 return havingColumnRefs; 902 } 903 904 /** 905 * Column references that appear in the {@code ORDER BY} clause's sort 906 * keys. Only physical column references are recorded — ordinal 907 * ({@code ORDER BY 1}) and projection-alias ({@code ORDER BY x}) 908 * forms are rejected by the builder, not silently emitted as 909 * {@code []}. Sort direction ({@code ASC}/{@code DESC}) and null 910 * placement ({@code NULLS FIRST}/{@code NULLS LAST}) are presentation 911 * metadata and are not modelled. 912 * 913 * <p>The flag is per-statement: in 914 * {@code WITH x AS (... ORDER BY id) SELECT id FROM x} the inner 915 * statement's {@code orderByColumnRefs} contains {@code id} while the 916 * outer's is empty. 917 */ 918 public List<ColumnRef> getOrderByColumnRefs() { 919 return orderByColumnRefs; 920 } 921 922 /** 923 * Whether the statement applies row-deduplication. True for 924 * {@code SELECT DISTINCT}, Oracle's deprecated synonym 925 * {@code SELECT UNIQUE}, AND PostgreSQL / Greenplum 926 * {@code SELECT DISTINCT ON (cols)}; false for {@code SELECT}, 927 * {@code SELECT ALL}, and the absence of any row-filter clause. 928 * The flag is per-statement, never per-output. 929 * 930 * <p>For {@code DISTINCT ON (cols)} the partition keys live on 931 * {@link #getDistinctOnColumnRefs()}; the boolean here pins the 932 * semantic invariant that the statement deduplicates rows 933 * regardless of which key shape is used. 934 */ 935 public boolean isDistinct() { 936 return distinct; 937 } 938 939 /** 940 * Column references in the {@code DISTINCT ON (cols)} partition list 941 * (PostgreSQL / Greenplum). Empty for plain {@code SELECT DISTINCT}, 942 * {@code SELECT UNIQUE}, {@code SELECT ALL}, and the absence of any 943 * row-filter clause. 944 * 945 * <p>Invariant: {@code !distinctOnColumnRefs.isEmpty()} implies 946 * {@link #isDistinct()} == {@code true}. The reverse does not hold 947 * (plain {@code DISTINCT} also returns {@code true}). 948 * 949 * <p>The list collects physical column refs the same way 950 * {@code groupByColumnRefs} does: column refs inside compound 951 * expressions ({@code a + b}, {@code CASE WHEN ...}) and aggregate 952 * arguments ({@code COUNT(x)}) are descended into; subqueries and 953 * window functions in {@code DISTINCT ON} are rejected by the 954 * builder so they cannot leak inner-scope refs. 955 * 956 * <p>Oracle, MySQL, Redshift and other non-PG vendors silently 957 * accept {@code DISTINCT ON (...)} as plain {@code DISTINCT} — 958 * their parser drops the ON expression list, so this slot stays 959 * empty for those vendors regardless of the surface SQL. 960 */ 961 public List<ColumnRef> getDistinctOnColumnRefs() { 962 return distinctOnColumnRefs; 963 } 964 965 /** 966 * Column references that appear in the {@code QUALIFY} clause's 967 * predicate (Snowflake / BigQuery / Teradata). QUALIFY filters rows on 968 * window-function results; it is row-influence in the same family as 969 * {@code WHERE} and {@code HAVING}. 970 * 971 * <p>Two surface forms reduce to the SAME set of influencing base 972 * columns: 973 * <ul> 974 * <li>Inline window form 975 * ({@code QUALIFY ROW_NUMBER() OVER (PARTITION BY a ORDER BY b) = 1}) 976 * — the window's PARTITION BY / ORDER BY / argument refs are 977 * collected directly ({@code a}, {@code b}).</li> 978 * <li>Projection-alias form ({@code QUALIFY rn = 1} where 979 * {@code rn} aliases a window projection) — the alias resolves to 980 * the matching {@link OutputColumn}; its influencing columns are 981 * {@code getSources()} unioned with the 982 * {@link WindowSpec#getPartitionRefs()} and 983 * {@link WindowSpec#getOrderRefs()} of its window spec (a window 984 * {@link OutputColumn#getSources()} is empty on its own).</li> 985 * </ul> 986 * 987 * <p>Subqueries in QUALIFY are rejected by the builder (they would 988 * leak inner-scope refs); window functions are admitted (the whole 989 * point of QUALIFY). 990 * 991 * <p>Like {@code havingColumnRefs}, this slot is row-influence 992 * semantically but deliberately does <i>not</i> contribute to the 993 * canonical lineage model (slice 7 / {@code CanonicalLineageEdge}). 994 * The canonical model is a parity contract with dlineage, which 995 * exposes no per-clause QUALIFY field; emitting QUALIFY-derived 996 * canonical edges only on the IR side would manufacture 997 * divergence-by-design. 998 */ 999 public List<ColumnRef> getQualifyColumnRefs() { 1000 return qualifyColumnRefs; 1001 } 1002 1003 /** 1004 * Structured per-top-level-element view of the {@code GROUP BY} (slice 1005 * 128): one {@link GroupingElement} per top-level grouping item in 1006 * document order, each tagged {@code SIMPLE} / {@code ROLLUP} / 1007 * {@code CUBE} / {@code GROUPING_SETS} with its flattened member 1008 * columns. Empty iff the statement has no {@code GROUP BY} items; a 1009 * plain {@code GROUP BY a, b} yields {@code [SIMPLE: a, SIMPLE: b]}. 1010 * 1011 * <p>This is additive to {@link #getGroupByColumnRefs()}, which still 1012 * returns the flat, deduplicated, document-order union of every 1013 * grouping column (slice 127) regardless of grouping structure. 1014 * {@code groupingElements} preserves the structure the flat list 1015 * discards — relevant for governance (a {@code ROLLUP}/{@code CUBE} 1016 * changes output cardinality and produces super-aggregate rows) and 1017 * for downstream OpenLineage / DataHub consumers. 1018 */ 1019 public List<GroupingElement> getGroupingElements() { 1020 return groupingElements; 1021 } 1022 1023 /** 1024 * Slice 129 — columns CONSUMED by a {@code PIVOT} operator, in document 1025 * order: the {@code FOR} / pivot column(s) first, then the 1026 * aggregation-function argument column(s). All resolve to the underlying 1027 * pivot source relation ({@link #getRelations()}). Empty on every 1028 * non-PIVOT statement. 1029 * 1030 * <p>These are the input columns a PIVOT reads; the IN-list values 1031 * become the new output column names (synthesised in a later sub-slice). 1032 * Function names, literals, and column-alias nodes (e.g. the Oracle 1033 * {@code SUM(quantity) AS q}) are not columns and do not appear here. 1034 * 1035 * <p>Slice 129 (sub-slice a) admits only a PIVOT over a base-table 1036 * source with an explicit (non-{@code *}) projection and no other query 1037 * clauses; broader shapes (UNPIVOT, subquery source, {@code SELECT *} 1038 * expansion, output-column lineage) are deferred to later sub-slices and 1039 * rejected with structured {@code PIVOT_*} diagnostics until then. 1040 */ 1041 public List<ColumnRef> getPivotColumnRefs() { 1042 return pivotColumnRefs; 1043 } 1044 1045 /** 1046 * Set-operation kind for the outer statement of a set-op program 1047 * (slice 12). Returns null on every regular SELECT statement and on 1048 * every CTE / FROM-subquery / scalar / set-op-branch body. The 1049 * {@code _ALL} variants encode {@code TSelectSqlStatement#isAll()}; 1050 * {@code MINUS} (Oracle / Spark / Hive) and {@code EXCEPT} 1051 * (PostgreSQL / SQL Server / standard) are kept distinct because the 1052 * parser exposes them as separate 1053 * {@link gudusoft.gsqlparser.ESetOperatorType} values, even though 1054 * they are semantically equivalent. 1055 */ 1056 public SetOperator getSetOperator() { 1057 return setOperator; 1058 } 1059 1060 /** 1061 * Per-statement row-limit metadata (slice 70). Returns null when no 1062 * row-limit clause was present, or when the row-limit clause is in 1063 * slice-71 / 72 territory (TOP, standalone OFFSET, PG inline 1064 * {@code LIMIT N OFFSET M}, MySQL inline {@code LIMIT M, N}, 1065 * set-op outer row-limit) — those surfaces continue to be rejected 1066 * by the builder with their existing diagnostic codes. 1067 * 1068 * <p>When non-null, the {@link RowLimit#getKind()} captures which 1069 * surface SQL form was used ({@code LIMIT} vs {@code FETCH FIRST}) 1070 * and {@link RowLimit#getCount()} captures the verbatim count text. 1071 * 1072 * <p>Row-limit metadata does <i>not</i> change column lineage. The 1073 * canonical lineage model (slice 7 / {@code CanonicalLineageEdge}) 1074 * deliberately ignores it: row-limit is presentation-time pruning, 1075 * not a column-flow influence. ORDER BY refs, output sources, 1076 * filter / join / group-by / having refs are all unaffected. 1077 */ 1078 public RowLimit getRowLimit() { 1079 return rowLimit; 1080 } 1081 1082 /** 1083 * Slice 78 — write-side target for INSERT statements. Non-null only on 1084 * {@code "INSERT"}-kind statements; null on every {@code "SELECT"}-kind 1085 * statement (whether the SELECT is an outer, CTE body, FROM-subquery 1086 * body, scalar-subquery body, or set-op branch). 1087 * 1088 * <p>When non-null, {@link TargetRelation#getBinding()} is the target 1089 * table (kind = {@link RelationKind#TABLE}) and 1090 * {@link TargetRelation#getColumns()} holds the verbatim SQL column-list 1091 * spellings (empty list when the SQL author omitted the column list). 1092 * 1093 * <p>Cross-statement {@link LineageEdge}s for INSERT use 1094 * {@link LineageRef#tableColumn(String, String)} as the {@code from} 1095 * endpoint (target_table, target_col) and 1096 * {@link LineageRef#statementOutput(int, String)} as the {@code to} 1097 * endpoint (source SELECT body statement index + output name). 1098 */ 1099 public TargetRelation getTarget() { 1100 return target; 1101 } 1102 1103 /** 1104 * Join-analysis facts for this query block (slice 167, GAP 1/2/4): 1105 * the structured {@link JoinGraph}, WHERE filter predicates, and 1106 * query-block scope. Never null ({@link JoinAnalysisFacts#EMPTY} when 1107 * not populated). 1108 */ 1109 public JoinAnalysisFacts getJoinAnalysisFacts() { 1110 return joinAnalysisFacts; 1111 } 1112 1113 /** 1114 * Convenience accessor for the structured {@link JoinGraph} (slice 1115 * 167). Never null ({@link JoinGraph#EMPTY} when this block has no 1116 * modelled joins). 1117 */ 1118 public JoinGraph getJoinGraph() { 1119 return joinAnalysisFacts.getJoinGraph(); 1120 } 1121 1122 /** 1123 * Optional block-level source span (slice 179, R5) covering this 1124 * statement's own text, or {@code null} when not set. The same span is 1125 * surfaced on {@link QueryBlockScope#getSourceSpan()}. 1126 */ 1127 public SourceSpan getSourceSpan() { 1128 return sourceSpan; 1129 } 1130}