Skip to content

Commit 563f533

Browse files
feat(snowflake)!: Transpilation support for TO_DECIMAL, TO_NUMBER, TO_NUMERIC
1 parent dffaf52 commit 563f533

File tree

1 file changed

+57
-99
lines changed

1 file changed

+57
-99
lines changed

sqlglot/dialects/duckdb.py

Lines changed: 57 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -4182,78 +4182,42 @@ def strtok_sql(self, expression: exp.Strtok) -> str:
41824182
return self.function_fallback_sql(expression)
41834183

41844184
def tonumber_sql(self, expression: exp.ToNumber) -> str:
4185-
# TO_NUMBER(expr) -> CAST(expr AS BIGINT)
4186-
# TO_NUMBER(expr, precision, scale) -> CAST(expr AS DECIMAL(precision, scale))
4187-
# TO_NUMBER(expr, format, precision, scale) -> CAST(REGEXP_REPLACE(expr, ...) AS DECIMAL(precision, scale))
4188-
41894185
this = expression.this
41904186
format_arg = expression.args.get("format")
41914187
precision_arg = expression.args.get("precision")
41924188
scale_arg = expression.args.get("scale")
41934189

4194-
# Determine if format_arg is actually a format string or precision
4195-
actual_precision = None
4196-
actual_scale = None
4197-
format_string = None
4198-
4199-
if format_arg:
4200-
if format_arg.is_string:
4201-
# It's a format string
4202-
format_string = format_arg.this # Get the string value
4203-
actual_precision = precision_arg
4204-
actual_scale = scale_arg
4205-
else:
4206-
# It's numeric, so it's actually the precision
4207-
actual_precision = format_arg
4208-
actual_scale = precision_arg
4190+
# Parse arguments: format_arg could be format string or precision
4191+
if format_arg and format_arg.is_string:
4192+
format_string = format_arg.this
4193+
precision, scale = precision_arg, scale_arg
4194+
else:
4195+
format_string = None
4196+
precision, scale = format_arg, precision_arg
42094197

