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.QualifiedName;
007import gudusoft.gsqlparser.resolver2.model.ScopeChild;
008import gudusoft.gsqlparser.resolver2.namespace.INamespace;
009
010import java.util.Collections;
011import java.util.List;
012
013/**
014 * Abstract base class for all scopes.
015 * Implements delegation pattern - most methods delegate to parent.
016 *
017 * Subclasses only need to override methods where they have special behavior.
018 */
019public abstract class AbstractScope implements IScope {
020
021    /** Parent scope (never null - root has EmptyScope parent) */
022    protected final IScope parent;
023
024    /** Associated AST node */
025    protected final TParseTreeNode node;
026
027    /** Scope type */
028    protected final ScopeType scopeType;
029
030    protected AbstractScope(IScope parent, TParseTreeNode node, ScopeType scopeType) {
031        if (parent == null) {
032            throw new IllegalArgumentException("Parent scope cannot be null. Use EmptyScope for root.");
033        }
034        this.parent = parent;
035        this.node = node;
036        this.scopeType = scopeType;
037    }
038
039    /**
040     * Where a name this scope cannot resolve continues its search, when that is not
041     * {@link #parent}. See {@link #setLookupParent(IScope)}.
042     */
043    private IScope lookupParent;
044
045    @Override
046    public IScope getParent() {
047        return parent;
048    }
049
050    /**
051     * The scope an unresolved name climbs to: the parent, unless the search must skip it.
052     */
053    public IScope getLookupParent() {
054        return lookupParent != null ? lookupParent : parent;
055    }
056
057    /**
058     * Make an unresolved name skip the parent and continue at {@code lookupParent}.
059     *
060     * <p>A derived-table body and a CTE body cannot see the FROM clause that contains
061     * them: in {@code SELECT ... FROM (SELECT x FROM a) d JOIN t9 ...} the {@code x} can
062     * never be a column of {@code t9} or of {@code d} itself, only of {@code a} or of a
063     * query enclosing the whole statement. Only name lookup skips; {@link #getParent()}
064     * still returns the structural parent.</p>
065     */
066    public void setLookupParent(IScope lookupParent) {
067        this.lookupParent = lookupParent;
068    }
069
070    @Override
071    public TParseTreeNode getNode() {
072        return node;
073    }
074
075    @Override
076    public ScopeType getScopeType() {
077        return scopeType;
078    }
079
080    /**
081     * Default implementation: delegate to parent
082     */
083    @Override
084    public void resolve(List<String> names,
085                       INameMatcher matcher,
086                       boolean deep,
087                       IResolved resolved) {
088        getLookupParent().resolve(names, matcher, deep, resolved);
089    }
090
091    /**
092     * Default implementation: delegate to parent
093     */
094    @Override
095    public INamespace resolveTable(String tableName) {
096        return parent.resolveTable(tableName);
097    }
098
099    /**
100     * Default implementation: not supported (most scopes don't have children)
101     */
102    @Override
103    public void addChild(INamespace namespace, String alias, boolean nullable) {
104        throw new UnsupportedOperationException(
105            "Scope type " + scopeType + " does not support adding children");
106    }
107
108    /**
109     * Default implementation: empty list
110     */
111    @Override
112    public List<ScopeChild> getChildren() {
113        return Collections.emptyList();
114    }
115
116    /**
117     * Default implementation: delegate to parent
118     */
119    /**
120     * The namespaces a name in this scope can see: the scope's own, then those of its lookup parent, as
121     * {@link #resolve} walks them. A non-lateral derived table or CTE body hides its owner's FROM scope by a
122     * lookup parent (ScopeBuilder.setLookupParent), so the structural parent is not followed here either
123     * (Mantis 4820, Codex review).
124     */
125    @Override
126    public List<INamespace> getVisibleNamespaces() {
127        return getLookupParent().getVisibleNamespaces();
128    }
129
130    /**
131     * Adds the namespaces {@code scope} itself declares (its {@link IScope#getChildren()}), each once. Used by the
132     * scopes that own a FROM scope: the FROM scope's parent is the owner, so the owner must read the FROM scope's own
133     * children instead of calling {@code fromScope.getVisibleNamespaces()}, which would re-enter the owner and recurse
134     * until the stack overflows (Mantis 4820).
135     */
136    protected static void addOwnNamespaces(List<INamespace> out, IScope scope) {
137        if (scope == null) {
138            return;
139        }
140        for (ScopeChild child : scope.getChildren()) {
141            if (child != null) {
142                addDistinct(out, child.getNamespace());
143            }
144        }
145    }
146
147    /** Appends every namespace of {@code more} that {@code out} does not already hold (by identity). */
148    protected static void addAllDistinct(List<INamespace> out, List<INamespace> more) {
149        if (more == null) {
150            return;
151        }
152        for (INamespace ns : more) {
153            addDistinct(out, ns);
154        }
155    }
156
157    private static void addDistinct(List<INamespace> out, INamespace ns) {
158        if (ns == null) {
159            return;
160        }
161        for (INamespace present : out) {
162            if (present == ns) {
163                return;
164            }
165        }
166        out.add(ns);
167    }
168
169    /**
170     * Default implementation: delegate to parent
171     */
172    @Override
173    public QualifiedName fullyQualify(String name) {
174        return parent.fullyQualify(name);
175    }
176
177    /**
178     * Default implementation: check ancestry
179     */
180    @Override
181    public boolean isWithin(IScope ancestorScope) {
182        if (this == ancestorScope) {
183            return true;
184        }
185        if (parent == null) {
186            return false;
187        }
188        return parent.isWithin(ancestorScope);
189    }
190
191    @Override
192    public String toString() {
193        StringBuilder sb = new StringBuilder();
194        sb.append(scopeType);
195        if (node != null) {
196            sb.append("(").append(node.getClass().getSimpleName()).append(")");
197        }
198        return sb.toString();
199    }
200}