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}