Repository: dbt-labs/dbt-core

Issue:
Snowflake Describe queries to show columns in a table
### Describe the feature
The information_schema is extremely slow in Snowflake and it would be nice to use the describe functionality described here:https://docs.snowflake.com/en/sql-reference/sql/desc-table.html

I noticed that 0.16.0 implemented "show schema" but this only solved part of the problem.
 
I am running many models in parallel and I am seeing issues around the information schema where queries can take around a minute to return table information. 

### Describe alternatives you've considered
I don't know of any alternatives since this is built into DBT. 

### Additional context
This feature is database specific. 

### Who will this benefit?
Anyone using Snowflake.  



Discussion:
I am also getting these errors quite a bit: `090030 (22000): Information schema query returned too much data.  Please repeat query with more selective predicates.
  compiled SQL at dbt/target/compiled`
Hey @brittianwarner - thanks for opening this issue! We had a good thread on this topic which is worth checking out if you haven't seen it already: https://github.com/fishtown-analytics/dbt/issues/1877

Let's make this happen. `desc table` sounds like it will work great, and we've implemented a lot of the things we needed to implement to make this particular change tractable.
Sounds great!  Let me know if I can help in any way.

Resolving patch:
diff --git a/core/dbt/adapters/base/column.py b/core/dbt/adapters/base/column.py
--- a/core/dbt/adapters/base/column.py
+++ b/core/dbt/adapters/base/column.py
@@ -1,6 +1,8 @@
 from dataclasses import dataclass
+import re
 
 from hologram import JsonSchemaMixin
+from dbt.exceptions import RuntimeException
 
 from typing import Dict, ClassVar, Any, Optional
 
@@ -74,7 +76,7 @@ def is_numeric(self) -> bool:
 
     def string_size(self) -> int:
         if not self.is_string():
-            raise RuntimeError("Called string_size() on non-string field!")
+            raise RuntimeException("Called string_size() on non-string field!")
 
         if self.dtype == 'text' or self.char_size is None:
             # char_size should never be None. Handle it reasonably just in case
@@ -108,3 +110,46 @@ def numeric_type(cls, dtype: str, precision: Any, scale: Any) -> str:
 
     def __repr__(self) -> str:
         return "<Column {} ({})>".format(self.name, self.data_type)
+
+    @classmethod
+    def from_description(cls, name: str, raw_data_type: str) -> 'Column':
+        match = re.match(r'([^(]+)(\([^)]+\))?', raw_data_type)
+        if match is None:
+            raise RuntimeException(
+                f'Could not interpret data type "{raw_data_type}"'
+            )
+        data_type, size_info = match.groups()
+        char_size = None
+        numeric_precision = None
+        numeric_scale = None
+        if size_info is not None:
+            # strip out the parentheses
+            size_info = size_info[1:-1]
+            parts = size_info.split(',')
+            if len(parts) == 1:
+                try:
+                    char_size = int(parts[0])
+                except ValueError:
+                    raise RuntimeException(
+                        f'Could not interpret data_type "{raw_data_type}": '
+                        f'could not convert "{parts[0]}" to an integer'
+                    )
+            elif len(parts) == 2:
+                try:
+                    numeric_precision = int(parts[0])
+                except ValueError:
+                    raise RuntimeException(
+                        f'Could not interpret data_type "{raw_data_type}": '
+                        f'could not convert "{parts[0]}" to an integer'
+                    )
+                try:
+                    numeric_scale = int(parts[1])
+                except ValueError:
+                    raise RuntimeException(
+                        f'Could not interpret data_type "{raw_data_type}": '
+                        f'could not convert "{parts[1]}" to an integer'
+                    )
+
+        return cls(
+            name, data_type, char_size, numeric_precision, numeric_scale
+        )
diff --git a/plugins/snowflake/dbt/adapters/snowflake/column.py b/plugins/snowflake/dbt/adapters/snowflake/column.py
--- a/plugins/snowflake/dbt/adapters/snowflake/column.py
+++ b/plugins/snowflake/dbt/adapters/snowflake/column.py
@@ -1,6 +1,7 @@
 from dataclasses import dataclass
 
 from dbt.adapters.base.column import Column
