001package gudusoft.gsqlparser.resolver2.namespace;
002
003import gudusoft.gsqlparser.TBaseType;
004import gudusoft.gsqlparser.nodes.TTable;
005import gudusoft.gsqlparser.resolver2.ColumnLevel;
006import gudusoft.gsqlparser.resolver2.model.ColumnSource;
007import gudusoft.gsqlparser.stmt.TVarDeclStmt;
008
009import java.util.*;
010
011/**
012 * Namespace representing PL/SQL variables declared in a block.
013 *
014 * <p>PL/SQL blocks (<<label>>) can declare variables in their DECLARE section.
015 * These variables can be referenced using the block label as a qualifier:
016 * <pre>
017 * &lt;&lt;main&gt;&gt;
018 * DECLARE
019 *   ename VARCHAR2(10) := 'KING';
020 * BEGIN
021 *   DELETE FROM emp WHERE ename = main.ename;  -- main.ename is the variable
022 * END;
023 * </pre>
024 *
025 * <p>This namespace allows the resolver to distinguish between PL/SQL variables
026 * (like main.ename) and table columns (like emp.ename) by registering the
027 * block's variables as a namespace that can be queried by the block's label.
028 */
029public class PlsqlVariableNamespace implements INamespace {
030
031    /** The block label (e.g., "main" from <<main>>) */
032    private final String blockLabel;
033
034    /** Map of variable name (lowercase) -> variable declaration */
035    private final Map<String, TVarDeclStmt> variables = new LinkedHashMap<>();
036
037    /** Map of column name (lowercase) -> ColumnSource for the variables */
038    private final Map<String, ColumnSource> columnSources = new LinkedHashMap<>();
039
040    /** Set of parameter names (lowercase) for procedure/function parameters */
041    private final Set<String> parameterNames = new HashSet<>();
042
043    /** Whether this namespace has been validated */
044    private boolean validated = false;
045
046    /**
047     * Create a new PL/SQL variable namespace for a labeled block.
048     *
049     * @param blockLabel The block label (e.g., "main" from <<main>>)
050     */
051    public PlsqlVariableNamespace(String blockLabel) {
052        this.blockLabel = blockLabel;
053    }
054
055    /**
056     * Add a variable declaration to this namespace.
057     *
058     * @param varDecl The variable declaration statement
059     */
060    public void addVariable(TVarDeclStmt varDecl) {
061        if (varDecl != null && varDecl.getElementName() != null) {
062            String varName = varDecl.getElementName().toString();
063            // Strip quotes from variable names (e.g., "myvar" -> myvar)
064            String varNameNormalized = TBaseType.getTextWithoutQuoted(varName);
065            String varNameLower = varNameNormalized.toLowerCase();
066            variables.put(varNameLower, varDecl);
067            // Create a ColumnSource with the variable declaration as the definition node
068            // This marks the source as coming from a PL/SQL block, not a table
069            ColumnSource source = new ColumnSource(this, varNameNormalized, varDecl.getElementName(), 1.0, "plsql_variable");
070            columnSources.put(varNameLower, source);
071        }
072    }
073
074    /**
075     * Add a procedure/function parameter to this namespace.
076     * Parameters are tracked separately from variables since they don't need
077     * ColumnSource lineage - we only need to know if a name is a parameter
078     * to filter it from column reference collection.
079     *
080     * @param paramName The parameter name (as String)
081     */
082    public void addParameter(String paramName) {
083        if (paramName != null && !paramName.isEmpty()) {
084            // Strip quotes from parameter names (e.g., "A1" -> A1)
085            String normalized = TBaseType.getTextWithoutQuoted(paramName).toLowerCase();
086            parameterNames.add(normalized);
087        }
088    }
089
090    /**
091     * Check if a variable with the given name exists in this block.
092     *
093     * @param variableName The variable name to check
094     * @return true if the variable exists
095     */
096    public boolean hasVariable(String variableName) {
097        String normalized = TBaseType.getTextWithoutQuoted(variableName).toLowerCase();
098        return variables.containsKey(normalized);
099    }
100
101    @Override
102    public String getDisplayName() {
103        return blockLabel != null ? blockLabel : "(anonymous block)";
104    }
105
106    /**
107     * Check if a name is a parameter.
108     *
109     * @param name The name to check
110     * @return true if the name is a registered parameter
111     */
112    public boolean hasParameter(String name) {
113        String normalized = TBaseType.getTextWithoutQuoted(name).toLowerCase();
114        return parameterNames.contains(normalized);
115    }
116
117    @Override
118    public ColumnLevel hasColumn(String columnName) {
119        // Strip quotes and normalize to lowercase for comparison
120        String nameLower = TBaseType.getTextWithoutQuoted(columnName).toLowerCase();
121        // Check variables, parameters, and columnSources
122        if (variables.containsKey(nameLower) || parameterNames.contains(nameLower)) {
123            return ColumnLevel.EXISTS;
124        }
125        return ColumnLevel.NOT_EXISTS;
126    }
127
128    @Override
129    public ColumnSource resolveColumn(String columnName) {
130        String nameLower = TBaseType.getTextWithoutQuoted(columnName).toLowerCase();
131        return columnSources.get(nameLower);
132    }
133
134    @Override
135    public Map<String, ColumnSource> getAllColumnSources() {
136        return Collections.unmodifiableMap(columnSources);
137    }
138
139    @Override
140    public TTable getFinalTable() {
141        // PL/SQL variables don't have a physical table
142        return null;
143    }
144
145    @Override
146    public List<TTable> getAllFinalTables() {
147        // PL/SQL variables don't have physical tables
148        return Collections.emptyList();
149    }
150
151    @Override
152    public boolean isValidated() {
153        return validated;
154    }
155
156    @Override
157    public void validate() {
158        validated = true;
159    }
160
161    @Override
162    public Object getNode() {
163        return null;
164    }
165
166    /**
167     * Get the block label.
168     *
169     * @return The block label
170     */
171    public String getBlockLabel() {
172        return blockLabel;
173    }
174
175    /**
176     * Get all variable declarations in this block.
177     *
178     * @return Unmodifiable map of variable name -> declaration
179     */
180    public Map<String, TVarDeclStmt> getVariables() {
181        return Collections.unmodifiableMap(variables);
182    }
183
184    @Override
185    public String toString() {
186        return "PlsqlVariableNamespace(" + getDisplayName() +
187               ", vars=" + variables.size() +
188               ", params=" + parameterNames.size() + ")";
189    }
190}