Repository: tobymao/sqlglot

Issue:
Inconsistent formatting when leading_comma=True
Hi! This is something I noticed when trying out the [new formatting settings](https://github.com/TobikoData/sqlmesh/pull/2064) in SQLMesh, although I believe it's unrelated to that patch.  

```pycon
>>> import sqlglot
>>> print(sqlglot.__version__)
23.0.5
>>> sql = "select a, b, c from my_table"
>>> # first column has extra indentation
>>> print(sqlglot.parse_one(sql).sql(pretty=True, pad=4, indent=4, leading_comma=True))
SELECT
        a
    , b
    , c
FROM my_table
>>> # this is not the case when leading_comma=False
>>> print(sqlglot.parse_one(sql).sql(pretty=True, pad=4, indent=4, leading_comma=False))
SELECT
    a,
    b,
    c
FROM my_table
>>> # if pad=2, it formats consistently, but using 2 spaces rather than 4
>>> # (it also formats exactly the same with pad=2 and indent=2)
>>> print(sqlglot.parse_one(sql).sql(pretty=True, pad=2, indent=4, leading_comma=True))
SELECT
    a
  , b
  , c
```

There doesn't seem to be a combination of settings which 4-space-indents consistently with leading commas.
Am I misunderstanding something about how `pad` and `indent` interact?

Discussion:

Patch:
diff --git a/sqlglot/dialects/redshift.py b/sqlglot/dialects/redshift.py
index a41b6ea8..70066677 100644
--- a/sqlglot/dialects/redshift.py
+++ b/sqlglot/dialects/redshift.py
@@ -171,6 +171,8 @@ class Redshift(Postgres):
             ),
             exp.SortKeyProperty: lambda self,
             e: f"{'COMPOUND ' if e.args['compound'] else ''}SORTKEY({self.format_args(*e.this)})",
+            exp.StartsWith: lambda self,
+            e: f"{self.sql(e.this)} LIKE {self.sql(e.expression)} || '%'",
             exp.TableSample: no_tablesample_sql,
             exp.TsOrDsAdd: date_delta_sql("DATEADD"),
             exp.TsOrDsDiff: date_delta_sql("DATEDIFF"),
diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py
index 6c914f7d..da206544 100644
--- a/sqlglot/expressions.py
+++ b/sqlglot/expressions.py
@@ -6970,6 +6970,8 @@ def convert(value: t.Any, copy: bool = False) -> Expression:
         return null()
     if isinstance(value, numbers.Number):
         return Literal.number(value)
+    if isinstance(value, bytes):
+        return HexString(this=value.hex())
     if isinstance(value, datetime.datetime):
         datetime_literal = Literal.string(
             (value if value.tzinfo else value.replace(tzinfo=datetime.timezone.utc)).isoformat()
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
index 804df019..3186becf 100644
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -46,9 +46,11 @@ class Generator(metaclass=_Generator):
             'safe': Only quote identifiers that are case insensitive.
         normalize: Whether to normalize identifiers to lowercase.
             Default: False.
-        pad: The pad size in a formatted string.
+        pad: The pad size in a formatted string. For example, this affects the indentation of
+            a projection in a query, relative to its nesting level.
             Default: 2.
-        indent: The indentation size in a formatted string.
+        indent: The indentation size in a formatted string. For example, this affects the
+            indentation of subqueries and filters under a `WHERE` clause.
             Default: 2.
         normalize_functions: How to normalize function names. Possible values are:
             "upper" or True (default): Convert names to uppercase.
@@ -3221,7 +3223,7 @@ class Generator(metaclass=_Generator):
         num_sqls = len(expressions)
 
         # These are calculated once in case we have the leading_comma / pretty option set, correspondingly
-        pad = " " * self.pad
+        pad = " " * len(sep)
         stripped_sep = sep.strip()
 
         result_sqls = []


Test patch:
diff --git a/tests/dialects/test_redshift.py b/tests/dialects/test_redshift.py
index 896ee451..7affe31f 100644
--- a/tests/dialects/test_redshift.py
+++ b/tests/dialects/test_redshift.py
@@ -139,6 +139,15 @@ class TestRedshift(Validator):
                 "presto": "LENGTH(x)",
             },
         )
+        self.validate_all(
+            "x LIKE 'abc' || '%'",
+            read={
+                "duckdb": "STARTS_WITH(x, 'abc')",
+            },
+            write={
+                "redshift": "x LIKE 'abc' || '%'",
+            },
+        )
 
         self.validate_all(
             "SELECT SYSDATE",
diff --git a/tests/test_expressions.py b/tests/test_expressions.py
index 54bc223c..9365fc4e 100644
--- a/tests/test_expressions.py
+++ b/tests/test_expressions.py
@@ -802,6 +802,7 @@ class TestExpressions(unittest.TestCase):
             ),
             (datetime.date(2022, 10, 1), "DATE_STR_TO_DATE('2022-10-01')"),
             (math.nan, "NULL"),
+            (b"\x00\x00\x00\x00\x00\x00\x07\xd3", "2003"),
         ]:
             with self.subTest(value):
                 self.assertEqual(exp.convert(value).sql(), expected)
diff --git a/tests/test_transpile.py b/tests/test_transpile.py
index 0170e230..f6fd2f9a 100644
--- a/tests/test_transpile.py
+++ b/tests/test_transpile.py
@@ -66,6 +66,24 @@ class TestTranspile(unittest.TestCase):
         )
 
     def test_leading_comma(self):
+        self.validate(
+            "SELECT a, b, c FROM (SELECT a, b, c FROM t)",
+            "SELECT\n"
+            "      a\n"
+            "    , b\n"
+            "    , c\n"
+            "FROM (\n"
+            "    SELECT\n"
+            "          a\n"
+            "        , b\n"
+            "        , c\n"
+            "    FROM t\n"
+            ")",
+            leading_comma=True,
+            pretty=True,
+            pad=4,
+            indent=4,
+        )
         self.validate(
             "SELECT FOO, BAR, BAZ",
             "SELECT\n    FOO\n  , BAR\n  , BAZ",