001package gudusoft.gsqlparser.dlineage.symbol;
002
003import gudusoft.gsqlparser.TCustomSqlStatement;
004import gudusoft.gsqlparser.dlineage.dataflow.model.ModelBindingManager;
005import gudusoft.gsqlparser.dlineage.dataflow.model.ModelFactory;
006import gudusoft.gsqlparser.dlineage.dataflow.model.OraclePackage;
007import gudusoft.gsqlparser.dlineage.dataflow.model.Procedure;
008import gudusoft.gsqlparser.dlineage.routine.RoutineAliasKey;
009import gudusoft.gsqlparser.stmt.TCommonBlock;
010import gudusoft.gsqlparser.stmt.TStoredProcedureSqlStatement;
011import gudusoft.gsqlparser.stmt.oracle.TPlsqlCreatePackage;
012import gudusoft.gsqlparser.stmt.teradata.TTeradataCreateProcedure;
013
014/**
015 * The scope a local symbol is declared in: the declaring routine's written name as frozen when the routine was
016 * declared (its model's display key, defaults included), prefixed by its package's; null at top level. It is found
017 * by the walk {@code DlineageUtil.getProcedureParentName} makes (the statement itself when it is a named routine, else
018 * the enclosing one, a block's owner included), but read from the routine's model, never re-composed from text with
019 * the session's defaults of the moment.
020 */
021public final class LocalScopes {
022
023    private LocalScopes() {
024    }
025
026    /** The routine statement a statement's local symbols belong to, or null at top level. */
027    public static TCustomSqlStatement routineOf(TCustomSqlStatement stmt) {
028        TCustomSqlStatement current = stmt;
029        boolean first = true;
030        while (current != null) {
031            if (current instanceof TStoredProcedureSqlStatement
032                    && ((TStoredProcedureSqlStatement) current).getStoredProcedureName() != null) {
033                return current;
034            }
035            if (!first && current instanceof TTeradataCreateProcedure
036                    && ((TTeradataCreateProcedure) current).getProcedureName() != null) {
037                return current;
038            }
039            first = false;
040            TCustomSqlStatement parent = current.getParentStmt();
041            if (parent instanceof TCommonBlock && ((TCommonBlock) parent).getBlockBody() != null
042                    && ((TCommonBlock) parent).getBlockBody().getParentObjectName() instanceof TStoredProcedureSqlStatement) {
043                parent = (TStoredProcedureSqlStatement) ((TCommonBlock) parent).getBlockBody().getParentObjectName();
044            }
045            current = parent;
046        }
047        return null;
048    }
049
050    /**
051     * The frozen scope of a routine model: its display key, qualified by its package's; a routine declared inside
052     * another routine is scoped inside that routine's scope, so same-named nested routines of two routines are two
053     * scopes.
054     */
055    public static Object scopeOf(ModelBindingManager manager, Procedure procedure) {
056        RoutineAliasKey own = ownScopeOf(procedure);
057        if (own == null || manager == null
058                || !(procedure.getProcedureObject() instanceof TCustomSqlStatement)) {
059            return own;
060        }
061        TCustomSqlStatement parent = ((TCustomSqlStatement) procedure.getProcedureObject()).getParentStmt();
062        TCustomSqlStatement enclosing = parent == null ? null : routineOf(parent);
063        Procedure outer = enclosing == null ? null : manager.getRoutineModel(enclosing);
064        if (outer == null || outer == procedure) {
065            return own;
066        }
067        Object outerScope = scopeOf(manager, outer);
068        return outerScope == null ? own : new NestedScope(outerScope, own);
069    }
070
071    private static RoutineAliasKey ownScopeOf(Procedure procedure) {
072        if (procedure == null || procedure.getAliasKey() == null) {
073            return null;
074        }
075        if (procedure.getParentPackage() != null && procedure.getParentPackage().getAliasKey() != null) {
076            return procedure.getAliasKey().qualifiedBy(procedure.getParentPackage().getAliasKey());
077        }
078        return procedure.getAliasKey();
079    }
080
081    /** The scope of a routine declared inside another routine: the enclosing routine's scope, then its own. */
082    static final class NestedScope {
083        private final Object outer;
084        private final Object own;
085
086        NestedScope(Object outer, Object own) {
087            this.outer = outer;
088            this.own = own;
089        }
090
091        @Override
092        public boolean equals(Object o) {
093            if (this == o) {
094                return true;
095            }
096            if (!(o instanceof NestedScope)) {
097                return false;
098            }
099            NestedScope that = (NestedScope) o;
100            return outer.equals(that.outer) && own.equals(that.own);
101        }
102
103        @Override
104        public int hashCode() {
105            return outer.hashCode() * 31 + own.hashCode();
106        }
107
108        @Override
109        public String toString() {
110            return outer + "/" + own;
111        }
112    }
113
114    /**
115     * The scopes a statement's references see, innermost first: its routine's, the enclosing routines' (nested
116     * routines), then the package's. Never the top level: a routine body does not see a batch's or an anonymous
117     * block's variables. Empty at top level.
118     */
119    public static java.util.List<Object> chainOf(ModelBindingManager manager, TCustomSqlStatement stmt) {
120        java.util.List<Object> chain = new java.util.ArrayList<Object>();
121        TCustomSqlStatement routine = stmt == null ? null : routineOf(stmt);
122        Procedure innermost = null;
123        boolean first = true;
124        while (routine != null) {
125            Object scope = scopeOf(manager, routine);
126            if (scope != null && !chain.contains(scope)) {
127                chain.add(scope);
128            }
129            if (first) {
130                innermost = manager.getRoutineModel(routine);
131                first = false;
132            }
133            TCustomSqlStatement parent = routine.getParentStmt();
134            routine = parent == null ? null : routineOf(parent);
135        }
136        if (innermost != null && innermost.getParentPackage() != null
137                && innermost.getParentPackage().getAliasKey() != null
138                && !chain.contains(innermost.getParentPackage().getAliasKey())) {
139            chain.add(innermost.getParentPackage().getAliasKey());
140        }
141        return chain;
142    }
143
144    /**
145     * The scope of a statement's local symbols: its routine's frozen scope; a package's own key for declarations at
146     * package level (a package is a named routine statement with no routine model); null at top level.
147     */
148    public static Object scopeOf(ModelBindingManager manager, TCustomSqlStatement stmt) {
149        TCustomSqlStatement routine = stmt == null ? null : routineOf(stmt);
150        if (routine == null) {
151            return null;
152        }
153        Procedure procedure = manager.getRoutineModel(routine);
154        if (procedure != null) {
155            return scopeOf(manager, procedure);
156        }
157        Object model = manager.getModel(routine);
158        if (!(model instanceof OraclePackage) && routine instanceof TPlsqlCreatePackage) {
159            // A package body is not bound to the package model its specification created; the package index is.
160            RoutineAliasKey[] keys = ModelFactory.packageAliasKeys((TStoredProcedureSqlStatement) routine);
161            model = keys == null ? null : manager.getOraclePackage(keys[0]);
162        }
163        return model instanceof OraclePackage ? ((OraclePackage) model).getAliasKey() : null;
164    }
165}