Repository: tobymao/sqlglot (2022-09-16 ~ 2022-10-06)

problem_statement: [Postgres] Parser does not support string concatenation operator inside substring function
Example (valid sql):
```
select substring(2022::char(4) || lpad(3::char(2), 2, '0') from 3 for 4)
```
Result:
```
ParseError: Expecting ). Line 1, Col: 31.
  select substring(2022::char(4)||lpad(3::char(2), 2, '0') from 3 for 4)

Expected table name. Line 1, Col: 63.
  select substring(2022::char(4)||lpad(3::char(2), 2, '0') from 3 for 4)

Required keyword: 'this' missing for <class 'sqlglot.expressions.Table'>. Line 1, Col: 63.
  select substring(2022::char(4)||lpad(3::char(2), 2, '0') from 3 for 4)
```

And it seems like https://github.com/tobymao/sqlglot/issues/421 is still an issue here (I'm using sqlglot 6.0.7).
date: 2022-09-16
patch: diff --git a/sqlglot/parser.py b/sqlglot/parser.py
index 2df99526a..63e408979 100644
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -1936,12 +1936,12 @@ class Parser:
         # Postgres supports the form: substring(string [from int] [for int])
         # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6
 
-        args = self._parse_csv(self._parse_term)
+        args = self._parse_csv(self._parse_bitwise)
 
         if self._match(TokenType.FROM):
-            args.append(self._parse_term())
+            args.append(self._parse_bitwise())
             if self._match(TokenType.FOR):
-                args.append(self._parse_term())
+                args.append(self._parse_bitwise())
 
         this = exp.Substring.from_arg_list(args)
         self.validate_expression(this, args)

test_patch: diff --git a/tests/dialects/test_dialect.py b/tests/dialects/test_dialect.py
index aa49843b78..d31d0f6cde 100644
--- a/tests/dialects/test_dialect.py
+++ b/tests/dialects/test_dialect.py
@@ -13,9 +13,6 @@ from sqlglot import (
 class Validator(unittest.TestCase):
     dialect = None
 
-    def validate(self, sql, target, **kwargs):
-        self.assertEqual(transpile(sql, **kwargs)[0], target)
-
     def validate_identity(self, sql):
         self.assertEqual(transpile(sql, read=self.dialect, write=self.dialect)[0], sql)
 
@@ -36,9 +33,7 @@ class Validator(unittest.TestCase):
         for read_dialect, read_sql in (read or {}).items():
             with self.subTest(f"{read_dialect} -> {sql}"):
                 self.assertEqual(
-                    parse_one(read_sql, read_dialect).sql(
-                        self.dialect, unsupported_level=ErrorLevel.IGNORE
-                    ),
+                    parse_one(read_sql, read_dialect).sql(self.dialect, unsupported_level=ErrorLevel.IGNORE),
                     sql,
                 )
 
@@ -46,9 +41,7 @@ class Validator(unittest.TestCase):
             with self.subTest(f"{sql} -> {write_dialect}"):
                 if write_sql is UnsupportedError:
                     with self.assertRaises(UnsupportedError):
-                        expression.sql(
-                            write_dialect, unsupported_level=ErrorLevel.RAISE
-                        )
+                        expression.sql(write_dialect, unsupported_level=ErrorLevel.RAISE)
                 else:
                     self.assertEqual(
                         expression.sql(
@@ -82,11 +75,37 @@ class TestDialect(Validator):
                 "oracle": "CAST(a AS CLOB)",
                 "postgres": "CAST(a AS TEXT)",
                 "presto": "CAST(a AS VARCHAR)",
+                "redshift": "CAST(a AS TEXT)",
                 "snowflake": "CAST(a AS TEXT)",
                 "spark": "CAST(a AS STRING)",
                 "starrocks": "CAST(a AS STRING)",
             },
         )
+        self.validate_all(
+            "CAST(MAP('a', '1') AS MAP(TEXT, TEXT))",
+            write={
+                "clickhouse": "CAST(map('a', '1') AS Map(TEXT, TEXT))",
+            },
+        )
+        self.validate_all(
+            "CAST(ARRAY(1, 2) AS ARRAY<TINYINT>)",
+            write={
+                "clickhouse": "CAST([1, 2] AS Array(TINYINT))",
+            },
+        )
+        self.validate_all(
+            "CAST((1, 2) AS STRUCT<a: TINYINT, b: TINYINT>)",
+            write={
+                "clickhouse": "CAST((1, 2) AS Tuple(a TINYINT, b TINYINT))",
+            },
+        )
+        self.validate_all(
+            "CAST(a AS DATETIME)",
+            write={
+                "postgres": "CAST(a AS TIMESTAMP)",
+                "sqlite": "CAST(a AS DATETIME)",
+            },
+        )
         self.validate_all(
             "CAST(a AS STRING)",
             write={
@@ -97,6 +116,7 @@ class TestDialect(Validator):
                 "oracle": "CAST(a AS CLOB)",
                 "postgres": "CAST(a AS TEXT)",
                 "presto": "CAST(a AS VARCHAR)",
+                "redshift": "CAST(a AS TEXT)",
                 "snowflake": "CAST(a AS TEXT)",
                 "spark": "CAST(a AS STRING)",
                 "starrocks": "CAST(a AS STRING)",
@@ -112,6 +132,7 @@ class TestDialect(Validator):
                 "oracle": "CAST(a AS VARCHAR2)",
                 "postgres": "CAST(a AS VARCHAR)",
                 "presto": "CAST(a AS VARCHAR)",
+                "redshift": "CAST(a AS VARCHAR)",
                 "snowflake": "CAST(a AS VARCHAR)",
                 "spark": "CAST(a AS STRING)",
                 "starrocks": "CAST(a AS VARCHAR)",
@@ -127,6 +148,7 @@ class TestDialect(Validator):
                 "oracle": "CAST(a AS VARCHAR2(3))",
                 "postgres": "CAST(a AS VARCHAR(3))",
                 "presto": "CAST(a AS VARCHAR(3))",
+                "redshift": "CAST(a AS VARCHAR(3))",
                 "snowflake": "CAST(a AS VARCHAR(3))",
                 "spark": "CAST(a AS VARCHAR(3))",
                 "starrocks": "CAST(a AS VARCHAR(3))",
@@ -142,12 +164,26 @@ class TestDialect(Validator):
                 "oracle": "CAST(a AS NUMBER)",
                 "postgres": "CAST(a AS SMALLINT)",
                 "presto": "CAST(a AS SMALLINT)",
+                "redshift": "CAST(a AS SMALLINT)",
                 "snowflake": "CAST(a AS SMALLINT)",
                 "spark": "CAST(a AS SHORT)",
                 "sqlite": "CAST(a AS INTEGER)",
                 "starrocks": "CAST(a AS SMALLINT)",
             },
         )
+        self.validate_all(
+            "TRY_CAST(a AS DOUBLE)",
+            read={
+                "postgres": "CAST(a AS DOUBLE PRECISION)",
+                "redshift": "CAST(a AS DOUBLE PRECISION)",
+            },
+            write={
+                "duckdb": "TRY_CAST(a AS DOUBLE)",
+                "postgres": "CAST(a AS DOUBLE PRECISION)",
+                "redshift": "CAST(a AS DOUBLE PRECISION)",
+            },
+        )
+
         self.validate_all(
             "CAST(a AS DOUBLE)",
             write={
@@ -159,6 +195,7 @@ class TestDialect(Validator):
                 "oracle": "CAST(a AS DOUBLE PRECISION)",
                 "postgres": "CAST(a AS DOUBLE PRECISION)",
                 "presto": "CAST(a AS DOUBLE)",
+                "redshift": "CAST(a AS DOUBLE PRECISION)",
                 "snowflake": "CAST(a AS DOUBLE)",
                 "spark": "CAST(a AS DOUBLE)",
                 "starrocks": "CAST(a AS DOUBLE)",
@@ -168,13 +205,22 @@ class TestDialect(Validator):
             "CAST('1 DAY' AS INTERVAL)",
             write={
                 "postgres": "CAST('1 DAY' AS INTERVAL)",
+                "redshift": "CAST('1 DAY' AS INTERVAL)",
             },
         )
         self.validate_all(
-            "CAST(a AS TIMESTAMP)", write={"starrocks": "CAST(a AS DATETIME)"}
+            "CAST(a AS TIMESTAMP)",
+            write={
+                "starrocks": "CAST(a AS DATETIME)",
+                "redshift": "CAST(a AS TIMESTAMP)",
+            },
         )
         self.validate_all(
-            "CAST(a AS TIMESTAMPTZ)", write={"starrocks": "CAST(a AS DATETIME)"}
+            "CAST(a AS TIMESTAMPTZ)",
+            write={
+                "starrocks": "CAST(a AS DATETIME)",
+                "redshift": "CAST(a AS TIMESTAMPTZ)",
+            },
         )
         self.validate_all("CAST(a AS TINYINT)", write={"oracle": "CAST(a AS NUMBER)"})
         self.validate_all("CAST(a AS SMALLINT)", write={"oracle": "CAST(a AS NUMBER)"})
@@ -197,6 +243,7 @@ class TestDialect(Validator):
                 "duckdb": "STRPTIME(x, '%Y-%m-%dT%H:%M:%S')",
                 "hive": "CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, 'yyyy-MM-ddTHH:mm:ss')) AS TIMESTAMP)",
                 "presto": "DATE_PARSE(x, '%Y-%m-%dT%H:%i:%S')",
+                "redshift": "TO_TIMESTAMP(x, 'YYYY-MM-DDTHH:MI:SS')",
                 "spark": "TO_TIMESTAMP(x, 'yyyy-MM-ddTHH:mm:ss')",
             },
         )
@@ -205,7 +252,10 @@ class TestDialect(Validator):
             write={
                 "duckdb": "STRPTIME('2020-01-01', '%Y-%m-%d')",
                 "hive": "CAST('2020-01-01' AS TIMESTAMP)",
+                "oracle": "TO_TIMESTAMP('2020-01-01', 'YYYY-MM-DD')",
+                "postgres": "TO_TIMESTAMP('2020-01-01', 'YYYY-MM-DD')",
                 "presto": "DATE_PARSE('2020-01-01', '%Y-%m-%d')",
+                "redshift": "TO_TIMESTAMP('2020-01-01', 'YYYY-MM-DD')",
                 "spark": "TO_TIMESTAMP('2020-01-01', 'yyyy-MM-dd')",
             },
         )
@@ -215,6 +265,9 @@ class TestDialect(Validator):
                 "duckdb": "STRPTIME(x, '%y')",
                 "hive": "CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, 'yy')) AS TIMESTAMP)",
                 "presto": "DATE_PARSE(x, '%y')",
+                "oracle": "TO_TIMESTAMP(x, 'YY')",
+                "postgres": "TO_TIMESTAMP(x, 'YY')",
+                "redshift": "TO_TIMESTAMP(x, 'YY')",
                 "spark": "TO_TIMESTAMP(x, 'yy')",
             },
         )
@@ -224,6 +277,7 @@ class TestDialect(Validator):
                 "duckdb": "EPOCH(STRPTIME('2020-01-01', '%Y-%M-%d'))",
                 "hive": "UNIX_TIMESTAMP('2020-01-01', 'yyyy-mm-dd')",
                 "presto": "TO_UNIXTIME(DATE_PARSE('2020-01-01', '%Y-%i-%d'))",
