fakesnow 0.4.1__tar.gz → 0.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. {fakesnow-0.4.1/fakesnow.egg-info → fakesnow-0.5.1}/PKG-INFO +8 -6
  2. {fakesnow-0.4.1 → fakesnow-0.5.1}/README.md +7 -5
  3. {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow/fakes.py +35 -4
  4. {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow/info_schema.py +10 -3
  5. {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow/transforms.py +1 -4
  6. {fakesnow-0.4.1 → fakesnow-0.5.1/fakesnow.egg-info}/PKG-INFO +8 -6
  7. {fakesnow-0.4.1 → fakesnow-0.5.1}/pyproject.toml +1 -1
  8. {fakesnow-0.4.1 → fakesnow-0.5.1}/tests/test_fakes.py +100 -76
  9. {fakesnow-0.4.1 → fakesnow-0.5.1}/LICENSE +0 -0
  10. {fakesnow-0.4.1 → fakesnow-0.5.1}/MANIFEST.in +0 -0
  11. {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow/__init__.py +0 -0
  12. {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow/checks.py +0 -0
  13. {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow/expr.py +0 -0
  14. {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow/fixtures.py +0 -0
  15. {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow/py.typed +0 -0
  16. {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow.egg-info/SOURCES.txt +0 -0
  17. {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow.egg-info/dependency_links.txt +0 -0
  18. {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow.egg-info/requires.txt +0 -0
  19. {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow.egg-info/top_level.txt +0 -0
  20. {fakesnow-0.4.1 → fakesnow-0.5.1}/setup.cfg +0 -0
  21. {fakesnow-0.4.1 → fakesnow-0.5.1}/setup.py +0 -0
  22. {fakesnow-0.4.1 → fakesnow-0.5.1}/tests/test_checks.py +0 -0
  23. {fakesnow-0.4.1 → fakesnow-0.5.1}/tests/test_expr.py +0 -0
  24. {fakesnow-0.4.1 → fakesnow-0.5.1}/tests/test_patch.py +0 -0
  25. {fakesnow-0.4.1 → fakesnow-0.5.1}/tests/test_transforms.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fakesnow
3
- Version: 0.4.1
3
+ Version: 0.5.1
4
4
  Summary: Fake Snowflake Connector for Python. Run Snowflake DB locally.
5
5
  License: MIT License
6
6
 
@@ -109,12 +109,13 @@ def _fakesnow_session() -> Iterator[None]:
109
109
 
110
110
  ## Implementation coverage
111
111
 
112
- - [x] multiple databases
113
- - [x] cursors
112
+ - [x] cursors and standard SQL
114
113
  - [x] [get_result_batches()](https://docs.snowflake.com/en/user-guide/python-connector-api#get_result_batches)
115
- - [x] [write_pandas(..)](https://docs.snowflake.com/en/user-guide/python-connector-api#write_pandas)
116
- - [x] table comments
114
+ - [x] information schema
115
+ - [x] multiple databases
117
116
  - [x] [qmark binding](https://docs.snowflake.com/en/user-guide/python-connector-example#binding-data)
117
+ - [x] table comments
118
+ - [x] [write_pandas(..)](https://docs.snowflake.com/en/user-guide/python-connector-api#write_pandas)
118
119
  - [ ] [access control](https://docs.snowflake.com/en/user-guide/security-access-control-overview)
119
120
  - [ ] standalone/out of process api/support for faking non-python connectors
120
121
  - [ ] [stored procedures](https://docs.snowflake.com/en/sql-reference/stored-procedures)
@@ -122,8 +123,9 @@ def _fakesnow_session() -> Iterator[None]:
122
123
  Partial support
123
124
 
124
125
  - [x] date functions
125
- - [x] tags
126
+ - [x] regex functions
126
127
  - [x] semi-structured data
128
+ - [x] tags
127
129
 
128
130
  For more detail see [tests/test_fakes.py](tests/test_fakes.py)
129
131
 
@@ -75,12 +75,13 @@ def _fakesnow_session() -> Iterator[None]:
75
75
 
76
76
  ## Implementation coverage
77
77
 
78
- - [x] multiple databases
79
- - [x] cursors
78
+ - [x] cursors and standard SQL
80
79
  - [x] [get_result_batches()](https://docs.snowflake.com/en/user-guide/python-connector-api#get_result_batches)
81
- - [x] [write_pandas(..)](https://docs.snowflake.com/en/user-guide/python-connector-api#write_pandas)
82
- - [x] table comments
80
+ - [x] information schema
81
+ - [x] multiple databases
83
82
  - [x] [qmark binding](https://docs.snowflake.com/en/user-guide/python-connector-example#binding-data)
83
+ - [x] table comments
84
+ - [x] [write_pandas(..)](https://docs.snowflake.com/en/user-guide/python-connector-api#write_pandas)
84
85
  - [ ] [access control](https://docs.snowflake.com/en/user-guide/security-access-control-overview)
85
86
  - [ ] standalone/out of process api/support for faking non-python connectors
86
87
  - [ ] [stored procedures](https://docs.snowflake.com/en/sql-reference/stored-procedures)
@@ -88,8 +89,9 @@ def _fakesnow_session() -> Iterator[None]:
88
89
  Partial support
89
90
 
90
91
  - [x] date functions
91
- - [x] tags
92
+ - [x] regex functions
92
93
  - [x] semi-structured data
94
+ - [x] tags
93
95
 
94
96
  For more detail see [tests/test_fakes.py](tests/test_fakes.py)
95
97
 
@@ -46,6 +46,7 @@ class FakeSnowflakeCursor:
46
46
  self._use_dict_result = use_dict_result
47
47
  self._last_sql = None
48
48
  self._last_params = None
49
+ self._sqlstate = None
49
50
 
50
51
  def __enter__(self) -> Self:
51
52
  return self
@@ -76,7 +77,7 @@ class FakeSnowflakeCursor:
76
77
  # use a cursor to avoid destroying an unfetched result on the main connection
77
78
  with self._duck_conn.cursor() as cur:
78
79
  assert self._conn.database, "Not implemented when database is None"
79
- assert self._conn.schema, "Not implemented when database is None"
80
+ assert self._conn.schema, "Not implemented when schema is None"
80
81
 
81
82
  # match database and schema used on the main connection
82
83
  cur.execute(f"SET SCHEMA = '{self._conn.database}.{self._conn.schema}'")
@@ -91,6 +92,20 @@ class FakeSnowflakeCursor:
91
92
  params: Sequence[Any] | dict[Any, Any] | None = None,
92
93
  *args: Any,
93
94
  **kwargs: Any,
95
+ ) -> FakeSnowflakeCursor:
96
+ try:
97
+ self._sqlstate = None
98
+ return self._execute(command, params, *args, **kwargs)
99
+ except snowflake.connector.errors.ProgrammingError as e:
100
+ self._sqlstate = e.sqlstate
101
+ raise e
102
+
103
+ def _execute(
104
+ self,
105
+ command: str | exp.Expression,
106
+ params: Sequence[Any] | dict[Any, Any] | None = None,
107
+ *args: Any,
108
+ **kwargs: Any,
94
109
  ) -> FakeSnowflakeCursor:
95
110
  self._arrow_table = None
96
111
 
@@ -108,13 +123,13 @@ class FakeSnowflakeCursor:
108
123
  msg=f"Cannot perform {cmd}. This session does not have a current database. Call 'USE DATABASE', or use a qualified name.", # noqa: E501
109
124
  errno=90105,
110
125
  sqlstate="22000",
111
- ) from None
126
+ )
112
127
  elif no_schema and not self._conn.schema_set:
113
128
  raise snowflake.connector.errors.ProgrammingError(
114
129
  msg=f"Cannot perform {cmd}. This session does not have a current schema. Call 'USE SCHEMA', or use a qualified name.", # noqa: E501
115
130
  errno=90106,
116
131
  sqlstate="22000",
117
- ) from None
132
+ )
118
133
 
119
134
  transformed = (
120
135
  expression.transform(transforms.upper_case_unquoted_identifiers)
@@ -247,13 +262,21 @@ class FakeSnowflakeCursor:
247
262
  # TODO: return number of rows updated/inserted (using returning)
248
263
  return None
249
264
 
265
+ @property
266
+ def sfqid(self) -> str | None:
267
+ return "fakesnow"
268
+
269
+ @property
270
+ def sqlstate(self) -> str | None:
271
+ return self._sqlstate
272
+
250
273
  @staticmethod
251
274
  def _describe_as_result_metadata(describe_results: list) -> list[ResultMetadata]:
252
275
  # fmt: off
253
276
  def as_result_metadata(column_name: str, column_type: str, _: str) -> ResultMetadata:
254
277
  # see https://docs.snowflake.com/en/user-guide/python-connector-api.html#type-codes
255
278
  # and https://arrow.apache.org/docs/python/api/datatypes.html#type-checking
256
- if column_type == "BIGINT":
279
+ if column_type in {"BIGINT", "INTEGER"}:
257
280
  return ResultMetadata(
258
281
  name=column_name, type_code=0, display_size=None, internal_size=None, precision=38, scale=0, is_nullable=True # noqa: E501
259
282
  )
@@ -288,6 +311,14 @@ class FakeSnowflakeCursor:
288
311
  return ResultMetadata(
289
312
  name=column_name, type_code=8, display_size=None, internal_size=None, precision=0, scale=9, is_nullable=True # noqa: E501
290
313
  )
314
+ elif column_type == "BLOB":
315
+ return ResultMetadata(
316
+ name=column_name, type_code=11, display_size=None, internal_size=8388608, precision=None, scale=None, is_nullable=True # noqa: E501
317
+ )
318
+ elif column_type == "TIME":
319
+ return ResultMetadata(
320
+ name=column_name, type_code=12, display_size=None, internal_size=None, precision=0, scale=9, is_nullable=True # noqa: E501
321
+ )
291
322
  else:
292
323
  # TODO handle more types
293
324
  raise NotImplementedError(f"for column type {column_type}")
@@ -37,11 +37,18 @@ create view ${catalog}.information_schema.columns_snowflake AS
37
37
  select table_catalog, table_schema, table_name, column_name, ordinal_position, column_default, is_nullable,
38
38
  case when starts_with(data_type, 'DECIMAL') or data_type='BIGINT' then 'NUMBER'
39
39
  when data_type='VARCHAR' then 'TEXT'
40
+ when data_type='DOUBLE' then 'FLOAT'
41
+ when data_type='BLOB' then 'BINARY'
42
+ when data_type='TIMESTAMP' then 'TIMESTAMP_NTZ'
40
43
  else data_type end as data_type,
41
44
  ext_character_maximum_length as character_maximum_length, ext_character_octet_length as character_octet_length,
42
- case when data_type='BIGINT' then 38 else numeric_precision end as numeric_precision,
43
- case when data_type='BIGINT' then 10 else numeric_precision_radix end as numeric_precision_radix,
44
- numeric_scale,
45
+ case when data_type='BIGINT' then 38
46
+ when data_type='DOUBLE' then NULL
47
+ else numeric_precision end as numeric_precision,
48
+ case when data_type='BIGINT' then 10
49
+ when data_type='DOUBLE' then NULL
50
+ else numeric_precision_radix end as numeric_precision_radix,
51
+ case when data_type='DOUBLE' then NULL else numeric_scale end as numeric_scale,
45
52
  collation_name, is_identity, identity_generation, identity_cycle
46
53
  from ${catalog}.information_schema.columns
47
54
  left join ${catalog}.information_schema.columns_ext ext
@@ -181,10 +181,7 @@ def float_to_double(expression: exp.Expression) -> exp.Expression:
181
181
  """
182
182
 
183
183
  if isinstance(expression, exp.DataType) and expression.this == exp.DataType.Type.FLOAT:
184
- # TODO don't copy!
185
- new = expression.copy()
186
- new.args["this"] = exp.DataType.Type.DOUBLE
187
- return new
184
+ expression.args["this"] = exp.DataType.Type.DOUBLE
188
185
 
189
186
  return expression
190
187
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fakesnow
3
- Version: 0.4.1
3
+ Version: 0.5.1
4
4
  Summary: Fake Snowflake Connector for Python. Run Snowflake DB locally.
5
5
  License: MIT License
6
6
 
@@ -109,12 +109,13 @@ def _fakesnow_session() -> Iterator[None]:
109
109
 
110
110
  ## Implementation coverage
111
111
 
112
- - [x] multiple databases
113
- - [x] cursors
112
+ - [x] cursors and standard SQL
114
113
  - [x] [get_result_batches()](https://docs.snowflake.com/en/user-guide/python-connector-api#get_result_batches)
115
- - [x] [write_pandas(..)](https://docs.snowflake.com/en/user-guide/python-connector-api#write_pandas)
116
- - [x] table comments
114
+ - [x] information schema
115
+ - [x] multiple databases
117
116
  - [x] [qmark binding](https://docs.snowflake.com/en/user-guide/python-connector-example#binding-data)
117
+ - [x] table comments
118
+ - [x] [write_pandas(..)](https://docs.snowflake.com/en/user-guide/python-connector-api#write_pandas)
118
119
  - [ ] [access control](https://docs.snowflake.com/en/user-guide/security-access-control-overview)
119
120
  - [ ] standalone/out of process api/support for faking non-python connectors
120
121
  - [ ] [stored procedures](https://docs.snowflake.com/en/sql-reference/stored-procedures)
@@ -122,8 +123,9 @@ def _fakesnow_session() -> Iterator[None]:
122
123
  Partial support
123
124
 
124
125
  - [x] date functions
125
- - [x] tags
126
+ - [x] regex functions
126
127
  - [x] semi-structured data
128
+ - [x] tags
127
129
 
128
130
  For more detail see [tests/test_fakes.py](tests/test_fakes.py)
129
131
 
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "fakesnow"
3
3
  description = "Fake Snowflake Connector for Python. Run Snowflake DB locally."
4
- version = "0.4.1"
4
+ version = "0.5.1"
5
5
  readme = "README.md"
6
6
  license = { file = "LICENSE" }
7
7
  classifiers = ["License :: OSI Approved :: MIT License"]
@@ -1,3 +1,5 @@
1
+ # ruff: noqa: E501
2
+
1
3
  import datetime
2
4
  import json
3
5
 
@@ -8,6 +10,7 @@ import snowflake.connector
8
10
  import snowflake.connector.cursor
9
11
  import snowflake.connector.pandas_tools
10
12
  from pandas.testing import assert_frame_equal
13
+ from snowflake.connector.cursor import ResultMetadata
11
14
 
12
15
 
13
16
  def test_alter_table(cur: snowflake.connector.cursor.SnowflakeCursor):
@@ -62,7 +65,7 @@ def test_connect_without_database(_fakesnow_no_auto_create: None):
62
65
  cur.execute("SELECT * FROM jaffles.customers")
63
66
 
64
67
  assert (
65
- "090105 (22000): Cannot perform SELECT. This session does not have a current database. Call 'USE DATABASE', or use a qualified name." # noqa: E501
68
+ "090105 (22000): Cannot perform SELECT. This session does not have a current database. Call 'USE DATABASE', or use a qualified name."
66
69
  in str(excinfo.value)
67
70
  )
68
71
 
@@ -70,7 +73,7 @@ def test_connect_without_database(_fakesnow_no_auto_create: None):
70
73
  cur.execute("create schema jaffles")
71
74
 
72
75
  assert (
73
- "090105 (22000): Cannot perform CREATE SCHEMA. This session does not have a current database. Call 'USE DATABASE', or use a qualified name." # noqa: E501
76
+ "090105 (22000): Cannot perform CREATE SCHEMA. This session does not have a current database. Call 'USE DATABASE', or use a qualified name."
74
77
  in str(excinfo.value)
75
78
  )
76
79
 
@@ -86,7 +89,7 @@ def test_connect_without_database(_fakesnow_no_auto_create: None):
86
89
  cur.execute("create table customers (ID int, FIRST_NAME varchar, LAST_NAME varchar)")
87
90
 
88
91
  assert (
89
- "090105 (22000): Cannot perform CREATE TABLE. This session does not have a current database. Call 'USE DATABASE', or use a qualified name." # noqa: E501
92
+ "090105 (22000): Cannot perform CREATE TABLE. This session does not have a current database. Call 'USE DATABASE', or use a qualified name."
90
93
  in str(excinfo.value)
91
94
  )
92
95
 
@@ -112,7 +115,7 @@ def test_connect_without_schema(_fakesnow: None):
112
115
  cur.execute("create table customers (ID int, FIRST_NAME varchar, LAST_NAME varchar)")
113
116
 
114
117
  assert (
115
- "090106 (22000): Cannot perform CREATE TABLE. This session does not have a current schema. Call 'USE SCHEMA', or use a qualified name." # noqa: E501
118
+ "090106 (22000): Cannot perform CREATE TABLE. This session does not have a current schema. Call 'USE SCHEMA', or use a qualified name."
116
119
  in str(excinfo.value)
117
120
  )
118
121
 
@@ -148,7 +151,7 @@ def test_connect_with_non_existent_db_or_schema(_fakesnow_no_auto_create: None):
148
151
  cur.execute("create table foobar (i int)")
149
152
 
150
153
  assert (
151
- "090105 (22000): Cannot perform CREATE TABLE. This session does not have a current database. Call 'USE DATABASE', or use a qualified name." # noqa: E501
154
+ "090105 (22000): Cannot perform CREATE TABLE. This session does not have a current database. Call 'USE DATABASE', or use a qualified name."
152
155
  in str(excinfo.value)
153
156
  )
154
157
 
@@ -164,7 +167,7 @@ def test_connect_with_non_existent_db_or_schema(_fakesnow_no_auto_create: None):
164
167
  cur.execute("create table foobar (i int)")
165
168
 
166
169
  assert (
167
- "090106 (22000): Cannot perform CREATE TABLE. This session does not have a current schema. Call 'USE SCHEMA', or use a qualified name." # noqa: E501
170
+ "090106 (22000): Cannot perform CREATE TABLE. This session does not have a current schema. Call 'USE SCHEMA', or use a qualified name."
168
171
  in str(excinfo.value)
169
172
  )
170
173
 
@@ -184,88 +187,64 @@ def test_current_database_schema(conn: snowflake.connector.SnowflakeConnection):
184
187
  def test_describe(cur: snowflake.connector.cursor.SnowflakeCursor):
185
188
  cur.execute(
186
189
  """
187
- create table customers (
188
- ID int, CNAME varchar, AMOUNT decimal(10,2), PCT real, ACTIVE boolean,
189
- UPDATE_AT timestamp, UPDATE_AT_NTZ timestamp_ntz(9), INSERTIONDATE DATE
190
+ create or replace table example (
191
+ XBOOLEAN BOOLEAN, XDOUBLE DOUBLE, XFLOAT FLOAT,
192
+ XNUMBER82 NUMBER(8,2), XNUMBER NUMBER, XDECIMAL DECIMAL, XNUMERIC NUMERIC,
193
+ XINT INT, XINTEGER INTEGER, XBIGINT BIGINT, XSMALLINT SMALLINT, XTINYINT TINYINT, XBYTEINT BYTEINT,
194
+ XVARCHAR20 VARCHAR(20), XVARCHAR VARCHAR, XTEXT TEXT,
195
+ XTIMESTAMP TIMESTAMP, XTIMESTAMP_NTZ9 TIMESTAMP_NTZ(9), XDATE DATE, XTIME TIME,
196
+ XBINARY BINARY
190
197
  )
191
198
  """
192
199
  )
193
200
  # fmt: off
194
201
  expected_metadata = [
195
- snowflake.connector.cursor.ResultMetadata(
196
- name="ID", type_code=0, display_size=None, internal_size=None, precision=38, scale=0, is_nullable=True # type: ignore # noqa: E501
197
- ),
198
- snowflake.connector.cursor.ResultMetadata(
199
- name="CNAME", type_code=2, display_size=None, internal_size=16777216, precision=None, scale=None, is_nullable=True, # type: ignore # noqa: E501
200
- ),
201
- snowflake.connector.cursor.ResultMetadata(
202
- name="AMOUNT", type_code=0, display_size=None, internal_size=None, precision=10, scale=2, is_nullable=True, # type: ignore # noqa: E501
203
- ),
204
- snowflake.connector.cursor.ResultMetadata(
205
- name="PCT", type_code=1, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True, # type: ignore # noqa: E501
206
- ),
207
- snowflake.connector.cursor.ResultMetadata(
208
- name="ACTIVE", type_code=13, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True, # type: ignore # noqa: E501
209
- ),
210
- snowflake.connector.cursor.ResultMetadata(
211
- name='UPDATE_AT', type_code=8, display_size=None, internal_size=None, precision=0, scale=9, is_nullable=True # type: ignore # noqa: E501
212
- ),
213
- snowflake.connector.cursor.ResultMetadata(
214
- name='UPDATE_AT_NTZ', type_code=8, display_size=None, internal_size=None, precision=0, scale=9, is_nullable=True # type: ignore # noqa: E501
215
- ),
216
- snowflake.connector.cursor.ResultMetadata(
217
- name='INSERTIONDATE', type_code=3, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True # type: ignore # noqa: E501
218
- ),
202
+ ResultMetadata(name='XBOOLEAN', type_code=13, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True),
203
+ ResultMetadata(name='XDOUBLE', type_code=1, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True),
204
+ ResultMetadata(name='XFLOAT', type_code=1, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True),
205
+ ResultMetadata(name='XNUMBER82', type_code=0, display_size=None, internal_size=None, precision=8, scale=2, is_nullable=True),
206
+ ResultMetadata(name='XNUMBER', type_code=0, display_size=None, internal_size=None, precision=38, scale=0, is_nullable=True),
207
+ ResultMetadata(name='XDECIMAL', type_code=0, display_size=None, internal_size=None, precision=38, scale=0, is_nullable=True),
208
+ ResultMetadata(name='XNUMERIC', type_code=0, display_size=None, internal_size=None, precision=38, scale=0, is_nullable=True),
209
+ ResultMetadata(name='XINT', type_code=0, display_size=None, internal_size=None, precision=38, scale=0, is_nullable=True),
210
+ ResultMetadata(name='XINTEGER', type_code=0, display_size=None, internal_size=None, precision=38, scale=0, is_nullable=True),
211
+ ResultMetadata(name='XBIGINT', type_code=0, display_size=None, internal_size=None, precision=38, scale=0, is_nullable=True),
212
+ ResultMetadata(name='XSMALLINT', type_code=0, display_size=None, internal_size=None, precision=38, scale=0, is_nullable=True),
213
+ ResultMetadata(name='XTINYINT', type_code=0, display_size=None, internal_size=None, precision=38, scale=0, is_nullable=True),
214
+ ResultMetadata(name='XBYTEINT', type_code=0, display_size=None, internal_size=None, precision=38, scale=0, is_nullable=True),
215
+ # TODO: store actual size
216
+ ResultMetadata(name='XVARCHAR20', type_code=2, display_size=None, internal_size=16777216, precision=None, scale=None, is_nullable=True),
217
+ ResultMetadata(name='XVARCHAR', type_code=2, display_size=None, internal_size=16777216, precision=None, scale=None, is_nullable=True),
218
+ ResultMetadata(name='XTEXT', type_code=2, display_size=None, internal_size=16777216, precision=None, scale=None, is_nullable=True),
219
+ ResultMetadata(name='XTIMESTAMP', type_code=8, display_size=None, internal_size=None, precision=0, scale=9, is_nullable=True),
220
+ ResultMetadata(name='XTIMESTAMP_NTZ9', type_code=8, display_size=None, internal_size=None, precision=0, scale=9, is_nullable=True),
221
+ ResultMetadata(name='XDATE', type_code=3, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True),
222
+ ResultMetadata(name='XTIME', type_code=12, display_size=None, internal_size=None, precision=0, scale=9, is_nullable=True),
223
+ ResultMetadata(name='XBINARY', type_code=11, display_size=None, internal_size=8388608, precision=None, scale=None, is_nullable=True)
219
224
  ]
220
225
  # fmt: on
221
226
 
222
- assert cur.describe("select * from customers") == expected_metadata
227
+ assert cur.describe("select * from example") == expected_metadata
228
+ cur.execute("select * from example")
229
+ assert cur.description == expected_metadata
223
230
 
224
- cur.execute("select * from customers")
231
+ # test with params
232
+ assert cur.describe("select * from example where XNUMBER = ?", (1,)) == expected_metadata
233
+ cur.execute("select * from example where XNUMBER = ?", (1,))
225
234
  assert cur.description == expected_metadata
226
235
 
227
236
 
228
- def test_describe_with_params(cur: snowflake.connector.cursor.SnowflakeCursor):
229
- cur.execute(
230
- """
231
- create table customers (
232
- ID int, CNAME varchar, AMOUNT decimal(10,2), PCT real, ACTIVE boolean,
233
- UPDATE_AT timestamp, UPDATE_AT_NTZ timestamp_ntz(9), INSERTIONDATE DATE
234
- )
235
- """
236
- )
237
+ def test_describe_info_schema(cur: snowflake.connector.cursor.SnowflakeCursor):
238
+ # tests we can handle the column types returned from the info schema, which are created by duckdb
239
+ # and so don't go through our transforms
240
+ cur.execute("select column_name, ordinal_position from information_schema.columns")
237
241
  # fmt: off
238
242
  expected_metadata = [
239
- snowflake.connector.cursor.ResultMetadata(
240
- name="ID", type_code=0, display_size=None, internal_size=None, precision=38, scale=0, is_nullable=True # type: ignore # noqa: E501
241
- ),
242
- snowflake.connector.cursor.ResultMetadata(
243
- name="CNAME", type_code=2, display_size=None, internal_size=16777216, precision=None, scale=None, is_nullable=True, # type: ignore # noqa: E501
244
- ),
245
- snowflake.connector.cursor.ResultMetadata(
246
- name="AMOUNT", type_code=0, display_size=None, internal_size=None, precision=10, scale=2, is_nullable=True, # type: ignore # noqa: E501
247
- ),
248
- snowflake.connector.cursor.ResultMetadata(
249
- name="PCT", type_code=1, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True, # type: ignore # noqa: E501
250
- ),
251
- snowflake.connector.cursor.ResultMetadata(
252
- name="ACTIVE", type_code=13, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True, # type: ignore # noqa: E501
253
- ),
254
- snowflake.connector.cursor.ResultMetadata(
255
- name='UPDATE_AT', type_code=8, display_size=None, internal_size=None, precision=0, scale=9, is_nullable=True # type: ignore # noqa: E501
256
- ),
257
- snowflake.connector.cursor.ResultMetadata(
258
- name='UPDATE_AT_NTZ', type_code=8, display_size=None, internal_size=None, precision=0, scale=9, is_nullable=True # type: ignore # noqa: E501
259
- ),
260
- snowflake.connector.cursor.ResultMetadata(
261
- name='INSERTIONDATE', type_code=3, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True # type: ignore # noqa: E501
262
- ),
243
+ ResultMetadata(name='column_name', type_code=2, display_size=None, internal_size=16777216, precision=None, scale=None, is_nullable=True),
244
+ ResultMetadata(name='ordinal_position', type_code=0, display_size=None, internal_size=None, precision=38, scale=0, is_nullable=True)
263
245
  ]
264
246
  # fmt: on
265
247
 
266
- assert cur.describe("select * from customers where id = ?", (1,)) == expected_metadata
267
-
268
- cur.execute("select * from customers where id = ?", (1,))
269
248
  assert cur.description == expected_metadata
270
249
 
271
250
 
@@ -390,8 +369,8 @@ def test_information_schema_columns_numeric(cur: snowflake.connector.cursor.Snow
390
369
  # see https://docs.snowflake.com/en/sql-reference/data-types-numeric
391
370
  cur.execute(
392
371
  """
393
- create table example (
394
- XNUMBER82 NUMBER(8,2), XNUMBER NUMBER, XDECIMAL DECIMAL, XNUMERIC NUMERIC,
372
+ create or replace table example (
373
+ XBOOLEAN BOOLEAN, XDOUBLE DOUBLE, XFLOAT FLOAT, XNUMBER82 NUMBER(8,2), XNUMBER NUMBER, XDECIMAL DECIMAL, XNUMERIC NUMERIC,
395
374
  XINT INT, XINTEGER INTEGER, XBIGINT BIGINT, XSMALLINT SMALLINT, XTINYINT TINYINT, XBYTEINT BYTEINT
396
375
  )
397
376
  """
@@ -405,6 +384,9 @@ def test_information_schema_columns_numeric(cur: snowflake.connector.cursor.Snow
405
384
  )
406
385
 
407
386
  assert cur.fetchall() == [
387
+ ("XBOOLEAN", "BOOLEAN", None, None, None),
388
+ ("XDOUBLE", "FLOAT", None, None, None),
389
+ ("XFLOAT", "FLOAT", None, None, None),
408
390
  ("XNUMBER82", "NUMBER", 8, 10, 2),
409
391
  ("XNUMBER", "NUMBER", 38, 10, 0),
410
392
  ("XDECIMAL", "NUMBER", 38, 10, 0),
@@ -418,11 +400,38 @@ def test_information_schema_columns_numeric(cur: snowflake.connector.cursor.Snow
418
400
  ]
419
401
 
420
402
 
403
+ def test_information_schema_columns_other(cur: snowflake.connector.cursor.SnowflakeCursor):
404
+ # see https://docs.snowflake.com/en/sql-reference/data-types-datetime
405
+ cur.execute(
406
+ """
407
+ create or replace table example (
408
+ XTIMESTAMP TIMESTAMP, XTIMESTAMP_NTZ9 TIMESTAMP_NTZ(9), XDATE DATE, XTIME TIME,
409
+ XBINARY BINARY
410
+ )
411
+ """
412
+ )
413
+
414
+ cur.execute(
415
+ """
416
+ select column_name,data_type
417
+ from information_schema.columns where table_name = 'EXAMPLE' order by ordinal_position
418
+ """
419
+ )
420
+
421
+ assert cur.fetchall() == [
422
+ ("XTIMESTAMP", "TIMESTAMP_NTZ"),
423
+ ("XTIMESTAMP_NTZ9", "TIMESTAMP_NTZ"),
424
+ ("XDATE", "DATE"),
425
+ ("XTIME", "TIME"),
426
+ ("XBINARY", "BINARY"),
427
+ ]
428
+
429
+
421
430
  def test_information_schema_columns_text(cur: snowflake.connector.cursor.SnowflakeCursor):
422
431
  # see https://docs.snowflake.com/en/sql-reference/data-types-text
423
432
  cur.execute(
424
433
  """
425
- create table example (
434
+ create or replace table example (
426
435
  XVARCHAR20 VARCHAR(20), XVARCHAR VARCHAR, XTEXT TEXT
427
436
  )
428
437
  """
@@ -507,7 +516,7 @@ def test_semi_structured_types(cur: snowflake.connector.cursor.SnowflakeCursor):
507
516
  """insert into semis(emails, name, notes) SELECT [1, 2], parse_json('{"k": "v1"}'), parse_json('["foo"]')"""
508
517
  )
509
518
  cur.execute(
510
- """insert into semis(emails, name, notes) VALUES ([3,4], parse_json('{"k": "v2"}'), parse_json('{"b": "ar"}'))""" # noqa: E501
519
+ """insert into semis(emails, name, notes) VALUES ([3,4], parse_json('{"k": "v2"}'), parse_json('{"b": "ar"}'))"""
511
520
  )
512
521
 
513
522
  # results are returned as strings, because the underlying type is JSON (duckdb) / VARIANT (snowflake)
@@ -522,6 +531,21 @@ def test_semi_structured_types(cur: snowflake.connector.cursor.SnowflakeCursor):
522
531
  assert cur.fetchall() == [('"foo"',), (None,)]
523
532
 
524
533
 
534
+ def test_sqlstate(cur: snowflake.connector.cursor.SnowflakeCursor):
535
+ cur.execute("select 'hello world'")
536
+ # sqlstate is None on success
537
+ assert cur.sqlstate is None
538
+
539
+ with pytest.raises(snowflake.connector.errors.ProgrammingError) as _:
540
+ cur.execute("select * from this_table_does_not_exist")
541
+
542
+ assert cur.sqlstate == "42S02"
543
+
544
+
545
+ def test_sfqid(cur: snowflake.connector.cursor.SnowflakeCursor):
546
+ assert cur.sfqid == "fakesnow"
547
+
548
+
525
549
  def test_table_comments(cur: snowflake.connector.cursor.SnowflakeCursor):
526
550
  def read_comment() -> str:
527
551
  cur.execute(
@@ -626,7 +650,7 @@ def test_use_invalid_schema(_fakesnow: None):
626
650
  cur.execute("create table foobar (i int)")
627
651
 
628
652
  assert (
629
- "090106 (22000): Cannot perform CREATE TABLE. This session does not have a current schema. Call 'USE SCHEMA', or use a qualified name." # noqa: E501
653
+ "090106 (22000): Cannot perform CREATE TABLE. This session does not have a current schema. Call 'USE SCHEMA', or use a qualified name."
630
654
  in str(excinfo.value)
631
655
  )
632
656
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes