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