+                "starrocks": "UNIX_TIMESTAMP('2020-01-01', '%Y-%i-%d')",
             },
         )
         self.validate_all(
@@ -232,6 +286,7 @@ class TestDialect(Validator):
                 "duckdb": "CAST('2020-01-01' AS DATE)",
                 "hive": "TO_DATE('2020-01-01')",
                 "presto": "DATE_PARSE('2020-01-01', '%Y-%m-%d %H:%i:%s')",
+                "starrocks": "TO_DATE('2020-01-01')",
             },
         )
         self.validate_all(
@@ -255,7 +310,10 @@ class TestDialect(Validator):
             write={
                 "duckdb": "STRFTIME(x, '%Y-%m-%d')",
                 "hive": "DATE_FORMAT(x, 'yyyy-MM-dd')",
+                "oracle": "TO_CHAR(x, 'YYYY-MM-DD')",
+                "postgres": "TO_CHAR(x, 'YYYY-MM-DD')",
                 "presto": "DATE_FORMAT(x, '%Y-%m-%d')",
+                "redshift": "TO_CHAR(x, 'YYYY-MM-DD')",
             },
         )
         self.validate_all(
@@ -264,6 +322,7 @@ class TestDialect(Validator):
                 "duckdb": "CAST(x AS TEXT)",
                 "hive": "CAST(x AS STRING)",
                 "presto": "CAST(x AS VARCHAR)",
+                "redshift": "CAST(x AS TEXT)",
             },
         )
         self.validate_all(
@@ -305,6 +364,7 @@ class TestDialect(Validator):
                 "duckdb": "STRFTIME(TO_TIMESTAMP(CAST(x AS BIGINT)), y)",
                 "hive": "FROM_UNIXTIME(x, y)",
                 "presto": "DATE_FORMAT(FROM_UNIXTIME(x), y)",
+                "starrocks": "FROM_UNIXTIME(x, y)",
             },
         )
         self.validate_all(
@@ -312,7 +372,10 @@ class TestDialect(Validator):
             write={
                 "duckdb": "TO_TIMESTAMP(CAST(x AS BIGINT))",
                 "hive": "FROM_UNIXTIME(x)",
+                "oracle": "TO_DATE('1970-01-01','YYYY-MM-DD') + (x / 86400)",
+                "postgres": "TO_TIMESTAMP(x)",
                 "presto": "FROM_UNIXTIME(x)",
+                "starrocks": "FROM_UNIXTIME(x)",
             },
         )
         self.validate_all(
@@ -636,12 +699,8 @@ class TestDialect(Validator):
     def test_joined_tables(self):
         self.validate_identity("SELECT * FROM (tbl1 LEFT JOIN tbl2 ON 1 = 1)")
         self.validate_identity("SELECT * FROM (tbl1 JOIN tbl2 JOIN tbl3)")
-        self.validate_identity(
-            "SELECT * FROM (tbl1 JOIN (tbl2 JOIN tbl3) ON bla = foo)"
-        )
-        self.validate_identity(
-            "SELECT * FROM (tbl1 JOIN LATERAL (SELECT * FROM bla) AS tbl)"
-        )
+        self.validate_identity("SELECT * FROM (tbl1 JOIN (tbl2 JOIN tbl3) ON bla = foo)")
+        self.validate_identity("SELECT * FROM (tbl1 JOIN LATERAL (SELECT * FROM bla) AS tbl)")
 
         self.validate_all(
             "SELECT * FROM (tbl1 LEFT JOIN tbl2 ON 1 = 1)",
@@ -671,6 +730,7 @@ class TestDialect(Validator):
             "SELECT * FROM a UNION SELECT * FROM b",
             read={
                 "bigquery": "SELECT * FROM a UNION DISTINCT SELECT * FROM b",
+                "clickhouse": "SELECT * FROM a UNION DISTINCT SELECT * FROM b",
                 "duckdb": "SELECT * FROM a UNION SELECT * FROM b",
                 "presto": "SELECT * FROM a UNION SELECT * FROM b",
                 "spark": "SELECT * FROM a UNION SELECT * FROM b",
@@ -686,6 +746,7 @@ class TestDialect(Validator):
             "SELECT * FROM a UNION ALL SELECT * FROM b",
             read={
                 "bigquery": "SELECT * FROM a UNION ALL SELECT * FROM b",
+                "clickhouse": "SELECT * FROM a UNION ALL SELECT * FROM b",
                 "duckdb": "SELECT * FROM a UNION ALL SELECT * FROM b",
                 "presto": "SELECT * FROM a UNION ALL SELECT * FROM b",
                 "spark": "SELECT * FROM a UNION ALL SELECT * FROM b",
@@ -774,9 +835,7 @@ class TestDialect(Validator):
         )
 
     def test_operators(self):
-        self.validate_identity(
-            "some.column LIKE 'foo' || another.column || 'bar' || LOWER(x)"
-        )
+        self.validate_identity("some.column LIKE 'foo' || another.column || 'bar' || LOWER(x)")
         self.validate_identity("some.column LIKE 'foo' + another.column + 'bar'")
 
         self.validate_all(
@@ -811,11 +870,34 @@ class TestDialect(Validator):
             },
         )
         self.validate_all(
-            "STR_POSITION(x, 'a')",
+            "POSITION(' ' in x)",
+            write={
+                "duckdb": "STRPOS(x, ' ')",
+                "postgres": "STRPOS(x, ' ')",
+                "presto": "STRPOS(x, ' ')",
+                "spark": "LOCATE(' ', x)",
+                "clickhouse": "position(x, ' ')",
+                "snowflake": "POSITION(' ', x)",
+            },
+        )
+        self.validate_all(
+            "STR_POSITION('a', x)",
             write={
                 "duckdb": "STRPOS(x, 'a')",
+                "postgres": "STRPOS(x, 'a')",
                 "presto": "STRPOS(x, 'a')",
                 "spark": "LOCATE('a', x)",
+                "clickhouse": "position(x, 'a')",
+                "snowflake": "POSITION('a', x)",
+            },
+        )
+        self.validate_all(
+            "POSITION('a', x, 3)",
+            write={
+                "presto": "STRPOS(SUBSTR(x, 3), 'a') + 3 - 1",
+                "spark": "LOCATE('a', x, 3)",
+                "clickhouse": "position(x, 'a', 3)",
+                "snowflake": "POSITION('a', x, 3)",
             },
         )
         self.validate_all(
@@ -955,6 +1037,7 @@ class TestDialect(Validator):
             read={
                 "clickhouse": '`x` + "y"',
                 "sqlite": '`x` + "y"',
+                "redshift": '"x" + "y"',
             },
         )
         self.validate_all(
@@ -967,7 +1050,7 @@ class TestDialect(Validator):
         self.validate_all(
             "SELECT * FROM VALUES ('x'), ('y') AS t(z)",
             write={
-                "spark": "SELECT * FROM (VALUES ('x'), ('y')) AS t(z)",
+                "spark": "SELECT * FROM VALUES ('x'), ('y') AS t(z)",
             },
         )
         self.validate_all(
@@ -1017,6 +1100,7 @@ class TestDialect(Validator):
                 "oracle": "CREATE TABLE t (b1 BLOB, b2 BLOB(1024), c1 CLOB, c2 CLOB(1024))",
                 "postgres": "CREATE TABLE t (b1 BYTEA, b2 BYTEA(1024), c1 TEXT, c2 TEXT(1024))",
                 "sqlite": "CREATE TABLE t (b1 BLOB, b2 BLOB(1024), c1 TEXT, c2 TEXT(1024))",
+                "redshift": "CREATE TABLE t (b1 VARBYTE, b2 VARBYTE(1024), c1 TEXT, c2 TEXT(1024))",
             },
         )
 
diff --git a/tests/dialects/test_hive.py b/tests/dialects/test_hive.py
index eccd75af23..acb3be9a96 100644
--- a/tests/dialects/test_hive.py
+++ b/tests/dialects/test_hive.py
@@ -127,17 +127,17 @@ class TestHive(Validator):
 
     def test_ddl(self):
         self.validate_all(
-            "CREATE TABLE test STORED AS parquet TBLPROPERTIES ('x' = '1', 'Z' = '2') AS SELECT 1",
+            "CREATE TABLE test STORED AS parquet TBLPROPERTIES ('x'='1', 'Z'='2') AS SELECT 1",
             write={
-                "presto": "CREATE TABLE test WITH (FORMAT = 'parquet', x = '1', Z = '2') AS SELECT 1",
-                "hive": "CREATE TABLE test STORED AS PARQUET TBLPROPERTIES ('x' = '1', 'Z' = '2') AS SELECT 1",
-                "spark": "CREATE TABLE test STORED AS PARQUET TBLPROPERTIES ('x' = '1', 'Z' = '2') AS SELECT 1",
+                "presto": "CREATE TABLE test WITH (FORMAT='parquet', x='1', Z='2') AS SELECT 1",
+                "hive": "CREATE TABLE test STORED AS PARQUET TBLPROPERTIES ('x'='1', 'Z'='2') AS SELECT 1",
+                "spark": "CREATE TABLE test USING PARQUET TBLPROPERTIES ('x'='1', 'Z'='2') AS SELECT 1",
             },
         )
         self.validate_all(
             "CREATE TABLE x (w STRING) PARTITIONED BY (y INT, z INT)",
             write={
-                "presto": "CREATE TABLE x (w VARCHAR, y INTEGER, z INTEGER) WITH (PARTITIONED_BY = ARRAY['y', 'z'])",
+                "presto": "CREATE TABLE x (w VARCHAR, y INTEGER, z INTEGER) WITH (PARTITIONED_BY=ARRAY['y', 'z'])",
                 "hive": "CREATE TABLE x (w STRING) PARTITIONED BY (y INT, z INT)",
                 "spark": "CREATE TABLE x (w STRING) PARTITIONED BY (y INT, z INT)",
             },
@@ -247,7 +247,7 @@ class TestHive(Validator):
                 "presto": "DATE_DIFF('day', CAST(SUBSTR(CAST(b AS VARCHAR), 1, 10) AS DATE), CAST(SUBSTR(CAST(a AS VARCHAR), 1, 10) AS DATE))",
                 "hive": "DATEDIFF(TO_DATE(a), TO_DATE(b))",
                 "spark": "DATEDIFF(TO_DATE(a), TO_DATE(b))",
-                "": "DATE_DIFF(TS_OR_DS_TO_DATE(a), TS_OR_DS_TO_DATE(b))",
+                "": "DATEDIFF(TS_OR_DS_TO_DATE(a), TS_OR_DS_TO_DATE(b))",
             },
         )
         self.validate_all(
@@ -295,7 +295,7 @@ class TestHive(Validator):
                 "presto": "DATE_DIFF('day', CAST(SUBSTR(CAST(x AS VARCHAR), 1, 10) AS DATE), CAST(SUBSTR(CAST(CAST(SUBSTR(CAST(y AS VARCHAR), 1, 10) AS DATE) AS VARCHAR), 1, 10) AS DATE))",
                 "hive": "DATEDIFF(TO_DATE(TO_DATE(y)), TO_DATE(x))",
                 "spark": "DATEDIFF(TO_DATE(TO_DATE(y)), TO_DATE(x))",
-                "": "DATE_DIFF(TS_OR_DS_TO_DATE(TS_OR_DS_TO_DATE(y)), TS_OR_DS_TO_DATE(x))",
+                "": "DATEDIFF(TS_OR_DS_TO_DATE(TS_OR_DS_TO_DATE(y)), TS_OR_DS_TO_DATE(x))",
             },
         )
         self.validate_all(
@@ -341,6 +341,21 @@ class TestHive(Validator):
                 "spark": "PERCENTILE(x, 0.5)",
             },
         )
+        self.validate_all(
+            "PERCENTILE_APPROX(x, 0.5)",
+            read={
+                "hive": "PERCENTILE_APPROX(x, 0.5)",
+                "presto": "APPROX_PERCENTILE(x, 0.5)",
+                "duckdb": "APPROX_QUANTILE(x, 0.5)",
+                "spark": "PERCENTILE_APPROX(x, 0.5)",
+            },
+            write={
+                "hive": "PERCENTILE_APPROX(x, 0.5)",
+                "presto": "APPROX_PERCENTILE(x, 0.5)",
+                "duckdb": "APPROX_QUANTILE(x, 0.5)",
+                "spark": "PERCENTILE_APPROX(x, 0.5)",
+            },
+        )
         self.validate_all(
             "APPROX_COUNT_DISTINCT(a)",
             write={
@@ -435,11 +450,21 @@ class TestHive(Validator):
         )
         self.validate_all(
             "MAP(a, b, c, d)",
+            read={
+                "": "VAR_MAP(a, b, c, d)",
+                "clickhouse": "map(a, b, c, d)",
+                "duckdb": "MAP(LIST_VALUE(a, c), LIST_VALUE(b, d))",
+                "hive": "MAP(a, b, c, d)",
+                "presto": "MAP(ARRAY[a, c], ARRAY[b, d])",
+                "spark": "MAP(a, b, c, d)",
+            },
             write={
+                "": "MAP(ARRAY(a, c), ARRAY(b, d))",
+                "clickhouse": "map(a, b, c, d)",
                 "duckdb": "MAP(LIST_VALUE(a, c), LIST_VALUE(b, d))",
                 "presto": "MAP(ARRAY[a, c], ARRAY[b, d])",
                 "hive": "MAP(a, b, c, d)",
-                "spark": "MAP_FROM_ARRAYS(ARRAY(a, c), ARRAY(b, d))",
+                "spark": "MAP(a, b, c, d)",
             },
         )
         self.validate_all(
@@ -448,7 +473,7 @@ class TestHive(Validator):
                 "duckdb": "MAP(LIST_VALUE(a), LIST_VALUE(b))",
                 "presto": "MAP(ARRAY[a], ARRAY[b])",
                 "hive": "MAP(a, b)",
-                "spark": "MAP_FROM_ARRAYS(ARRAY(a), ARRAY(b))",
+                "spark": "MAP(a, b)",
             },
         )
         self.validate_all(
diff --git a/tests/dialects/test_postgres.py b/tests/dialects/test_postgres.py
index 9c4d4f0a61..db5fc2d1ff 100644
--- a/tests/dialects/test_postgres.py
+++ b/tests/dialects/test_postgres.py
@@ -72,6 +72,9 @@ class TestPostgres(Validator):
         self.validate_identity(
             "SELECT * FROM x WHERE SUBSTRING('Thomas' FROM '%#\"o_a#\"_' FOR '#') IN ('mas')"
         )
+        self.validate_identity(
+            "SELECT SUBSTRING('bla' + 'foo' || 'bar' FROM 3 - 1 + 5 FOR 4 + SOME_FUNC(arg1, arg2))"
+        )
         self.validate_identity("SELECT TRIM(' X' FROM ' XXX ')")
         self.validate_identity(
             "SELECT TRIM(LEADING 'bla' FROM ' XXX ' COLLATE utf8_bin)"
@@ -128,6 +131,12 @@ class TestPostgres(Validator):
                 "spark": "SELECT * FROM x WHERE SUBSTRING(col1, 3 + LENGTH(col1) - 10, 10) IN (col2)",
             },
         )
