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}