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    /** Raw text of this value (null for SQL NULL); package-visible for binding tooling. */
062    String text() {
063        return text;
064    }
065
066    public boolean isNull() {
067        return kind == Kind.NULL;
068    }
069
070    public boolean isNumber() {
071        return kind == Kind.NUMBER;
072    }
073
074    /** The string form of the value (null only when {@link #isNull()}). */
075    public String asText() {
076        return text;
077    }
078
079    /** The numeric value, or null when this is not a number binding. */
080    public Double asNumber() {
081        return number;
082    }
083
084    static String formatNumber(double value) {
085        if (value == Math.rint(value) && !Double.isInfinite(value)) {
086            return Long.toString((long) value);
087        }
088        return Double.toString(value);
089    }
090
091    @Override
092    public String toString() {
093        switch (kind) {
094            case NULL:
095                return "NULL";
096            case NUMBER:
097                return text;
098            default:
099                return "'" + text + "'";
100        }
101    }
102}