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}