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