From 9a23e28b0aa2269d30cc61cfb50af0370dc796ec Mon Sep 17 00:00:00 2001 From: George Sittas Date: Sat, 2 Nov 2024 10:52:39 +0200 Subject: [PATCH 1/3] Fix(parser)!: always parse INFORMATION_SCHEMA.X table ref into a dot --- sqlglot/dialects/bigquery.py | 9 ++++++++- sqlglot/dialects/databricks.py | 6 ++++++ sqlglot/dialects/hive.py | 1 + sqlglot/dialects/sqlite.py | 1 + sqlglot/optimizer/qualify_tables.py | 2 +- sqlglot/parser.py | 6 +++++- sqlglot/tokens.py | 2 ++ tests/dialects/test_bigquery.py | 7 +++++++ tests/fixtures/optimizer/qualify_tables.sql | 16 ++++++++++++++++ 9 files changed, 47 insertions(+), 3 deletions(-) diff --git a/sqlglot/dialects/bigquery.py b/sqlglot/dialects/bigquery.py index 3a71708820..fe8083c530 100644 --- a/sqlglot/dialects/bigquery.py +++ b/sqlglot/dialects/bigquery.py @@ -574,12 +574,19 @@ def _parse_table_parts( table.set("db", exp.Identifier(this=parts[0])) table.set("this", exp.Identifier(this=parts[1])) - if isinstance(table.this, exp.Identifier) and any("." in p.name for p in table.parts): + if isinstance(table.this, (exp.Identifier, exp.Dot)) and any( + "." in p.name for p in table.parts + ): catalog, db, this, *rest = ( exp.to_identifier(p, quoted=True) for p in split_num_words(".".join(p.name for p in table.parts), ".", 3) ) + if db and db.name.upper() == "INFORMATION_SCHEMA": + this = exp.Dot.build([db, this]) # type: ignore + db = catalog + catalog, *rest = rest or [None] + if rest and this: this = exp.Dot.build([this, *rest]) # type: ignore diff --git a/sqlglot/dialects/databricks.py b/sqlglot/dialects/databricks.py index d1127f8658..58f00dd0b9 100644 --- a/sqlglot/dialects/databricks.py +++ b/sqlglot/dialects/databricks.py @@ -35,6 +35,12 @@ class Databricks(Spark): class JSONPathTokenizer(jsonpath.JSONPathTokenizer): IDENTIFIERS = ["`", '"'] + class Tokenizer(Spark.Tokenizer): + KEYWORDS = { + **Spark.Tokenizer.KEYWORDS, + "INFORMATION_SCHEMA": TokenType.INFORMATION_SCHEMA, + } + class Parser(Spark.Parser): LOG_DEFAULTS_TO_LN = True STRICT_CAST = True diff --git a/sqlglot/dialects/hive.py b/sqlglot/dialects/hive.py index af759d10c8..e2202895f7 100644 --- a/sqlglot/dialects/hive.py +++ b/sqlglot/dialects/hive.py @@ -259,6 +259,7 @@ class Tokenizer(tokens.Tokenizer): "VERSION AS OF": TokenType.VERSION_SNAPSHOT, "SERDEPROPERTIES": TokenType.SERDE_PROPERTIES, } + KEYWORDS.pop("INFORMATION_SCHEMA") NUMERIC_LITERALS = { "L": "BIGINT", diff --git a/sqlglot/dialects/sqlite.py b/sqlglot/dialects/sqlite.py index 68deeb062d..8d52d92250 100644 --- a/sqlglot/dialects/sqlite.py +++ b/sqlglot/dialects/sqlite.py @@ -109,6 +109,7 @@ class Tokenizer(tokens.Tokenizer): KEYWORDS = tokens.Tokenizer.KEYWORDS.copy() KEYWORDS.pop("/*+") + KEYWORDS.pop("INFORMATION_SCHEMA") class Parser(parser.Parser): FUNCTIONS = { diff --git a/sqlglot/optimizer/qualify_tables.py b/sqlglot/optimizer/qualify_tables.py index edf78e989d..0a212f58d8 100644 --- a/sqlglot/optimizer/qualify_tables.py +++ b/sqlglot/optimizer/qualify_tables.py @@ -51,7 +51,7 @@ def qualify_tables( catalog = exp.parse_identifier(catalog, dialect=dialect) if catalog else None def _qualify(table: exp.Table) -> None: - if isinstance(table.this, exp.Identifier): + if isinstance(table.this, (exp.Identifier, exp.Dot)): if not table.args.get("db"): table.set("db", db) if not table.args.get("catalog") and table.args.get("db"): diff --git a/sqlglot/parser.py b/sqlglot/parser.py index e035dc909f..943b135703 100644 --- a/sqlglot/parser.py +++ b/sqlglot/parser.py @@ -483,6 +483,7 @@ class Parser(metaclass=_Parser): TokenType.FORMAT, TokenType.FULL, TokenType.IDENTIFIER, + TokenType.INFORMATION_SCHEMA, TokenType.IS, TokenType.ISNULL, TokenType.INTERVAL, @@ -3604,8 +3605,9 @@ def _parse_table_parts( db = None table: t.Optional[exp.Expression | str] = self._parse_table_part(schema=schema) + prev = self._prev while self._match(TokenType.DOT): - if catalog: + if catalog or (prev and prev.token_type == TokenType.INFORMATION_SCHEMA): # This allows nesting the table in arbitrarily many dot expressions if needed table = self.expression( exp.Dot, this=table, expression=self._parse_table_part(schema=schema) @@ -3616,6 +3618,8 @@ def _parse_table_parts( # "" used for tsql FROM a..b case table = self._parse_table_part(schema=schema) or "" + prev = self._prev + if ( wildcard and self._is_connected() diff --git a/sqlglot/tokens.py b/sqlglot/tokens.py index 3cd5669884..a3b7dfb406 100644 --- a/sqlglot/tokens.py +++ b/sqlglot/tokens.py @@ -289,6 +289,7 @@ class TokenType(AutoName): ILIKE_ANY = auto() IN = auto() INDEX = auto() + INFORMATION_SCHEMA = auto() INNER = auto() INSERT = auto() INTERSECT = auto() @@ -731,6 +732,7 @@ class Tokenizer(metaclass=_Tokenizer): "ILIKE": TokenType.ILIKE, "IN": TokenType.IN, "INDEX": TokenType.INDEX, + "INFORMATION_SCHEMA": TokenType.INFORMATION_SCHEMA, "INET": TokenType.INET, "INNER": TokenType.INNER, "INSERT": TokenType.INSERT, diff --git a/tests/dialects/test_bigquery.py b/tests/dialects/test_bigquery.py index 0e91f924b5..f986faf09f 100644 --- a/tests/dialects/test_bigquery.py +++ b/tests/dialects/test_bigquery.py @@ -85,6 +85,13 @@ def test_bigquery(self): "PARSE_TIMESTAMP('%Y-%m-%dT%H:%M:%E*S%z', x)", ) + for prefix in ("c.db.", "db.", ""): + with self.subTest(f"Parsing {prefix}INFORMATION_SCHEMA.X into a Table"): + table = parse_one( + f"`{prefix}INFORMATION_SCHEMA.X`", dialect="bigquery", into=exp.Table + ) + self.assertIsInstance(table.this, exp.Dot) + table = parse_one("x-0._y.z", dialect="bigquery", into=exp.Table) self.assertEqual(table.catalog, "x-0") self.assertEqual(table.db, "_y") diff --git a/tests/fixtures/optimizer/qualify_tables.sql b/tests/fixtures/optimizer/qualify_tables.sql index 49e07faf9b..2e3bcf3947 100644 --- a/tests/fixtures/optimizer/qualify_tables.sql +++ b/tests/fixtures/optimizer/qualify_tables.sql @@ -14,6 +14,22 @@ SELECT 1 FROM x.y.z AS z; SELECT 1 FROM x.y.z AS z; SELECT 1 FROM x.y.z AS z; +# title: only information schema +SELECT * FROM information_schema.tables; +SELECT * FROM c.db.information_schema.tables AS tables; + +# title: information schema with db +SELECT * FROM y.information_schema.tables; +SELECT * FROM c.y.information_schema.tables AS tables; + +# title: information schema with db, catalog +SELECT * FROM x.y.information_schema.tables; +SELECT * FROM x.y.information_schema.tables AS tables; + +# title: information schema with db, catalog, alias +SELECT * FROM x.y.information_schema.tables AS z; +SELECT * FROM x.y.information_schema.tables AS z; + # title: redshift unnest syntax, z.a should be a column, not a table # dialect: redshift SELECT 1 FROM y.z AS z, z.a; From 378efbb1afea60954b88313d056e1f70fc13820b Mon Sep 17 00:00:00 2001 From: George Sittas Date: Mon, 4 Nov 2024 17:17:17 +0200 Subject: [PATCH 2/3] PR feedback --- sqlglot/dialects/bigquery.py | 12 ++++++++++++ sqlglot/dialects/databricks.py | 6 ------ sqlglot/dialects/hive.py | 1 - sqlglot/dialects/sqlite.py | 1 - sqlglot/parser.py | 6 +----- sqlglot/schema.py | 14 +++++--------- sqlglot/tokens.py | 2 -- tests/dialects/test_bigquery.py | 9 +++------ tests/fixtures/optimizer/qualify_tables.sql | 4 ++++ 9 files changed, 25 insertions(+), 30 deletions(-) diff --git a/sqlglot/dialects/bigquery.py b/sqlglot/dialects/bigquery.py index fe8083c530..6b1ca1057b 100644 --- a/sqlglot/dialects/bigquery.py +++ b/sqlglot/dialects/bigquery.py @@ -561,6 +561,18 @@ def _parse_table_parts( schema=schema, is_db_reference=is_db_reference, wildcard=True ) + # The `INFORMATION_SCHEMA` views in BigQuery need to be qualified by a region or + # dataset, so if the project identifier is omitted we need to fix the ast so that + # the `INFORMATION_SCHEMA.X` bit is represented as a Dot. Otherwise, we wouldn't + # correctly qualify a `Table` node that references these views, because it would + # seem like the "catalog" part is set, when it'd actually be the region/dataset. + # + # See: https://cloud.google.com/bigquery/docs/information-schema-intro#syntax + if table.db.upper() == "INFORMATION_SCHEMA": + table.set("this", exp.Dot.build([table.args["db"].pop(), table.this.pop()])) + table.set("db", table.args.get("catalog")) + table.set("catalog", None) + # proj-1.db.tbl -- `1.` is tokenized as a float so we need to unravel it here if not table.catalog: if table.db: diff --git a/sqlglot/dialects/databricks.py b/sqlglot/dialects/databricks.py index 58f00dd0b9..d1127f8658 100644 --- a/sqlglot/dialects/databricks.py +++ b/sqlglot/dialects/databricks.py @@ -35,12 +35,6 @@ class Databricks(Spark): class JSONPathTokenizer(jsonpath.JSONPathTokenizer): IDENTIFIERS = ["`", '"'] - class Tokenizer(Spark.Tokenizer): - KEYWORDS = { - **Spark.Tokenizer.KEYWORDS, - "INFORMATION_SCHEMA": TokenType.INFORMATION_SCHEMA, - } - class Parser(Spark.Parser): LOG_DEFAULTS_TO_LN = True STRICT_CAST = True diff --git a/sqlglot/dialects/hive.py b/sqlglot/dialects/hive.py index e2202895f7..af759d10c8 100644 --- a/sqlglot/dialects/hive.py +++ b/sqlglot/dialects/hive.py @@ -259,7 +259,6 @@ class Tokenizer(tokens.Tokenizer): "VERSION AS OF": TokenType.VERSION_SNAPSHOT, "SERDEPROPERTIES": TokenType.SERDE_PROPERTIES, } - KEYWORDS.pop("INFORMATION_SCHEMA") NUMERIC_LITERALS = { "L": "BIGINT", diff --git a/sqlglot/dialects/sqlite.py b/sqlglot/dialects/sqlite.py index 8d52d92250..68deeb062d 100644 --- a/sqlglot/dialects/sqlite.py +++ b/sqlglot/dialects/sqlite.py @@ -109,7 +109,6 @@ class Tokenizer(tokens.Tokenizer): KEYWORDS = tokens.Tokenizer.KEYWORDS.copy() KEYWORDS.pop("/*+") - KEYWORDS.pop("INFORMATION_SCHEMA") class Parser(parser.Parser): FUNCTIONS = { diff --git a/sqlglot/parser.py b/sqlglot/parser.py index 943b135703..e035dc909f 100644 --- a/sqlglot/parser.py +++ b/sqlglot/parser.py @@ -483,7 +483,6 @@ class Parser(metaclass=_Parser): TokenType.FORMAT, TokenType.FULL, TokenType.IDENTIFIER, - TokenType.INFORMATION_SCHEMA, TokenType.IS, TokenType.ISNULL, TokenType.INTERVAL, @@ -3605,9 +3604,8 @@ def _parse_table_parts( db = None table: t.Optional[exp.Expression | str] = self._parse_table_part(schema=schema) - prev = self._prev while self._match(TokenType.DOT): - if catalog or (prev and prev.token_type == TokenType.INFORMATION_SCHEMA): + if catalog: # This allows nesting the table in arbitrarily many dot expressions if needed table = self.expression( exp.Dot, this=table, expression=self._parse_table_part(schema=schema) @@ -3618,8 +3616,6 @@ def _parse_table_parts( # "" used for tsql FROM a..b case table = self._parse_table_part(schema=schema) or "" - prev = self._prev - if ( wildcard and self._is_connected() diff --git a/sqlglot/schema.py b/sqlglot/schema.py index 8a551949b0..c8362e0811 100644 --- a/sqlglot/schema.py +++ b/sqlglot/schema.py @@ -151,9 +151,7 @@ def supported_table_args(self) -> t.Tuple[str, ...]: return self._supported_table_args def table_parts(self, table: exp.Table) -> t.List[str]: - if isinstance(table.this, exp.ReadCSV): - return [table.this.name] - return [table.text(part) for part in exp.TABLE_PARTS if table.text(part)] + return [part.name for part in reversed(table.parts)] def find( self, table: exp.Table, raise_on_missing: bool = True, ensure_data_types: bool = False @@ -417,12 +415,10 @@ def _normalize_table( normalized_table = exp.maybe_parse(table, into=exp.Table, dialect=dialect, copy=normalize) if normalize: - for arg in exp.TABLE_PARTS: - value = normalized_table.args.get(arg) - if isinstance(value, exp.Identifier): - normalized_table.set( - arg, - normalize_name(value, dialect=dialect, is_table=True, normalize=normalize), + for part in normalized_table.parts: + if isinstance(part, exp.Identifier): + part.replace( + normalize_name(part, dialect=dialect, is_table=True, normalize=normalize) ) return normalized_table diff --git a/sqlglot/tokens.py b/sqlglot/tokens.py index a3b7dfb406..3cd5669884 100644 --- a/sqlglot/tokens.py +++ b/sqlglot/tokens.py @@ -289,7 +289,6 @@ class TokenType(AutoName): ILIKE_ANY = auto() IN = auto() INDEX = auto() - INFORMATION_SCHEMA = auto() INNER = auto() INSERT = auto() INTERSECT = auto() @@ -732,7 +731,6 @@ class Tokenizer(metaclass=_Tokenizer): "ILIKE": TokenType.ILIKE, "IN": TokenType.IN, "INDEX": TokenType.INDEX, - "INFORMATION_SCHEMA": TokenType.INFORMATION_SCHEMA, "INET": TokenType.INET, "INNER": TokenType.INNER, "INSERT": TokenType.INSERT, diff --git a/tests/dialects/test_bigquery.py b/tests/dialects/test_bigquery.py index f986faf09f..a4f6d3e914 100644 --- a/tests/dialects/test_bigquery.py +++ b/tests/dialects/test_bigquery.py @@ -9,7 +9,6 @@ UnsupportedError, exp, parse, - parse_one, transpile, ) from sqlglot.helper import logger as helper_logger @@ -87,17 +86,15 @@ def test_bigquery(self): for prefix in ("c.db.", "db.", ""): with self.subTest(f"Parsing {prefix}INFORMATION_SCHEMA.X into a Table"): - table = parse_one( - f"`{prefix}INFORMATION_SCHEMA.X`", dialect="bigquery", into=exp.Table - ) + table = self.parse_one(f"`{prefix}INFORMATION_SCHEMA.X`", into=exp.Table) self.assertIsInstance(table.this, exp.Dot) - table = parse_one("x-0._y.z", dialect="bigquery", into=exp.Table) + table = self.parse_one("x-0._y.z", into=exp.Table) self.assertEqual(table.catalog, "x-0") self.assertEqual(table.db, "_y") self.assertEqual(table.name, "z") - table = parse_one("x-0._y", dialect="bigquery", into=exp.Table) + table = self.parse_one("x-0._y", into=exp.Table) self.assertEqual(table.db, "x-0") self.assertEqual(table.name, "_y") diff --git a/tests/fixtures/optimizer/qualify_tables.sql b/tests/fixtures/optimizer/qualify_tables.sql index 2e3bcf3947..441a0794d9 100644 --- a/tests/fixtures/optimizer/qualify_tables.sql +++ b/tests/fixtures/optimizer/qualify_tables.sql @@ -15,18 +15,22 @@ SELECT 1 FROM x.y.z AS z; SELECT 1 FROM x.y.z AS z; # title: only information schema +# dialect: bigquery SELECT * FROM information_schema.tables; SELECT * FROM c.db.information_schema.tables AS tables; # title: information schema with db +# dialect: bigquery SELECT * FROM y.information_schema.tables; SELECT * FROM c.y.information_schema.tables AS tables; # title: information schema with db, catalog +# dialect: bigquery SELECT * FROM x.y.information_schema.tables; SELECT * FROM x.y.information_schema.tables AS tables; # title: information schema with db, catalog, alias +# dialect: bigquery SELECT * FROM x.y.information_schema.tables AS z; SELECT * FROM x.y.information_schema.tables AS z; From faff23eb2312f2290255cc1e62665983c1f1d359 Mon Sep 17 00:00:00 2001 From: George Sittas Date: Mon, 4 Nov 2024 19:37:33 +0200 Subject: [PATCH 3/3] Refactor: produce a single identifeir for information schema view --- sqlglot/dialects/bigquery.py | 53 +++++++++++++-------- tests/dialects/test_bigquery.py | 21 ++++++-- tests/fixtures/optimizer/qualify_tables.sql | 8 ++-- tests/test_optimizer.py | 2 +- 4 files changed, 54 insertions(+), 30 deletions(-) diff --git a/sqlglot/dialects/bigquery.py b/sqlglot/dialects/bigquery.py index 6b1ca1057b..1f0f7a0fe6 100644 --- a/sqlglot/dialects/bigquery.py +++ b/sqlglot/dialects/bigquery.py @@ -561,18 +561,6 @@ def _parse_table_parts( schema=schema, is_db_reference=is_db_reference, wildcard=True ) - # The `INFORMATION_SCHEMA` views in BigQuery need to be qualified by a region or - # dataset, so if the project identifier is omitted we need to fix the ast so that - # the `INFORMATION_SCHEMA.X` bit is represented as a Dot. Otherwise, we wouldn't - # correctly qualify a `Table` node that references these views, because it would - # seem like the "catalog" part is set, when it'd actually be the region/dataset. - # - # See: https://cloud.google.com/bigquery/docs/information-schema-intro#syntax - if table.db.upper() == "INFORMATION_SCHEMA": - table.set("this", exp.Dot.build([table.args["db"].pop(), table.this.pop()])) - table.set("db", table.args.get("catalog")) - table.set("catalog", None) - # proj-1.db.tbl -- `1.` is tokenized as a float so we need to unravel it here if not table.catalog: if table.db: @@ -586,19 +574,12 @@ def _parse_table_parts( table.set("db", exp.Identifier(this=parts[0])) table.set("this", exp.Identifier(this=parts[1])) - if isinstance(table.this, (exp.Identifier, exp.Dot)) and any( - "." in p.name for p in table.parts - ): + if isinstance(table.this, exp.Identifier) and any("." in p.name for p in table.parts): + alias = table.this catalog, db, this, *rest = ( exp.to_identifier(p, quoted=True) for p in split_num_words(".".join(p.name for p in table.parts), ".", 3) ) - - if db and db.name.upper() == "INFORMATION_SCHEMA": - this = exp.Dot.build([db, this]) # type: ignore - db = catalog - catalog, *rest = rest or [None] - if rest and this: this = exp.Dot.build([this, *rest]) # type: ignore @@ -606,6 +587,36 @@ def _parse_table_parts( this=this, db=db, catalog=catalog, pivots=table.args.get("pivots") ) table.meta["quoted_table"] = True + else: + alias = None + + # The `INFORMATION_SCHEMA` views in BigQuery need to be qualified by a region or + # dataset, so if the project identifier is omitted we need to fix the ast so that + # the `INFORMATION_SCHEMA.X` bit is represented as a single (quoted) Identifier. + # Otherwise, we wouldn't correctly qualify a `Table` node that references these + # views, because it would seem like the "catalog" part is set, when it'd actually + # be the region/dataset. Merging the two identifiers into a single one is done to + # avoid producing a 4-part Table reference, which would cause issues in the schema + # module, when there are 3-part table names mixed with information schema views. + # + # See: https://cloud.google.com/bigquery/docs/information-schema-intro#syntax + table_parts = table.parts + if len(table_parts) > 1 and table_parts[-2].name.upper() == "INFORMATION_SCHEMA": + # We need to alias the table here to avoid breaking existing qualified columns. + # This is expected to be safe, because if there's an actual alias coming up in + # the token stream, it will overwrite this one. If there isn't one, we are only + # exposing the name that can be used to reference the view explicitly (a no-op). + exp.alias_( + table, + t.cast(exp.Identifier, alias or table_parts[-1]), + table=True, + copy=False, + ) + + info_schema_view = f"{table_parts[-2].name}.{table_parts[-1].name}" + table.set("this", exp.Identifier(this=info_schema_view, quoted=True)) + table.set("db", seq_get(table_parts, -3)) + table.set("catalog", seq_get(table_parts, -4)) return table diff --git a/tests/dialects/test_bigquery.py b/tests/dialects/test_bigquery.py index a4f6d3e914..cd2e04f906 100644 --- a/tests/dialects/test_bigquery.py +++ b/tests/dialects/test_bigquery.py @@ -87,7 +87,11 @@ def test_bigquery(self): for prefix in ("c.db.", "db.", ""): with self.subTest(f"Parsing {prefix}INFORMATION_SCHEMA.X into a Table"): table = self.parse_one(f"`{prefix}INFORMATION_SCHEMA.X`", into=exp.Table) - self.assertIsInstance(table.this, exp.Dot) + this = table.this + + self.assertIsInstance(this, exp.Identifier) + self.assertTrue(this.quoted) + self.assertEqual(this.name, "INFORMATION_SCHEMA.X") table = self.parse_one("x-0._y.z", into=exp.Table) self.assertEqual(table.catalog, "x-0") @@ -203,9 +207,6 @@ def test_bigquery(self): self.validate_identity( "MERGE INTO dataset.NewArrivals USING (SELECT * FROM UNNEST([('microwave', 10, 'warehouse #1'), ('dryer', 30, 'warehouse #1'), ('oven', 20, 'warehouse #2')])) ON FALSE WHEN NOT MATCHED THEN INSERT ROW WHEN NOT MATCHED BY SOURCE THEN DELETE" ) - self.validate_identity( - "SELECT * FROM `SOME_PROJECT_ID.SOME_DATASET_ID.INFORMATION_SCHEMA.SOME_VIEW`" - ) self.validate_identity( "SELECT * FROM test QUALIFY a IS DISTINCT FROM b WINDOW c AS (PARTITION BY d)" ) @@ -236,6 +237,18 @@ def test_bigquery(self): self.validate_identity( "CREATE OR REPLACE VIEW test (tenant_id OPTIONS (description='Test description on table creation')) AS SELECT 1 AS tenant_id, 1 AS customer_id", ) + self.validate_identity( + "SELECT * FROM `proj.dataset.INFORMATION_SCHEMA.SOME_VIEW`", + "SELECT * FROM `proj.dataset.INFORMATION_SCHEMA.SOME_VIEW` AS `proj.dataset.INFORMATION_SCHEMA.SOME_VIEW`", + ) + self.validate_identity( + "SELECT * FROM region_or_dataset.INFORMATION_SCHEMA.TABLES", + "SELECT * FROM region_or_dataset.`INFORMATION_SCHEMA.TABLES` AS TABLES", + ) + self.validate_identity( + "SELECT * FROM proj.region_or_dataset.INFORMATION_SCHEMA.TABLES", + "SELECT * FROM proj.region_or_dataset.`INFORMATION_SCHEMA.TABLES` AS TABLES", + ) self.validate_identity( "CREATE VIEW `d.v` OPTIONS (expiration_timestamp=TIMESTAMP '2020-01-02T04:05:06.007Z') AS SELECT 1 AS c", "CREATE VIEW `d.v` OPTIONS (expiration_timestamp=CAST('2020-01-02T04:05:06.007Z' AS TIMESTAMP)) AS SELECT 1 AS c", diff --git a/tests/fixtures/optimizer/qualify_tables.sql b/tests/fixtures/optimizer/qualify_tables.sql index 441a0794d9..03e8dbee75 100644 --- a/tests/fixtures/optimizer/qualify_tables.sql +++ b/tests/fixtures/optimizer/qualify_tables.sql @@ -17,22 +17,22 @@ SELECT 1 FROM x.y.z AS z; # title: only information schema # dialect: bigquery SELECT * FROM information_schema.tables; -SELECT * FROM c.db.information_schema.tables AS tables; +SELECT * FROM c.db.`information_schema.tables` AS tables; # title: information schema with db # dialect: bigquery SELECT * FROM y.information_schema.tables; -SELECT * FROM c.y.information_schema.tables AS tables; +SELECT * FROM c.y.`information_schema.tables` AS tables; # title: information schema with db, catalog # dialect: bigquery SELECT * FROM x.y.information_schema.tables; -SELECT * FROM x.y.information_schema.tables AS tables; +SELECT * FROM x.y.`information_schema.tables` AS tables; # title: information schema with db, catalog, alias # dialect: bigquery SELECT * FROM x.y.information_schema.tables AS z; -SELECT * FROM x.y.information_schema.tables AS z; +SELECT * FROM x.y.`information_schema.tables` AS z; # title: redshift unnest syntax, z.a should be a column, not a table # dialect: redshift diff --git a/tests/test_optimizer.py b/tests/test_optimizer.py index d5fc214d9c..0fa4ff6ccc 100644 --- a/tests/test_optimizer.py +++ b/tests/test_optimizer.py @@ -315,7 +315,7 @@ def test_qualify_columns(self, logger): ), dialect="bigquery", ).sql(), - 'WITH "x" AS (SELECT "y"."a" AS "a" FROM "DB"."y" AS "y" CROSS JOIN "a"."b"."INFORMATION_SCHEMA"."COLUMNS" AS "COLUMNS") SELECT "x"."a" AS "a" FROM "x" AS "x"', + 'WITH "x" AS (SELECT "y"."a" AS "a" FROM "DB"."y" AS "y" CROSS JOIN "a"."b"."INFORMATION_SCHEMA.COLUMNS" AS "columns") SELECT "x"."a" AS "a" FROM "x" AS "x"', ) self.assertEqual(