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}