001package gudusoft.gsqlparser.resolver2.scope;
002
003import gudusoft.gsqlparser.nodes.TParseTreeNode;
004import gudusoft.gsqlparser.resolver2.ScopeType;
005import gudusoft.gsqlparser.resolver2.matcher.INameMatcher;
006import gudusoft.gsqlparser.resolver2.model.ResolvePath;
007import gudusoft.gsqlparser.resolver2.model.ScopeChild;
008import gudusoft.gsqlparser.resolver2.namespace.INamespace;
009
010import java.util.ArrayList;
011import java.util.Collections;
012import java.util.List;
013
014/**
015 * Base class for scopes that manage a list of child namespaces.
016 * Used for FROM clauses, which contain multiple tables/subqueries.
017 *
018 * Key features:
019 * 1. Maintains ordered list of children
020 * 2. Resolves names by searching through children
021 * 3. Supports aliases
022 */
023public abstract class ListBasedScope extends AbstractScope {
024
025    /** Child namespaces in this scope */
026    protected final List<ScopeChild> children = new ArrayList<>();
027
028    protected ListBasedScope(IScope parent, TParseTreeNode node, ScopeType scopeType) {
029        super(parent, node, scopeType);
030    }
031
032    @Override
033    public void addChild(INamespace namespace, String alias, boolean nullable) {
034        if (alias == null) {
035            throw new IllegalArgumentException("Alias cannot be null in ListBasedScope");
036        }
037        children.add(new ScopeChild(children.size(), alias, namespace, nullable));
038    }
039
040    @Override
041    public List<ScopeChild> getChildren() {
042        return Collections.unmodifiableList(children);
043    }
044
045    @Override
046    public INamespace resolveTable(String tableName) {
047        // Search in children only - do NOT delegate to parent
048        // The parent (SelectScope) already handles fallback to outer scopes
049        // Delegating to parent causes infinite recursion:
050        // SelectScope.resolveTable -> fromScope.resolveTable -> parent.resolveTable -> SelectScope.resolveTable -> ...
051        for (ScopeChild child : children) {
052            if (tableName.equalsIgnoreCase(child.getAlias())) {
053                return child.getNamespace();
054            }
055        }
056
057        // Not found - return null, let caller handle fallback
058        return null;
059    }
060
061    @Override
062    public void resolve(List<String> names,
063                       INameMatcher matcher,
064                       boolean deep,
065                       IResolved resolved) {
066        if (names.isEmpty()) {
067            return;
068        }
069
070        String firstName = names.get(0);
071
072        // Search through children
073        for (ScopeChild child : children) {
074            if (matcher.matches(child.getAlias(), firstName)) {
075                // Found matching child
076                INamespace namespace = child.getNamespace();
077                ResolvePath path = new ResolvePath();
078
079                if (names.size() == 1) {
080                    // Just the table alias
081                    resolved.found(namespace, child.isNullable(), this, path, Collections.emptyList());
082                } else {
083                    // Resolve remaining parts in the namespace
084                    List<String> remaining = names.subList(1, names.size());
085                    resolveInNamespace(namespace, child.isNullable(), remaining, matcher, path, resolved);
086                }
087            }
088        }
089
090        // Also try parent scope
091        parent.resolve(names, matcher, deep, resolved);
092    }
093
094    /**
095     * Resolve remaining name parts within a namespace.
096     * For example, if we found table "t" and need to resolve "t.col1",
097     * this method handles resolving "col1" within table t.
098     */
099    protected void resolveInNamespace(INamespace namespace,
100                                     boolean nullable,
101                                     List<String> remainingNames,
102                                     INameMatcher matcher,
103                                     ResolvePath path,
104                                     IResolved resolved) {
105        if (remainingNames.isEmpty()) {
106            resolved.found(namespace, nullable, this, path, Collections.emptyList());
107            return;
108        }
109
110        // For now, simple implementation - just mark as found with remaining names
111        // Full implementation would traverse into structured types
112        resolved.found(namespace, nullable, this, path, remainingNames);
113    }
114
115    @Override
116    public List<INamespace> getVisibleNamespaces() {
117        List<INamespace> visible = new ArrayList<>();
118        for (ScopeChild child : children) {
119            visible.add(child.getNamespace());
120        }
121        // Add parent's visible namespaces
122        visible.addAll(parent.getVisibleNamespaces());
123        return visible;
124    }
125
126    @Override
127    public String toString() {
128        return String.format("%s(children=%d)", scopeType, children.size());
129    }
130}