Repository: tobymao/sqlglot

Issue:
Issue with parsing columns with string literals in Trino dialect
Hi! I'm running into SQL parsing issues with simple string literals. Consider the following SQL -

```
sql_str = """
      SELECT '"abc"', ' "abc"'
      FROM table
"""

expression_tree = parse_one(sql_str, dialect="trino")
print(repr(expression_tree))
```

The output that we get is - 

```
Select(
  expressions=[
    Literal(this="abc", is_string=True),
    Literal(this="abc", is_string=True)],
  from=From(
    this=Table(
      this=Identifier(this=table, quoted=False))))
```

For both the columns, the parsed string is the same i.e. `"abc"`. 

**Official Documentation**
[Trino docs](https://trino.io/docs/current/index.html)

Discussion:
@georgesittas: This is a representation issue, the AST is indeed correct, i.e. whitespace _is_ preserved:

```python
>>> sqlglot.parse_one(sql_str, "trino").selects[1].name
' "abc"'
```

Solution patch:
diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py
index b08eb8a4..4a2531bd 100644
--- a/sqlglot/expressions.py
+++ b/sqlglot/expressions.py
@@ -7017,7 +7017,7 @@ def maybe_copy(instance, copy=True):
     return instance.copy() if copy and instance else instance
 
 
-def _to_s(node: t.Any, verbose: bool = False, level: int = 0) -> str:
+def _to_s(node: t.Any, verbose: bool = False, level: int = 0, repr_str: bool = False) -> str:
     """Generate a textual representation of an Expression tree"""
     indent = "\n" + ("  " * (level + 1))
     delim = f",{indent}"
@@ -7038,7 +7038,10 @@ def _to_s(node: t.Any, verbose: bool = False, level: int = 0) -> str:
             indent = ""
             delim = ", "
 
-        items = delim.join([f"{k}={_to_s(v, verbose, level + 1)}" for k, v in args.items()])
+        repr_str = node.is_string or (isinstance(node, Identifier) and node.quoted)
+        items = delim.join(
+            [f"{k}={_to_s(v, verbose, level + 1, repr_str=repr_str)}" for k, v in args.items()]
+        )
         return f"{node.__class__.__name__}({indent}{items})"
 
     if isinstance(node, list):
@@ -7046,6 +7049,10 @@ def _to_s(node: t.Any, verbose: bool = False, level: int = 0) -> str:
         items = f"{indent}{items}" if items else ""
         return f"[{items}]"
 
+    # We use the representation of the string to avoid stripping out important whitespace
+    if repr_str and isinstance(node, str):
+        node = repr(node)
+
     # Indent multiline strings to match the current level
     return indent.join(textwrap.dedent(str(node).strip("\n")).splitlines())
 
diff --git a/sqlglot/optimizer/pushdown_projections.py b/sqlglot/optimizer/pushdown_projections.py
index 5fa41393..c7831941 100644
--- a/sqlglot/optimizer/pushdown_projections.py
+++ b/sqlglot/optimizer/pushdown_projections.py
@@ -4,6 +4,7 @@ from sqlglot import alias, exp
 from sqlglot.optimizer.qualify_columns import Resolver
 from sqlglot.optimizer.scope import Scope, traverse_scope
 from sqlglot.schema import ensure_schema
+from sqlglot.errors import OptimizeError
 
 # Sentinel value that means an outer query selecting ALL columns
 SELECT_ALL = object()
@@ -49,6 +50,10 @@ def pushdown_projections(expression, schema=None, remove_unused_selections=True)
 
         if isinstance(scope.expression, exp.SetOperation):
             left, right = scope.union_scopes
+            if len(left.expression.selects) != len(right.expression.selects):
+                scope_sql = scope.expression.sql()
+                raise OptimizeError(f"Invalid set operation due to column mismatch: {scope_sql}.")
+
             referenced_columns[left] = parent_selections
 
             if any(select.is_star for select in right.expression.selects):


Test patch:
diff --git a/tests/test_expressions.py b/tests/test_expressions.py
index f357f85b..ec69d133 100644
--- a/tests/test_expressions.py
+++ b/tests/test_expressions.py
@@ -9,6 +9,19 @@ from sqlglot import ParseError, alias, exp, parse_one
 class TestExpressions(unittest.TestCase):
     maxDiff = None
 
+    def test_to_s(self):
+        self.assertEqual(repr(parse_one("5")), "Literal(this=5, is_string=False)")
+        self.assertEqual(repr(parse_one("5.3")), "Literal(this=5.3, is_string=False)")
+        self.assertEqual(repr(parse_one("True")), "Boolean(this=True)")
+        self.assertEqual(repr(parse_one("'  x'")), "Literal(this='  x', is_string=True)")
+        self.assertEqual(repr(parse_one("' \n  x'")), "Literal(this=' \\n  x', is_string=True)")
+        self.assertEqual(
+            repr(parse_one("   x ")), "Column(\n  this=Identifier(this=x, quoted=False))"
+        )
+        self.assertEqual(
+            repr(parse_one('"   x "')), "Column(\n  this=Identifier(this='   x ', quoted=True))"
+        )
+
     def test_arg_key(self):
         self.assertEqual(parse_one("sum(1)").find(exp.Literal).arg_key, "this")