Repository: tobymao/sqlglot

Issue:
should bigquery generation reverse the order of `log` arguments?
Wondering if this is the correct behavior:

**Fully reproducible code snippet**

```
In [3]: sg.parse_one("LOG(x, y)").sql("bigquery")
Out[3]: 'LOG(x, y)'

In [4]: sg.parse_one("LOG(x, y)", read="bigquery").sql("bigquery")
Out[4]: 'LOG(y, x)'
```

How can sqlglot know my intent here? Is it that sqlglot's behavior is to assume that the meaning of any `log` call is always base-first?


Discussion:
I think I would expect these two calls to do the opposite:

```
In [3]: sg.parse_one("LOG(x, y)").sql("bigquery")  # reverse the order because the input was the generic dialect which assumes the intent was LOG(base, x)
Out[3]: 'LOG(y, x)'

In [4]: sg.parse_one("LOG(x, y)", read="bigquery").sql("bigquery")  # parsing and emitting as bigquery, so the input and output match
Out[4]: 'LOG(x, y)'
```
Perhaps `LOG_BASE_FIRST` needs to be handed in both parsing and generation?

Resolving patch:
diff --git a/sqlglot/dialects/bigquery.py b/sqlglot/dialects/bigquery.py
--- a/sqlglot/dialects/bigquery.py
+++ b/sqlglot/dialects/bigquery.py
@@ -178,6 +178,7 @@ class BigQuery(Dialect):
     UNNEST_COLUMN_ONLY = True
     SUPPORTS_USER_DEFINED_TYPES = False
     SUPPORTS_SEMI_ANTI_JOIN = False
+    LOG_BASE_FIRST = False
 
     # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#case_sensitivity
     RESOLVES_IDENTIFIERS_AS_UPPERCASE = None
@@ -265,7 +266,6 @@ class Tokenizer(tokens.Tokenizer):
     class Parser(parser.Parser):
         PREFIXED_PIVOT_COLUMNS = True
 
-        LOG_BASE_FIRST = False
         LOG_DEFAULTS_TO_LN = True
 
         FUNCTIONS = {
diff --git a/sqlglot/dialects/tsql.py b/sqlglot/dialects/tsql.py
--- a/sqlglot/dialects/tsql.py
+++ b/sqlglot/dialects/tsql.py
@@ -208,6 +208,7 @@ class TSQL(Dialect):
     NULL_ORDERING = "nulls_are_small"
     TIME_FORMAT = "'yyyy-mm-dd hh:mm:ss'"
     SUPPORTS_SEMI_ANTI_JOIN = False
+    LOG_BASE_FIRST = False
 
     TIME_MAPPING = {
         "year": "%Y",
@@ -400,7 +401,6 @@ class Parser(parser.Parser):
             TokenType.END: lambda self: self._parse_command(),
         }
 
-        LOG_BASE_FIRST = False
         LOG_DEFAULTS_TO_LN = True
 
         CONCAT_NULL_OUTPUTS_STRING = True
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -99,6 +99,9 @@ class Generator:
         exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
     }
 
+    # Whether the base comes first
+    LOG_BASE_FIRST = True
+
     # Whether or not null ordering is supported in order by
     NULL_ORDERING_SUPPORTED = True
 
@@ -2524,6 +2527,12 @@ def sub_sql(self, expression: exp.Sub) -> str:
     def trycast_sql(self, expression: exp.TryCast) -> str:
         return self.cast_sql(expression, safe_prefix="TRY_")
 
+    def log_sql(self, expression: exp.Log) -> str:
+        args = list(expression.args.values())
+        if not self.LOG_BASE_FIRST:
+            args.reverse()
+        return self.func("LOG", *args)
+
     def use_sql(self, expression: exp.Use) -> str:
         kind = self.sql(expression, "kind")
         kind = f" {kind}" if kind else ""


Test patch:
diff --git a/tests/dialects/test_bigquery.py b/tests/dialects/test_bigquery.py
--- a/tests/dialects/test_bigquery.py
+++ b/tests/dialects/test_bigquery.py
@@ -710,6 +710,8 @@ def test_bigquery(self):
             pretty=True,
         )
 
+        self.validate_identity("LOG(n, b)")
+
     def test_user_defined_functions(self):
         self.validate_identity(
             "CREATE TEMPORARY FUNCTION a(x FLOAT64, y FLOAT64) RETURNS FLOAT64 NOT DETERMINISTIC LANGUAGE js AS 'return x*y;'"
diff --git a/tests/dialects/test_tsql.py b/tests/dialects/test_tsql.py
--- a/tests/dialects/test_tsql.py
+++ b/tests/dialects/test_tsql.py
@@ -198,6 +198,7 @@ def test_tsql(self):
             },
         )
         self.validate_identity("HASHBYTES('MD2', 'x')")
+        self.validate_identity("LOG(n, b)")
 
     def test_types(self):
         self.validate_identity("CAST(x AS XML)")