001package gudusoft.gsqlparser.resolver2.binding;
002
003import gudusoft.gsqlparser.nodes.TObjectName;
004
005import java.util.IdentityHashMap;
006import java.util.Map;
007
008/**
009 * Identity-keyed store of {@link BindingTrace}s for one resolver run.
010 *
011 * <p>Owned by {@code TSQLResolver2} when
012 * {@code TSQLResolverConfig.captureBindingTrace} is enabled; {@code null}
013 * otherwise (zero overhead on the default path). Not thread-safe — a resolver
014 * instance is single-threaded and single-use, and so is its registry.
015 *
016 * <p>The resolver is iterative (up to {@code maxIterations} passes); a
017 * re-resolution of the same reference overwrites its earlier trace, so the
018 * registry ends up describing the FINAL pass — exactly what the P3 proof
019 * consumes.
020 *
021 * <p>Consumers must treat a missing trace as unprovable
022 * ({@code trace == null || !trace.complete} ⇒ refuse); never assume presence.
023 */
024public final class BindingTraceRegistry {
025
026    private final Map<TObjectName, BindingTrace> traces =
027            new IdentityHashMap<TObjectName, BindingTrace>();
028
029    /** Record (or overwrite with) the latest trace for a reference. */
030    public void register(BindingTrace trace) {
031        if (trace == null || trace.reference == null) {
032            return;
033        }
034        // A FAILED scope lookup does not rebind anything (NameResolver leaves
035        // sourceTable untouched on NOT_FOUND, UNNEST special case aside), so it
036        // must not overwrite a complete DIRECT trace — e.g. OUTPUT INSERTED.a
037        // is direct-bound by ScopeBuilder and then re-looked-up through scopes,
038        // where it is legitimately NOT_FOUND. The protection covers ONLY direct
039        // kinds: a newer scope trace always replaces an older scope trace, so
040        // an exact→ambiguous re-resolution in a later pass correctly degrades
041        // the trace to incomplete instead of preserving a stale complete one.
042        BindingTrace existing = traces.get(trace.reference);
043        if (existing != null && existing.complete
044                && existing.binderKind != BindingTrace.BinderKind.SCOPE_RESOLUTION
045                && trace.binderKind == BindingTrace.BinderKind.SCOPE_RESOLUTION
046                && trace.status != gudusoft.gsqlparser.resolver2.ResolutionStatus.EXACT_MATCH) {
047            return;
048        }
049        traces.put(trace.reference, trace);
050    }
051
052    /** Drop a reference's trace (⇒ unprovable). Used by post-resolution corrections. */
053    public void invalidate(TObjectName reference) {
054        if (reference != null) {
055            traces.remove(reference);
056        }
057    }
058
059    /**
060     * Consistency invalidation for post-resolution correction passes (QUALIFY /
061     * USING / subquery-alias / MERGE restores): those handlers may change or
062     * clear {@code sourceTable} AFTER the trace was captured. If a complete
063     * scope trace's table definition no longer matches the reference's current
064     * {@code sourceTable}, the trace is stale — drop it (fail-closed). Non-table
065     * definitions (CTE, derived table) cannot be cross-checked this way and are
066     * left to the overwrite policy.
067     */
068    public void invalidateIfInconsistent(TObjectName reference) {
069        if (reference == null) {
070            return;
071        }
072        BindingTrace t = traces.get(reference);
073        if (t != null && t.complete
074                && t.binderKind == BindingTrace.BinderKind.SCOPE_RESOLUTION
075                && reference.getSourceTable() != t.observedSourceTable) {
076            traces.remove(reference);
077        }
078    }
079
080    /** Reset for a fresh resolver run (traces describe exactly one run). */
081    public void clear() {
082        traces.clear();
083    }
084
085    /** The final-pass trace for a reference, or {@code null} (⇒ unprovable). */
086    public BindingTrace traceFor(TObjectName reference) {
087        return reference == null ? null : traces.get(reference);
088    }
089
090    public int size() {
091        return traces.size();
092    }
093}