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 * &lt;&lt;main&gt;&gt;
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}