Spaces:
Running
fix: stop all-rows-fail on numeric column type mismatch (22018)
Browse filesRoot cause: Snowflake DESCRIBE TABLE returns FIXED(38,0) for INT columns
and REAL for FLOAT columns, but convert_value only checked for NUMBER/INT/FLOAT.
So string values (even valid ones like "0.035") passed through uncoerced to
Snowflake, which rejected them with 22018 on every row.
Three-part fix:
1. generator.py entity check: store float(entity_value) not the raw string,
and strip currency/unit chars before parsing. Add FIXED/REAL/DOUBLE/MONEY
to the is_numeric_col check.
2. legitdata_bridge.py convert_value: add FIXED/REAL/DOUBLE/MONEY to numeric
type check, and strip non-numeric chars from strings before float().
3. generic.py generate_key + generator.py _infer_strategy: add same types
so integer PKs and inferred strategies use numeric returns.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@@ -232,12 +232,18 @@ class KeyPairSnowflakeWriter:
|
|
| 232 |
if isinstance(value, str) and len(value) > max_len:
|
| 233 |
value = value[:max_len]
|
| 234 |
|
| 235 |
-
# Clamp numbers to fit DECIMAL precision
|
| 236 |
-
|
| 237 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
if isinstance(value, str):
|
|
|
|
|
|
|
| 239 |
try:
|
| 240 |
-
value = float(
|
| 241 |
except (ValueError, TypeError):
|
| 242 |
return None # Can't coerce — use NULL rather than crash
|
| 243 |
precision = info.get('precision', 38)
|
|
|
|
| 232 |
if isinstance(value, str) and len(value) > max_len:
|
| 233 |
value = value[:max_len]
|
| 234 |
|
| 235 |
+
# Clamp numbers to fit DECIMAL precision.
|
| 236 |
+
# Includes Snowflake's internal DESCRIBE TABLE names: FIXED (integers), REAL (floats).
|
| 237 |
+
elif any(t in data_type for t in (
|
| 238 |
+
'NUMBER', 'DECIMAL', 'NUMERIC', 'INT', 'FLOAT',
|
| 239 |
+
'FIXED', 'REAL', 'DOUBLE', 'MONEY',
|
| 240 |
+
)):
|
| 241 |
+
# If a string landed in a numeric column, strip formatting and coerce
|
| 242 |
if isinstance(value, str):
|
| 243 |
+
import re as _re
|
| 244 |
+
cleaned = _re.sub(r'[^\d.\-+eE]', '', value.replace(',', ''))
|
| 245 |
try:
|
| 246 |
+
value = float(cleaned) if cleaned else None
|
| 247 |
except (ValueError, TypeError):
|
| 248 |
return None # Can't coerce — use NULL rather than crash
|
| 249 |
precision = info.get('precision', 38)
|
|
@@ -935,16 +935,21 @@ class LegitGenerator:
|
|
| 935 |
entity_value = entity[col_name]
|
| 936 |
# Check if column is numeric but AI gave us text
|
| 937 |
data_type_upper = (column.data_type or '').upper()
|
| 938 |
-
is_numeric_col = any(t in data_type_upper for t in (
|
| 939 |
-
|
|
|
|
|
|
|
|
|
|
| 940 |
if is_numeric_col:
|
| 941 |
-
#
|
|
|
|
|
|
|
|
|
|
| 942 |
try:
|
| 943 |
-
float(
|
| 944 |
-
row[col_name] = entity_value
|
| 945 |
continue
|
| 946 |
except (ValueError, TypeError):
|
| 947 |
-
# AI gave us text
|
| 948 |
if i == 0:
|
| 949 |
print(f" [WARN] {col_name}: AI gave '{entity_value}' for numeric column, using inferred instead")
|
| 950 |
pass # Fall through to generic generation below
|
|
@@ -1649,8 +1654,8 @@ class LegitGenerator:
|
|
| 1649 |
if semantic_strategy:
|
| 1650 |
return semantic_strategy
|
| 1651 |
|
| 1652 |
-
# Check if data type is numeric
|
| 1653 |
-
is_numeric = any(t in data_type for t in ('INT', 'NUMBER', 'NUMERIC', 'DECIMAL', 'BIGINT', 'SMALLINT'))
|
| 1654 |
|
| 1655 |
# Key columns (CustomerKey, ProductKey, etc.)
|
| 1656 |
if 'key' in col_lower and 'keyboard' not in col_lower:
|
|
|
|
| 935 |
entity_value = entity[col_name]
|
| 936 |
# Check if column is numeric but AI gave us text
|
| 937 |
data_type_upper = (column.data_type or '').upper()
|
| 938 |
+
is_numeric_col = any(t in data_type_upper for t in (
|
| 939 |
+
'INT', 'NUMBER', 'NUMERIC', 'DECIMAL', 'BIGINT', 'SMALLINT',
|
| 940 |
+
'FLOAT', 'REAL', 'DOUBLE', 'FIXED', 'MONEY',
|
| 941 |
+
))
|
| 942 |
+
|
| 943 |
if is_numeric_col:
|
| 944 |
+
# Coerce to float and store as Python numeric, not string.
|
| 945 |
+
# Strip currency symbols / units before trying.
|
| 946 |
+
import re as _re
|
| 947 |
+
cleaned = _re.sub(r'[^\d.\-+eE]', '', str(entity_value).replace(',', ''))
|
| 948 |
try:
|
| 949 |
+
row[col_name] = float(cleaned)
|
|
|
|
| 950 |
continue
|
| 951 |
except (ValueError, TypeError):
|
| 952 |
+
# AI gave us unparseable text — fall through to generic
|
| 953 |
if i == 0:
|
| 954 |
print(f" [WARN] {col_name}: AI gave '{entity_value}' for numeric column, using inferred instead")
|
| 955 |
pass # Fall through to generic generation below
|
|
|
|
| 1654 |
if semantic_strategy:
|
| 1655 |
return semantic_strategy
|
| 1656 |
|
| 1657 |
+
# Check if data type is numeric (includes Snowflake internal names FIXED/REAL)
|
| 1658 |
+
is_numeric = any(t in data_type for t in ('INT', 'NUMBER', 'NUMERIC', 'DECIMAL', 'BIGINT', 'SMALLINT', 'FLOAT', 'REAL', 'DOUBLE', 'FIXED', 'MONEY'))
|
| 1659 |
|
| 1660 |
# Key columns (CustomerKey, ProductKey, etc.)
|
| 1661 |
if 'key' in col_lower and 'keyboard' not in col_lower:
|
|
@@ -189,7 +189,7 @@ class GenericSourcer:
|
|
| 189 |
# Check if column expects a numeric type
|
| 190 |
if data_type:
|
| 191 |
data_type_upper = data_type.upper()
|
| 192 |
-
if any(t in data_type_upper for t in ('INT', 'NUMBER', 'NUMERIC', 'DECIMAL', 'BIGINT', 'SMALLINT')):
|
| 193 |
return self._key_counters[prefix]
|
| 194 |
|
| 195 |
return f"{prefix}_{self._key_counters[prefix]:05d}"
|
|
|
|
| 189 |
# Check if column expects a numeric type
|
| 190 |
if data_type:
|
| 191 |
data_type_upper = data_type.upper()
|
| 192 |
+
if any(t in data_type_upper for t in ('INT', 'NUMBER', 'NUMERIC', 'DECIMAL', 'BIGINT', 'SMALLINT', 'FIXED', 'REAL', 'DOUBLE')):
|
| 193 |
return self._key_counters[prefix]
|
| 194 |
|
| 195 |
return f"{prefix}_{self._key_counters[prefix]:05d}"
|