4210-
# Process format string to build preprocessing expression
4211-
if format_string:
4212-
# Determine what characters need to be stripped based on format
4213-
chars_to_remove = []
4214-
4215-
# Check for common format patterns
4216-
format_lower = format_string.lower()
4217-
4218-
# Comma separator (9,999.99 or 999,999)
4219-
if "," in format_string:
4220-
chars_to_remove.append(",")
4221-
4222-
# Currency symbols ($, £, €, etc.)
4223-
if "$" in format_string:
4224-
chars_to_remove.append("$")
4225-
if "£" in format_string:
4226-
chars_to_remove.append("£")
4227-
if "€" in format_string:
4228-
chars_to_remove.append("€")
4229-
if "¥" in format_string:
4230-
chars_to_remove.append("¥")
4231-
4232-
# Hexadecimal format (XXX or xxx)
4233-
if format_lower in ("xxx", "xxxx"):
4234-
# Hexadecimal conversion: ('0x' || input)::UBIGINT::BIGINT
4235-
hex_expr = exp.Cast(
4236-
this=exp.Cast(
4237-
this=exp.Concat(expressions=[exp.Literal.string("0x"), this]),
4238-
to=exp.DataType(this=exp.DataType.Type.UBIGINT),
4198+
# Handle hexadecimal format
4199+
if format_string and format_string.lower() in ("xxx", "xxxx"):
4200+
return self.sql(
4201+
exp.cast(
4202+
exp.cast(
4203+
exp.Concat(expressions=[exp.Literal.string("0x"), this]),
4204+
exp.DataType.Type.UBIGINT,
42394205
),
4240-
to=exp.DataType(this=exp.DataType.Type.BIGINT),
4206+
exp.DataType.Type.BIGINT,
4207+
)
4208+
)
4209+
4210+
# Strip formatting characters based on format string
4211+
if format_string:
4212+
chars_to_remove = {",": ",", "$": "$", "£": "£", "€": "€", "¥": "¥"}
4213+
chars = [c for symbol, c in chars_to_remove.items() if symbol in format_string]
4214+
4215+
if chars:
4216+
pattern = (
4217+
chars[0]
4218+
if len(chars) == 1
4219+
else f"[{''.join(c if c not in '.^$*+?{}[]\\|()' else f'\\{c}' for c in chars)}]"
42414220
)
4242-
return self.sql(hex_expr)
4243-
4244-
# Build REGEXP_REPLACE to strip formatting characters
4245-
if chars_to_remove:
4246-
# Build pattern: [$,] for multiple characters
4247-
if len(chars_to_remove) == 1:
4248-
pattern = chars_to_remove[0]
4249-
else:
4250-
# Escape special regex characters
4251-
escaped = [
4252-
c if c not in ".^$*+?{}[]\\|()" else f"\\{c}" for c in chars_to_remove
4253-
]
4254-
pattern = "[" + "".join(escaped) + "]"
4255-
4256-
# REGEXP_REPLACE(input, pattern, '', 'g')
42574221
this = exp.RegexpReplace(
42584222
this=this,
42594223
expression=exp.Literal.string(pattern),
@@ -4262,44 +4226,38 @@ def tonumber_sql(self, expression: exp.ToNumber) -> str:
42624226
)
42634227

42644228
# Determine target type
4265-
if actual_precision is None:
4266-
# No precision/scale -> BIGINT (matches NUMBER(38, 0))
4267-
target_type = exp.DataType(this=exp.DataType.Type.BIGINT)
4268-
else:
4269-
# Get precision value
4270-
if isinstance(actual_precision, exp.Literal):
4271-
prec_val = int(actual_precision.to_py())
4272-
else:
4273-
# Dynamic precision not supported
4274-
self.unsupported(
4275-
"TO_NUMBER with non-literal precision is not supported. "
4276-
"Using DOUBLE instead of DECIMAL."
4277-
)
4278-
return self.sql(exp.cast(this, exp.DataType.Type.DOUBLE))
4279-
4280-
# Get scale value (default to 0)
4281-
if actual_scale is not None:
4282-
if isinstance(actual_scale, exp.Literal):
4283-
scale_val = int(actual_scale.to_py())
4284-
else:
4285-
self.unsupported(
4286-
"TO_NUMBER with non-literal scale is not supported. "
4287-
"Using DOUBLE instead of DECIMAL."
4288-
)
4289-
return self.sql(exp.cast(this, exp.DataType.Type.DOUBLE))
4290-
else:
4291-
scale_val = 0
4229+
if precision is None:
4230+
return self.sql(exp.cast(this, exp.DataType.Type.BIGINT))
42924231

4293-
# Build DECIMAL(precision, scale) type
4294-
target_type = exp.DataType(
4295-
this=exp.DataType.Type.DECIMAL,
4296-
expressions=[
4297-
exp.DataTypeParam(this=exp.Literal.number(prec_val)),
4298-
exp.DataTypeParam(this=exp.Literal.number(scale_val)),
4299-
],
4232+
# Extract precision and scale values
4233+
if not isinstance(precision, exp.Literal):
4234+
self.unsupported(
4235+
"TO_NUMBER with non-literal precision is not supported. Using DOUBLE instead of DECIMAL."
43004236
)
4237+
return self.sql(exp.cast(this, exp.DataType.Type.DOUBLE))
4238+
4239+
prec_val = int(precision.to_py())
43014240

4302-
return self.sql(exp.Cast(this=this, to=target_type))
4241+
if scale and not isinstance(scale, exp.Literal):
4242+
self.unsupported(
4243+
"TO_NUMBER with non-literal scale is not supported. Using DOUBLE instead of DECIMAL."
4244+
)
4245+
return self.sql(exp.cast(this, exp.DataType.Type.DOUBLE))
4246+
4247+
scale_val = int(scale.to_py()) if scale else 0
4248+
4249+
return self.sql(
4250+
exp.cast(
4251+
this,
4252+
exp.DataType(
4253+
this=exp.DataType.Type.DECIMAL,
4254+
expressions=[
4255+
exp.DataTypeParam(this=exp.Literal.number(prec_val)),
4256+
exp.DataTypeParam(this=exp.Literal.number(scale_val)),
4257+
],
4258+
),
4259+
)
4260+
)
43034261

43044262
def approxquantile_sql(self, expression: exp.ApproxQuantile) -> str:
43054263
result = self.func("APPROX_QUANTILE", expression.this, expression.args.get("quantile"))

0 commit comments

Comments
 (0)