001package gudusoft.gsqlparser.resolver2.namespace; 002 003import gudusoft.gsqlparser.nodes.TObjectName; 004import gudusoft.gsqlparser.nodes.TTable; 005import gudusoft.gsqlparser.resolver2.ColumnLevel; 006import gudusoft.gsqlparser.resolver2.model.ColumnSource; 007import gudusoft.gsqlparser.stmt.TCursorDeclStmt; 008import gudusoft.gsqlparser.stmt.TVarDeclStmt; 009import gudusoft.gsqlparser.stmt.oracle.TPlsqlCreatePackage; 010 011import java.util.*; 012 013/** 014 * Namespace representing Oracle PL/SQL package members (variables, constants, cursors). 015 * 016 * <p>Package members declared in PACKAGE or PACKAGE BODY can be referenced: 017 * <ul> 018 * <li>Unqualified (within package body): {@code gn_err_no}</li> 019 * <li>Qualified (anywhere): {@code pkg_name.gn_err_no}</li> 020 * <li>Schema-qualified: {@code schema.pkg_name.gn_err_no}</li> 021 * </ul> 022 * 023 * <p>This namespace returns {@code getFinalTable() == null} since package members 024 * are not table columns. 025 */ 026public class OraclePackageNamespace implements INamespace { 027 028 /** 029 * Represents the kind of package member. 030 */ 031 public enum MemberKind { 032 VARIABLE, // Regular variable 033 CONSTANT, // CONSTANT declaration 034 CURSOR, // Explicit cursor 035 REF_CURSOR, // REF CURSOR type/variable 036 EXCEPTION // User-defined exception 037 } 038 039 /** 040 * Represents a single package member (variable, constant, cursor, etc.) 041 */ 042 public static class PackageMember { 043 public final String name; 044 public final MemberKind kind; 045 public final Object definitionNode; // TVarDeclStmt, TCursorDeclStmt, etc. 046 public final double confidence; 047 public final String evidence; 048 049 public PackageMember(String name, MemberKind kind, Object defNode) { 050 this.name = name; 051 this.kind = kind; 052 this.definitionNode = defNode; 053 this.confidence = 1.0; // Always high confidence for declared members 054 this.evidence = "oracle_package_" + kind.name().toLowerCase(); 055 } 056 057 @Override 058 public String toString() { 059 return name + ":" + kind; 060 } 061 } 062 063 /** Package name (simple name, e.g., "PACK_DAMAGE_AUTO_SAVE") */ 064 private final String packageName; 065 066 /** Schema name (if known) */ 067 private final String schemaName; 068 069 /** AST node for the package (spec or body) */ 070 private final TPlsqlCreatePackage packageNode; 071 072 /** Map of member name (lowercase) -> PackageMember */ 073 private final Map<String, PackageMember> members = new LinkedHashMap<>(); 074 075 /** Map of column name (lowercase) -> ColumnSource for the variables */ 076 private final Map<String, ColumnSource> columnSources = new LinkedHashMap<>(); 077 078 /** Validation flag */ 079 private boolean validated = false; 080 081 /** 082 * Create a new Oracle package namespace. 083 * 084 * @param packageName The simple package name 085 * @param schemaName The schema name (may be null) 086 * @param packageNode The AST node for the package 087 */ 088 public OraclePackageNamespace(String packageName, String schemaName, 089 TPlsqlCreatePackage packageNode) { 090 this.packageName = packageName; 091 this.schemaName = schemaName; 092 this.packageNode = packageNode; 093 } 094 095 /** 096 * Add a variable/constant member from TVarDeclStmt. 097 * 098 * @param varDecl The variable declaration statement 099 */ 100 public void addVariable(TVarDeclStmt varDecl) { 101 if (varDecl == null || varDecl.getElementName() == null) return; 102 103 String name = varDecl.getElementName().toString(); 104 String nameLower = name.toLowerCase(); 105 106 // Determine if it's a constant (check if declaration contains CONSTANT keyword) 107 MemberKind kind = isConstantDeclaration(varDecl) 108 ? MemberKind.CONSTANT 109 : MemberKind.VARIABLE; 110 111 PackageMember member = new PackageMember(name, kind, varDecl); 112 members.put(nameLower, member); 113 114 // Create ColumnSource for resolution 115 ColumnSource source = new ColumnSource(this, name, 116 varDecl.getElementName(), member.confidence, member.evidence); 117 columnSources.put(nameLower, source); 118 } 119 120 /** 121 * Add a cursor member from TCursorDeclStmt. 122 * 123 * @param cursorDecl The cursor declaration statement 124 */ 125 public void addCursor(TCursorDeclStmt cursorDecl) { 126 if (cursorDecl == null || cursorDecl.getCursorName() == null) return; 127 128 String name = cursorDecl.getCursorName().toString(); 129 String nameLower = name.toLowerCase(); 130 131 // Determine cursor kind 132 MemberKind kind = (cursorDecl.getKind() == TCursorDeclStmt.kind_ref_cursor_type_definition) 133 ? MemberKind.REF_CURSOR 134 : MemberKind.CURSOR; 135 136 PackageMember member = new PackageMember(name, kind, cursorDecl); 137 members.put(nameLower, member); 138 139 ColumnSource source = new ColumnSource(this, name, 140 cursorDecl.getCursorName(), member.confidence, member.evidence); 141 columnSources.put(nameLower, source); 142 } 143 144 /** 145 * Add a member directly by name and kind. 146 * Used for adding members discovered through other means (e.g., exception declarations). 147 * 148 * @param name The member name 149 * @param kind The member kind 150 * @param definitionNode The AST node where this member is defined 151 */ 152 public void addMember(String name, MemberKind kind, Object definitionNode) { 153 if (name == null || name.isEmpty()) return; 154 155 String nameLower = name.toLowerCase(); 156 PackageMember member = new PackageMember(name, kind, definitionNode); 157 members.put(nameLower, member); 158 159 // Create ColumnSource - use TObjectName if available, otherwise null 160 Object defNode = null; 161 if (definitionNode instanceof TObjectName) { 162 defNode = definitionNode; 163 } 164 ColumnSource source = new ColumnSource(this, name, 165 (defNode instanceof gudusoft.gsqlparser.nodes.TParseTreeNode) 166 ? (gudusoft.gsqlparser.nodes.TParseTreeNode) defNode 167 : null, 168 member.confidence, member.evidence); 169 columnSources.put(nameLower, source); 170 } 171 172 /** 173 * Merge members from another package namespace (spec + body merging). 174 * 175 * @param other The other namespace to merge from 176 */ 177 public void mergeFrom(OraclePackageNamespace other) { 178 if (other == null) return; 179 members.putAll(other.members); 180 columnSources.putAll(other.columnSources); 181 } 182 183 /** 184 * Check if a declaration is a CONSTANT. 185 * This is a heuristic based on the variable declaration's attributes. 186 */ 187 private boolean isConstantDeclaration(TVarDeclStmt varDecl) { 188 // Check if the declaration has the isConstant flag set 189 // The TVarDeclStmt may have an isConstant() method or we can check the tokens 190 try { 191 // Try to check via reflection or known API 192 // For now, check the toString() for CONSTANT keyword 193 String declText = varDecl.toString(); 194 if (declText != null) { 195 String upperText = declText.toUpperCase(); 196 // Look for CONSTANT keyword after the variable name 197 int nameEnd = declText.indexOf(varDecl.getElementName().toString()); 198 if (nameEnd > 0) { 199 String afterName = upperText.substring(nameEnd); 200 return afterName.contains("CONSTANT"); 201 } 202 } 203 } catch (Exception e) { 204 // Ignore and default to VARIABLE 205 } 206 return false; 207 } 208 209 // === INamespace Implementation === 210 211 @Override 212 public String getDisplayName() { 213 return schemaName != null 214 ? schemaName + "." + packageName 215 : packageName; 216 } 217 218 @Override 219 public ColumnLevel hasColumn(String columnName) { 220 return members.containsKey(columnName.toLowerCase()) 221 ? ColumnLevel.EXISTS 222 : ColumnLevel.NOT_EXISTS; 223 } 224 225 @Override 226 public ColumnSource resolveColumn(String columnName) { 227 return columnSources.get(columnName.toLowerCase()); 228 } 229 230 @Override 231 public Map<String, ColumnSource> getAllColumnSources() { 232 return Collections.unmodifiableMap(columnSources); 233 } 234 235 @Override 236 public TTable getFinalTable() { 237 return null; // Package members are NOT table columns 238 } 239 240 @Override 241 public List<TTable> getAllFinalTables() { 242 return Collections.emptyList(); 243 } 244 245 @Override 246 public boolean isValidated() { 247 return validated; 248 } 249 250 @Override 251 public void validate() { 252 validated = true; 253 } 254 255 @Override 256 public Object getNode() { 257 return packageNode; 258 } 259 260 // === Accessors === 261 262 /** 263 * Get the package name. 264 * 265 * @return The simple package name 266 */ 267 public String getPackageName() { 268 return packageName; 269 } 270 271 /** 272 * Get the schema name. 273 * 274 * @return The schema name, or null if not specified 275 */ 276 public String getSchemaName() { 277 return schemaName; 278 } 279 280 /** 281 * Get all members in this package. 282 * 283 * @return Unmodifiable map of member name (lowercase) -> PackageMember 284 */ 285 public Map<String, PackageMember> getMembers() { 286 return Collections.unmodifiableMap(members); 287 } 288 289 /** 290 * Check if this package has a member with the given name. 291 * 292 * @param name The member name to check 293 * @return true if the member exists 294 */ 295 public boolean hasMember(String name) { 296 return name != null && members.containsKey(name.toLowerCase()); 297 } 298 299 /** 300 * Get a specific member by name. 301 * 302 * @param name The member name 303 * @return The PackageMember, or null if not found 304 */ 305 public PackageMember getMember(String name) { 306 return name != null ? members.get(name.toLowerCase()) : null; 307 } 308 309 @Override 310 public String toString() { 311 return "OraclePackageNamespace(" + getDisplayName() + 312 ", members=" + members.size() + ")"; 313 } 314}