+        self.validate_all(
+            "SELECT SUBSTRING(CAST(2022 AS CHAR(4)) || LPAD(CAST(3 AS CHAR(2)), 2, '0') FROM 3 FOR 4)",
+            read={
+                "postgres": "SELECT SUBSTRING(2022::CHAR(4) || LPAD(3::CHAR(2), 2, '0') FROM 3 FOR 4)",
+            },
+        )
         self.validate_all(
             "SELECT TRIM(BOTH ' XXX ')",
             write={


----

problem_statement: Error when parsing position function call
```
In [6]: import sqlglot as sg

In [7]: print(sg.__version__)
6.2.10

In [8]: sg.parse_one("position('a_b_c', '_') > 0")

...

File /nix/store/9cfygcnsr7d5mw8a4dxrmf0xjfl0qnwl-python3-3.10.6-env/lib/python3.10/site-packages/sqlglot/parser.py:2033, in Parser._parse_position(self)
   2031 if self._match(TokenType.IN):
   2032     string = self._parse_bitwise()
-> 2033 return self.expression(exp.StrPosition, this=string, substr=substr)

UnboundLocalError: local variable 'string' referenced before assignment
```
date: 2022-10-06
patch: diff --git a/sqlglot/dialects/clickhouse.py b/sqlglot/dialects/clickhouse.py
index 55dad7ad9..6a141a1a0 100644
--- a/sqlglot/dialects/clickhouse.py
+++ b/sqlglot/dialects/clickhouse.py
@@ -1,10 +1,16 @@
 from sqlglot import exp
-from sqlglot.dialects.dialect import Dialect, inline_array_sql
+from sqlglot.dialects.dialect import Dialect, inline_array_sql, var_map_sql
 from sqlglot.generator import Generator
-from sqlglot.parser import Parser
+from sqlglot.helper import csv
+from sqlglot.parser import Parser, parse_var_map
 from sqlglot.tokens import Tokenizer, TokenType
 
 
+def _lower_func(sql):
+    index = sql.index("(")
+    return sql[:index].lower() + sql[index:]
+
+
 class ClickHouse(Dialect):
     normalize_functions = None
     null_ordering = "nulls_are_last"
@@ -16,6 +22,7 @@ class ClickHouse(Dialect):
             **Tokenizer.KEYWORDS,
             "NULLABLE": TokenType.NULLABLE,
             "FINAL": TokenType.FINAL,
+            "DATETIME64": TokenType.DATETIME,
             "INT8": TokenType.TINYINT,
             "INT16": TokenType.SMALLINT,
             "INT32": TokenType.INT,
@@ -25,6 +32,11 @@ class ClickHouse(Dialect):
         }
 
     class Parser(Parser):
+        FUNCTIONS = {
+            **Parser.FUNCTIONS,
+            "MAP": parse_var_map,
+        }
+
         def _parse_table(self, schema=False):
             this = super()._parse_table(schema)
 
@@ -39,10 +51,16 @@ class ClickHouse(Dialect):
         TYPE_MAPPING = {
             **Generator.TYPE_MAPPING,
             exp.DataType.Type.NULLABLE: "Nullable",
+            exp.DataType.Type.DATETIME: "DateTime64",
         }
 
         TRANSFORMS = {
             **Generator.TRANSFORMS,
             exp.Array: inline_array_sql,
+            exp.StrPosition: lambda self, e: f"position({csv(self.sql(e, 'this'), self.sql(e, 'substr'), self.sql(e, 'position'))})",
             exp.Final: lambda self, e: f"{self.sql(e, 'this')} FINAL",
+            exp.Map: lambda self, e: _lower_func(var_map_sql(self, e)),
+            exp.VarMap: lambda self, e: _lower_func(var_map_sql(self, e)),
         }
+
+        EXPLICIT_UNION = True
diff --git a/sqlglot/dialects/snowflake.py b/sqlglot/dialects/snowflake.py
index 148dfb595..770240af1 100644
--- a/sqlglot/dialects/snowflake.py
+++ b/sqlglot/dialects/snowflake.py
@@ -1,5 +1,10 @@
 from sqlglot import exp
-from sqlglot.dialects.dialect import Dialect, format_time_lambda, rename_func
+from sqlglot.dialects.dialect import (
+    Dialect,
+    format_time_lambda,
+    inline_array_sql,
+    rename_func,
+)
 from sqlglot.expressions import Literal
 from sqlglot.generator import Generator
 from sqlglot.helper import list_get
@@ -23,9 +28,7 @@ def _snowflake_to_timestamp(args):
 
         # case: <numeric_expr> [ , <scale> ]
         if second_arg.name not in ["0", "3", "9"]:
-            raise ValueError(
-                f"Scale for snowflake numeric timestamp is {second_arg}, but should be 0, 3, or 9"
-            )
+            raise ValueError(f"Scale for snowflake numeric timestamp is {second_arg}, but should be 0, 3, or 9")
 
         if second_arg.name == "0":
             timescale = exp.UnixToTime.SECONDS
@@ -106,6 +109,18 @@ class Snowflake(Dialect):
             "ARRAYAGG": exp.ArrayAgg.from_arg_list,
             "IFF": exp.If.from_arg_list,
             "TO_TIMESTAMP": _snowflake_to_timestamp,
+            "ARRAY_CONSTRUCT": exp.Array.from_arg_list,
+            "RLIKE": exp.RegexpLike.from_arg_list,
+        }
+
+        FUNCTION_PARSERS = {
+            **Parser.FUNCTION_PARSERS,
+            "DATE_PART": lambda self: self._parse_extract(),
+        }
+
+        FUNC_TOKENS = {
+            *Parser.FUNC_TOKENS,
+            TokenType.RLIKE,
         }
 
         COLUMN_OPERATORS = {
@@ -117,13 +132,29 @@ class Snowflake(Dialect):
             ),
         }
 
+        PROPERTY_PARSERS = {
+            **Parser.PROPERTY_PARSERS,
+            TokenType.PARTITION_BY: lambda self: self._parse_partitioned_by(),
+        }
+
     class Tokenizer(Tokenizer):
         QUOTES = ["'", "$$"]
         ESCAPE = "\\"
+
+        SINGLE_TOKENS = {
+            **Tokenizer.SINGLE_TOKENS,
+            "$": TokenType.DOLLAR,  # needed to break for quotes
+        }
+
         KEYWORDS = {
             **Tokenizer.KEYWORDS,
             "QUALIFY": TokenType.QUALIFY,
             "DOUBLE PRECISION": TokenType.DOUBLE,
+            "TIMESTAMP_LTZ": TokenType.TIMESTAMPLTZ,
+            "TIMESTAMP_NTZ": TokenType.TIMESTAMP,
+            "TIMESTAMP_TZ": TokenType.TIMESTAMPTZ,
+            "TIMESTAMPNTZ": TokenType.TIMESTAMP,
+            "SAMPLE": TokenType.TABLE_SAMPLE,
         }
 
     class Generator(Generator):
@@ -132,6 +163,23 @@ class Snowflake(Dialect):
             exp.If: rename_func("IFF"),
             exp.StrToTime: lambda self, e: f"TO_TIMESTAMP({self.sql(e, 'this')}, {self.format_time(e)})",
             exp.UnixToTime: _unix_to_time,
+            exp.Array: inline_array_sql,
+            exp.StrPosition: rename_func("POSITION"),
+            exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'value')}",
+        }
+
+        TYPE_MAPPING = {
+            **Generator.TYPE_MAPPING,
+            exp.DataType.Type.TIMESTAMP: "TIMESTAMPNTZ",
+        }
+
+        ROOT_PROPERTIES = {
+            exp.PartitionedByProperty,
+            exp.ReturnsProperty,
+            exp.LanguageProperty,
+            exp.SchemaCommentProperty,
+            exp.ExecuteAsProperty,
+            exp.VolatilityProperty,
         }
 
         def except_op(self, expression):
diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py
index 1e66147bb..bc6beaa1c 100644
--- a/sqlglot/expressions.py
+++ b/sqlglot/expressions.py
@@ -1,12 +1,17 @@
-import inspect
+import numbers
 import re
-import sys
 from collections import deque
 from copy import deepcopy
 from enum import auto
 
 from sqlglot.errors import ParseError
-from sqlglot.helper import AutoName, camel_to_snake_case, ensure_list
+from sqlglot.helper import (
+    AutoName,
+    camel_to_snake_case,
+    ensure_list,
+    list_get,
+    subclasses,
+)
 
 
 class _Expression(type):
@@ -30,12 +35,13 @@ class Expression(metaclass=_Expression):
 
     key = None
     arg_types = {"this": True}
-    __slots__ = ("args", "parent", "arg_key")
+    __slots__ = ("args", "parent", "arg_key", "type")
 
     def __init__(self, **args):
         self.args = args
         self.parent = None
         self.arg_key = None
+        self.type = None
 
         for arg_key, value in self.args.items():
             self._set_parent(arg_key, value)
@@ -47,10 +53,7 @@ class Expression(metaclass=_Expression):
         return hash(
             (
                 self.key,
-                tuple(
-                    (k, tuple(v) if isinstance(v, list) else v)
-                    for k, v in _norm_args(self).items()
-                ),
+                tuple((k, tuple(v) if isinstance(v, list) else v) for k, v in _norm_args(self).items()),
             )
         )
 
@@ -116,9 +119,22 @@ class Expression(metaclass=_Expression):
                 item.parent = parent
         return new
 
+    def append(self, arg_key, value):
+        """
+        Appends value to arg_key if it's a list or sets it as a new list.
+
+        Args:
+            arg_key (str): name of the list expression arg
+            value (Any): value to append to the list
+        """
+        if not isinstance(self.args.get(arg_key), list):
+            self.args[arg_key] = []
+        self.args[arg_key].append(value)
+        self._set_parent(arg_key, value)
+
     def set(self, arg_key, value):
         """
-        Sets `arg` to `value`.
+        Sets `arg_key` to `value`.
 
         Args:
             arg_key (str): name of the expression arg
@@ -197,21 +213,23 @@ class Expression(metaclass=_Expression):
         """
         return self.find_ancestor(Select)
 
-    def walk(self, bfs=True):
+    def walk(self, bfs=True, prune=None):
         """
         Returns a generator object which visits all nodes in this tree.
 
         Args:
             bfs (bool): if set to True the BFS traversal order will be applied,
                 otherwise the DFS traversal will be used instead.
+            prune ((node, parent, arg_key) -> bool): callable that returns True if
+                the generator should stop traversing this branch of the tree.
 
         Returns:
             the generator object.
         """
         if bfs:
-            yield from self.bfs()
+            yield from self.bfs(prune=prune)
         else:
-            yield from self.dfs()
+            yield from self.dfs(prune=prune)
 
     def dfs(self, parent=None, key=None, prune=None):
         """
@@ -287,9 +305,7 @@ class Expression(metaclass=_Expression):
 
         A AND B AND C -> [A, B, C]
         """
-        for node, _, _ in self.dfs(
-            prune=lambda n, p, *_: p and not isinstance(n, self.__class__)
-        ):
+        for node, _, _ in self.dfs(prune=lambda n, p, *_: p and not isinstance(n, self.__class__)):
             if not isinstance(node, self.__class__):
                 yield node.unnest() if unnest else node
 
@@ -322,9 +338,7 @@ class Expression(metaclass=_Expression):
 
         args = {
             k: ", ".join(
-                v.to_s(hide_missing=hide_missing, level=level + 1)
-                if hasattr(v, "to_s")
-                else str(v)
+                v.to_s(hide_missing=hide_missing, level=level + 1) if hasattr(v, "to_s") else str(v)
                 for v in ensure_list(vs)
                 if v is not None
             )
@@ -344,7 +358,8 @@ class Expression(metaclass=_Expression):
 
         Args:
             fun (function): a function which takes a node as an argument and returns a
-                new transformed node or the same node without modifications.
+                new transformed node or the same node without modifications. If the function
+                returns None, then the corresponding node will be removed from the syntax tree.
             copy (bool): if set to True a new tree instance is constructed, otherwise the tree is
                 modified in place.
 
@@ -354,17 +369,13 @@ class Expression(metaclass=_Expression):
         node = self.copy() if copy else self
         new_node = fun(node, *args, **kwargs)
 
-        if new_node is None:
-            raise ValueError("A transformed node cannot be None")
-        if not isinstance(new_node, Expression):
+        if new_node is None or not isinstance(new_node, Expression):
             return new_node
         if new_node is not node:
             new_node.parent = node.parent
             return new_node
 
-        replace_children(
-            new_node, lambda child: child.transform(fun, *args, copy=False, **kwargs)
-        )
+        replace_children(new_node, lambda child: child.transform(fun, *args, copy=False, **kwargs))
         return new_node
 
     def replace(self, expression):
@@ -380,7 +391,7 @@ class Expression(metaclass=_Expression):
             'SELECT y FROM tbl'
 
         Args:
-            expression (Expression): new node
+            expression (Expression|None): new node
 
         Returns :
             the new expression or expressions
@@ -394,6 +405,12 @@ class Expression(metaclass=_Expression):
         replace_children(parent, lambda child: expression if child is self else child)
         return expression
 
+    def pop(self):
+        """
+        Remove this expression from its AST.
+        """
+        self.replace(None)
+
     def assert_is(self, type_):
         """
         Assert that this `Expression` is an instance of `type_`.
@@ -491,12 +508,20 @@ class DerivedTable(Expression):
         return [select.alias_or_name for select in self.selects]
 
 
+class UDTF(DerivedTable):
+    pass
+
+
 class Annotation(Expression):
     arg_types = {
         "this": True,
         "expression": True,
     }
 
+    @property
+    def alias(self):
+        return self.expression.alias_or_name
+
 
 class Cache(Expression):
     arg_types = {
@@ -523,9 +548,18 @@ class Create(Expression):
         "temporary": False,
         "replace": False,
         "unique": False,
+        "materialized": False,
     }
 
 
+class UserDefinedFunction(Expression):
+    arg_types = {"this": True, "expressions": False}
+
+
+class UserDefinedFunctionKwarg(Expression):
+    arg_types = {"this": True, "kind": True, "default": False}
+
+
 class CharacterSet(Expression):
     arg_types = {"this": True, "default": False}
 
@@ -554,6 +588,10 @@ class BitString(Condition):
     pass
 
 
+class HexString(Condition):
+    pass
+
+
 class Column(Condition):
     arg_types = {"this": True, "table": False}
 
@@ -574,35 +612,44 @@ class ColumnConstraint(Expression):
     arg_types = {"this": False, "kind": True}
 
 
-class AutoIncrementColumnConstraint(Expression):
+class ColumnConstraintKind(Expression):
+    pass
+
+
+class AutoIncrementColumnConstraint(ColumnConstraintKind):
     pass
 
 
-class CheckColumnConstraint(Expression):
+class CheckColumnConstraint(ColumnConstraintKind):
     pass
 
 
-class CollateColumnConstraint(Expression):
+class CollateColumnConstraint(ColumnConstraintKind):
     pass
 
 
-class CommentColumnConstraint(Expression):
+class CommentColumnConstraint(ColumnConstraintKind):
     pass
 
 
-class DefaultColumnConstraint(Expression):
+class DefaultColumnConstraint(ColumnConstraintKind):
     pass
 
 
-class NotNullColumnConstraint(Expression):
+class GeneratedAsIdentityColumnConstraint(ColumnConstraintKind):
+    # this: True -> ALWAYS, this: False -> BY DEFAULT
+    arg_types = {"this": True, "expression": False}
+
+
+class NotNullColumnConstraint(ColumnConstraintKind):
     pass
 
 
-class PrimaryKeyColumnConstraint(Expression):
+class PrimaryKeyColumnConstraint(ColumnConstraintKind):
     pass
 
 
-class UniqueColumnConstraint(Expression):
+class UniqueColumnConstraint(ColumnConstraintKind):
     pass
 
 
@@ -615,7 +662,13 @@ class Delete(Expression):
 
 
 class Drop(Expression):
-    arg_types = {"this": False, "kind": False, "exists": False}
+    arg_types = {
+        "this": False,
+        "kind": False,
+        "exists": False,
+        "temporary": False,
+        "materialized": False,
+    }
 
 
 class Filter(Expression):
@@ -659,9 +712,7 @@ class Identifier(Expression):
         return bool(self.args.get("quoted"))
 
     def __eq__(self, other):
-        return isinstance(other, self.__class__) and _norm_arg(self.this) == _norm_arg(
-            other.this
-        )
+        return isinstance(other, self.__class__) and _norm_arg(self.this) == _norm_arg(other.this)
 
     def __hash__(self):
         return hash((self.key, self.this.lower()))
@@ -717,9 +768,7 @@ class Literal(Condition):
 
     def __eq__(self, other):
         return (
-            isinstance(other, Literal)
-            and self.this == other.this
-            and self.args["is_string"] == other.args["is_string"]
+            isinstance(other, Literal) and self.this == other.this and self.args["is_string"] == other.args["is_string"]
         )
 
     def __hash__(self):
@@ -741,6 +790,7 @@ class Join(Expression):
         "side": False,
         "kind": False,
         "using": False,
+        "natural": False,
     }
 
     @property
@@ -793,7 +843,7 @@ class Join(Expression):
         return join
 
 
-class Lateral(DerivedTable):
+class Lateral(UDTF):
     arg_types = {"this": True, "outer": False, "alias": False}
 
 
@@ -829,10 +879,6 @@ class Ordered(Expression):
     arg_types = {"this": True, "desc": True, "nulls_first": True}
 
 
-class Properties(Expression):
-    arg_types = {"expressions": True}
-
-
 class Property(Expression):
     arg_types = {"this": True, "value": True}
 
@@ -877,6 +923,46 @@ class AnonymousProperty(Property):
     pass
 
 
+class ReturnsProperty(Property):
+    arg_types = {"this": True, "value": True, "is_table": False}
+
+
+class LanguageProperty(Property):
+    pass
+
+
+class ExecuteAsProperty(Property):
+    pass
+
+
+class VolatilityProperty(Property):
+    arg_types = {"this": True}
+
+
+class Properties(Expression):
+    arg_types = {"expressions": True}
+
+    PROPERTY_KEY_MAPPING = {
+        "AUTO_INCREMENT": AutoIncrementProperty,
+        "CHARACTER_SET": CharacterSetProperty,
+        "COLLATE": CollateProperty,
+        "COMMENT": SchemaCommentProperty,
+        "ENGINE": EngineProperty,
+        "FORMAT": FileFormatProperty,
+        "LOCATION": LocationProperty,
+        "PARTITIONED_BY": PartitionedByProperty,
+        "TABLE_FORMAT": TableFormatProperty,
+    }
+
+    @classmethod
+    def from_dict(cls, properties_dict):
+        expressions = []
+        for key, value in properties_dict.items():
+            property_cls = cls.PROPERTY_KEY_MAPPING.get(key.upper(), AnonymousProperty)
+            expressions.append(property_cls(this=Literal.string(key), value=convert(value)))
+        return cls(expressions=expressions)
+
+
 class Qualify(Expression):
     pass
 
@@ -980,6 +1066,7 @@ class Subqueryable:
 QUERY_MODIFIERS = {
     "laterals": False,
     "joins": False,
+    "pivots": False,
     "where": False,
     "group": False,
     "having": False,
@@ -995,7 +1082,14 @@ QUERY_MODIFIERS = {
 
 
 class Table(Expression):
-    arg_types = {"this": True, "db": False, "catalog": False, **QUERY_MODIFIERS}
+    arg_types = {
+        "this": True,
+        "db": False,
+        "catalog": False,
+        "laterals": False,
+        "joins": False,
+        "pivots": False,
+    }
 
 
 class Union(Subqueryable, Expression):
@@ -1028,7 +1122,7 @@ class Intersect(Union):
     pass
 
 
-class Unnest(DerivedTable):
+class Unnest(UDTF):
     arg_types = {
         "expressions": True,
         "ordinality": False,
@@ -1046,8 +1140,12 @@ class Update(Expression):
     }
 
 
-class Values(Expression):
-    arg_types = {"expressions": True}
+class Values(UDTF):
+    arg_types = {
+        "expressions": True,
+        "ordinality": False,
+        "alias": False,
+    }
 
 
 class Var(Expression):
@@ -1408,7 +1506,9 @@ class Select(Subqueryable, Expression):
             join.this.replace(join.this.subquery())
 
         if join_type:
-            side, kind = maybe_parse(join_type, into="JOIN_TYPE", **parse_args)
+            natural, side, kind = maybe_parse(join_type, into="JOIN_TYPE", **parse_args)
+            if natural:
+                join.set("natural", True)
             if side:
                 join.set("side", side.text)
             if kind:
@@ -1540,18 +1640,7 @@ class Select(Subqueryable, Expression):
         )
         properties_expression = None
         if properties:
-            properties_str = " ".join(
-                [
-                    f"{k} = '{v}'" if isinstance(v, str) else f"{k} = {v}"
-                    for k, v in properties.items()
-                ]
-            )
-            properties_expression = maybe_parse(
-                properties_str,
-                into=Properties,
-                dialect=dialect,
-                **opts,
-            )
+            properties_expression = Properties.from_dict(properties)
 
         return Create(
             this=table_expression,
@@ -1596,6 +1685,16 @@ class TableSample(Expression):
         "percent": False,
         "rows": False,
         "size": False,
+        "seed": False,
+    }
+
+
+class Pivot(Expression):
+    arg_types = {
+        "this": False,
+        "expressions": True,
+        "field": True,
+        "unpivot": True,
     }
 
 
@@ -1631,6 +1730,10 @@ class Star(Expression):
         return "*"
 
 
+class Parameter(Expression):
+    pass
+
+
 class Placeholder(Expression):
     arg_types = {}
 
@@ -1669,21 +1772,35 @@ class DataType(Expression):
         INTERVAL = auto()
         TIMESTAMP = auto()
         TIMESTAMPTZ = auto()
+        TIMESTAMPLTZ = auto()
         DATE = auto()
         DATETIME = auto()
         ARRAY = auto()
         MAP = auto()
         UUID = auto()
         GEOGRAPHY = auto()
+        GEOMETRY = auto()
         STRUCT = auto()
         NULLABLE = auto()
+        HLLSKETCH = auto()
+        SUPER = auto()
+        SERIAL = auto()
+        SMALLSERIAL = auto()
+        BIGSERIAL = auto()
+        XML = auto()
+        UNIQUEIDENTIFIER = auto()
+        MONEY = auto()
+        SMALLMONEY = auto()
+        ROWVERSION = auto()
+        IMAGE = auto()
+        VARIANT = auto()
+        OBJECT = auto()
+        UNKNOWN = auto()  # Sentinel value, useful for the optimizer
 
     @classmethod
     def build(cls, dtype, **kwargs):
         return DataType(
-            this=dtype
-            if isinstance(dtype, DataType.Type)
-            else DataType.Type[dtype.upper()],
+            this=dtype if isinstance(dtype, DataType.Type) else DataType.Type[dtype.upper()],
             **kwargs,
         )
 
@@ -1811,6 +1928,14 @@ class Like(Binary, Predicate):
     pass
 
 
+class SimilarTo(Binary, Predicate):
+    pass
+
+
+class Distance(Binary):
+    pass
+
+
 class LT(Binary, Predicate):
     pass
 
@@ -1937,33 +2062,23 @@ class Func(Condition):
 
     @classmethod
     def from_arg_list(cls, args):
-        args_num = len(args)
-
-        all_arg_keys = list(cls.arg_types)
-        # If this function supports variable length argument treat the last argument as such.
-        non_var_len_arg_keys = (
-            all_arg_keys[:-1] if cls.is_var_len_args else all_arg_keys
-        )
+        if cls.is_var_len_args:
+            all_arg_keys = list(cls.arg_types)
+            # If this function supports variable length argument treat the last argument as such.
+            non_var_len_arg_keys = all_arg_keys[:-1] if cls.is_var_len_args else all_arg_keys
+            num_non_var = len(non_var_len_arg_keys)
+
+            args_dict = {arg_key: arg for arg, arg_key in zip(args, non_var_len_arg_keys)}
+            args_dict[all_arg_keys[-1]] = args[num_non_var:]
+        else:
+            args_dict = {arg_key: arg for arg, arg_key in zip(args, cls.arg_types)}
 
-        args_dict = {}
-        arg_idx = 0
-        for arg_key in non_var_len_arg_keys:
-            if arg_idx >= args_num:
-                break
-            if args[arg_idx] is not None:
-                args_dict[arg_key] = args[arg_idx]
-            arg_idx += 1
-
-        if arg_idx < args_num and cls.is_var_len_args:
-            args_dict[all_arg_keys[-1]] = args[arg_idx:]
         return cls(**args_dict)
 
     @classmethod
     def sql_names(cls):
         if cls is Func:
-            raise NotImplementedError(
-                "SQL name is only supported by concrete function implementations"
-            )
+            raise NotImplementedError("SQL name is only supported by concrete function implementations")
         if not hasattr(cls, "_sql_names"):
             cls._sql_names = [camel_to_snake_case(cls.__name__)]
         return cls._sql_names
@@ -2057,6 +2172,7 @@ class TryCast(Cast):
 
 
 class Ceil(Func):
+    arg_types = {"this": True, "decimals": False}
     _sql_names = ["CEIL", "CEILING"]
 
 
@@ -2187,7 +2303,7 @@ class Explode(Func):
 
 
 class Floor(Func):
-    pass
+    arg_types = {"this": True, "decimals": False}
 
 
 class Greatest(Func):
@@ -2195,6 +2311,10 @@ class Greatest(Func):
     is_var_len_args = True
 
 
+class GroupConcat(Func):
+    arg_types = {"this": True, "separator": False}
+
+
 class If(Func):
     arg_types = {"this": True, "true": True, "false": False}
 
@@ -2262,6 +2382,11 @@ class Map(Func):
     arg_types = {"keys": True, "values": True}
 
 
+class VarMap(Func):
+    arg_types = {"keys": True, "values": True}
+    is_var_len_args = True
+
+
 class Max(AggFunc):
     pass
 
@@ -2291,12 +2416,16 @@ class Quantile(AggFunc):
     arg_types = {"this": True, "quantile": True}
 
 
+class ApproxQuantile(Quantile):
+    pass
+
+
 class Reduce(Func):
     arg_types = {"this": True, "initial": True, "merge": True, "finish": True}
 
 
 class RegexpLike(Func):
-    arg_types = {"this": True, "expression": True}
+    arg_types = {"this": True, "expression": True, "flag": False}
 
 
 class RegexpSplit(Func):
@@ -2330,7 +2459,7 @@ class Substring(Func):
 
 
 class StrPosition(Func):
-    arg_types = {"this": True, "substr": True, "position": False}
+    arg_types = {"substr": True, "this": True, "position": False}
 
 
 class StrToDate(Func):
@@ -2465,6 +2594,8 @@ def _norm_args(expression):
     for k, arg in expression.args.items():
         if isinstance(arg, list):
             arg = [_norm_arg(a) for a in arg]
+            if not arg:
+                arg = None
         else:
             arg = _norm_arg(arg)
 
@@ -2478,19 +2609,7 @@ def _norm_arg(arg):
     return arg.lower() if isinstance(arg, str) else arg
 
 
-def _all_functions():
-    return [
-        obj
-        for _, obj in inspect.getmembers(
-            sys.modules[__name__],
-            lambda obj: inspect.isclass(obj)
-            and issubclass(obj, Func)
-            and obj not in (AggFunc, Anonymous, Func),
-        )
-    ]
-
-
-ALL_FUNCTIONS = _all_functions()
+ALL_FUNCTIONS = subclasses(__name__, Func, (AggFunc, Anonymous, Func))
 
 
 def maybe_parse(
@@ -2661,9 +2780,7 @@ def _apply_conjunction_builder(
 
 
 def _combine(expressions, operator, dialect=None, **opts):
-    expressions = [
-        condition(expression, dialect=dialect, **opts) for expression in expressions
-    ]
+    expressions = [condition(expression, dialect=dialect, **opts) for expression in expressions]
     this = expressions[0]
     if expressions[1:]:
         this = _wrap_operator(this)
@@ -2722,6 +2839,37 @@ def from_(*expressions, dialect=None, **opts):
     return Select().from_(*expressions, dialect=dialect, **opts)
 
 
+def update(table, properties, where=None, from_=None, dialect=None, **opts):
+    """
+    Creates an update statement.
+
+    Example:
+        >>> update("my_table", {"x": 1, "y": "2", "z": None}, from_="baz", where="id > 1").sql()
+        "UPDATE my_table SET x = 1, y = '2', z = NULL FROM baz WHERE id > 1"
+
+    Args:
+        *properties (Dict[str, Any]): dictionary of properties to set which are
+            auto converted to sql objects eg None -> NULL
+        where (str): sql conditional parsed into a WHERE statement
+        from_ (str): sql statement parsed into a FROM statement
+        dialect (str): the dialect used to parse the input expressions.
+        **opts: other options to use to parse the input expressions.
+
+    Returns:
+        Update: the syntax tree for the UPDATE statement.
+    """
+    update = Update(this=maybe_parse(table, into=Table, dialect=dialect))
+    update.set(
+        "expressions",
+        [EQ(this=maybe_parse(k, dialect=dialect, **opts), expression=convert(v)) for k, v in properties.items()],
+    )
+    if from_:
+        update.set("from", maybe_parse(from_, into=From, dialect=dialect, prefix="FROM", **opts))
+    if where:
+        update.set("where", maybe_parse(where, into=Where, dialect=dialect, prefix="WHERE", **opts))
+    return update
+
+
 def condition(expression, dialect=None, **opts):
     """
     Initialize a logical condition expression.
@@ -2837,9 +2985,7 @@ def to_identifier(alias, quoted=None):
             quoted = not re.match(SAFE_IDENTIFIER_RE, alias)
         identifier = Identifier(this=alias, quoted=quoted)
     else:
-        raise ValueError(
-            f"Alias needs to be a string or an Identifier, got: {alias.__class__}"
-        )
+        raise ValueError(f"Alias needs to be a string or an Identifier, got: {alias.__class__}")
     return identifier
 
 
@@ -2911,12 +3057,13 @@ def column(col, table=None, quoted=None):
 
 
 def table_(table, db=None, catalog=None, quoted=None):
-    """
-    Build a Table.
+    """Build a Table.
+
     Args:
         table (str or Expression): column name
         db (str or Expression): db name
         catalog (str or Expression): catalog name
+
     Returns:
         Table: table instance
     """
@@ -2927,6 +3074,39 @@ def table_(table, db=None, catalog=None, quoted=None):
     )
 
 
+def convert(value):
+    """Convert a python value into an expression object.
+
+    Raises an error if a conversion is not possible.
+
+    Args:
+        value (Any): a python object
+
+    Returns:
+        Expression: the equivalent expression object
+    """
+    if isinstance(value, Expression):
+        return value
+    if value is None:
+        return NULL
+    if isinstance(value, bool):
+        return Boolean(this=value)
+    if isinstance(value, str):
+        return Literal.string(value)
+    if isinstance(value, numbers.Number):
+        return Literal.number(value)
+    if isinstance(value, tuple):
+        return Tuple(expressions=[convert(v) for v in value])
+    if isinstance(value, list):
+        return Array(expressions=[convert(v) for v in value])
+    if isinstance(value, dict):
+        return Map(
+            keys=[convert(k) for k in value.keys()],
+            values=[convert(v) for v in value.values()],
+        )
+    raise ValueError(f"Cannot convert {value}")
+
+
 def replace_children(expression, fun):
     """
     Replace children of an expression with the result of a lambda fun(child) -> exp.
@@ -2947,7 +3127,7 @@ def replace_children(expression, fun):
             else:
                 new_child_nodes.append(cn)
 
-        expression.args[k] = new_child_nodes if is_list_arg else new_child_nodes[0]
+        expression.args[k] = new_child_nodes if is_list_arg else list_get(new_child_nodes, 0)
 
 
 def column_table_names(expression):
@@ -2968,6 +3148,60 @@ def column_table_names(expression):
     return list(dict.fromkeys(column.table for column in expression.find_all(Column)))
 
 
+def table_name(table):
+    """Get the full name of a table as a string.
+
+    Args:
+        table (exp.Table | str): Table expression node or string.
+
+    Examples:
+        >>> from sqlglot import exp, parse_one
+        >>> table_name(parse_one("select * from a.b.c").find(exp.Table))
+        'a.b.c'
+
+    Returns:
+        str: the table name
+    """
+
+    table = maybe_parse(table, into=Table)
+
+    return ".".join(
+        part
+        for part in (
+            table.text("catalog"),
+            table.text("db"),
+            table.name,
+        )
+        if part
+    )
+
+
+def replace_tables(expression, mapping):
+    """Replace all tables in expression according to the mapping.
+
+    Args:
+        expression (sqlglot.Expression): Expression node to be transformed and replaced
+        mapping (Dict[str, str]): Mapping of table names
+
+    Examples:
+        >>> from sqlglot import exp, parse_one
+        >>> replace_tables(parse_one("select * from a.b"), {"a.b": "c"}).sql()
+        'SELECT * FROM "c"'
+
+    Returns:
+        The mapped expression
+    """
+
+    def _replace_tables(node):
+        if isinstance(node, Table):
+            new_name = mapping.get(table_name(node))
+            if new_name:
+                return table_(*reversed(new_name.split(".")), quoted=True)
+        return node
+
+    return expression.transform(_replace_tables)
+
+
 TRUE = Boolean(this=True)
 FALSE = Boolean(this=False)
 NULL = Null()
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
index 63e408979..1323ae042 100644
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -8,6 +8,18 @@ from sqlglot.tokens import Token, Tokenizer, TokenType
 logger = logging.getLogger("sqlglot")
 
 
+def parse_var_map(args):
+    keys = []
+    values = []
+    for i in range(0, len(args), 2):
+        keys.append(args[i])
+        values.append(args[i + 1])
+    return exp.VarMap(
+        keys=exp.Array(expressions=keys),
+        values=exp.Array(expressions=values),
+    )
+
+
 class Parser:
     """
     Parser consumes a list of tokens produced by the :class:`~sqlglot.tokens.Tokenizer`
@@ -48,6 +60,7 @@ class Parser:
             start=exp.Literal.number(1),
             length=exp.Literal.number(10),
         ),
+        "VAR_MAP": parse_var_map,
     }
 
     NO_PAREN_FUNCTIONS = {
@@ -81,11 +94,26 @@ class Parser:
         TokenType.INTERVAL,
         TokenType.TIMESTAMP,
         TokenType.TIMESTAMPTZ,
+        TokenType.TIMESTAMPLTZ,
         TokenType.DATETIME,
         TokenType.DATE,
         TokenType.DECIMAL,
         TokenType.UUID,
         TokenType.GEOGRAPHY,
+        TokenType.GEOMETRY,
+        TokenType.HLLSKETCH,
+        TokenType.SUPER,
+        TokenType.SERIAL,
+        TokenType.SMALLSERIAL,
+        TokenType.BIGSERIAL,
+        TokenType.XML,
+        TokenType.UNIQUEIDENTIFIER,
+        TokenType.MONEY,
+        TokenType.SMALLMONEY,
+        TokenType.ROWVERSION,
+        TokenType.IMAGE,
+        TokenType.VARIANT,
+        TokenType.OBJECT,
         *NESTED_TYPE_TOKENS,
     }
 
@@ -101,6 +129,8 @@ class Parser:
     ID_VAR_TOKENS = {
         TokenType.VAR,
         TokenType.ALTER,
+        TokenType.ALWAYS,
+        TokenType.ANTI,
         TokenType.BEGIN,
         TokenType.BOTH,
         TokenType.BUCKET,
@@ -108,38 +138,50 @@ class Parser:
         TokenType.COLLATE,
         TokenType.COMMIT,
         TokenType.CONSTRAINT,
-        TokenType.CONVERT,
         TokenType.DEFAULT,
         TokenType.DELETE,
+        TokenType.DETERMINISTIC,
+        TokenType.EXECUTE,
         TokenType.ENGINE,
         TokenType.ESCAPE,
         TokenType.EXPLAIN,
         TokenType.FALSE,
         TokenType.FIRST,
         TokenType.FOLLOWING,
-        TokenType.FOR,
         TokenType.FORMAT,
         TokenType.FUNCTION,
+        TokenType.GENERATED,
+        TokenType.IDENTITY,
         TokenType.IF,
         TokenType.INDEX,
         TokenType.ISNULL,
+        TokenType.IMMUTABLE,
         TokenType.INTERVAL,
         TokenType.LAZY,
+        TokenType.LANGUAGE,
         TokenType.LEADING,
         TokenType.LOCATION,
+        TokenType.MATERIALIZED,
+        TokenType.NATURAL,
         TokenType.NEXT,
         TokenType.ONLY,
         TokenType.OPTIMIZE,
         TokenType.OPTIONS,
         TokenType.ORDINALITY,
+        TokenType.PARTITIONED_BY,
         TokenType.PERCENT,
+        TokenType.PIVOT,
         TokenType.PRECEDING,
         TokenType.RANGE,
         TokenType.REFERENCES,
+        TokenType.RETURNS,
         TokenType.ROWS,
         TokenType.SCHEMA_COMMENT,
+        TokenType.SEED,
+        TokenType.SEMI,
         TokenType.SET,
         TokenType.SHOW,
+        TokenType.STABLE,
         TokenType.STORED,
         TokenType.TABLE,
         TokenType.TABLE_FORMAT,
@@ -150,25 +192,23 @@ class Parser:
         TokenType.TRUE,
         TokenType.UNBOUNDED,
         TokenType.UNIQUE,
+        TokenType.UNPIVOT,
         TokenType.PROPERTIES,
+        TokenType.PROCEDURE,
+        TokenType.VOLATILE,
         *SUBQUERY_PREDICATES,
         *TYPE_TOKENS,
     }
 
-    CASTS = {
-        TokenType.CAST,
-        TokenType.TRY_CAST,
-    }
+    TABLE_ALIAS_TOKENS = ID_VAR_TOKENS - {TokenType.NATURAL}
 
     TRIM_TYPES = {TokenType.LEADING, TokenType.TRAILING, TokenType.BOTH}
 
     FUNC_TOKENS = {
-        TokenType.CONVERT,
         TokenType.CURRENT_DATE,
         TokenType.CURRENT_DATETIME,
         TokenType.CURRENT_TIMESTAMP,
         TokenType.CURRENT_TIME,
-        TokenType.EXTRACT,
         TokenType.FILTER,
         TokenType.FIRST,
         TokenType.FORMAT,
@@ -177,8 +217,6 @@ class Parser:
         TokenType.PRIMARY_KEY,
         TokenType.REPLACE,
         TokenType.ROW,
-        TokenType.SUBSTRING,
-        TokenType.TRIM,
         TokenType.UNNEST,
         TokenType.VAR,
         TokenType.LEFT,
@@ -187,8 +225,7 @@ class Parser:
         TokenType.DATETIME,
         TokenType.TIMESTAMP,
         TokenType.TIMESTAMPTZ,
-        *CASTS,
-        *NESTED_TYPE_TOKENS,
+        *TYPE_TOKENS,
         *SUBQUERY_PREDICATES,
     }
 
@@ -224,6 +261,7 @@ class Parser:
 
     FACTOR = {
         TokenType.DIV: exp.IntDiv,
+        TokenType.LR_ARROW: exp.Distance,
         TokenType.SLASH: exp.Div,
         TokenType.STAR: exp.Mul,
     }
@@ -231,6 +269,7 @@ class Parser:
     TIMESTAMPS = {
         TokenType.TIMESTAMP,
         TokenType.TIMESTAMPTZ,
+        TokenType.TIMESTAMPLTZ,
     }
 
     SET_OPERATIONS = {
@@ -249,6 +288,8 @@ class Parser:
         TokenType.INNER,
         TokenType.OUTER,
         TokenType.CROSS,
+        TokenType.SEMI,
+        TokenType.ANTI,
     }
 
     COLUMN_OPERATORS = {
@@ -292,6 +333,10 @@ class Parser:
         exp.Condition: lambda self: self._parse_conjunction(),
         exp.Expression: lambda self: self._parse_statement(),
         exp.Properties: lambda self: self._parse_properties(),
+        exp.Where: lambda self: self._parse_where(),
+        exp.Ordered: lambda self: self._parse_ordered(),
+        exp.Having: lambda self: self._parse_having(),
+        exp.With: lambda self: self._parse_with(),
         "JOIN_TYPE": lambda self: self._parse_join_side_and_kind(),
     }
 
@@ -308,14 +353,14 @@ class Parser:
     PRIMARY_PARSERS = {
         TokenType.STRING: lambda _, token: exp.Literal.string(token.text),
         TokenType.NUMBER: lambda _, token: exp.Literal.number(token.text),
-        TokenType.STAR: lambda self, _: exp.Star(
-            **{"except": self._parse_except(), "replace": self._parse_replace()}
-        ),
+        TokenType.STAR: lambda self, _: exp.Star(**{"except": self._parse_except(), "replace": self._parse_replace()}),
         TokenType.NULL: lambda *_: exp.Null(),
         TokenType.TRUE: lambda *_: exp.Boolean(this=True),
         TokenType.FALSE: lambda *_: exp.Boolean(this=False),
         TokenType.PLACEHOLDER: lambda *_: exp.Placeholder(),
+        TokenType.PARAMETER: lambda self, _: exp.Parameter(this=self._parse_var() or self._parse_primary()),
         TokenType.BIT_STRING: lambda _, token: exp.BitString(this=token.text),
+        TokenType.HEX_STRING: lambda _, token: exp.HexString(this=token.text),
         TokenType.INTRODUCER: lambda self, token: self.expression(
             exp.Introducer,
             this=token.text,
@@ -336,28 +381,36 @@ class Parser:
         TokenType.RLIKE: lambda self, this: self.expression(
             exp.RegexpLike, this=this, expression=self._parse_bitwise()
         ),
+        TokenType.SIMILAR_TO: lambda self, this: self.expression(
+            exp.SimilarTo, this=this, expression=self._parse_bitwise()
+        ),
     }
 
     PROPERTY_PARSERS = {
         TokenType.AUTO_INCREMENT: lambda self: self._parse_auto_increment(),
         TokenType.CHARACTER_SET: lambda self: self._parse_character_set(),
-        TokenType.COLLATE: lambda self: self._parse_collate(),
-        TokenType.ENGINE: lambda self: self._parse_engine(),
-        TokenType.FORMAT: lambda self: self._parse_format(),
         TokenType.LOCATION: lambda self: self.expression(
             exp.LocationProperty,
             this=exp.Literal.string("LOCATION"),
             value=self._parse_string(),
         ),
-        TokenType.PARTITIONED_BY: lambda self: self.expression(
-            exp.PartitionedByProperty,
-            this=exp.Literal.string("PARTITIONED_BY"),
-            value=self._parse_schema(),
-        ),
+        TokenType.PARTITIONED_BY: lambda self: self._parse_partitioned_by(),
         TokenType.SCHEMA_COMMENT: lambda self: self._parse_schema_comment(),
         TokenType.STORED: lambda self: self._parse_stored(),
-        TokenType.TABLE_FORMAT: lambda self: self._parse_table_format(),
-        TokenType.USING: lambda self: self._parse_table_format(),
+        TokenType.RETURNS: lambda self: self._parse_returns(),
+        TokenType.COLLATE: lambda self: self._parse_property_assignment(exp.CollateProperty),
+        TokenType.COMMENT: lambda self: self._parse_property_assignment(exp.SchemaCommentProperty),
+        TokenType.FORMAT: lambda self: self._parse_property_assignment(exp.FileFormatProperty),
+        TokenType.TABLE_FORMAT: lambda self: self._parse_property_assignment(exp.TableFormatProperty),
+        TokenType.USING: lambda self: self._parse_property_assignment(exp.TableFormatProperty),
+        TokenType.LANGUAGE: lambda self: self._parse_property_assignment(exp.LanguageProperty),
+        TokenType.EXECUTE: lambda self: self._parse_execute_as(),
+        TokenType.DETERMINISTIC: lambda self: self.expression(
+            exp.VolatilityProperty, this=exp.Literal.string("IMMUTABLE")
+        ),
+        TokenType.IMMUTABLE: lambda self: self.expression(exp.VolatilityProperty, this=exp.Literal.string("IMMUTABLE")),
+        TokenType.STABLE: lambda self: self.expression(exp.VolatilityProperty, this=exp.Literal.string("STABLE")),
+        TokenType.VOLATILE: lambda self: self.expression(exp.VolatilityProperty, this=exp.Literal.string("VOLATILE")),
     }
 
     CONSTRAINT_PARSERS = {
@@ -372,30 +425,22 @@ class Parser:
     }
 
     FUNCTION_PARSERS = {
-        TokenType.CONVERT: lambda self, _: self._parse_convert(),
-        TokenType.EXTRACT: lambda self, _: self._parse_extract(),
-        TokenType.SUBSTRING: lambda self, _: self._parse_substring(),
-        TokenType.TRIM: lambda self, _: self._parse_trim(),
-        **{
-            token_type: lambda self, token_type: self._parse_cast(
-                self.STRICT_CAST and token_type == TokenType.CAST
-            )
-            for token_type in CASTS
-        },
+        "CONVERT": lambda self: self._parse_convert(),
+        "EXTRACT": lambda self: self._parse_extract(),
+        "POSITION": lambda self: self._parse_position(),
+        "SUBSTRING": lambda self: self._parse_substring(),
+        "TRIM": lambda self: self._parse_trim(),
+        "CAST": lambda self: self._parse_cast(self.STRICT_CAST),
+        "TRY_CAST": lambda self: self._parse_cast(False),
     }
 
     QUERY_MODIFIER_PARSERS = {
-        "laterals": lambda self: self._parse_laterals(),
-        "joins": lambda self: self._parse_joins(),
         "where": lambda self: self._parse_where(),
         "group": lambda self: self._parse_group(),
         "having": lambda self: self._parse_having(),
         "qualify": lambda self: self._parse_qualify(),
-        "window": lambda self: self._match(TokenType.WINDOW)
-        and self._parse_window(self._parse_id_var(), alias=True),
-        "distribute": lambda self: self._parse_sort(
-            TokenType.DISTRIBUTE_BY, exp.Distribute
-        ),
+        "window": lambda self: self._match(TokenType.WINDOW) and self._parse_window(self._parse_id_var(), alias=True),
+        "distribute": lambda self: self._parse_sort(TokenType.DISTRIBUTE_BY, exp.Distribute),
         "sort": lambda self: self._parse_sort(TokenType.SORT_BY, exp.Sort),
         "cluster": lambda self: self._parse_sort(TokenType.CLUSTER_BY, exp.Cluster),
         "order": lambda self: self._parse_order(),
@@ -403,7 +448,9 @@ class Parser:
         "offset": lambda self: self._parse_offset(),
     }
 
-    CREATABLES = {TokenType.TABLE, TokenType.VIEW, TokenType.FUNCTION, TokenType.INDEX}
+    MODIFIABLES = (exp.Subquery, exp.Subqueryable, exp.Table)
+
+    CREATABLES = {TokenType.TABLE, TokenType.VIEW, TokenType.FUNCTION, TokenType.INDEX, TokenType.PROCEDURE}
 
     STRICT_CAST = True
 
@@ -468,9 +515,7 @@ class Parser:
         Returns
             the list of syntax trees (:class:`~sqlglot.expressions.Expression`).
         """
-        return self._parse(
-            parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql
-        )
+        return self._parse(parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql)
 
     def parse_into(self, expression_types, raw_tokens, sql=None):
         for expression_type in ensure_list(expression_types):
@@ -543,21 +588,13 @@ class Parser:
 
         for k in expression.args:
             if k not in expression.arg_types:
-                self.raise_error(
-                    f"Unexpected keyword: '{k}' for {expression.__class__}"
-                )
+                self.raise_error(f"Unexpected keyword: '{k}' for {expression.__class__}")
         for k, mandatory in expression.arg_types.items():
             v = expression.args.get(k)
             if mandatory and (v is None or (isinstance(v, list) and not v)):
-                self.raise_error(
-                    f"Required keyword: '{k}' missing for {expression.__class__}"
-                )
+                self.raise_error(f"Required keyword: '{k}' missing for {expression.__class__}")
 
-        if (
-            args
-            and len(args) > len(expression.arg_types)
-            and not expression.is_var_len_args
-        ):
+        if args and len(args) > len(expression.arg_types) and not expression.is_var_len_args:
             self.raise_error(
                 f"The number of provided arguments ({len(args)}) is greater than "
                 f"the maximum number of supported arguments ({len(expression.arg_types)})"
@@ -605,64 +642,62 @@ class Parser:
             )
 
         expression = self._parse_expression()
-        expression = (
-            self._parse_set_operations(expression)
-            if expression
-            else self._parse_select()
-        )
+        expression = self._parse_set_operations(expression) if expression else self._parse_select()
         self._parse_query_modifiers(expression)
         return expression
 
     def _parse_drop(self):
-        if self._match(TokenType.TABLE):
-            kind = "TABLE"
-        elif self._match(TokenType.VIEW):
-            kind = "VIEW"
-        else:
-            self.raise_error("Expected TABLE or View")
+        temporary = self._match(TokenType.TEMPORARY)
+        materialized = self._match(TokenType.MATERIALIZED)
+        kind = self._match_set(self.CREATABLES) and self._prev.text
+        if not kind:
+            self.raise_error("Expected TABLE, VIEW, INDEX, FUNCTION, or PROCEDURE")
+            return
 
         return self.expression(
             exp.Drop,
             exists=self._parse_exists(),
             this=self._parse_table(schema=True),
             kind=kind,
+            temporary=temporary,
+            materialized=materialized,
         )
 
     def _parse_exists(self, not_=False):
-        return (
-            self._match(TokenType.IF)
-            and (not not_ or self._match(TokenType.NOT))
-            and self._match(TokenType.EXISTS)
-        )
+        return self._match(TokenType.IF) and (not not_ or self._match(TokenType.NOT)) and self._match(TokenType.EXISTS)
 
     def _parse_create(self):
         replace = self._match(TokenType.OR) and self._match(TokenType.REPLACE)
         temporary = self._match(TokenType.TEMPORARY)
         unique = self._match(TokenType.UNIQUE)
+        materialized = self._match(TokenType.MATERIALIZED)
+
+        if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False):
+            self._match(TokenType.TABLE)
 
         create_token = self._match_set(self.CREATABLES) and self._prev
 
         if not create_token:
-            self.raise_error("Expected TABLE, VIEW, INDEX, or FUNCTION")
+            self.raise_error("Expected TABLE, VIEW, INDEX, FUNCTION, or PROCEDURE")
+            return
 
         exists = self._parse_exists(not_=True)
         this = None
         expression = None
         properties = None
 
-        if create_token.token_type == TokenType.FUNCTION:
-            this = self._parse_var()
+        if create_token.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE):
+            this = self._parse_user_defined_function()
+            properties = self._parse_properties()
             if self._match(TokenType.ALIAS):
-                expression = self._parse_string()
+                expression = self._parse_select_or_expression()
         elif create_token.token_type == TokenType.INDEX:
             this = self._parse_index()
         elif create_token.token_type in (TokenType.TABLE, TokenType.VIEW):
             this = self._parse_table(schema=True)
-            properties = self._parse_properties(
-                this if isinstance(this, exp.Schema) else None
-            )
+            properties = self._parse_properties()
             if self._match(TokenType.ALIAS):
-                expression = self._parse_select()
+                expression = self._parse_select(nested=True)
 
         return self.expression(
             exp.Create,
@@ -674,9 +709,10 @@ class Parser:
             temporary=temporary,
             replace=replace,
             unique=unique,
+            materialized=materialized,
         )
 
