001package gudusoft.gsqlparser.resolver2.model;
002
003import gudusoft.gsqlparser.nodes.TObjectName;
004import gudusoft.gsqlparser.nodes.TTable;
005import gudusoft.gsqlparser.resolver2.binding.BindingSkipReason;
006import gudusoft.gsqlparser.resolver2.matcher.INameMatcher;
007import gudusoft.gsqlparser.resolver2.matcher.DefaultNameMatcher;
008
009import java.util.*;
010
011/**
012 * Global context for resolution results.
013 * Provides efficient querying and statistics about resolved columns.
014 *
015 * Design principles:
016 * 1. Reverse indexing for O(1) queries
017 * 2. Categorized storage for quick access to different resolution states
018 * 3. Statistics for monitoring resolution quality
019 */
020public class ResolutionContext {
021
022    // === Core storage: forward mapping ===
023
024    /** All processed column references -> resolution results */
025    private final Map<TObjectName, ResolutionResult> resolutions = new LinkedHashMap<>();
026
027    // === Reverse indexes for efficient queries ===
028
029    /** Table -> all columns referencing that table */
030    private final Map<TTable, List<TObjectName>> tableReferences = new HashMap<>();
031
032    /** Column name -> all column references with that name (for conflict detection) */
033    private final Map<String, List<TObjectName>> columnNameIndex = new HashMap<>();
034
035    // === Categorized lists for quick access ===
036
037    /** Exactly matched columns (EXACT_MATCH) */
038    private final List<TObjectName> exactMatches = new ArrayList<>();
039
040    /** Ambiguous columns (AMBIGUOUS) */
041    private final List<TObjectName> ambiguousColumns = new ArrayList<>();
042
043    /** Unresolved columns (NOT_FOUND) */
044    private final List<TObjectName> unresolvedColumns = new ArrayList<>();
045
046    // === Statistics ===
047
048    /** Table reference counts */
049    private final Map<TTable, Integer> tableReferenceCounts = new HashMap<>();
050
051    /** Total column references processed */
052    private int totalColumnReferences = 0;
053
054    // === Binding-trace state (S3) ===
055    //
056    // Identity-keyed per-TObjectName trace, used by BindingDiagnosticPostPass
057    // (S5+) as deterministic input. Both maps stay null until enableBindingTrace()
058    // is called by TSQLResolver2 — keeping the default-off path allocation-free
059    // (plan §10.1 perf gate). IdentityHashMap on purpose: two TObjectName
060    // instances with equal toString must NOT collide.
061
062    private boolean bindingTraceEnabled = false;
063    private IdentityHashMap<TObjectName, ResolutionResult> columnResolutionResults;
064    private IdentityHashMap<TObjectName, BindingSkipReason> columnSkipReasons;
065
066
067    // ===== Internal methods: called by NameResolver =====
068
069    /**
070     * Register a resolution result.
071     * Called by NameResolver when a column is resolved.
072     */
073    public void registerResolution(TObjectName objName, ResolutionResult result) {
074        // 1. Forward storage
075        resolutions.put(objName, result);
076        totalColumnReferences++;
077
078        // 2. Update reverse index by column name
079        String columnName = objName.getColumnNameOnly();
080        if (columnName != null) {
081            columnNameIndex.computeIfAbsent(columnName, k -> new ArrayList<>())
082                          .add(objName);
083        }
084
085        // 3. Categorize by status
086        switch (result.getStatus()) {
087            case EXACT_MATCH:
088                exactMatches.add(objName);
089                ColumnSource source = result.getColumnSource();
090                if (source != null) {
091                    updateTableReference(source.getFinalTable(), objName);
092                }
093                break;
094
095            case AMBIGUOUS:
096                ambiguousColumns.add(objName);
097                // For ambiguous: all candidates count as references
098                AmbiguousColumnSource ambiguous = result.getAmbiguousSource();
099                if (ambiguous != null) {
100                    for (ColumnSource candidate : ambiguous.getCandidates()) {
101                        updateTableReference(candidate.getFinalTable(), objName);
102                    }
103                }
104                break;
105
106            case NOT_FOUND:
107                unresolvedColumns.add(objName);
108                break;
109        }
110
111        // Binding-trace tap (S3). All resolver call sites flow through
112        // registerResolution — both NameResolver.resolve() and the four
113        // bypass paths in TSQLResolver2 (USING-left, USING-right, Teradata
114        // NAMED alias, QUALIFY clause alias) — so a single put here covers
115        // every reference that produces a ResolutionResult.
116        if (bindingTraceEnabled && columnResolutionResults != null && objName != null) {
117            columnResolutionResults.put(objName, result);
118        }
119    }
120
121    /**
122     * Update table reference index
123     */
124    private void updateTableReference(TTable table, TObjectName objName) {
125        if (table == null) return;
126
127        // Reverse index
128        tableReferences.computeIfAbsent(table, k -> new ArrayList<>())
129                      .add(objName);
130
131        // Reference count
132        tableReferenceCounts.merge(table, 1, Integer::sum);
133    }
134
135
136    // ===== Public query API (Level 2 API) =====
137
138    /**
139     * Get all column references to a specific table.
140     * Complexity: O(1)
141     *
142     * @param table Target table
143     * @return List of TObjectName referencing that table
144     */
145    public List<TObjectName> getReferencesTo(TTable table) {
146        return tableReferences.getOrDefault(table, Collections.emptyList());
147    }
148
149    /**
150     * Get all column references to a specific table.column.
151     * Complexity: O(m) where m = number of references to the table
152     *
153     * @param table Target table
154     * @param columnName Target column name
155     * @return List of matching TObjectName
156     */
157    public List<TObjectName> getReferencesTo(TTable table, String columnName) {
158        INameMatcher matcher = new DefaultNameMatcher();
159
160        return getReferencesTo(table).stream()
161            .filter(obj -> {
162                String colName = obj.getColumnNameOnly();
163                return colName != null && matcher.matches(colName, columnName);
164            })
165            .collect(java.util.stream.Collectors.toList());
166    }
167
168    /**
169     * Find all column references with a given name (for conflict detection).
170     * Complexity: O(1)
171     */
172    public List<TObjectName> getColumnsByName(String columnName) {
173        return columnNameIndex.getOrDefault(columnName, Collections.emptyList());
174    }
175
176    /**
177     * Get all exactly matched columns
178     */
179    public List<TObjectName> getExactMatches() {
180        return Collections.unmodifiableList(exactMatches);
181    }
182
183    /**
184     * Get all ambiguous columns
185     */
186    public List<TObjectName> getAmbiguousColumns() {
187        return Collections.unmodifiableList(ambiguousColumns);
188    }
189
190    /**
191     * Get all unresolved columns
192     */
193    public List<TObjectName> getUnresolvedColumns() {
194        return Collections.unmodifiableList(unresolvedColumns);
195    }
196
197    /**
198     * Get reference count for a specific table
199     */
200    public int getTableReferenceCount(TTable table) {
201        return tableReferenceCounts.getOrDefault(table, 0);
202    }
203
204    /**
205     * Get all tables that have been referenced
206     */
207    public Set<TTable> getAllReferencedTables() {
208        return tableReferences.keySet();
209    }
210
211    /**
212     * Get resolution statistics
213     */
214    public ResolutionStatistics getStatistics() {
215        return new ResolutionStatistics(
216            totalColumnReferences,
217            exactMatches.size(),
218            ambiguousColumns.size(),
219            unresolvedColumns.size(),
220            tableReferences.size()
221        );
222    }
223
224    /**
225     * Get resolution result for a specific TObjectName
226     */
227    public ResolutionResult getResolution(TObjectName objName) {
228        return resolutions.get(objName);
229    }
230
231    /**
232     * Clear all data (for reuse or testing)
233     */
234    public void clear() {
235        resolutions.clear();
236        tableReferences.clear();
237        columnNameIndex.clear();
238        exactMatches.clear();
239        ambiguousColumns.clear();
240        unresolvedColumns.clear();
241        tableReferenceCounts.clear();
242        totalColumnReferences = 0;
243        if (columnResolutionResults != null) {
244            columnResolutionResults.clear();
245        }
246        if (columnSkipReasons != null) {
247            columnSkipReasons.clear();
248        }
249    }
250
251    // ===== Binding-trace API (S3, plan §7.3) =====
252
253    /**
254     * Enable per-{@link TObjectName} binding-trace recording.
255     *
256     * <p>Allocates the identity-keyed trace and skip-reason maps on first call;
257     * subsequent calls are no-ops. {@code TSQLResolver2.resolve()} flips this
258     * on at the start of resolution when either {@code emitBindingDiagnostics}
259     * or {@code bindingIncludeSuccessfulReferences} is set.</p>
260     */
261    public void enableBindingTrace() {
262        if (!bindingTraceEnabled) {
263            bindingTraceEnabled = true;
264            columnResolutionResults = new IdentityHashMap<>();
265            columnSkipReasons = new IdentityHashMap<>();
266        }
267    }
268
269    /**
270     * @return {@code true} once {@link #enableBindingTrace()} has been called
271     *         for this context.
272     */
273    public boolean isBindingTraceEnabled() {
274        return bindingTraceEnabled;
275    }
276
277    /**
278     * Get the recorded resolution for a specific {@link TObjectName} reference.
279     *
280     * <p>Identity-keyed (two references with equal {@code toString} are kept
281     * separate). Returns {@code null} when no resolution has been recorded for
282     * this reference, including when the trace is disabled.</p>
283     */
284    public ResolutionResult getColumnResolutionResult(TObjectName objName) {
285        if (columnResolutionResults == null || objName == null) {
286            return null;
287        }
288        return columnResolutionResults.get(objName);
289    }
290
291    /**
292     * Get the skip-reason recorded for a specific {@link TObjectName} reference.
293     */
294    public BindingSkipReason getColumnSkipReason(TObjectName objName) {
295        if (columnSkipReasons == null || objName == null) {
296            return null;
297        }
298        return columnSkipReasons.get(objName);
299    }
300
301    /**
302     * Record an explicit binding-skip reason for a reference. Silently ignored
303     * when the trace is disabled.
304     */
305    public void recordColumnSkipReason(TObjectName objName, BindingSkipReason reason) {
306        if (!bindingTraceEnabled || objName == null || reason == null) {
307            return;
308        }
309        columnSkipReasons.put(objName, reason);
310    }
311}