001package gudusoft.gsqlparser.dlineage.routine;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.EParameterMode;
005import gudusoft.gsqlparser.catalog.runtime.CatalogLookupName;
006import gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind;
007import gudusoft.gsqlparser.nodes.TParameterDeclaration;
008import gudusoft.gsqlparser.nodes.TParameterDeclarationList;
009import gudusoft.gsqlparser.sqlenv.CanonKey;
010import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType;
011import gudusoft.gsqlparser.sqlenv.IdentifierInputForm;
012import gudusoft.gsqlparser.sqlenv.IdentifierService;
013
014import java.util.ArrayList;
015import java.util.Arrays;
016import java.util.Collections;
017import java.util.List;
018import java.util.Objects;
019
020/**
021 * One declared routine: its name as written (one interpretation), its kind, its parameters, and the scope it
022 * is declared in when it is local to another routine.
023 *
024 * <p>Its {@link #identity() identity} is the routine it declares: the name's keys, the namespace it lives in,
025 * and the parameters as far as the vendor tells routines of one name apart by them ({@link Signature}). Two
026 * declarations with one identity are one routine declared twice ({@code CREATE OR REPLACE}, a package
027 * specification and its body); where the vendor overloads, a same-named declaration with other parameters is
028 * another routine. Parameter counts are only lookup aliases: a call supplies arguments, not types, and matches
029 * a declaration when the number of arguments lies between its required inputs and its inputs, or equals its
030 * parameter count.</p>
031 */
032public final class RoutineDeclaration {
033
034    /** How much of its parameter list a vendor identifies a routine by. */
035    public enum Signature {
036        /** The name alone: the vendor does not overload (SQL Server, MySQL, BigQuery, HANA, ...), or a standalone
037         *  Oracle routine, which CREATE OR REPLACE replaces whatever its parameters. */
038        NAME_ONLY,
039        /** The number of parameters (DB2 procedures). */
040        COUNT,
041        /** The types of the input parameters (PostgreSQL and its family: OUT parameters are not part of it). */
042        INPUT_TYPES,
043        /** The types of all parameters (DB2 functions, Snowflake, Teradata functions). */
044        TYPES,
045        /** Kind, parameter names, modes and types: an Oracle package member or local routine, matched between a
046         *  specification and its body as Oracle matches it. */
047        NAMED_TYPES
048    }
049
050    /** The signature a vendor identifies a routine of this kind by. */
051    public static Signature signatureOf(EDbVendor vendor, CatalogObjectKind kind, boolean memberOrLocal) {
052        switch (vendor) {
053        case dbvoracle:
054        case dbvdameng:
055            return memberOrLocal ? Signature.NAMED_TYPES : Signature.NAME_ONLY;
056        case dbvpostgresql:
057        case dbvgreenplum:
058        case dbvedb:
059        case dbvgaussdb:
060        case dbvredshift:
061            return Signature.INPUT_TYPES;
062        case dbvdb2:
063            return kind == CatalogObjectKind.PROCEDURE ? Signature.COUNT : Signature.TYPES;
064        case dbvsnowflake:
065            return Signature.TYPES;
066        case dbvteradata:
067            return kind == CatalogObjectKind.FUNCTION ? Signature.TYPES : Signature.NAME_ONLY;
068        default:
069            return Signature.NAME_ONLY;
070        }
071    }
072
073    /** Whether the vendor tells routines of this kind apart by their parameters (their identity is not the name alone). */
074    public static boolean identityOverloads(EDbVendor vendor, CatalogObjectKind kind, boolean memberOrLocal) {
075        return signatureOf(vendor, kind, memberOrLocal) != Signature.NAME_ONLY;
076    }
077
078    /**
079     * Whether declarations of this kind are keyed with their argument counts, so a call is looked up by its
080     * argument count: where the vendor overloads routines of the kind, and wherever the legacy registry keyed them
081     * so (standalone Oracle routines, MySQL, HANA). A count alias on a routine alone under its name finds nothing it
082     * should not; the identity stays the name alone.
083     */
084    public static boolean usesCountAliases(EDbVendor vendor, CatalogObjectKind kind, boolean memberOrLocal) {
085        return identityOverloads(vendor, kind, memberOrLocal)
086                || gudusoft.gsqlparser.dlineage.util.DlineageUtil.supportFunctionOverride(vendor);
087    }
088
089    /** Whether a call site keys its lookup with the argument count: some routine of the vendor uses count aliases. */
090    public static boolean callsCounted(EDbVendor vendor) {
091        return usesCountAliases(vendor, CatalogObjectKind.FUNCTION, true)
092                || usesCountAliases(vendor, CatalogObjectKind.PROCEDURE, true);
093    }
094
095    /** The namespace a kind lives in: one for functions and procedures (as today), except where the vendor
096     *  keeps them apart (DB2); triggers apart everywhere. */
097    static CatalogObjectKind namespaceOf(EDbVendor vendor, CatalogObjectKind kind) {
098        if (kind == CatalogObjectKind.TRIGGER) {
099            return CatalogObjectKind.TRIGGER;
100        }
101        if (vendor == EDbVendor.dbvdb2 && (kind == CatalogObjectKind.FUNCTION || kind == CatalogObjectKind.PROCEDURE)) {
102            return kind;
103        }
104        return CatalogObjectKind.ROUTINE;
105    }
106
107    private final Object origin;
108    private Object representative;
109    private final RoutineName name;
110    private final CatalogObjectKind namespace;
111    private final List<ArgumentTypeKey> types;
112    private final int totalCount;
113    private final int inputCount;
114    private final int requiredCount;
115    private final boolean unboundedInputs;
116    private final boolean signatureAliases;
117    private final Identity identity;
118
119    RoutineDeclaration(Object origin, RoutineName name, CatalogObjectKind namespace, List<ArgumentTypeKey> types,
120            int totalCount, int inputCount, int requiredCount, boolean unboundedInputs, boolean signatureAliases,
121            Identity identity) {
122        this.origin = origin;
123        this.representative = origin;
124        this.name = name;
125        this.namespace = namespace;
126        this.types = types == null ? null : Collections.unmodifiableList(types);
127        this.totalCount = totalCount;
128        this.inputCount = inputCount;
129        this.requiredCount = requiredCount;
130        this.unboundedInputs = unboundedInputs;
131        this.signatureAliases = signatureAliases;
132        this.identity = identity;
133    }
134
135    /**
136     * A declaration from its AST: {@code scope} the routine it is local to (null for a top-level or package
137     * routine). A statement with no parameter list ({@code parameters} null: Oracle {@code CREATE PROCEDURE p IS},
138     * DB2 {@code CREATE FUNCTION f ()}) declares a routine with no parameters.
139     */
140    public static RoutineDeclaration of(Object origin, RoutineName name, TParameterDeclarationList parameters,
141            Object scope, EDbVendor vendor, IdentifierService service) {
142        boolean memberOrLocal = name.isPackageMember() || scope != null;
143        int size = parameters == null ? 0 : parameters.size();
144        List<ArgumentTypeKey> types = new ArrayList<ArgumentTypeKey>(size);
145        List<EParameterMode> modes = new ArrayList<EParameterMode>(size);
146        List<Object> parameterNames = new ArrayList<Object>(size);
147        List<ArgumentTypeKey> inputTypes = new ArrayList<ArgumentTypeKey>(size);
148        int total = size;
149        int inputs = 0;
150        int required = 0;
151        boolean unbounded = false;
152        if (parameters != null) {
153            for (int i = 0; i < parameters.size(); i++) {
154                TParameterDeclaration parameter = parameters.getParameterDeclarationItem(i);
155                ArgumentTypeKey type = ArgumentTypeKey.of(parameter.getDataType(), vendor, service);
156                EParameterMode mode = parameter.getParameterMode();
157                types.add(type);
158                modes.add(mode);
159                parameterNames.add(parameterKey(parameter, service));
160                if (mode != EParameterMode.out && mode != EParameterMode.output) {
161                    // IN, INOUT, VARIADIC, or no mode written (IN).
162                    inputs++;
163                    inputTypes.add(type);
164                    if (mode == EParameterMode.variadic) {
165                        // Any number of arguments, none included.
166                        unbounded = true;
167                    } else if (parameter.getDefaultValue() == null) {
168                        required++;
169                    }
170                }
171            }
172        }
173        Signature signature = signatureOf(vendor, name.getKind(), memberOrLocal);
174        Object key;
175        switch (signature) {
176        case COUNT:
177            key = Integer.valueOf(total);
178            break;
179        case INPUT_TYPES:
180            key = inputTypes;
181            break;
182        case TYPES:
183            key = types;
184            break;
185        case NAMED_TYPES:
186            key = Arrays.asList(name.getKind(), types, modes, parameterNames);
187            break;
188        default:
189            key = null;
190        }
191        CatalogObjectKind namespace = namespaceOf(vendor, name.getKind());
192        return new RoutineDeclaration(origin, name, namespace, types, total, inputs, required, unbounded,
193                usesCountAliases(vendor, name.getKind(), memberOrLocal),
194                new Identity(name, namespace, scope, key));
195    }
196
197    /** {@link #of(Object, RoutineName, TParameterDeclarationList, Object, EDbVendor, IdentifierService)} for a
198     *  top-level routine; {@code model} is not kept (the typed registry holds declarations, not legacy models). */
199    public static RoutineDeclaration of(Object model, Object origin, RoutineName name, TParameterDeclarationList parameters,
200            EDbVendor vendor, IdentifierService service) {
201        return of(origin, name, parameters, null, vendor, service);
202    }
203
204    private static Object parameterKey(TParameterDeclaration parameter, IdentifierService service) {
205        if (parameter.getParameterName() == null) {
206            return "";
207        }
208        String text = parameter.getParameterName().toString();
209        try {
210            return service.canonKeyOf(text, ESQLDataObjectType.dotParameter, IdentifierInputForm.SQL_LEXICAL);
211        } catch (IllegalArgumentException notKeyable) {
212            return text;
213        }
214    }
215
216    /** A routine known only from a call or an EXEC of a module never defined here: a name and a count. */
217    public static RoutineDeclaration ofCall(Object origin, RoutineName name, Integer argumentCount, EDbVendor vendor) {
218        int count = argumentCount == null ? 0 : argumentCount;
219        CatalogObjectKind namespace = namespaceOf(vendor, name.getKind());
220        return new RoutineDeclaration(origin, name, namespace, null, count, count, count, false, argumentCount != null,
221                new Identity(name, namespace, null, argumentCount));
222    }
223
224    /** The statement or node this occurrence was declared by. */
225    public Object getOrigin() {
226        return origin;
227    }
228
229    /** The first occurrence of this declaration's identity: the one a later occurrence replaced keeps its place. */
230    public Object getRepresentative() {
231        return representative;
232    }
233
234    void setRepresentative(Object representative) {
235        this.representative = representative;
236    }
237
238    public RoutineName getName() {
239        return name;
240    }
241
242    public CatalogObjectKind getNamespace() {
243        return namespace;
244    }
245
246    public List<ArgumentTypeKey> getTypes() {
247        return types;
248    }
249
250    public int getTotalCount() {
251        return totalCount;
252    }
253
254    /** Whether the declaration is keyed with its signature (a lookup by name only reaches it by the fallback). */
255    public boolean hasSignatureAliases() {
256        return signatureAliases;
257    }
258
259    /**
260     * Whether a call with {@code count} arguments can be this routine: any count for a routine keyed by its name
261     * alone, else its parameter count, or a count from its required inputs up to its inputs (no upper bound with a
262     * VARIADIC parameter).
263     */
264    public boolean acceptsCount(int count) {
265        return !signatureAliases || count == totalCount
266                || (count >= requiredCount && (unboundedInputs || count <= inputCount));
267    }
268
269    public Identity identity() {
270        return identity;
271    }
272
273    /** The identity of a declared routine. */
274    public static final class Identity {
275        private final List<Object> containers;
276        private final CanonKey terminal;
277        private final CatalogObjectKind namespace;
278        private final Object scope;
279        private final Object signature;
280
281        Identity(RoutineName name, CatalogObjectKind namespace, Object scope, Object signature) {
282            List<Object> keys = new ArrayList<Object>();
283            for (RoutineName.Level level : name.getContainers()) {
284                keys.add(level.getState() == CatalogLookupName.SlotState.PRESENT ? level.getKey()
285                        : level.getRole() + ":" + level.getState());
286            }
287            this.containers = keys;
288            this.namespace = namespace;
289            this.terminal = name.terminalKey(name.getKind());
290            this.scope = scope;
291            this.signature = signature;
292        }
293
294        @Override
295        public boolean equals(Object o) {
296            if (this == o) {
297                return true;
298            }
299            if (!(o instanceof Identity)) {
300                return false;
301            }
302            Identity other = (Identity) o;
303            return namespace == other.namespace && scope == other.scope && containers.equals(other.containers)
304                    && terminal.equals(other.terminal) && Objects.equals(signature, other.signature);
305        }
306
307        @Override
308        public int hashCode() {
309            return Objects.hash(namespace, System.identityHashCode(scope), containers, terminal, signature);
310        }
311    }
312
313    /** The scope a declaration is local to, if any: visible only to lookups inside it. */
314    Object scope() {
315        return identity.scope;
316    }
317
318    @Override
319    public String toString() {
320        return name + (types == null ? "" : String.valueOf(types));
321    }
322}