-    def _parse_property(self, schema):
+    def _parse_property(self):
         if self._match_set(self.PROPERTY_PARSERS):
             return self.PROPERTY_PARSERS[self._prev.token_type](self)
         if self._match_pair(TokenType.DEFAULT, TokenType.CHARACTER_SET):
@@ -686,56 +722,34 @@ class Parser:
             key = self._parse_var().this
             self._match(TokenType.EQ)
 
-            if key.upper() == "PARTITIONED_BY":
-                expression = exp.PartitionedByProperty
-                value = self._parse_schema() or self._parse_bracket(self._parse_field())
-
-                if schema and not isinstance(value, exp.Schema):
-                    columns = {v.name.upper() for v in value.expressions}
-                    partitions = [
-                        expression
-                        for expression in schema.expressions
-                        if expression.this.name.upper() in columns
-                    ]
-                    schema.set(
-                        "expressions",
-                        [e for e in schema.expressions if e not in partitions],
-                    )
-                    value = self.expression(exp.Schema, expressions=partitions)
-            else:
-                value = self._parse_column()
-                expression = exp.AnonymousProperty
-
             return self.expression(
-                expression,
+                exp.AnonymousProperty,
                 this=exp.Literal.string(key),
-                value=value,
+                value=self._parse_column(),
             )
+
         return None
 
-    def _parse_stored(self):
-        self._match(TokenType.ALIAS)
+    def _parse_property_assignment(self, exp_class):
+        prop = self._prev.text
         self._match(TokenType.EQ)
-        return self.expression(
-            exp.FileFormatProperty,
-            this=exp.Literal.string("FORMAT"),
-            value=exp.Literal.string(self._parse_var().name),
-        )
+        return self.expression(exp_class, this=prop, value=self._parse_var_or_string())
 
-    def _parse_format(self):
+    def _parse_partitioned_by(self):
         self._match(TokenType.EQ)
         return self.expression(
-            exp.FileFormatProperty,
-            this=exp.Literal.string("FORMAT"),
-            value=self._parse_string() or self._parse_var(),
+            exp.PartitionedByProperty,
+            this=exp.Literal.string("PARTITIONED_BY"),
+            value=self._parse_schema() or self._parse_bracket(self._parse_field()),
         )
 
-    def _parse_engine(self):
+    def _parse_stored(self):
+        self._match(TokenType.ALIAS)
         self._match(TokenType.EQ)
         return self.expression(
-            exp.EngineProperty,
-            this=exp.Literal.string("ENGINE"),
-            value=self._parse_var_or_string(),
+            exp.FileFormatProperty,
+            this=exp.Literal.string("FORMAT"),
+            value=exp.Literal.string(self._parse_var().name),
         )
 
     def _parse_auto_increment(self):
@@ -746,14 +760,6 @@ class Parser:
             value=self._parse_var() or self._parse_number(),
         )
 
-    def _parse_collate(self):
-        self._match(TokenType.EQ)
-        return self.expression(
-            exp.CollateProperty,
-            this=exp.Literal.string("COLLATE"),
-            value=self._parse_var_or_string(),
-        )
-
     def _parse_schema_comment(self):
         self._match(TokenType.EQ)
         return self.expression(
@@ -771,26 +777,44 @@ class Parser:
             default=default,
         )
 
-    def _parse_table_format(self):
-        self._match(TokenType.EQ)
+    def _parse_returns(self):
+        is_table = self._match(TokenType.TABLE)
+        if is_table:
+            if self._match(TokenType.LT):
+                value = self.expression(
+                    exp.Schema,
+                    this="TABLE",
+                    expressions=self._parse_csv(self._parse_struct_kwargs),
+                )
+                if not self._match(TokenType.GT):
+                    self.raise_error("Expecting >")
+            else:
+                value = self._parse_schema("TABLE")
+        else:
+            value = self._parse_types()
+
         return self.expression(
-            exp.TableFormatProperty,
-            this=exp.Literal.string("TABLE_FORMAT"),
-            value=self._parse_var_or_string(),
+            exp.ReturnsProperty,
+            this=exp.Literal.string("RETURNS"),
+            value=value,
+            is_table=is_table,
         )
 
-    def _parse_properties(self, schema=None):
-        """
-        Schema is included since if the table schema is defined and we later get a partition by expression
-        then we will define those columns in the partition by section and not in with the rest of the
-        columns
-        """
+    def _parse_execute_as(self):
+        self._match(TokenType.ALIAS)
+        return self.expression(
+            exp.ExecuteAsProperty,
+            this=exp.Literal.string("EXECUTE AS"),
+            value=self._parse_var(),
+        )
+
+    def _parse_properties(self):
         properties = []
 
         while True:
             if self._match(TokenType.WITH):
                 self._match_l_paren()
-                properties.extend(self._parse_csv(lambda: self._parse_property(schema)))
+                properties.extend(self._parse_csv(lambda: self._parse_property()))
                 self._match_r_paren()
             elif self._match(TokenType.PROPERTIES):
                 self._match_l_paren()
@@ -805,7 +829,7 @@ class Parser:
                 )
                 self._match_r_paren()
             else:
-                identified_property = self._parse_property(schema)
+                identified_property = self._parse_property()
                 if not identified_property:
                     break
                 properties.append(identified_property)
@@ -822,7 +846,7 @@ class Parser:
             this=self._parse_table(schema=True),
             exists=self._parse_exists(),
             partition=self._parse_partition(),
-            expression=self._parse_select(),
+            expression=self._parse_select(nested=True),
             overwrite=overwrite,
         )
 
@@ -840,8 +864,7 @@ class Parser:
             exp.Update,
             **{
                 "this": self._parse_table(schema=True),
-                "expressions": self._match(TokenType.SET)
-                and self._parse_csv(self._parse_equality),
+                "expressions": self._match(TokenType.SET) and self._parse_csv(self._parse_equality),
                 "from": self._parse_from(),
                 "where": self._parse_where(),
             },
@@ -876,7 +899,7 @@ class Parser:
             this=table,
             lazy=lazy,
             options=options,
-            expression=self._parse_select(),
+            expression=self._parse_select(nested=True),
         )
 
     def _parse_partition(self):
@@ -905,10 +928,21 @@ class Parser:
         self._match_r_paren()
         return self.expression(exp.Tuple, expressions=expressions)
 
-    def _parse_select(self, table=None):
-        index = self._index
+    def _parse_select(self, nested=False, table=False):
+        cte = self._parse_with()
+        if cte:
+            this = self._parse_statement()
 
-        if self._match(TokenType.SELECT):
+            if not this:
+                self.raise_error("Failed to parse any statement following CTE")
+                return cte
+
+            if "with" in this.arg_types:
+                this.set("with", cte)
+            else:
+                self.raise_error(f"{this.key} does not support CTE")
+                this = cte
+        elif self._match(TokenType.SELECT):
             hint = self._parse_hint()
             all_ = self._match(TokenType.ALL)
             distinct = self._match(TokenType.DISTINCT)
@@ -923,9 +957,7 @@ class Parser:
                 self.raise_error("Cannot specify both ALL and DISTINCT after SELECT")
 
             limit = self._parse_limit(top=True)
