Repository: tobymao/sqlglot

Issue:
Support for SHOW SCHEMAS and SHOW OBJECTS (Snowflake)
- [SHOW SCHEMAS docs](https://docs.snowflake.com/en/sql-reference/sql/show-schemas)
- [SHOW OBJECTS docs](https://docs.snowflake.com/en/sql-reference/sql/show-objects)
 
NB: Although not mentioned in the docs, SHOW OBJECTS supports a similar syntax to SHOW SCHEMAS, eg: this is valid
```
show terse objects in db1.schema1 starts with 'a' limit 1 from 'b';
```


Discussion:
this is parsed already as a command, more comprehensive parsing is not planned, but we'd happily accept a pr. 

Resolving patch:
diff --git a/sqlglot/dialects/snowflake.py b/sqlglot/dialects/snowflake.py
--- a/sqlglot/dialects/snowflake.py
+++ b/sqlglot/dialects/snowflake.py
@@ -501,6 +501,10 @@ class Parser(parser.Parser):
         }
 
         SHOW_PARSERS = {
+            "SCHEMAS": _show_parser("SCHEMAS"),
+            "TERSE SCHEMAS": _show_parser("SCHEMAS"),
+            "OBJECTS": _show_parser("OBJECTS"),
+            "TERSE OBJECTS": _show_parser("OBJECTS"),
             "PRIMARY KEYS": _show_parser("PRIMARY KEYS"),
             "TERSE PRIMARY KEYS": _show_parser("PRIMARY KEYS"),
             "COLUMNS": _show_parser("COLUMNS"),
@@ -610,21 +614,37 @@ def _parse_show_snowflake(self, this: str) -> exp.Show:
             scope = None
             scope_kind = None
 
+            # will identity SHOW TERSE SCHEMAS but not SHOW TERSE PRIMARY KEYS
+            # which is syntactically valid but has no effect on the output
+            terse = self._tokens[self._index - 2].text.upper() == "TERSE"
+
             like = self._parse_string() if self._match(TokenType.LIKE) else None
 
             if self._match(TokenType.IN):
                 if self._match_text_seq("ACCOUNT"):
                     scope_kind = "ACCOUNT"
                 elif self._match_set(self.DB_CREATABLES):
-                    scope_kind = self._prev.text
+                    scope_kind = self._prev.text.upper()
                     if self._curr:
                         scope = self._parse_table()
                 elif self._curr:
-                    scope_kind = "TABLE"
+                    scope_kind = "SCHEMA" if this == "OBJECTS" else "TABLE"
                     scope = self._parse_table()
 
             return self.expression(
-                exp.Show, this=this, like=like, scope=scope, scope_kind=scope_kind
+                exp.Show,
+                **{
+                    "terse": terse,
+                    "this": this,
+                    "like": like,
+                    "scope": scope,
+                    "scope_kind": scope_kind,
+                    "starts_with": self._parse_string()
+                    if self._match(TokenType.STARTS_WITH)
+                    else None,
+                    "limit": self._parse_limit(),
+                    "from": self._parse_string() if self._match(TokenType.FROM) else None,
+                },
             )
 
         def _parse_alter_table_swap(self) -> exp.SwapTable:
@@ -850,6 +870,7 @@ def unnest_sql(self, expression: exp.Unnest) -> str:
             return f"{explode}{alias}"
 
         def show_sql(self, expression: exp.Show) -> str:
+            terse = "TERSE " if expression.args.get("terse") else ""
             like = self.sql(expression, "like")
             like = f" LIKE {like}" if like else ""
 
@@ -860,7 +881,19 @@ def show_sql(self, expression: exp.Show) -> str:
             if scope_kind:
                 scope_kind = f" IN {scope_kind}"
 
-            return f"SHOW {expression.name}{like}{scope_kind}{scope}"
+            starts_with = self.sql(expression, "starts_with")
+            if starts_with:
+                starts_with = f" STARTS WITH {starts_with}"
+
+            limit = self.sql(expression, "limit")
+
+            from_ = self.sql(expression, "from")
+            if from_:
+                from_ = f" FROM {from_}"
+
+            return (
+                f"SHOW {terse}{expression.name}{like}{scope_kind}{scope}{starts_with}{limit}{from_}"
+            )
 
         def regexpextract_sql(self, expression: exp.RegexpExtract) -> str:
             # Other dialects don't support all of the following parameters, so we need to
diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py
--- a/sqlglot/expressions.py
+++ b/sqlglot/expressions.py
@@ -1130,9 +1130,12 @@ class SetItem(Expression):
 class Show(Expression):
     arg_types = {
         "this": True,
+        "terse": False,
         "target": False,
         "offset": False,
+        "starts_with": False,
         "limit": False,
+        "from": False,
         "like": False,
         "where": False,
         "db": False,
diff --git a/sqlglot/tokens.py b/sqlglot/tokens.py
--- a/sqlglot/tokens.py
+++ b/sqlglot/tokens.py
@@ -341,6 +341,7 @@ class TokenType(AutoName):
     SOME = auto()
     SORT_BY = auto()
     START_WITH = auto()
+    STARTS_WITH = auto()
     STRUCT = auto()
     TABLE_SAMPLE = auto()
     TEMPORARY = auto()
@@ -732,6 +733,7 @@ class Tokenizer(metaclass=_Tokenizer):
         "SOME": TokenType.SOME,
         "SORT BY": TokenType.SORT_BY,
         "START WITH": TokenType.START_WITH,
+        "STARTS WITH": TokenType.STARTS_WITH,
         "TABLE": TokenType.TABLE,
         "TABLESAMPLE": TokenType.TABLE_SAMPLE,
         "TEMP": TokenType.TEMPORARY,


Test patch:
diff --git a/tests/dialects/test_snowflake.py b/tests/dialects/test_snowflake.py
--- a/tests/dialects/test_snowflake.py
+++ b/tests/dialects/test_snowflake.py
@@ -1461,6 +1461,18 @@ def test_show(self):
             'SHOW TERSE PRIMARY KEYS IN "TEST"."PUBLIC"."customers"',
             'SHOW PRIMARY KEYS IN TABLE "TEST"."PUBLIC"."customers"',
         )
+        self.validate_identity(
+            "show terse schemas in database db1 starts with 'a' limit 10 from 'b'",
+            "SHOW TERSE SCHEMAS IN DATABASE db1 STARTS WITH 'a' LIMIT 10 FROM 'b'",
+        )
+        self.validate_identity(
+            "show terse objects in schema db1.schema1 starts with 'a' limit 10 from 'b'",
+            "SHOW TERSE OBJECTS IN SCHEMA db1.schema1 STARTS WITH 'a' LIMIT 10 FROM 'b'",
+        )
+        self.validate_identity(
+            "show terse objects in db1.schema1 starts with 'a' limit 10 from 'b'",
+            "SHOW TERSE OBJECTS IN SCHEMA db1.schema1 STARTS WITH 'a' LIMIT 10 FROM 'b'",
+        )
 
         ast = parse_one('SHOW PRIMARY KEYS IN "TEST"."PUBLIC"."customers"', read="snowflake")
         table = ast.find(exp.Table)
@@ -1481,6 +1493,16 @@ def test_show(self):
 
         self.assertEqual(literal.sql(dialect="snowflake"), "'_testing%'")
 
+        ast = parse_one("SHOW SCHEMAS IN DATABASE db1", read="snowflake")
+        self.assertEqual(ast.args.get("scope_kind"), "DATABASE")
+        table = ast.find(exp.Table)
+        self.assertEqual(table.sql(dialect="snowflake"), "db1")
+
+        ast = parse_one("SHOW OBJECTS IN db1.schema1", read="snowflake")
+        self.assertEqual(ast.args.get("scope_kind"), "SCHEMA")
+        table = ast.find(exp.Table)
+        self.assertEqual(table.sql(dialect="snowflake"), "db1.schema1")
+
     def test_swap(self):
         ast = parse_one("ALTER TABLE a SWAP WITH b", read="snowflake")
         assert isinstance(ast, exp.AlterTable)