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 }
022
023    private final Kind kind;
024    private final String text;
025    private final Double number;
026
027    private SqlValue(Kind kind, String text, Double number) {
028        this.kind = kind;
029        this.text = text;
030        this.number = number;
031    }
032
033    /** A string binding, e.g. {@code @SourceDB = 'QSP'}. */
034    public static SqlValue of(String value) {
035        if (value == null) {
036            return nullValue();
037        }
038        return new SqlValue(Kind.STRING, value, null);
039    }
040
041    /** An integer binding, e.g. {@code @Debug = 0}. Used for decidable IF and concatenation. */
042    public static SqlValue of(long value) {
043        return new SqlValue(Kind.NUMBER, Long.toString(value), (double) value);
044    }
045
046    /** A floating-point binding. */
047    public static SqlValue of(double value) {
048        return new SqlValue(Kind.NUMBER, formatNumber(value), value);
049    }
050
051    /** A boolean binding, mapped to T-SQL BIT semantics (1/0). */
052    public static SqlValue of(boolean value) {
053        return of(value ? 1L : 0L);
054    }
055
056    /** An explicit SQL NULL binding. */
057    public static SqlValue nullValue() {
058        return new SqlValue(Kind.NULL, null, null);
059    }
060
061    public boolean isNull() {
062        return kind == Kind.NULL;
063    }
064
065    public boolean isNumber() {
066        return kind == Kind.NUMBER;
067    }
068
069    /** The string form of the value (null only when {@link #isNull()}). */
070    public String asText() {
071        return text;
072    }
073
074    /** The numeric value, or null when this is not a number binding. */
075    public Double asNumber() {
076        return number;
077    }
078
079    static String formatNumber(double value) {
080        if (value == Math.rint(value) && !Double.isInfinite(value)) {
081            return Long.toString((long) value);
082        }
083        return Double.toString(value);
084    }
085
086    @Override
087    public String toString() {
088        switch (kind) {
089            case NULL:
090                return "NULL";
091            case NUMBER:
092                return text;
093            default:
094                return "'" + text + "'";
095        }
096    }
097}