Repository: tobymao/sqlglot

Issue:
parse raises exception trying to parse Snowflake trim function with second argument
## Issue
Attempting to parse a query that includes the `trim` function with a second argument using the Snowflake dialect raises a ParseError.

## Expected Behavior
No exception should be raised, the trim function should be parsed regardless if it has one or two arguments, when using the Snowflake dialect. Snowflake documentation of trim function [here](https://docs.snowflake.com/en/sql-reference/functions/trim.html). 

## Example query

```sql
SELECT number_column,
        trim(date_column, 'UTC')
FROM schema.table_name
```

## Test and traceback

```
In [6]: sqlglot.parse("""SELECT number_column,
   ...: trim(date_column, 'UTC')
   ...: FROM schema.table_name""","snowflake")
---------------------------------------------------------------------------
ParseError                                Traceback (most recent call last)
Input In [6], in <cell line: 1>()
----> 1 sqlglot.parse("""SELECT number_column,
      2 trim(date_column, 'UTC')
      3 FROM schema.table_name""","snowflake")

File ~/.local/lib/python3.8/site-packages/sqlglot/__init__.py:55, in parse(sql, read, **opts)
     43 """
     44 Parses the given SQL string into a collection of syntax trees, one per parsed SQL statement.
     45
   (...)
     52     The resulting syntax tree collection.
     53 """
     54 dialect = Dialect.get_or_raise(read)()
---> 55 return dialect.parse(sql, **opts)

File ~/.local/lib/python3.8/site-packages/sqlglot/dialects/dialect.py:149, in Dialect.parse(self, sql, **opts)
    148 def parse(self, sql, **opts):
--> 149     return self.parser(**opts).parse(self.tokenizer.tokenize(sql), sql)

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:591, in Parser.parse(self, raw_tokens, sql)
    579 def parse(self, raw_tokens, sql=None):
    580     """
    581     Parses the given list of tokens and returns a list of syntax trees, one tree
    582     per parsed SQL statement.
   (...)
    589         the list of syntax trees (:class:`~sqlglot.expressions.Expression`).
    590     """
--> 591     return self._parse(
    592         parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql
    593     )

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:624, in Parser._parse(self, parse_method, raw_tokens, sql)
    622 self._tokens = tokens
    623 self._advance()
--> 624 expressions.append(parse_method(self))
    626 if self._index < len(self._tokens):
    627     self.raise_error("Invalid expression / Unexpected token")

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:725, in Parser._parse_statement(self)
    718     return self.expression(
    719         exp.Command,
    720         this=self._prev.text,
    721         expression=self._parse_string(),
    722     )
    724 expression = self._parse_expression()
--> 725 expression = self._parse_set_operations(expression) if expression else self._parse_select()
    727 self._parse_query_modifiers(expression)
    728 return expression

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:1108, in Parser._parse_select(self, nested, table)
   1105     self.raise_error("Cannot specify both ALL and DISTINCT after SELECT")
   1107 limit = self._parse_limit(top=True)
-> 1108 expressions = self._parse_csv(self._parse_expression)
   1110 this = self.expression(
   1111     exp.Select,
   1112     hint=hint,
   (...)
   1115     limit=limit,
   1116 )
   1117 this.comment = comment

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:2570, in Parser._parse_csv(self, parse_method)
   2567 if parse_result and self._prev_comment is not None:
   2568     parse_result.comment = self._prev_comment
-> 2570 parse_result = parse_method()
   2571 if parse_result is not None:
   2572     items.append(parse_result)

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:1646, in Parser._parse_expression(self)
   1645 def _parse_expression(self):
-> 1646     return self._parse_alias(self._parse_conjunction())

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:1649, in Parser._parse_conjunction(self)
   1648 def _parse_conjunction(self):
-> 1649     return self._parse_tokens(self._parse_equality, self.CONJUNCTION)

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:2577, in Parser._parse_tokens(self, parse_method, expressions)
   2576 def _parse_tokens(self, parse_method, expressions):
-> 2577     this = parse_method()
   2579     while self._match_set(expressions):
   2580         this = self.expression(
   2581             expressions[self._prev.token_type], this=this, expression=parse_method()
   2582         )

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:1652, in Parser._parse_equality(self)
   1651 def _parse_equality(self):
-> 1652     return self._parse_tokens(self._parse_comparison, self.EQUALITY)

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:2577, in Parser._parse_tokens(self, parse_method, expressions)
   2576 def _parse_tokens(self, parse_method, expressions):
-> 2577     this = parse_method()
   2579     while self._match_set(expressions):
   2580         this = self.expression(
   2581             expressions[self._prev.token_type], this=this, expression=parse_method()
   2582         )

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:1655, in Parser._parse_comparison(self)
   1654 def _parse_comparison(self):
-> 1655     return self._parse_tokens(self._parse_range, self.COMPARISON)

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:2577, in Parser._parse_tokens(self, parse_method, expressions)
   2576 def _parse_tokens(self, parse_method, expressions):
-> 2577     this = parse_method()
   2579     while self._match_set(expressions):
   2580         this = self.expression(
   2581             expressions[self._prev.token_type], this=this, expression=parse_method()
   2582         )

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:1658, in Parser._parse_range(self)
   1657 def _parse_range(self):
-> 1658     this = self._parse_bitwise()
   1659     negate = self._match(TokenType.NOT)
   1661     if self._match_set(self.RANGE_PARSERS):

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:1719, in Parser._parse_bitwise(self)
   1718 def _parse_bitwise(self):
-> 1719     this = self._parse_term()
   1721     while True:
   1722         if self._match_set(self.BITWISE):

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:1742, in Parser._parse_term(self)
   1741 def _parse_term(self):
-> 1742     return self._parse_tokens(self._parse_factor, self.TERM)

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:2577, in Parser._parse_tokens(self, parse_method, expressions)
   2576 def _parse_tokens(self, parse_method, expressions):
-> 2577     this = parse_method()
   2579     while self._match_set(expressions):
   2580         this = self.expression(
   2581             expressions[self._prev.token_type], this=this, expression=parse_method()
   2582         )

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:1745, in Parser._parse_factor(self)
   1744 def _parse_factor(self):
-> 1745     return self._parse_tokens(self._parse_unary, self.FACTOR)

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:2577, in Parser._parse_tokens(self, parse_method, expressions)
   2576 def _parse_tokens(self, parse_method, expressions):
-> 2577     this = parse_method()
   2579     while self._match_set(expressions):
   2580         this = self.expression(
   2581             expressions[self._prev.token_type], this=this, expression=parse_method()
   2582         )

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:1754, in Parser._parse_unary(self)
   1752 if self._match(TokenType.DASH):
   1753     return self.expression(exp.Neg, this=self._parse_unary())
-> 1754 return self._parse_at_time_zone(self._parse_type())

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:1766, in Parser._parse_type(self)
   1764 index = self._index
   1765 type_token = self._parse_types(check_func=True)
-> 1766 this = self._parse_column()
   1768 if type_token:
   1769     if this:

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:1887, in Parser._parse_column(self)
   1886 def _parse_column(self):
-> 1887     this = self._parse_field()
   1888     if isinstance(this, exp.Identifier):
   1889         this = self.expression(exp.Column, this=this)

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:1958, in Parser._parse_field(self, any_token)
   1957 def _parse_field(self, any_token=False):
-> 1958     return self._parse_primary() or self._parse_function() or self._parse_id_var(any_token)

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:2007, in Parser._parse_function(self, functions)
   2004     else:
   2005         this = self.expression(exp.Anonymous, this=this, expressions=args)
-> 2007 self._match_r_paren(this)
   2008 return self._parse_window(this)

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:2709, in Parser._match_r_paren(self, expression)
   2707 def _match_r_paren(self, expression=None):
   2708     if not self._match(TokenType.R_PAREN):
-> 2709         self.raise_error("Expecting )")
   2710     if expression and self._prev_comment:
   2711         expression.comment = self._prev_comment

File ~/.local/lib/python3.8/site-packages/sqlglot/parser.py:652, in Parser.raise_error(self, message, token)
    647 error = ParseError(
    648     f"{message}. Line {token.line}, Col: {token.col}.\n"
    649     f"  {start_context}\033[4m{highlight}\033[0m{end_context}"
    650 )
    651 if self.error_level == ErrorLevel.IMMEDIATE:
--> 652     raise error
    653 self.errors.append(error)

ParseError: Expecting ). Line 2, Col: 17.
  SELECT number_column,
trim(date_column, 'UTC')
FROM schema.table_name
```


Discussion:
Thanks for the great report, I'll take a look.

Resolving patch:
diff --git a/sqlglot/dialects/snowflake.py b/sqlglot/dialects/snowflake.py
--- a/sqlglot/dialects/snowflake.py
+++ b/sqlglot/dialects/snowflake.py
@@ -148,6 +148,7 @@ class Parser(parser.Parser):
             **parser.Parser.FUNCTION_PARSERS,
             "DATE_PART": _parse_date_part,
         }
