001package gudusoft.gsqlparser.resolver2.model;
002
003import gudusoft.gsqlparser.nodes.TTable;
004import gudusoft.gsqlparser.resolver2.namespace.INamespace;
005import gudusoft.gsqlparser.resolver2.scope.IScope;
006import gudusoft.gsqlparser.resolver2.scope.SelectScope;
007
008import java.util.HashMap;
009import java.util.List;
010import java.util.Map;
011
012/**
013 * Index structure for fast lookups in FromScope.
014 *
015 * <p>This class optimizes O(N) linear scans to O(1) hash lookups for common
016 * operations like finding tables by alias/name and finding candidate namespaces
017 * for star column inference.</p>
018 *
019 * <p>The index is built lazily on first access and cached for subsequent lookups.
020 * This avoids the overhead of building the index when it's not needed.</p>
021 *
022 * <p><b>Performance improvement:</b> For queries with N tables and M column references,
023 * reduces complexity from O(N*M) to O(N + M).</p>
024 *
025 * @since 3.1.0.9
026 */
027public class FromScopeIndex {
028
029    /**
030     * Map from lowercase alias/name to ScopeChild for O(1) lookup.
031     * Key is always lowercase for case-insensitive matching.
032     */
033    private final Map<String, ScopeChild> aliasMap;
034
035    /**
036     * Map from lowercase namespace display name to ScopeChild.
037     * Used when lookup by alias fails but table name might match.
038     */
039    private final Map<String, ScopeChild> nameMap;
040
041    /**
042     * First namespace that supports star columns or dynamic inference.
043     * Used for unqualified column resolution when no table prefix is specified.
044     */
045    private final INamespace preferredStarNamespace;
046
047    /**
048     * Total number of children in the FromScope.
049     */
050    private final int childCount;
051
052    /**
053     * Creates an index from the given list of scope children.
054     *
055     * @param children The list of ScopeChild from a FromScope
056     */
057    public FromScopeIndex(List<ScopeChild> children) {
058        this.aliasMap = new HashMap<>();
059        this.nameMap = new HashMap<>();
060        this.childCount = children != null ? children.size() : 0;
061
062        INamespace firstStar = null;
063
064        if (children != null) {
065            for (ScopeChild child : children) {
066                // Index by alias (lowercase for case-insensitive matching)
067                String alias = child.getAlias();
068                if (alias != null && !alias.isEmpty()) {
069                    aliasMap.put(alias.toLowerCase(), child);
070                }
071
072                // Also index by namespace display name
073                INamespace ns = child.getNamespace();
074                if (ns != null) {
075                    String displayName = ns.getDisplayName();
076                    if (displayName != null && !displayName.isEmpty()) {
077                        nameMap.put(displayName.toLowerCase(), child);
078                    }
079
080                    // Track first star/dynamic namespace
081                    if (firstStar == null &&
082                        (ns.hasStarColumn() || ns.supportsDynamicInference())) {
083                        firstStar = ns;
084                    }
085                }
086            }
087        }
088
089        this.preferredStarNamespace = firstStar;
090    }
091
092    /**
093     * Finds a ScopeChild by its alias (case-insensitive).
094     *
095     * @param alias The alias to search for
096     * @return The matching ScopeChild, or null if not found
097     */
098    public ScopeChild findByAlias(String alias) {
099        if (alias == null || alias.isEmpty()) {
100            return null;
101        }
102        return aliasMap.get(alias.toLowerCase());
103    }
104
105    /**
106     * Finds a ScopeChild by alias first, then by namespace name (case-insensitive).
107     *
108     * @param qualifier The qualifier (alias or table name) to search for
109     * @return The matching ScopeChild, or null if not found
110     */
111    public ScopeChild findByQualifier(String qualifier) {
112        if (qualifier == null || qualifier.isEmpty()) {
113            return null;
114        }
115
116        String lowerQualifier = qualifier.toLowerCase();
117
118        // First try alias
119        ScopeChild result = aliasMap.get(lowerQualifier);
120        if (result != null) {
121            return result;
122        }
123
124        // Then try namespace name
125        return nameMap.get(lowerQualifier);
126    }
127
128    /**
129     * Gets the TTable from a ScopeChild by qualifier, if the namespace represents a table.
130     *
131     * @param qualifier The qualifier (alias or table name) to search for
132     * @return The TTable if found and the namespace is a table, null otherwise
133     */
134    public TTable findTableByQualifier(String qualifier) {
135        ScopeChild child = findByAlias(qualifier);
136        if (child != null) {
137            INamespace ns = child.getNamespace();
138            if (ns != null && ns.getNode() instanceof TTable) {
139                return (TTable) ns.getNode();
140            }
141        }
142        return null;
143    }
144
145    /**
146     * Gets the first namespace that supports star columns or dynamic inference.
147     * Used for resolving unqualified column references.
148     *
149     * @return The preferred star namespace, or null if none exists
150     */
151    public INamespace getPreferredStarNamespace() {
152        return preferredStarNamespace;
153    }
154
155    /**
156     * Finds a candidate namespace for column resolution.
157     *
158     * <p>If a table prefix is provided, looks up by that prefix.
159     * Otherwise, returns the first star/dynamic namespace for unqualified columns.</p>
160     *
161     * @param tablePrefix The table prefix (alias or name), or null for unqualified columns
162     * @return The candidate namespace, or null if not found
163     */
164    public INamespace findCandidateNamespace(String tablePrefix) {
165        if (tablePrefix != null && !tablePrefix.isEmpty()) {
166            // Qualified reference - find by prefix
167            ScopeChild child = findByQualifier(tablePrefix);
168            if (child != null) {
169                return child.getNamespace();
170            }
171            return null;
172        } else {
173            // Unqualified reference - use star namespace
174            return preferredStarNamespace;
175        }
176    }
177
178    /**
179     * Gets the total number of children indexed.
180     *
181     * @return The child count
182     */
183    public int getChildCount() {
184        return childCount;
185    }
186
187    /**
188     * Checks if the index is empty.
189     *
190     * @return true if no children are indexed
191     */
192    public boolean isEmpty() {
193        return childCount == 0;
194    }
195
196    /**
197     * Creates an index from a scope, extracting the FromScope if needed.
198     *
199     * @param scope The scope (SelectScope, UpdateScope, or FromScope)
200     * @return A new FromScopeIndex, or null if no FromScope is found
201     */
202    public static FromScopeIndex fromScope(IScope scope) {
203        if (scope == null) {
204            return null;
205        }
206
207        IScope fromScope = null;
208
209        if (scope instanceof SelectScope) {
210            fromScope = ((SelectScope) scope).getFromScope();
211        } else if (scope instanceof gudusoft.gsqlparser.resolver2.scope.UpdateScope) {
212            fromScope = ((gudusoft.gsqlparser.resolver2.scope.UpdateScope) scope).getFromScope();
213        } else if (scope instanceof gudusoft.gsqlparser.resolver2.scope.FromScope) {
214            fromScope = scope;
215        }
216
217        if (fromScope == null) {
218            return null;
219        }
220
221        return new FromScopeIndex(fromScope.getChildren());
222    }
223
224    @Override
225    public String toString() {
226        return String.format("FromScopeIndex(aliases=%d, names=%d, hasStar=%b)",
227            aliasMap.size(), nameMap.size(), preferredStarNamespace != null);
228    }
229}