+from dbt.exceptions import RuntimeException
 
 
 @dataclass
@@ -19,3 +20,12 @@ def is_float(self):
         return self.dtype.lower() in [
             'float', 'float4', 'float8', 'double', 'double precision', 'real',
         ]
+
+    def string_size(self) -> int:
+        if not self.is_string():
+            raise RuntimeException("Called string_size() on non-string field!")
+
+        if self.dtype == 'text' or self.char_size is None:
+            return 16777216
+        else:
+            return int(self.char_size)


Test patch:
diff --git a/test/unit/test_snowflake_adapter.py b/test/unit/test_snowflake_adapter.py
--- a/test/unit/test_snowflake_adapter.py
+++ b/test/unit/test_snowflake_adapter.py
@@ -6,6 +6,7 @@
 import dbt.flags as flags
 
 from dbt.adapters.snowflake import SnowflakeAdapter
+from dbt.adapters.snowflake.column import SnowflakeColumn
 from dbt.adapters.base.query_headers import MacroQueryStringSetter
 from dbt.clients import agate_helper
 from dbt.logger import GLOBAL_LOGGER as logger  # noqa
@@ -449,3 +450,72 @@ def test_convert_time_type(self):
         expected = ['time', 'time', 'time']
         for col_idx, expect in enumerate(expected):
             assert SnowflakeAdapter.convert_time_type(agate_table, col_idx) == expect
+
+
+class TestSnowflakeColumn(unittest.TestCase):
+    def test_text_from_description(self):
+        col = SnowflakeColumn.from_description('my_col', 'TEXT')
+        assert col.column == 'my_col'
+        assert col.dtype == 'TEXT'
+        assert col.char_size is None
+        assert col.numeric_precision is None
+        assert col.numeric_scale is None
+        assert col.is_float() is False
+        assert col.is_number() is False
+        assert col.is_numeric() is False
+        assert col.is_string() is True
+        assert col.is_integer() is False
+        assert col.string_size() == 16777216
+
+        col = SnowflakeColumn.from_description('my_col', 'VARCHAR')
+        assert col.column == 'my_col'
+        assert col.dtype == 'VARCHAR'
+        assert col.char_size is None
+        assert col.numeric_precision is None
+        assert col.numeric_scale is None
+        assert col.is_float() is False
+        assert col.is_number() is False
+        assert col.is_numeric() is False
+        assert col.is_string() is True
+        assert col.is_integer() is False
+        assert col.string_size() == 16777216
+
+    def test_sized_varchar_from_description(self):
+        col = SnowflakeColumn.from_description('my_col', 'VARCHAR(256)')
+        assert col.column == 'my_col'
+        assert col.dtype == 'VARCHAR'
+        assert col.char_size == 256
+        assert col.numeric_precision is None
+        assert col.numeric_scale is None
+        assert col.is_float() is False
+        assert col.is_number() is False
+        assert col.is_numeric() is False
+        assert col.is_string() is True
+        assert col.is_integer() is False
+        assert col.string_size() == 256
+
+    def test_sized_decimal_from_description(self):
+        col = SnowflakeColumn.from_description('my_col', 'DECIMAL(1, 0)')
+        assert col.column == 'my_col'
+        assert col.dtype == 'DECIMAL'
+        assert col.char_size is None
+        assert col.numeric_precision == 1
+        assert col.numeric_scale == 0
+        assert col.is_float() is False
+        assert col.is_number() is True
+        assert col.is_numeric() is True
+        assert col.is_string() is False
+        assert col.is_integer() is False
+
+    def test_float_from_description(self):
+        col = SnowflakeColumn.from_description('my_col', 'FLOAT8')
+        assert col.column == 'my_col'
+        assert col.dtype == 'FLOAT8'
+        assert col.char_size is None
+        assert col.numeric_precision is None
+        assert col.numeric_scale is None
+        assert col.is_float() is True
+        assert col.is_number() is True
+        assert col.is_numeric() is False
+        assert col.is_string() is False
+        assert col.is_integer() is False