+        FUNCTION_PARSERS.pop("TRIM")
 
         FUNC_TOKENS = {
             *parser.Parser.FUNC_TOKENS,
@@ -203,6 +204,7 @@ class Generator(generator.Generator):
             exp.StrPosition: rename_func("POSITION"),
             exp.Parameter: lambda self, e: f"${self.sql(e, 'this')}",
             exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'value')}",
+            exp.Trim: lambda self, e: f"TRIM({self.format_args(e.this, e.expression)})",
         }
 
         TYPE_MAPPING = {
diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py
--- a/sqlglot/expressions.py
+++ b/sqlglot/expressions.py
@@ -2821,8 +2821,8 @@ class TimeStrToUnix(Func):
 class Trim(Func):
     arg_types = {
         "this": True,
-        "position": False,
         "expression": False,
+        "position": False,
         "collation": False,
     }
 


Test patch:
diff --git a/tests/dialects/test_snowflake.py b/tests/dialects/test_snowflake.py
--- a/tests/dialects/test_snowflake.py
+++ b/tests/dialects/test_snowflake.py
@@ -169,6 +169,17 @@ def test_snowflake(self):
                 "snowflake": "SELECT a FROM test AS unpivot",
             },
         )
+        self.validate_all(
+            "trim(date_column, 'UTC')",
+            write={
+                "snowflake": "TRIM(date_column, 'UTC')",
+                "postgres": "TRIM('UTC' FROM date_column)",
+            },
+        )
+        self.validate_all(
+            "trim(date_column)",
+            write={"snowflake": "TRIM(date_column)"},
+        )
 
     def test_null_treatment(self):
         self.validate_all(