-            expressions = self._parse_csv(
-                lambda: self._parse_annotation(self._parse_expression())
-            )
+            expressions = self._parse_csv(lambda: self._parse_annotation(self._parse_expression()))
 
             this = self.expression(
                 exp.Select,
@@ -938,52 +970,13 @@ class Parser:
             if from_:
                 this.set("from", from_)
             self._parse_query_modifiers(this)
-        elif self._match(TokenType.WITH):
-            recursive = self._match(TokenType.RECURSIVE)
-
-            expressions = []
-
-            while True:
-                expressions.append(self._parse_cte())
-
-                if not self._match(TokenType.COMMA):
-                    break
-
-            cte = self.expression(
-                exp.With,
-                expressions=expressions,
-                recursive=recursive,
-            )
-            this = self._parse_statement()
-
-            if not this:
-                self.raise_error("Failed to parse any statement following CTE")
-                return cte
-
-            if "with" in this.arg_types:
-                this.set(
-                    "with",
-                    self.expression(
-                        exp.With,
-                        expressions=expressions,
-                        recursive=recursive,
-                    ),
-                )
-            else:
-                self.raise_error(f"{this.key} does not support CTE")
-        elif self._match(TokenType.L_PAREN):
-            this = self._parse_table() if table else self._parse_select()
-
-            if this:
-                self._parse_query_modifiers(this)
-                self._match_r_paren()
-                this = self._parse_subquery(this)
-            else:
-                self._retreat(index)
+        elif (table or nested) and self._match(TokenType.L_PAREN):
+            this = self._parse_table() if table else self._parse_select(nested=True)
+            self._parse_query_modifiers(this)
+            self._match_r_paren()
+            this = self._parse_subquery(this)
         elif self._match(TokenType.VALUES):
-            this = self.expression(
-                exp.Values, expressions=self._parse_csv(self._parse_value)
-            )
+            this = self.expression(exp.Values, expressions=self._parse_csv(self._parse_value))
             alias = self._parse_table_alias()
             if alias:
                 this = self.expression(exp.Subquery, this=this, alias=alias)
@@ -992,6 +985,26 @@ class Parser:
 
         return self._parse_set_operations(this) if this else None
 
+    def _parse_with(self):
+        if not self._match(TokenType.WITH):
+            return None
+
+        recursive = self._match(TokenType.RECURSIVE)
+
+        expressions = []
+
+        while True:
+            expressions.append(self._parse_cte())
+
+            if not self._match(TokenType.COMMA):
+                break
+
+        return self.expression(
+            exp.With,
+            expressions=expressions,
+            recursive=recursive,
+        )
+
     def _parse_cte(self):
         alias = self._parse_table_alias()
         if not alias or not alias.this:
@@ -1012,7 +1025,7 @@ class Parser:
 
     def _parse_table_alias(self):
         any_token = self._match(TokenType.ALIAS)
-        alias = self._parse_id_var(any_token)
+        alias = self._parse_id_var(any_token=any_token, tokens=self.TABLE_ALIAS_TOKENS)
         columns = None
 
         if self._match(TokenType.L_PAREN):
@@ -1029,12 +1042,32 @@ class Parser:
         )
 
     def _parse_subquery(self, this):
-        return self.expression(exp.Subquery, this=this, alias=self._parse_table_alias())
+        return self.expression(
+            exp.Subquery,
+            this=this,
+            pivots=self._parse_pivots(),
+            alias=self._parse_table_alias(),
+        )
 
     def _parse_query_modifiers(self, this):
-        if not isinstance(this, (exp.Subquery, exp.Subqueryable, exp.Table)):
+        if not isinstance(this, self.MODIFIABLES):
             return
 
+        table = isinstance(this, exp.Table)
+
+        while True:
+            lateral = self._parse_lateral()
+            join = self._parse_join()
+            comma = None if table else self._match(TokenType.COMMA)
+            if lateral:
+                this.append("laterals", lateral)
+            if join:
+                this.append("joins", join)
+            if comma:
+                this.args["from"].append("expressions", self._parse_table())
+            if not (lateral or join or comma):
+                break
+
         for key, parser in self.QUERY_MODIFIER_PARSERS.items():
             expression = parser(self)
 
@@ -1043,9 +1076,7 @@ class Parser:
 
     def _parse_annotation(self, expression):
         if self._match(TokenType.ANNOTATION):
-            return self.expression(
-                exp.Annotation, this=self._prev.text, expression=expression
-            )
+            return self.expression(exp.Annotation, this=self._prev.text, expression=expression)
 
         return expression
 
@@ -1063,9 +1094,6 @@ class Parser:
 
         return self.expression(exp.From, expressions=self._parse_csv(self._parse_table))
 
-    def _parse_laterals(self):
-        return self._parse_all(self._parse_lateral)
-
     def _parse_lateral(self):
         if not self._match(TokenType.LATERAL):
             return None
@@ -1074,9 +1102,8 @@ class Parser:
 
         if subquery:
             return self.expression(exp.Lateral, this=subquery)
-        elif not self._match(TokenType.VIEW):
-            self.raise_error("Expected subquery or VIEW after LATERAL")
 
+        self._match(TokenType.VIEW)
         outer = self._match(TokenType.OUTER)
 
         return self.expression(
@@ -1086,31 +1113,27 @@ class Parser:
             alias=self.expression(
                 exp.TableAlias,
                 this=self._parse_id_var(any_token=False),
-                columns=(
-                    self._parse_csv(self._parse_id_var)
-                    if self._match(TokenType.ALIAS)
-                    else None
-                ),
+                columns=(self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else None),
             ),
         )
 
-    def _parse_joins(self):
-        return self._parse_all(self._parse_join)
-
     def _parse_join_side_and_kind(self):
         return (
+            self._match(TokenType.NATURAL) and self._prev,
             self._match_set(self.JOIN_SIDES) and self._prev,
             self._match_set(self.JOIN_KINDS) and self._prev,
         )
 
     def _parse_join(self):
-        side, kind = self._parse_join_side_and_kind()
+        natural, side, kind = self._parse_join_side_and_kind()
 
         if not self._match(TokenType.JOIN):
             return None
 
-        kwargs = {"this": self._parse_lateral() or self._parse_table()}
+        kwargs = {"this": self._parse_table()}
 
+        if natural:
+            kwargs["natural"] = True
         if side:
             kwargs["side"] = side.text
         if kind:
@@ -1135,11 +1158,21 @@ class Parser:
         )
 
     def _parse_table(self, schema=False):
+        lateral = self._parse_lateral()
+
+        if lateral:
+            return lateral
+
         unnest = self._parse_unnest()
 
         if unnest:
             return unnest
 
+        values = self._parse_derived_table_values()
+
+        if values:
+            return values
+
         subquery = self._parse_select(table=True)
 
         if subquery:
@@ -1150,14 +1183,18 @@ class Parser:
         table = (not schema and self._parse_function()) or self._parse_id_var(False)
 
         while self._match(TokenType.DOT):
-            catalog = db
-            db = table
-            table = self._parse_id_var()
+            if catalog:
+                # This allows nesting the table in arbitrarily many dot expressions if needed
+                table = self.expression(exp.Dot, this=table, expression=self._parse_id_var())
+            else:
+                catalog = db
+                db = table
+                table = self._parse_id_var()
 
         if not table:
             self.raise_error("Expected table name")
 
-        this = self.expression(exp.Table, this=table, db=db, catalog=catalog)
+        this = self.expression(exp.Table, this=table, db=db, catalog=catalog, pivots=self._parse_pivots())
 
         if schema:
             return self._parse_schema(this=this)
@@ -1187,9 +1224,7 @@ class Parser:
         expressions = self._parse_csv(self._parse_column)
         self._match_r_paren()
 
-        ordinality = bool(
-            self._match(TokenType.WITH) and self._match(TokenType.ORDINALITY)
-        )
+        ordinality = bool(self._match(TokenType.WITH) and self._match(TokenType.ORDINALITY))
 
         alias = self._parse_table_alias()
 
@@ -1206,6 +1241,24 @@ class Parser:
             alias=alias,
         )
 
+    def _parse_derived_table_values(self):
+        is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES)
+        if not is_derived and not self._match(TokenType.VALUES):
+            return None
+
+        expressions = self._parse_csv(self._parse_value)
+
+        if is_derived:
+            self._match_r_paren()
+
+        alias = self._parse_table_alias()
+
+        return self.expression(
+            exp.Values,
+            expressions=expressions,
+            alias=alias,
+        )
+
     def _parse_table_sample(self):
         if not self._match(TokenType.TABLE_SAMPLE):
             return None
@@ -1217,6 +1270,7 @@ class Parser:
         percent = None
         rows = None
         size = None
+        seed = None
 
         self._match_l_paren()
 
@@ -1238,6 +1292,11 @@ class Parser:
 
         self._match_r_paren()
 
+        if self._match(TokenType.SEED):
+            self._match_l_paren()
+            seed = self._parse_number()
+            self._match_r_paren()
+
         return self.expression(
             exp.TableSample,
             method=method,
@@ -1247,6 +1306,51 @@ class Parser:
             percent=percent,
             rows=rows,
             size=size,
+            seed=seed,
+        )
+
+    def _parse_pivots(self):
+        return list(iter(self._parse_pivot, None))
+
+    def _parse_pivot(self):
+        index = self._index
+
+        if self._match(TokenType.PIVOT):
+            unpivot = False
+        elif self._match(TokenType.UNPIVOT):
+            unpivot = True
+        else:
+            return None
+
+        expressions = []
+        field = None
+
+        if not self._match(TokenType.L_PAREN):
+            self._retreat(index)
+            return None
+
+        if unpivot:
+            expressions = self._parse_csv(self._parse_column)
+        else:
+            expressions = self._parse_csv(lambda: self._parse_alias(self._parse_function()))
+
+        if not self._match(TokenType.FOR):
+            self.raise_error("Expecting FOR")
+
+        value = self._parse_column()
+
+        if not self._match(TokenType.IN):
+            self.raise_error("Expecting IN")
+
+        field = self._parse_in(value)
+
+        self._match_r_paren()
+
+        return self.expression(
+            exp.Pivot,
+            expressions=expressions,
+            field=field,
+            unpivot=unpivot,
         )
 
     def _parse_where(self):
@@ -1295,17 +1399,13 @@ class Parser:
         if not self._match(TokenType.ORDER_BY):
             return this
 
-        return self.expression(
-            exp.Order, this=this, expressions=self._parse_csv(self._parse_ordered)
-        )
+        return self.expression(exp.Order, this=this, expressions=self._parse_csv(self._parse_ordered))
 
     def _parse_sort(self, token_type, exp_class):
         if not self._match(token_type):
             return None
 
-        return self.expression(
-            exp_class, expressions=self._parse_csv(self._parse_ordered)
-        )
+        return self.expression(exp_class, expressions=self._parse_csv(self._parse_ordered))
 
     def _parse_ordered(self):
         this = self._parse_conjunction()
@@ -1320,22 +1420,17 @@ class Parser:
         if (
             not explicitly_null_ordered
             and (
-                (asc and self.null_ordering == "nulls_are_small")
-                or (desc and self.null_ordering != "nulls_are_small")
+                (asc and self.null_ordering == "nulls_are_small") or (desc and self.null_ordering != "nulls_are_small")
             )
             and self.null_ordering != "nulls_are_last"
         ):
             nulls_first = True
 
-        return self.expression(
-            exp.Ordered, this=this, desc=desc, nulls_first=nulls_first
-        )
+        return self.expression(exp.Ordered, this=this, desc=desc, nulls_first=nulls_first)
 
     def _parse_limit(self, this=None, top=False):
         if self._match(TokenType.TOP if top else TokenType.LIMIT):
-            return self.expression(
-                exp.Limit, this=this, expression=self._parse_number()
-            )
+            return self.expression(exp.Limit, this=this, expression=self._parse_number())
         if self._match(TokenType.FETCH):
             direction = self._match_set((TokenType.FIRST, TokenType.NEXT))
             direction = self._prev.text if direction else "FIRST"
@@ -1369,7 +1464,7 @@ class Parser:
             expression,
             this=this,
             distinct=self._match(TokenType.DISTINCT) or not self._match(TokenType.ALL),
-            expression=self._parse_select(),
+            expression=self._parse_select(nested=True),
         )
 
     def _parse_expression(self):
@@ -1411,9 +1506,7 @@ class Parser:
             this = self.expression(exp.In, this=this, unnest=unnest)
         else:
             self._match_l_paren()
-            expressions = self._parse_csv(
-                lambda: self._parse_select() or self._parse_expression()
-            )
+            expressions = self._parse_csv(self._parse_select_or_expression)
 
             if len(expressions) == 1 and isinstance(expressions[0], exp.Subqueryable):
                 this = self.expression(exp.In, this=this, query=expressions[0])
@@ -1445,13 +1538,9 @@ class Parser:
                     expression=self._parse_term(),
                 )
             elif self._match_pair(TokenType.LT, TokenType.LT):
-                this = self.expression(
-                    exp.BitwiseLeftShift, this=this, expression=self._parse_term()
-                )
+                this = self.expression(exp.BitwiseLeftShift, this=this, expression=self._parse_term())
             elif self._match_pair(TokenType.GT, TokenType.GT):
-                this = self.expression(
-                    exp.BitwiseRightShift, this=this, expression=self._parse_term()
-                )
+                this = self.expression(exp.BitwiseRightShift, this=this, expression=self._parse_term())
             else:
                 break
 
@@ -1521,7 +1610,7 @@ class Parser:
             elif nested:
                 expressions = self._parse_csv(self._parse_types)
             else:
-                expressions = self._parse_csv(self._parse_number)
+                expressions = self._parse_csv(self._parse_type)
 
             if not expressions:
                 self._retreat(index)
@@ -1539,13 +1628,20 @@ class Parser:
                 self.raise_error("Expecting >")
 
         if type_token in self.TIMESTAMPS:
-            tz = self._match(TokenType.WITH_TIME_ZONE)
-            self._match(TokenType.WITHOUT_TIME_ZONE)
+            tz = self._match(TokenType.WITH_TIME_ZONE) or type_token == TokenType.TIMESTAMPTZ
             if tz:
                 return exp.DataType(
                     this=exp.DataType.Type.TIMESTAMPTZ,
                     expressions=expressions,
                 )
