001package gudusoft.gsqlparser.resolver2.scope; 002 003import gudusoft.gsqlparser.TCustomSqlStatement; 004import gudusoft.gsqlparser.nodes.TBlockSqlNode; 005import gudusoft.gsqlparser.nodes.TObjectName; 006import gudusoft.gsqlparser.resolver2.ScopeType; 007import gudusoft.gsqlparser.resolver2.matcher.INameMatcher; 008import gudusoft.gsqlparser.resolver2.model.ScopeChild; 009import gudusoft.gsqlparser.resolver2.namespace.INamespace; 010import gudusoft.gsqlparser.resolver2.namespace.PlsqlVariableNamespace; 011 012import java.util.ArrayList; 013import java.util.Collections; 014import java.util.List; 015 016/** 017 * Scope for PL/SQL blocks (Oracle, PostgreSQL PL/pgSQL). 018 * 019 * <p>PL/SQL blocks can have labels (<<label_name>>) that allow referencing 020 * variables declared within the block using the label as a qualifier: 021 * <pre> 022 * <<main>> 023 * DECLARE 024 * ename VARCHAR2(10) := 'KING'; 025 * BEGIN 026 * DELETE FROM emp WHERE ename = main.ename; -- main.ename references the block variable 027 * END; 028 * </pre> 029 * 030 * <p>This scope: 031 * <ul> 032 * <li>Contains a PlsqlVariableNamespace for declared variables</li> 033 * <li>Uses the block label as the namespace alias</li> 034 * <li>Allows name resolution to distinguish block variables from table columns</li> 035 * </ul> 036 */ 037public class PlsqlBlockScope extends AbstractScope { 038 039 /** The block node (may be null for stored procedures) */ 040 private final TBlockSqlNode blockNode; 041 042 /** The block label (e.g., "main" from <<main>>) or procedure/function name */ 043 private final String blockLabel; 044 045 /** Namespace containing declared PL/SQL variables */ 046 private final PlsqlVariableNamespace variableNamespace; 047 048 /** Child scopes (for nested statements) */ 049 private final List<ScopeChild> children = new ArrayList<>(); 050 051 /** Counter for child ordinals */ 052 private int childOrdinal = 0; 053 054 /** 055 * Create a new PL/SQL block scope for a labeled block. 056 * 057 * @param parent The parent scope 058 * @param blockNode The block AST node (TBlockSqlNode) 059 */ 060 public PlsqlBlockScope(IScope parent, TBlockSqlNode blockNode) { 061 super(parent, blockNode, ScopeType.PLSQL_BLOCK); 062 this.blockNode = blockNode; 063 064 // Extract block label from the node 065 String label = null; 066 TObjectName labelName = blockNode.getLabelName(); 067 if (labelName != null) { 068 label = labelName.toString(); 069 // Remove angle brackets if present (e.g., "<<main>>" -> "main") 070 if (label != null) { 071 if (label.startsWith("<<") && label.endsWith(">>")) { 072 label = label.substring(2, label.length() - 2); 073 } 074 // Also handle case where it might just be the label without brackets 075 label = label.trim(); 076 } 077 } 078 this.blockLabel = label; 079 this.variableNamespace = new PlsqlVariableNamespace(blockLabel); 080 } 081 082 /** 083 * Create a new PL/SQL block scope for a stored procedure or function. 084 * This constructor is used when there's no TBlockSqlNode (e.g., CREATE PROCEDURE). 085 * 086 * @param parent The parent scope 087 * @param stmt The stored procedure/function statement 088 * @param procedureName The name of the procedure/function (used as the block label) 089 */ 090 public PlsqlBlockScope(IScope parent, TCustomSqlStatement stmt, String procedureName) { 091 super(parent, stmt, ScopeType.PLSQL_BLOCK); 092 this.blockNode = null; 093 this.blockLabel = procedureName; 094 this.variableNamespace = new PlsqlVariableNamespace(procedureName); 095 } 096 097 /** 098 * Get the block label. 099 * 100 * @return The block label, or null for anonymous blocks 101 */ 102 public String getBlockLabel() { 103 return blockLabel; 104 } 105 106 /** 107 * Get the variable namespace for this block. 108 * 109 * @return The PlsqlVariableNamespace containing declared variables 110 */ 111 public PlsqlVariableNamespace getVariableNamespace() { 112 return variableNamespace; 113 } 114 115 /** 116 * Get the block node. 117 * 118 * @return The TBlockSqlNode AST node 119 */ 120 public TBlockSqlNode getBlockNode() { 121 return blockNode; 122 } 123 124 @Override 125 public void addChild(INamespace namespace, String alias, boolean nullable) { 126 children.add(new ScopeChild(childOrdinal++, alias, namespace, nullable)); 127 } 128 129 @Override 130 public List<ScopeChild> getChildren() { 131 return children; 132 } 133 134 @Override 135 public INamespace resolveTable(String tableName) { 136 // First check if the table name matches our block label 137 // If so, return the variable namespace 138 if (blockLabel != null && blockLabel.equalsIgnoreCase(tableName)) { 139 return variableNamespace; 140 } 141 142 // Otherwise delegate to parent 143 return parent.resolveTable(tableName); 144 } 145 146 @Override 147 public void resolve(List<String> names, 148 INameMatcher matcher, 149 boolean deep, 150 IResolved resolved) { 151 // Handle unqualified variable references (e.g., dte_start_dte) 152 if (names.size() == 1) { 153 String name = names.get(0); 154 if (variableNamespace.hasColumn(name) == gudusoft.gsqlparser.resolver2.ColumnLevel.EXISTS) { 155 // This is a PL/SQL variable - mark it as resolved. 156 // Pass the variable name in remainingNames so NameResolver can resolve it 157 // to a ColumnSource from the PlsqlVariableNamespace. 158 resolved.found(variableNamespace, false, this, null, names); 159 return; 160 } 161 } 162 163 // Handle qualified variable references (e.g., main.ename) 164 if (names.size() >= 2 && blockLabel != null) { 165 String firstName = names.get(0); 166 if (matcher.matches(firstName, blockLabel)) { 167 // This is a qualified reference like main.ename 168 // The column name is the second part 169 String columnName = names.get(1); 170 if (variableNamespace.hasColumn(columnName) == gudusoft.gsqlparser.resolver2.ColumnLevel.EXISTS) { 171 // Found the variable - mark as resolved. 172 // Pass the column name in remainingNames so NameResolver can resolve it 173 // to a ColumnSource from the PlsqlVariableNamespace. 174 resolved.found(variableNamespace, false, this, null, Collections.singletonList(columnName)); 175 return; 176 } 177 } 178 } 179 180 // Otherwise delegate to parent 181 parent.resolve(names, matcher, deep, resolved); 182 } 183 184 @Override 185 public List<INamespace> getVisibleNamespaces() { 186 List<INamespace> namespaces = new ArrayList<>(); 187 namespaces.add(variableNamespace); 188 namespaces.addAll(parent.getVisibleNamespaces()); 189 return namespaces; 190 } 191 192 @Override 193 public String toString() { 194 return "PlsqlBlockScope(" + (blockLabel != null ? blockLabel : "anonymous") + 195 ", vars=" + variableNamespace.getVariables().size() + ")"; 196 } 197}