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}