001package gudusoft.gsqlparser.dlineage.dynamicsql;
002
003/**
004 * Where the parameter values a materialized dynamic SQL site was evaluated with came from (Mantis 4962): none (the
005 * procedure on its own, literal defaults only), the caller of the explicit API, a preset in the catalog, or a call
006 * of the procedure in its own file or in another unit of a multi-file analysis. When the bindings cover every call of
007 * the procedure in the analysis, a site is published with the bound values only: the unbound evaluation is then what
008 * a caller nobody wrote would see.
009 */
010public final class BindingSource {
011
012    public enum Kind { EXPLICIT, PRESET, SAME_FILE, CROSS_FILE, NONE }
013
014    /** The procedure evaluated on its own. */
015    public static final BindingSource NONE = new BindingSource(Kind.NONE, null, -1L, null, null);
016
017    private final Kind kind;
018    private final String callerUnit;
019    private final long callerLine;
020    private final String bindingHash;
021    private final String description;
022
023    private BindingSource(Kind kind, String callerUnit, long callerLine, String bindingHash, String description) {
024        this.kind = kind;
025        this.callerUnit = callerUnit;
026        this.callerLine = callerLine;
027        this.bindingHash = bindingHash;
028        this.description = description;
029    }
030
031    /** Bindings the caller of the explicit API supplied. */
032    public static BindingSource explicit(String bindingHash) {
033        return new BindingSource(Kind.EXPLICIT, null, -1L, bindingHash, "parameters bound by the caller");
034    }
035
036    /** Values preset in the catalog for the parameters of {@code routine} ({@code TSQLEnv}, metadata JSON). */
037    public static BindingSource preset(String routine, String bindingHash) {
038        return new BindingSource(Kind.PRESET, null, -1L, bindingHash,
039                "parameters preset in the catalog for " + routine);
040    }
041
042    /** Bindings a call of the procedure in its own file supplied. */
043    public static BindingSource sameFile(long callerLine, String bindingHash) {
044        return new BindingSource(Kind.SAME_FILE, null, callerLine, bindingHash,
045                "parameters bound by the call in the same file at line " + callerLine);
046    }
047
048    /** Bindings a call of the procedure in another unit of the analysis supplied. */
049    public static BindingSource crossFile(String callerUnit, long callerLine, String bindingHash) {
050        return new BindingSource(Kind.CROSS_FILE, callerUnit, callerLine, bindingHash,
051                "parameters bound by the call in " + callerUnit + " at line " + callerLine);
052    }
053
054    /**
055     * {@link #sameFile(long, String)} for a call in the body of {@code routine} (null: in a batch), which may not run
056     * on the path its values come from when {@code mayNotRun} (Mantis 4962).
057     */
058    public static BindingSource sameFile(long callerLine, String bindingHash, String routine, boolean mayNotRun) {
059        return new BindingSource(Kind.SAME_FILE, null, callerLine, bindingHash,
060                "parameters bound by the call in the same file at line " + callerLine + where(routine, mayNotRun));
061    }
062
063    /** {@link #crossFile(String, long, String)} for a call in the body of {@code routine}, or one that may not run. */
064    public static BindingSource crossFile(String callerUnit, long callerLine, String bindingHash, String routine,
065            boolean mayNotRun) {
066        return new BindingSource(Kind.CROSS_FILE, callerUnit, callerLine, bindingHash,
067                "parameters bound by the call in " + callerUnit + " at line " + callerLine + where(routine, mayNotRun));
068    }
069
070    private static String where(String routine, boolean mayNotRun) {
071        return (routine == null ? "" : ", in " + routine) + (mayNotRun ? " (the call may not run)" : "");
072    }
073
074    /**
075     * This binding, also supplied by {@code other} (two calls with the same values): the same values, a description
076     * that names both.
077     */
078    public BindingSource alsoAt(BindingSource other) {
079        if (other == null || other.description == null || other == this) {
080            return this;
081        }
082        if (description == null) {
083            return new BindingSource(kind, callerUnit, callerLine, bindingHash, other.description);
084        }
085        java.util.List<String> named = new java.util.ArrayList<String>(java.util.Arrays.asList(description.split("; ")));
086        for (String part : other.description.split("; ")) {
087            if (!named.contains(part)) {
088                named.add(part);
089            }
090        }
091        String combined = String.join("; ", named);
092        return combined.equals(description) ? this
093                : new BindingSource(kind, callerUnit, callerLine, bindingHash, combined);
094    }
095
096    public Kind getKind() {
097        return kind;
098    }
099
100    /** The analysis unit of the call, for {@link Kind#CROSS_FILE}; null otherwise. */
101    public String getCallerUnit() {
102        return callerUnit;
103    }
104
105    /** The line of the call, for {@link Kind#SAME_FILE} and {@link Kind#CROSS_FILE}; -1 otherwise. */
106    public long getCallerLine() {
107        return callerLine;
108    }
109
110    /** A digest of the bound values; null for {@link Kind#NONE}. */
111    public String getBindingHash() {
112        return bindingHash;
113    }
114
115    /** What a site's diagnostic says of the binding; null for {@link Kind#NONE}. */
116    public String getDescription() {
117        return description;
118    }
119
120    @Override
121    public String toString() {
122        return description == null ? "no binding" : description;
123    }
124}