@@ -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