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.
- {fakesnow-0.4.1/fakesnow.egg-info → fakesnow-0.5.1}/PKG-INFO +8 -6
- {fakesnow-0.4.1 → fakesnow-0.5.1}/README.md +7 -5
- {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow/fakes.py +35 -4
- {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow/info_schema.py +10 -3
- {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow/transforms.py +1 -4
- {fakesnow-0.4.1 → fakesnow-0.5.1/fakesnow.egg-info}/PKG-INFO +8 -6
- {fakesnow-0.4.1 → fakesnow-0.5.1}/pyproject.toml +1 -1
- {fakesnow-0.4.1 → fakesnow-0.5.1}/tests/test_fakes.py +100 -76
- {fakesnow-0.4.1 → fakesnow-0.5.1}/LICENSE +0 -0
- {fakesnow-0.4.1 → fakesnow-0.5.1}/MANIFEST.in +0 -0
- {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow/__init__.py +0 -0
- {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow/checks.py +0 -0
- {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow/expr.py +0 -0
- {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow/fixtures.py +0 -0
- {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow/py.typed +0 -0
- {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow.egg-info/SOURCES.txt +0 -0
- {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow.egg-info/dependency_links.txt +0 -0
- {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow.egg-info/requires.txt +0 -0
- {fakesnow-0.4.1 → fakesnow-0.5.1}/fakesnow.egg-info/top_level.txt +0 -0
- {fakesnow-0.4.1 → fakesnow-0.5.1}/setup.cfg +0 -0
- {fakesnow-0.4.1 → fakesnow-0.5.1}/setup.py +0 -0
- {fakesnow-0.4.1 → fakesnow-0.5.1}/tests/test_checks.py +0 -0
- {fakesnow-0.4.1 → fakesnow-0.5.1}/tests/test_expr.py +0 -0
- {fakesnow-0.4.1 → fakesnow-0.5.1}/tests/test_patch.py +0 -0
- {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.
|
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]
|
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]
|
116
|
-
- [x]
|
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]
|
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]
|
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]
|
82
|
-
- [x]
|
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]
|
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
|
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
|
-
)
|
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
|
-
)
|
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
|
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
|
43
|
-
|
44
|
-
|
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
|
-
|
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.
|
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]
|
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]
|
116
|
-
- [x]
|
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]
|
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,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."
|
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."
|
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."
|
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."
|
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."
|
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."
|
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
|
188
|
-
|
189
|
-
|
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
|
-
|
196
|
-
|
197
|
-
),
|
198
|
-
|
199
|
-
|
200
|
-
),
|
201
|
-
|
202
|
-
|
203
|
-
),
|
204
|
-
|
205
|
-
|
206
|
-
),
|
207
|
-
|
208
|
-
|
209
|
-
),
|
210
|
-
|
211
|
-
|
212
|
-
),
|
213
|
-
|
214
|
-
|
215
|
-
),
|
216
|
-
|
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
|
227
|
+
assert cur.describe("select * from example") == expected_metadata
|
228
|
+
cur.execute("select * from example")
|
229
|
+
assert cur.description == expected_metadata
|
223
230
|
|
224
|
-
|
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
|
229
|
-
|
230
|
-
|
231
|
-
|
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
|
-
|
240
|
-
|
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"}'))"""
|
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."
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|