001package gudusoft.gsqlparser.dlineage.dynamicsql;
002
003/**
004 * A concrete parameter binding value supplied by the caller of
005 * {@code DataFlowAnalyzer.resolveDynamicSqlLineage}.
006 *
007 * <p>This is the <em>input</em> side of dynamic-SQL resolution: the caller knows
008 * the value a parameter actually had at a call site (e.g. {@code @SourceDB='QSP'})
009 * and passes it in. It is intentionally simple — a string, a number, or SQL
010 * {@code NULL}. The evaluator widens these into the internal
011 * {@link SqlStringValue} lattice (which also models <em>partial</em> and
012 * <em>unknown</em> states that a caller can never supply).
013 *
014 * <p>NULL is represented explicitly (not as a Java {@code null} binding) so the
015 * evaluator can honor T-SQL NULL-propagation semantics
016 * ({@code VARCHAR + NULL -> NULL}). A Java {@code null} key/value in the bindings
017 * map is treated as "no binding for this parameter".
018 */
019public final class SqlValue {
020
021    private enum Kind { STRING, NUMBER, NULL, SUPPLIED_UNKNOWN }
022
023    private final Kind kind;
024    private final String text;
025    private final Double number;
026    private final String unknownReason;
027
028    private SqlValue(Kind kind, String text, Double number) {
029        this(kind, text, number, null);
030    }
031
032    private SqlValue(Kind kind, String text, Double number, String unknownReason) {
033        this.kind = kind;
034        this.text = text;
035        this.number = number;
036        this.unknownReason = unknownReason;
037    }
038
039    /**
040     * A value a caller passes that nobody knows (Mantis 4962): the parameter is bound, so its declared default does
041     * not apply, but its value is unknown and the evaluation keeps it as a placeholder with {@code reason}.
042     */
043    public static SqlValue suppliedUnknown(String reason) {
044        return new SqlValue(Kind.SUPPLIED_UNKNOWN, null, null, reason == null ? "a value that is not known" : reason);
045    }
046
047    /** True for {@link #suppliedUnknown}. */
048    public boolean isSuppliedUnknown() {
049        return kind == Kind.SUPPLIED_UNKNOWN;
050    }
051
052    /** Why a {@link #suppliedUnknown} value is not known; null for any other value. */
053    public String unknownReason() {
054        return unknownReason;
055    }
056
057    /** A string binding, e.g. {@code @SourceDB = 'QSP'}. */
058    public static SqlValue of(String value) {
059        if (value == null) {
060            return nullValue();
061        }
062        return new SqlValue(Kind.STRING, value, null);
063    }
064
065    /** An integer binding, e.g. {@code @Debug = 0}. Used for decidable IF and concatenation. */
066    public static SqlValue of(long value) {
067        return new SqlValue(Kind.NUMBER, Long.toString(value), (double) value);
068    }
069
070    /** A floating-point binding. */
071    public static SqlValue of(double value) {
072        return new SqlValue(Kind.NUMBER, formatNumber(value), value);
073    }
074
075    /**
076     * An exact number, its text kept as written (Mantis 4962): {@code 100000000000000000000} or a DECIMAL with many
077     * digits, which a double would round. The double it also carries is only for comparisons that read one.
078     */
079    public static SqlValue of(java.math.BigDecimal value) {
080        if (value == null) {
081            return nullValue();
082        }
083        return new SqlValue(Kind.NUMBER, value.toPlainString(), value.doubleValue());
084    }
085
086    /** A boolean binding, mapped to T-SQL BIT semantics (1/0). */
087    public static SqlValue of(boolean value) {
088        return of(value ? 1L : 0L);
089    }
090
091    /** An explicit SQL NULL binding. */
092    public static SqlValue nullValue() {
093        return new SqlValue(Kind.NULL, null, null);
094    }
095
096    /** Raw text of this value (null for SQL NULL); package-visible for binding tooling. */
097    String text() {
098        return text;
099    }
100
101    public boolean isNull() {
102        return kind == Kind.NULL;
103    }
104
105    public boolean isNumber() {
106        return kind == Kind.NUMBER;
107    }
108
109    /** The string form of the value (null only when {@link #isNull()}). */
110    public String asText() {
111        return text;
112    }
113
114    /** The numeric value, or null when this is not a number binding. */
115    public Double asNumber() {
116        return number;
117    }
118
119    static String formatNumber(double value) {
120        if (value == Math.rint(value) && !Double.isInfinite(value)) {
121            return Long.toString((long) value);
122        }
123        return Double.toString(value);
124    }
125
126    @Override
127    public String toString() {
128        switch (kind) {
129            case NULL:
130                return "NULL";
131            case NUMBER:
132                return text;
133            case SUPPLIED_UNKNOWN:
134                return "(supplied, not known)";
135            default:
136                return "'" + text + "'";
137        }
138    }
139}