+            ltz = self._match(TokenType.WITH_LOCAL_TIME_ZONE) or type_token == TokenType.TIMESTAMPLTZ
+            if ltz:
+                return exp.DataType(
+                    this=exp.DataType.Type.TIMESTAMPLTZ,
+                    expressions=expressions,
+                )
+            self._match(TokenType.WITHOUT_TIME_ZONE)
+
             return exp.DataType(
                 this=exp.DataType.Type.TIMESTAMP,
                 expressions=expressions,
@@ -1603,22 +1699,23 @@ class Parser:
         if self._match_set(self.PRIMARY_PARSERS):
             return self.PRIMARY_PARSERS[self._prev.token_type](self, self._prev)
 
+        if self._match_pair(TokenType.DOT, TokenType.NUMBER):
+            return exp.Literal.number(f"0.{self._prev.text}")
+
         if self._match(TokenType.L_PAREN):
             query = self._parse_select()
 
             if query:
                 expressions = [query]
             else:
-                expressions = self._parse_csv(
-                    lambda: self._parse_alias(self._parse_conjunction(), explicit=True)
-                )
+                expressions = self._parse_csv(lambda: self._parse_alias(self._parse_conjunction(), explicit=True))
 
             this = list_get(expressions, 0)
             self._parse_query_modifiers(this)
             self._match_r_paren()
 
             if isinstance(this, exp.Subqueryable):
-                return self._parse_subquery(this)
+                return self._parse_set_operations(self._parse_subquery(this))
             if len(expressions) > 1:
                 return self.expression(exp.Tuple, expressions=expressions)
             return self.expression(exp.Paren, this=this)
@@ -1626,11 +1723,7 @@ class Parser:
         return None
 
     def _parse_field(self, any_token=False):
-        return (
-            self._parse_primary()
-            or self._parse_function()
-            or self._parse_id_var(any_token)
-        )
+        return self._parse_primary() or self._parse_function() or self._parse_id_var(any_token)
 
     def _parse_function(self):
         if not self._curr:
@@ -1643,21 +1736,22 @@ class Parser:
 
         if not self._next or self._next.token_type != TokenType.L_PAREN:
             if token_type in self.NO_PAREN_FUNCTIONS:
-                return self.expression(
-                    self._advance() or self.NO_PAREN_FUNCTIONS[token_type]
-                )
+                return self.expression(self._advance() or self.NO_PAREN_FUNCTIONS[token_type])
             return None
 
         if token_type not in self.FUNC_TOKENS:
             return None
 
-        if self._match_set(self.FUNCTION_PARSERS):
-            self._advance()
-            this = self.FUNCTION_PARSERS[token_type](self, token_type)
+        this = self._curr.text
+        upper = this.upper()
+        self._advance(2)
+
+        parser = self.FUNCTION_PARSERS.get(upper)
+
+        if parser:
+            this = parser(self)
         else:
             subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type)
-            this = self._curr.text
-            self._advance(2)
 
             if subquery_predicate and self._curr.token_type in (
                 TokenType.SELECT,
@@ -1667,7 +1761,7 @@ class Parser:
                 self._match_r_paren()
                 return this
 
-            function = self.FUNCTIONS.get(this.upper())
+            function = self.FUNCTIONS.get(upper)
             args = self._parse_csv(self._parse_lambda)
 
             if function:
@@ -1678,6 +1772,27 @@ class Parser:
         self._match_r_paren()
         return self._parse_window(this)
 
+    def _parse_user_defined_function(self):
+        this = self._parse_id_var()
+
+        while self._match(TokenType.DOT):
+            this = self.expression(exp.Dot, this=this, expression=self._parse_id_var())
+
+        if not self._match(TokenType.L_PAREN):
+            return this
+        expressions = self._parse_csv(self._parse_udf_kwarg)
+        self._match_r_paren()
+        return self.expression(exp.UserDefinedFunction, this=this, expressions=expressions)
+
+    def _parse_udf_kwarg(self):
+        this = self._parse_id_var()
+        kind = self._parse_types()
+
+        if not kind:
+            return this
+
+        return self.expression(exp.UserDefinedFunctionKwarg, this=this, kind=kind)
+
     def _parse_lambda(self):
         index = self._index
 
@@ -1703,9 +1818,10 @@ class Parser:
 
             return self._parse_alias(self._parse_limit(self._parse_order(this)))
 
+        conjunction = self._parse_conjunction().transform(self._replace_lambda, {node.name for node in expressions})
         return self.expression(
             exp.Lambda,
-            this=self._parse_conjunction(),
+            this=conjunction,
             expressions=expressions,
         )
 
@@ -1715,10 +1831,7 @@ class Parser:
             self._retreat(index)
             return this
 
-        args = self._parse_csv(
-            lambda: self._parse_constraint()
-            or self._parse_column_def(self._parse_field())
-        )
+        args = self._parse_csv(lambda: self._parse_constraint() or self._parse_column_def(self._parse_field(True)))
         self._match_r_paren()
         return self.expression(exp.Schema, this=this, expressions=args)
 
@@ -1735,12 +1848,9 @@ class Parser:
                 break
             constraints.append(constraint)
 
-        return self.expression(
-            exp.ColumnDef, this=this, kind=kind, constraints=constraints
-        )
+        return self.expression(exp.ColumnDef, this=this, kind=kind, constraints=constraints)
 
     def _parse_column_constraint(self):
-        kind = None
         this = None
 
         if self._match(TokenType.CONSTRAINT):
@@ -1750,28 +1860,28 @@ class Parser:
             kind = exp.AutoIncrementColumnConstraint()
         elif self._match(TokenType.CHECK):
             self._match_l_paren()
-            kind = self.expression(
-                exp.CheckColumnConstraint, this=self._parse_conjunction()
-            )
+            kind = self.expression(exp.CheckColumnConstraint, this=self._parse_conjunction())
             self._match_r_paren()
         elif self._match(TokenType.COLLATE):
             kind = self.expression(exp.CollateColumnConstraint, this=self._parse_var())
         elif self._match(TokenType.DEFAULT):
-            kind = self.expression(
-                exp.DefaultColumnConstraint, this=self._parse_field()
-            )
-        elif self._match(TokenType.NOT) and self._match(TokenType.NULL):
+            kind = self.expression(exp.DefaultColumnConstraint, this=self._parse_field())
+        elif self._match_pair(TokenType.NOT, TokenType.NULL):
             kind = exp.NotNullColumnConstraint()
         elif self._match(TokenType.SCHEMA_COMMENT):
-            kind = self.expression(
-                exp.CommentColumnConstraint, this=self._parse_string()
-            )
+            kind = self.expression(exp.CommentColumnConstraint, this=self._parse_string())
         elif self._match(TokenType.PRIMARY_KEY):
             kind = exp.PrimaryKeyColumnConstraint()
         elif self._match(TokenType.UNIQUE):
             kind = exp.UniqueColumnConstraint()
-
-        if kind is None:
+        elif self._match(TokenType.GENERATED):
+            if self._match(TokenType.BY_DEFAULT):
+                kind = self.expression(exp.GeneratedAsIdentityColumnConstraint, this=False)
+            else:
+                self._match(TokenType.ALWAYS)
+                kind = self.expression(exp.GeneratedAsIdentityColumnConstraint, this=True)
+            self._match_pair(TokenType.ALIAS, TokenType.IDENTITY)
+        else:
             return None
 
         return self.expression(exp.ColumnConstraint, this=this, kind=kind)
@@ -1879,9 +1989,7 @@ class Parser:
         if not self._match(TokenType.END):
             self.raise_error("Expected END after CASE", self._prev)
 
-        return self._parse_window(
-            self.expression(exp.Case, this=expression, ifs=ifs, default=default)
-        )
+        return self._parse_window(self.expression(exp.Case, this=expression, ifs=ifs, default=default))
 
     def _parse_if(self):
         if self._match(TokenType.L_PAREN):
@@ -1901,8 +2009,11 @@ class Parser:
     def _parse_extract(self):
         this = self._parse_var() or self._parse_type()
 
-        if not self._match(TokenType.FROM):
-            self.raise_error("Expected FROM after EXTRACT", self._prev)
+        if self._match(TokenType.FROM):
+            return self.expression(exp.Extract, this=this, expression=self._parse_bitwise())
+
+        if not self._match(TokenType.COMMA):
+            self.raise_error("Expected FROM or comma after EXTRACT", self._prev)
 
         return self.expression(exp.Extract, this=this, expression=self._parse_bitwise())
 
@@ -1932,6 +2043,18 @@ class Parser:
             to = None
         return self.expression(exp.Cast, this=this, to=to)
 
+    def _parse_position(self):
+        args = self._parse_csv(self._parse_bitwise)
+
+        if self._match(TokenType.IN):
+            args.append(self._parse_bitwise())
+
+        # Note: we're parsing in order needle, haystack, position
+        this = exp.StrPosition.from_arg_list(args)
+        self.validate_expression(this, args)
+
+        return this
+
     def _parse_substring(self):
         # Postgres supports the form: substring(string [from int] [for int])
         # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6
@@ -1979,9 +2102,7 @@ class Parser:
     def _parse_window(self, this, alias=False):
         if self._match(TokenType.FILTER):
             self._match_l_paren()
-            this = self.expression(
-                exp.Filter, this=this, expression=self._parse_where()
-            )
+            this = self.expression(exp.Filter, this=this, expression=self._parse_where())
             self._match_r_paren()
 
         if self._match(TokenType.WITHIN_GROUP):
@@ -2070,13 +2191,9 @@ class Parser:
         self._match(TokenType.BETWEEN)
 
         return {
-            "value": (
-                self._match_set((TokenType.UNBOUNDED, TokenType.CURRENT_ROW))
-                and self._prev.text
-            )
+            "value": (self._match_set((TokenType.UNBOUNDED, TokenType.CURRENT_ROW)) and self._prev.text)
             or self._parse_bitwise(),
-            "side": self._match_set((TokenType.PRECEDING, TokenType.FOLLOWING))
-            and self._prev.text,
+            "side": self._match_set((TokenType.PRECEDING, TokenType.FOLLOWING)) and self._prev.text,
         }
 
     def _parse_alias(self, this, explicit=False):
@@ -2101,22 +2218,16 @@ class Parser:
 
         return this
 
-    def _parse_id_var(self, any_token=True):
+    def _parse_id_var(self, any_token=True, tokens=None):
         identifier = self._parse_identifier()
 
         if identifier:
             return identifier
 
-        if (
-            any_token
-            and self._curr
-            and self._curr.token_type not in self.RESERVED_KEYWORDS
-        ):
+        if any_token and self._curr and self._curr.token_type not in self.RESERVED_KEYWORDS:
             return self._advance() or exp.Identifier(this=self._prev.text, quoted=False)
 
-        return self._match_set(self.ID_VAR_TOKENS) and exp.Identifier(
-            this=self._prev.text, quoted=False
-        )
+        return self._match_set(tokens or self.ID_VAR_TOKENS) and exp.Identifier(this=self._prev.text, quoted=False)
 
     def _parse_string(self):
         if self._match(TokenType.STRING):
@@ -2155,9 +2266,7 @@ class Parser:
 
     def _parse_star(self):
         if self._match(TokenType.STAR):
-            return exp.Star(
-                **{"except": self._parse_except(), "replace": self._parse_replace()}
-            )
+            return exp.Star(**{"except": self._parse_except(), "replace": self._parse_replace()})
         return None
 
     def _parse_placeholder(self):
@@ -2195,21 +2304,19 @@ class Parser:
         this = parse()
 
         while self._match_set(expressions):
-            this = self.expression(
-                expressions[self._prev.token_type], this=this, expression=parse()
-            )
+            this = self.expression(expressions[self._prev.token_type], this=this, expression=parse())
 
         return this
 
-    def _parse_all(self, parse):
-        return list(iter(parse, None))
-
     def _parse_wrapped_id_vars(self):
         self._match_l_paren()
         expressions = self._parse_csv(self._parse_id_var)
         self._match_r_paren()
         return expressions
 
+    def _parse_select_or_expression(self):
+        return self._parse_select() or self._parse_expression()
+
     def _match(self, token_type):
         if not self._curr:
             return None
@@ -2234,10 +2341,7 @@ class Parser:
         if not self._curr or not self._next:
             return None
 
-        if (
-            self._curr.token_type == token_type_a
-            and self._next.token_type == token_type_b
-        ):
+        if self._curr.token_type == token_type_a and self._next.token_type == token_type_b:
             if advance:
                 self._advance(2)
             return True
@@ -2266,3 +2370,9 @@ class Parser:
         elif isinstance(this, exp.Identifier):
             this = self.expression(exp.Var, this=this.name)
         return this
+
+    def _replace_lambda(self, node, lambda_variables):
+        if isinstance(node, exp.Column):
+            if node.name in lambda_variables:
+                return node.this
+        return node


----

problem_statement: cast to map with clickhouse dialect ignores case
Yet another clickhouse case sensitivity bug :)


ClickHouse CLI:

```
albatross :) select cast(map('a', 1, 'b', 2) as Map(String, Int8));

SELECT CAST(map('a', 1, 'b', 2), 'Map(String, Int8)')

Query id: bab188fb-69f7-4c0f-9bde-ac524a08f6e8

┌─CAST(map('a', 1, 'b', 2), 'Map(String, Int8)')─┐
│ {'a':1,'b':2}                                  │
└────────────────────────────────────────────────┘

1 row in set. Elapsed: 0.001 sec.
```

sqlglot:

```
In [7]: sg.parse_one("cast(map('a', 1, 'b', 2) as Map(String, Int8))", read="clickhouse").sql(dialect="clickhouse")
Out[7]: "CAST(map('a', 1, 'b', 2) AS MAP(TEXT, TINYINT))"
```

Perhaps there's a way to disable all case transformations for the clickhouse dialect.
date: 2022-10-06
patch: diff --git a/sqlglot/helper.py b/sqlglot/helper.py
index e3d3990..f75a095 100644
--- a/sqlglot/helper.py
+++ b/sqlglot/helper.py
@@ -1,5 +1,7 @@
+import inspect
 import logging
 import re
+import sys
 from contextlib import contextmanager
 from enum import Enum
 
@@ -38,6 +40,18 @@ def camel_to_snake_case(name):
     return CAMEL_CASE_PATTERN.sub("_", name).upper()
 
 
+def subclasses(module_name, clazz, exclude=()):
+    return [
+        obj
+        for _, obj in inspect.getmembers(
+            sys.modules[module_name],
+            lambda obj: inspect.isclass(obj)
+            and issubclass(obj, clazz)
+            and obj not in exclude,
+        )
+    ]
+
+
 def while_changing(expression, func):
     while True:
         start = hash(expression)
diff --git a/sqlglot/dialects/clickhouse.py b/sqlglot/dialects/clickhouse.py
index 6a141a1a0..f25f6dac5 100644
--- a/sqlglot/dialects/clickhouse.py
+++ b/sqlglot/dialects/clickhouse.py
@@ -52,6 +52,9 @@ class ClickHouse(Dialect):
             **Generator.TYPE_MAPPING,
             exp.DataType.Type.NULLABLE: "Nullable",
             exp.DataType.Type.DATETIME: "DateTime64",
+            exp.DataType.Type.MAP: "Map",
+            exp.DataType.Type.ARRAY: "Array",
+            exp.DataType.Type.STRUCT: "Tuple",
         }
 
         TRANSFORMS = {