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