fakesnow 0.9.18__tar.gz → 0.9.20__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 (35) hide show
  1. {fakesnow-0.9.18 → fakesnow-0.9.20}/PKG-INFO +1 -1
  2. {fakesnow-0.9.18 → fakesnow-0.9.20}/fakesnow/__init__.py +2 -0
  3. {fakesnow-0.9.18 → fakesnow-0.9.20}/fakesnow/fakes.py +7 -3
  4. {fakesnow-0.9.18 → fakesnow-0.9.20}/fakesnow/transforms.py +16 -0
  5. fakesnow-0.9.20/fakesnow/variables.py +56 -0
  6. {fakesnow-0.9.18 → fakesnow-0.9.20}/fakesnow.egg-info/PKG-INFO +1 -1
  7. {fakesnow-0.9.18 → fakesnow-0.9.20}/fakesnow.egg-info/SOURCES.txt +2 -0
  8. {fakesnow-0.9.18 → fakesnow-0.9.20}/pyproject.toml +1 -1
  9. fakesnow-0.9.20/tests/test_connect.py +199 -0
  10. {fakesnow-0.9.18 → fakesnow-0.9.20}/tests/test_fakes.py +54 -172
  11. {fakesnow-0.9.18 → fakesnow-0.9.20}/tests/test_info_schema.py +18 -0
  12. {fakesnow-0.9.18 → fakesnow-0.9.20}/LICENSE +0 -0
  13. {fakesnow-0.9.18 → fakesnow-0.9.20}/README.md +0 -0
  14. {fakesnow-0.9.18 → fakesnow-0.9.20}/fakesnow/__main__.py +0 -0
  15. {fakesnow-0.9.18 → fakesnow-0.9.20}/fakesnow/checks.py +0 -0
  16. {fakesnow-0.9.18 → fakesnow-0.9.20}/fakesnow/cli.py +0 -0
  17. {fakesnow-0.9.18 → fakesnow-0.9.20}/fakesnow/expr.py +0 -0
  18. {fakesnow-0.9.18 → fakesnow-0.9.20}/fakesnow/fixtures.py +0 -0
  19. {fakesnow-0.9.18 → fakesnow-0.9.20}/fakesnow/global_database.py +0 -0
  20. {fakesnow-0.9.18 → fakesnow-0.9.20}/fakesnow/info_schema.py +0 -0
  21. {fakesnow-0.9.18 → fakesnow-0.9.20}/fakesnow/macros.py +0 -0
  22. {fakesnow-0.9.18 → fakesnow-0.9.20}/fakesnow/py.typed +0 -0
  23. {fakesnow-0.9.18 → fakesnow-0.9.20}/fakesnow.egg-info/dependency_links.txt +0 -0
  24. {fakesnow-0.9.18 → fakesnow-0.9.20}/fakesnow.egg-info/entry_points.txt +0 -0
  25. {fakesnow-0.9.18 → fakesnow-0.9.20}/fakesnow.egg-info/requires.txt +0 -0
  26. {fakesnow-0.9.18 → fakesnow-0.9.20}/fakesnow.egg-info/top_level.txt +0 -0
  27. {fakesnow-0.9.18 → fakesnow-0.9.20}/setup.cfg +0 -0
  28. {fakesnow-0.9.18 → fakesnow-0.9.20}/tests/test_checks.py +0 -0
  29. {fakesnow-0.9.18 → fakesnow-0.9.20}/tests/test_cli.py +0 -0
  30. {fakesnow-0.9.18 → fakesnow-0.9.20}/tests/test_expr.py +0 -0
  31. {fakesnow-0.9.18 → fakesnow-0.9.20}/tests/test_patch.py +0 -0
  32. {fakesnow-0.9.18 → fakesnow-0.9.20}/tests/test_sqlalchemy.py +0 -0
  33. {fakesnow-0.9.18 → fakesnow-0.9.20}/tests/test_transforms.py +0 -0
  34. {fakesnow-0.9.18 → fakesnow-0.9.20}/tests/test_users.py +0 -0
  35. {fakesnow-0.9.18 → fakesnow-0.9.20}/tests/test_write_pandas.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fakesnow
3
- Version: 0.9.18
3
+ Version: 0.9.20
4
4
  Summary: Fake Snowflake Connector for Python. Run, mock and test Snowflake DB locally.
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -13,6 +13,7 @@ import snowflake.connector
13
13
  import snowflake.connector.pandas_tools
14
14
 
15
15
  import fakesnow.fakes as fakes
16
+ from fakesnow.global_database import create_global_database
16
17
 
17
18
 
18
19
  @contextmanager
@@ -52,6 +53,7 @@ def patch(
52
53
  assert not isinstance(snowflake.connector.connect, mock.MagicMock), "Snowflake connector is already patched"
53
54
 
54
55
  duck_conn = duckdb.connect(database=":memory:")
56
+ create_global_database(duck_conn)
55
57
 
56
58
  fake_fns = {
57
59
  # every time we connect, create a new cursor (ie: connection) so we can isolate each connection's
@@ -32,7 +32,7 @@ import fakesnow.expr as expr
32
32
  import fakesnow.info_schema as info_schema
33
33
  import fakesnow.macros as macros
34
34
  import fakesnow.transforms as transforms
35
- from fakesnow.global_database import create_global_database
35
+ from fakesnow.variables import Variables
36
36
 
37
37
  SCHEMA_UNSET = "schema_unset"
38
38
  SQL_SUCCESS = "SELECT 'Statement executed successfully.' as 'status'"
@@ -134,6 +134,7 @@ class FakeSnowflakeCursor:
134
134
  if os.environ.get("FAKESNOW_DEBUG") == "snowflake":
135
135
  print(f"{command};{params=}" if params else f"{command};", file=sys.stderr)
136
136
 
137
+ command = self._inline_variables(command)
137
138
  command, params = self._rewrite_with_params(command, params)
138
139
  if self._conn.nop_regexes and any(re.match(p, command, re.IGNORECASE) for p in self._conn.nop_regexes):
139
140
  transformed = transforms.SUCCESS_NOP
@@ -148,6 +149,7 @@ class FakeSnowflakeCursor:
148
149
  def _transform(self, expression: exp.Expression) -> exp.Expression:
149
150
  return (
150
151
  expression.transform(transforms.upper_case_unquoted_identifiers)
152
+ .transform(transforms.update_variables, variables=self._conn.variables)
151
153
  .transform(transforms.set_schema, current_database=self._conn.database)
152
154
  .transform(transforms.create_database, db_path=self._conn.db_path)
153
155
  .transform(transforms.extract_comment_on_table)
@@ -501,6 +503,9 @@ class FakeSnowflakeCursor:
501
503
 
502
504
  return command, params
503
505
 
506
+ def _inline_variables(self, sql: str) -> str:
507
+ return self._conn.variables.inline_variables(sql)
508
+
504
509
 
505
510
  class FakeSnowflakeConnection:
506
511
  def __init__(
@@ -525,8 +530,7 @@ class FakeSnowflakeConnection:
525
530
  self.db_path = Path(db_path) if db_path else None
526
531
  self.nop_regexes = nop_regexes
527
532
  self._paramstyle = snowflake.connector.paramstyle
528
-
529
- create_global_database(duck_conn)
533
+ self.variables = Variables()
530
534
 
531
535
  # create database if needed
532
536
  if (
@@ -8,6 +8,7 @@ import sqlglot
8
8
  from sqlglot import exp
9
9
 
10
10
  from fakesnow.global_database import USERS_TABLE_FQ_NAME
11
+ from fakesnow.variables import Variables
11
12
 
12
13
  MISSING_DATABASE = "missing_database"
13
14
  SUCCESS_NOP = sqlglot.parse_one("SELECT 'Statement executed successfully.'")
@@ -1401,12 +1402,27 @@ def show_keys(
1401
1402
 
1402
1403
  if schema:
1403
1404
  statement += f"AND schema_name = '{schema}' "
1405
+ elif scope_kind == "TABLE":
1406
+ if not table:
1407
+ raise ValueError(f"SHOW PRIMARY KEYS with {scope_kind} scope requires a table")
1408
+
1409
+ statement += f"AND table_name = '{table.name}' "
1404
1410
  else:
1405
1411
  raise NotImplementedError(f"SHOW PRIMARY KEYS with {scope_kind} not yet supported")
1406
1412
  return sqlglot.parse_one(statement)
1407
1413
  return expression
1408
1414
 
1409
1415
 
1416
+ def update_variables(
1417
+ expression: exp.Expression,
1418
+ variables: Variables,
1419
+ ) -> exp.Expression:
1420
+ if Variables.is_variable_modifier(expression):
1421
+ variables.update_variables(expression)
1422
+ return SUCCESS_NOP # Nothing further to do if its a SET/UNSET operation.
1423
+ return expression
1424
+
1425
+
1410
1426
  class SHA256(exp.Func):
1411
1427
  _sql_names: ClassVar = ["SHA256"]
1412
1428
  arg_types: ClassVar = {"this": True}
@@ -0,0 +1,56 @@
1
+ import re
2
+
3
+ import snowflake.connector.errors
4
+ from sqlglot import exp
5
+
6
+
7
+ # Implements snowflake variables: https://docs.snowflake.com/en/sql-reference/session-variables#using-variables-in-sql
8
+ class Variables:
9
+ @classmethod
10
+ def is_variable_modifier(cls, expr: exp.Expression) -> bool:
11
+ return isinstance(expr, exp.Set) or cls._is_unset_expression(expr)
12
+
13
+ @classmethod
14
+ def _is_unset_expression(cls, expr: exp.Expression) -> bool:
15
+ if isinstance(expr, exp.Alias):
16
+ this_expr = expr.this.args.get("this")
17
+ return isinstance(this_expr, exp.Expression) and this_expr.this == "UNSET"
18
+ return False
19
+
20
+ def __init__(self) -> None:
21
+ self._variables = {}
22
+
23
+ def update_variables(self, expr: exp.Expression) -> None:
24
+ if isinstance(expr, exp.Set):
25
+ unset = expr.args.get("unset")
26
+ if not unset: # SET varname = value;
27
+ unset_expressions = expr.args.get("expressions")
28
+ assert unset_expressions, "SET without values in expression(s) is unexpected."
29
+ eq = unset_expressions[0].this
30
+ name = eq.this.sql()
31
+ value = eq.args.get("expression").sql()
32
+ self._set(name, value)
33
+ else:
34
+ # Haven't been able to produce this in tests yet due to UNSET being parsed as an Alias expression.
35
+ raise NotImplementedError("UNSET not supported yet")
36
+ elif self._is_unset_expression(expr): # Unfortunately UNSET varname; is parsed as an Alias expression :(
37
+ alias = expr.args.get("alias")
38
+ assert alias, "UNSET without value in alias attribute is unexpected."
39
+ name = alias.this
40
+ self._unset(name)
41
+
42
+ def _set(self, name: str, value: str) -> None:
43
+ self._variables[name] = value
44
+
45
+ def _unset(self, name: str) -> None:
46
+ self._variables.pop(name)
47
+
48
+ def inline_variables(self, sql: str) -> str:
49
+ for name, value in self._variables.items():
50
+ sql = re.sub(rf"\${name}", value, sql, flags=re.IGNORECASE)
51
+
52
+ if remaining_variables := re.search(r"(?<!\$)\$\w+", sql):
53
+ raise snowflake.connector.errors.ProgrammingError(
54
+ msg=f"Session variable '{remaining_variables.group().upper()}' does not exist"
55
+ )
56
+ return sql
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fakesnow
3
- Version: 0.9.18
3
+ Version: 0.9.20
4
4
  Summary: Fake Snowflake Connector for Python. Run, mock and test Snowflake DB locally.
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -13,6 +13,7 @@ fakesnow/info_schema.py
13
13
  fakesnow/macros.py
14
14
  fakesnow/py.typed
15
15
  fakesnow/transforms.py
16
+ fakesnow/variables.py
16
17
  fakesnow.egg-info/PKG-INFO
17
18
  fakesnow.egg-info/SOURCES.txt
18
19
  fakesnow.egg-info/dependency_links.txt
@@ -21,6 +22,7 @@ fakesnow.egg-info/requires.txt
21
22
  fakesnow.egg-info/top_level.txt
22
23
  tests/test_checks.py
23
24
  tests/test_cli.py
25
+ tests/test_connect.py
24
26
  tests/test_expr.py
25
27
  tests/test_fakes.py
26
28
  tests/test_info_schema.py
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "fakesnow"
3
3
  description = "Fake Snowflake Connector for Python. Run, mock and test Snowflake DB locally."
4
- version = "0.9.18"
4
+ version = "0.9.20"
5
5
  readme = "README.md"
6
6
  license = { file = "LICENSE" }
7
7
  classifiers = ["License :: OSI Approved :: MIT License"]
@@ -0,0 +1,199 @@
1
+ from __future__ import annotations
2
+
3
+ import concurrent.futures
4
+
5
+ # ruff: noqa: E501
6
+ # pyright: reportOptionalMemberAccess=false
7
+ import tempfile
8
+
9
+ import pytest
10
+ import snowflake.connector
11
+ import snowflake.connector.cursor
12
+ import snowflake.connector.pandas_tools
13
+
14
+ import fakesnow
15
+
16
+
17
+ def test_connect_auto_create(_fakesnow: None):
18
+ with snowflake.connector.connect(database="db1", schema="schema1"):
19
+ # creates db1 and schema1
20
+ pass
21
+
22
+ with snowflake.connector.connect(database="db1", schema="schema1"):
23
+ # connects again and reuses db1 and schema1
24
+ pass
25
+
26
+
27
+ def test_connect_different_sessions_use_database(_fakesnow_no_auto_create: None):
28
+ # connect without default database and schema
29
+ with snowflake.connector.connect() as conn1, conn1.cursor() as cur:
30
+ # use the table's fully qualified name
31
+ cur.execute("create database marts")
32
+ cur.execute("create schema marts.jaffles")
33
+ cur.execute("create table marts.jaffles.customers (ID int, FIRST_NAME varchar, LAST_NAME varchar)")
34
+ cur.execute("insert into marts.jaffles.customers values (1, 'Jenny', 'P')")
35
+
36
+ # use database and schema
37
+ cur.execute("use database marts")
38
+ cur.execute("use schema jaffles")
39
+ cur.execute("insert into customers values (2, 'Jasper', 'M')")
40
+
41
+ # in a separate connection, connect using the database and schema from above
42
+ with snowflake.connector.connect(database="marts", schema="jaffles") as conn2, conn2.cursor() as cur:
43
+ cur.execute("select id, first_name, last_name from customers")
44
+ assert cur.fetchall() == [(1, "Jenny", "P"), (2, "Jasper", "M")]
45
+
46
+
47
+ def test_connect_concurrently(_fakesnow: None) -> None:
48
+ with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
49
+ future_a = executor.submit(snowflake.connector.connect)
50
+ future_b = executor.submit(snowflake.connector.connect)
51
+
52
+ futures = [future_a, future_b]
53
+
54
+ for future in concurrent.futures.as_completed(futures):
55
+ # exceptions if any will be raised here. we want to avoid
56
+ # duckdb.duckdb.TransactionException: TransactionContext Error: Catalog write-write conflict
57
+ _ = future.result()
58
+
59
+
60
+ def test_connect_db_path_can_create_database() -> None:
61
+ with tempfile.TemporaryDirectory(prefix="fakesnow-test") as db_path, fakesnow.patch(db_path=db_path):
62
+ cursor = snowflake.connector.connect().cursor()
63
+ cursor.execute("CREATE DATABASE db2")
64
+
65
+
66
+ def test_connect_db_path_reuse():
67
+ with tempfile.TemporaryDirectory(prefix="fakesnow-test") as db_path:
68
+ with (
69
+ fakesnow.patch(db_path=db_path),
70
+ snowflake.connector.connect(database="db1", schema="schema1") as conn,
71
+ conn.cursor() as cur,
72
+ ):
73
+ # creates db1.schema1.example
74
+ cur.execute("create table example (x int)")
75
+ cur.execute("insert into example values (420)")
76
+
77
+ # reconnect
78
+ with (
79
+ fakesnow.patch(db_path=db_path),
80
+ snowflake.connector.connect(database="db1", schema="schema1") as conn,
81
+ conn.cursor() as cur,
82
+ ):
83
+ assert cur.execute("select * from example").fetchall() == [(420,)]
84
+
85
+
86
+ def test_connect_without_database(_fakesnow_no_auto_create: None):
87
+ with snowflake.connector.connect() as conn, conn.cursor() as cur:
88
+ with pytest.raises(snowflake.connector.errors.ProgrammingError) as excinfo:
89
+ cur.execute("select * from customers")
90
+
91
+ # actual snowflake error message is:
92
+ #
93
+ # 002003 (42S02): SQL compilation error:
94
+ # Object 'CUSTOMERS' does not exist or not authorized.
95
+ # assert (
96
+ # "002003 (42S02): Catalog Error: Table with name customers does not exist!"
97
+ # in str(excinfo.value)
98
+ # )
99
+
100
+ with pytest.raises(snowflake.connector.errors.ProgrammingError) as excinfo:
101
+ cur.execute("select * from jaffles.customers")
102
+
103
+ assert (
104
+ "090105 (22000): Cannot perform SELECT. This session does not have a current database. Call 'USE DATABASE', or use a qualified name."
105
+ in str(excinfo.value)
106
+ )
107
+
108
+ with pytest.raises(snowflake.connector.errors.ProgrammingError) as excinfo:
109
+ cur.execute("create schema jaffles")
110
+
111
+ assert (
112
+ "090105 (22000): Cannot perform CREATE SCHEMA. This session does not have a current database. Call 'USE DATABASE', or use a qualified name."
113
+ in str(excinfo.value)
114
+ )
115
+
116
+ with pytest.raises(snowflake.connector.errors.ProgrammingError) as excinfo:
117
+ cur.execute("use schema jaffles")
118
+
119
+ # assert (
120
+ # "002043 (02000): SQL compilation error:\nObject does not exist, or operation cannot be performed."
121
+ # in str(excinfo.value)
122
+ # )
123
+
124
+ with pytest.raises(snowflake.connector.errors.ProgrammingError) as excinfo:
125
+ cur.execute("create table customers (ID int, FIRST_NAME varchar, LAST_NAME varchar)")
126
+
127
+ assert (
128
+ "090105 (22000): Cannot perform CREATE TABLE. This session does not have a current database. Call 'USE DATABASE', or use a qualified name."
129
+ in str(excinfo.value)
130
+ )
131
+
132
+ # test description works without database
133
+ assert cur.execute("SELECT 1").fetchall() == [(1,)]
134
+ assert cur.description
135
+
136
+
137
+ def test_connect_without_schema(_fakesnow: None):
138
+ # database will be created but not schema
139
+ with snowflake.connector.connect(database="marts") as conn, conn.cursor() as cur:
140
+ assert not conn.schema
141
+
142
+ with pytest.raises(snowflake.connector.errors.ProgrammingError) as excinfo:
143
+ cur.execute("select * from customers")
144
+
145
+ # actual snowflake error message is:
146
+ #
147
+ # 002003 (42S02): SQL compilation error:
148
+ # Object 'CUSTOMERS' does not exist or not authorized.
149
+ # assert (
150
+ # "002003 (42S02): Catalog Error: Table with name customers does not exist!"
151
+ # in str(excinfo.value)
152
+ # )
153
+
154
+ with pytest.raises(snowflake.connector.errors.ProgrammingError) as excinfo:
155
+ cur.execute("create table customers (ID int, FIRST_NAME varchar, LAST_NAME varchar)")
156
+
157
+ assert (
158
+ "090106 (22000): Cannot perform CREATE TABLE. This session does not have a current schema. Call 'USE SCHEMA', or use a qualified name."
159
+ in str(excinfo.value)
160
+ )
161
+
162
+ # test description works without schema
163
+ assert cur.execute("SELECT 1").fetchall() == [(1,)]
164
+ assert cur.description
165
+
166
+ conn.execute_string("CREATE SCHEMA schema1; USE SCHEMA schema1;")
167
+ assert conn.schema == "SCHEMA1"
168
+
169
+
170
+ def test_connect_with_non_existent_db_or_schema(_fakesnow_no_auto_create: None):
171
+ # can connect with db that doesn't exist
172
+ with snowflake.connector.connect(database="marts") as conn, conn.cursor() as cur:
173
+ # but no valid database set
174
+ with pytest.raises(snowflake.connector.errors.ProgrammingError) as excinfo:
175
+ cur.execute("create table foobar (i int)")
176
+
177
+ assert (
178
+ "090105 (22000): Cannot perform CREATE TABLE. This session does not have a current database. Call 'USE DATABASE', or use a qualified name."
179
+ in str(excinfo.value)
180
+ )
181
+
182
+ # database still present on connection
183
+ assert conn.database == "MARTS"
184
+
185
+ cur.execute("CREATE database marts")
186
+
187
+ # can connect with schema that doesn't exist
188
+ with snowflake.connector.connect(database="marts", schema="jaffles") as conn, conn.cursor() as cur:
189
+ # but no valid schema set
190
+ with pytest.raises(snowflake.connector.errors.ProgrammingError) as excinfo:
191
+ cur.execute("create table foobar (i int)")
192
+
193
+ assert (
194
+ "090106 (22000): Cannot perform CREATE TABLE. This session does not have a current schema. Call 'USE SCHEMA', or use a qualified name."
195
+ in str(excinfo.value)
196
+ )
197
+
198
+ # schema still present on connection
199
+ assert conn.schema == "JAFFLES"
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
  # pyright: reportOptionalMemberAccess=false
5
5
  import datetime
6
6
  import json
7
+ import re
7
8
  import tempfile
8
9
  from decimal import Decimal
9
10
 
@@ -171,178 +172,6 @@ def test_close_cur(conn: snowflake.connector.SnowflakeConnection, cur: snowflake
171
172
  assert cur.close() is True
172
173
 
173
174
 
174
- def test_connect_auto_create(_fakesnow: None):
175
- with snowflake.connector.connect(database="db1", schema="schema1"):
176
- # creates db1 and schema1
177
- pass
178
-
179
- with snowflake.connector.connect(database="db1", schema="schema1"):
180
- # connects again and reuses db1 and schema1
181
- pass
182
-
183
-
184
- def test_connect_different_sessions_use_database(_fakesnow_no_auto_create: None):
185
- # connect without default database and schema
186
- with snowflake.connector.connect() as conn1, conn1.cursor() as cur:
187
- # use the table's fully qualified name
188
- cur.execute("create database marts")
189
- cur.execute("create schema marts.jaffles")
190
- cur.execute("create table marts.jaffles.customers (ID int, FIRST_NAME varchar, LAST_NAME varchar)")
191
- cur.execute("insert into marts.jaffles.customers values (1, 'Jenny', 'P')")
192
-
193
- # use database and schema
194
- cur.execute("use database marts")
195
- cur.execute("use schema jaffles")
196
- cur.execute("insert into customers values (2, 'Jasper', 'M')")
197
-
198
- # in a separate connection, connect using the database and schema from above
199
- with snowflake.connector.connect(database="marts", schema="jaffles") as conn2, conn2.cursor() as cur:
200
- cur.execute("select id, first_name, last_name from customers")
201
- assert cur.fetchall() == [(1, "Jenny", "P"), (2, "Jasper", "M")]
202
-
203
-
204
- def test_connect_reuse_db():
205
- with tempfile.TemporaryDirectory(prefix="fakesnow-test") as db_path:
206
- with (
207
- fakesnow.patch(db_path=db_path),
208
- snowflake.connector.connect(database="db1", schema="schema1") as conn,
209
- conn.cursor() as cur,
210
- ):
211
- # creates db1.schema1.example
212
- cur.execute("create table example (x int)")
213
- cur.execute("insert into example values (420)")
214
-
215
- # reconnect
216
- with (
217
- fakesnow.patch(db_path=db_path),
218
- snowflake.connector.connect(database="db1", schema="schema1") as conn,
219
- conn.cursor() as cur,
220
- ):
221
- assert cur.execute("select * from example").fetchall() == [(420,)]
222
-
223
-
224
- def test_connect_db_path_can_create_database() -> None:
225
- with tempfile.TemporaryDirectory(prefix="fakesnow-test") as db_path, fakesnow.patch(db_path=db_path):
226
- cursor = snowflake.connector.connect().cursor()
227
- cursor.execute("CREATE DATABASE db2")
228
-
229
-
230
- def test_connect_without_database(_fakesnow_no_auto_create: None):
231
- with snowflake.connector.connect() as conn, conn.cursor() as cur:
232
- with pytest.raises(snowflake.connector.errors.ProgrammingError) as excinfo:
233
- cur.execute("select * from customers")
234
-
235
- # actual snowflake error message is:
236
- #
237
- # 002003 (42S02): SQL compilation error:
238
- # Object 'CUSTOMERS' does not exist or not authorized.
239
- # assert (
240
- # "002003 (42S02): Catalog Error: Table with name customers does not exist!"
241
- # in str(excinfo.value)
242
- # )
243
-
244
- with pytest.raises(snowflake.connector.errors.ProgrammingError) as excinfo:
245
- cur.execute("select * from jaffles.customers")
246
-
247
- assert (
248
- "090105 (22000): Cannot perform SELECT. This session does not have a current database. Call 'USE DATABASE', or use a qualified name."
249
- in str(excinfo.value)
250
- )
251
-
252
- with pytest.raises(snowflake.connector.errors.ProgrammingError) as excinfo:
253
- cur.execute("create schema jaffles")
254
-
255
- assert (
256
- "090105 (22000): Cannot perform CREATE SCHEMA. This session does not have a current database. Call 'USE DATABASE', or use a qualified name."
257
- in str(excinfo.value)
258
- )
259
-
260
- with pytest.raises(snowflake.connector.errors.ProgrammingError) as excinfo:
261
- cur.execute("use schema jaffles")
262
-
263
- # assert (
264
- # "002043 (02000): SQL compilation error:\nObject does not exist, or operation cannot be performed."
265
- # in str(excinfo.value)
266
- # )
267
-
268
- with pytest.raises(snowflake.connector.errors.ProgrammingError) as excinfo:
269
- cur.execute("create table customers (ID int, FIRST_NAME varchar, LAST_NAME varchar)")
270
-
271
- assert (
272
- "090105 (22000): Cannot perform CREATE TABLE. This session does not have a current database. Call 'USE DATABASE', or use a qualified name."
273
- in str(excinfo.value)
274
- )
275
-
276
- # test description works without database
277
- assert cur.execute("SELECT 1").fetchall() == [(1,)]
278
- assert cur.description
279
-
280
-
281
- def test_connect_without_schema(_fakesnow: None):
282
- # database will be created but not schema
283
- with snowflake.connector.connect(database="marts") as conn, conn.cursor() as cur:
284
- assert not conn.schema
285
-
286
- with pytest.raises(snowflake.connector.errors.ProgrammingError) as excinfo:
287
- cur.execute("select * from customers")
288
-
289
- # actual snowflake error message is:
290
- #
291
- # 002003 (42S02): SQL compilation error:
292
- # Object 'CUSTOMERS' does not exist or not authorized.
293
- # assert (
294
- # "002003 (42S02): Catalog Error: Table with name customers does not exist!"
295
- # in str(excinfo.value)
296
- # )
297
-
298
- with pytest.raises(snowflake.connector.errors.ProgrammingError) as excinfo:
299
- cur.execute("create table customers (ID int, FIRST_NAME varchar, LAST_NAME varchar)")
300
-
301
- assert (
302
- "090106 (22000): Cannot perform CREATE TABLE. This session does not have a current schema. Call 'USE SCHEMA', or use a qualified name."
303
- in str(excinfo.value)
304
- )
305
-
306
- # test description works without schema
307
- assert cur.execute("SELECT 1").fetchall() == [(1,)]
308
- assert cur.description
309
-
310
- conn.execute_string("CREATE SCHEMA schema1; USE SCHEMA schema1;")
311
- assert conn.schema == "SCHEMA1"
312
-
313
-
314
- def test_connect_with_non_existent_db_or_schema(_fakesnow_no_auto_create: None):
315
- # can connect with db that doesn't exist
316
- with snowflake.connector.connect(database="marts") as conn, conn.cursor() as cur:
317
- # but no valid database set
318
- with pytest.raises(snowflake.connector.errors.ProgrammingError) as excinfo:
319
- cur.execute("create table foobar (i int)")
320
-
321
- assert (
322
- "090105 (22000): Cannot perform CREATE TABLE. This session does not have a current database. Call 'USE DATABASE', or use a qualified name."
323
- in str(excinfo.value)
324
- )
325
-
326
- # database still present on connection
327
- assert conn.database == "MARTS"
328
-
329
- cur.execute("CREATE database marts")
330
-
331
- # can connect with schema that doesn't exist
332
- with snowflake.connector.connect(database="marts", schema="jaffles") as conn, conn.cursor() as cur:
333
- # but no valid schema set
334
- with pytest.raises(snowflake.connector.errors.ProgrammingError) as excinfo:
335
- cur.execute("create table foobar (i int)")
336
-
337
- assert (
338
- "090106 (22000): Cannot perform CREATE TABLE. This session does not have a current schema. Call 'USE SCHEMA', or use a qualified name."
339
- in str(excinfo.value)
340
- )
341
-
342
- # schema still present on connection
343
- assert conn.schema == "JAFFLES"
344
-
345
-
346
175
  def test_create_database_respects_if_not_exists() -> None:
347
176
  with tempfile.TemporaryDirectory(prefix="fakesnow-test") as db_path, fakesnow.patch(db_path=db_path):
348
177
  cursor = snowflake.connector.connect().cursor()
@@ -1400,6 +1229,12 @@ def test_sfqid(cur: snowflake.connector.cursor.SnowflakeCursor):
1400
1229
  assert cur.sfqid == "fakesnow"
1401
1230
 
1402
1231
 
1232
+ def test_string_constant(cur: snowflake.connector.cursor.SnowflakeCursor):
1233
+ assert cur.execute("""
1234
+ select $$hello
1235
+ world$$""").fetchall() == [("hello\nworld",)]
1236
+
1237
+
1403
1238
  def test_tags_noop(cur: snowflake.connector.cursor.SnowflakeCursor):
1404
1239
  cur.execute("CREATE TABLE table1 (id int)")
1405
1240
  cur.execute("ALTER TABLE table1 SET TAG foo='bar'")
@@ -1557,6 +1392,53 @@ def test_use_invalid_schema(_fakesnow: None):
1557
1392
  )
1558
1393
 
1559
1394
 
1395
+ # Snowflake SQL variables: https://docs.snowflake.com/en/sql-reference/session-variables#using-variables-in-sql
1396
+ #
1397
+ # Variables are scoped to the session (Eg. The connection, not the cursor)
1398
+ # [x] Simple scalar variables: SET var1 = 1;
1399
+ # [x] Unset variables: UNSET var1;
1400
+ # [x] Simple SQL expression variables: SET INCREMENTAL_DATE = DATEADD( 'DAY', -7, CURRENT_DATE());
1401
+ # [x] Basic use of variables in SQL using $ syntax: SELECT $var1;
1402
+ # [ ] Multiple variables: SET (var1, var2) = (1, 'hello');
1403
+ # [ ] Variables set via 'properties' on the connection https://docs.snowflake.com/en/sql-reference/session-variables#setting-variables-on-connection
1404
+ # [ ] Using variables via the IDENTIFIER function: INSERT INTO IDENTIFIER($my_table_name) (i) VALUES (42);
1405
+ # [ ] Session variable functions: https://docs.snowflake.com/en/sql-reference/session-variables#session-variable-functions
1406
+ def test_variables(conn: snowflake.connector.SnowflakeConnection):
1407
+ with conn.cursor() as cur:
1408
+ cur.execute("SET var1 = 1;")
1409
+ cur.execute("SET var2 = 'hello';")
1410
+ cur.execute("SET var3 = DATEADD( 'DAY', -7, '2024-10-09');")
1411
+
1412
+ cur.execute("select $var1, $var2, $var3;")
1413
+ assert cur.fetchall() == [(1, "hello", datetime.datetime(2024, 10, 2, 0, 0))]
1414
+
1415
+ cur.execute("CREATE TABLE example (id int, name varchar);")
1416
+ cur.execute("INSERT INTO example VALUES (10, 'hello'), (20, 'world');")
1417
+ cur.execute("select id, name from example where name = $var2;")
1418
+ assert cur.fetchall() == [(10, "hello")]
1419
+
1420
+ cur.execute("UNSET var3;")
1421
+ with pytest.raises(
1422
+ snowflake.connector.errors.ProgrammingError, match=re.escape("Session variable '$VAR3' does not exist")
1423
+ ):
1424
+ cur.execute("select $var3;")
1425
+
1426
+ # variables are scoped to the session, so they should be available in a new cursor.
1427
+ with conn.cursor() as cur:
1428
+ cur.execute("select $var1, $var2")
1429
+ assert cur.fetchall() == [(1, "hello")]
1430
+
1431
+ # but not in a new connection.
1432
+ with (
1433
+ snowflake.connector.connect() as conn,
1434
+ conn.cursor() as cur,
1435
+ pytest.raises(
1436
+ snowflake.connector.errors.ProgrammingError, match=re.escape("Session variable '$VAR1' does not exist")
1437
+ ),
1438
+ ):
1439
+ cur.execute("select $var1;")
1440
+
1441
+
1560
1442
  def test_values(conn: snowflake.connector.SnowflakeConnection):
1561
1443
  with conn.cursor(snowflake.connector.cursor.DictCursor) as cur:
1562
1444
  cur.execute("select * from VALUES ('Amsterdam', 1), ('London', 2)")
@@ -193,3 +193,21 @@ def test_info_schema_views_with_views(conn: snowflake.connector.SnowflakeConnect
193
193
  "comment": None,
194
194
  }
195
195
  ]
196
+
197
+
198
+ def test_info_schema_show_primary_keys_from_table(cur: snowflake.connector.cursor.SnowflakeCursor) -> None:
199
+ cur.execute(
200
+ """
201
+ CREATE TABLE test_table (
202
+ ID varchar,
203
+ VERSION varchar,
204
+ PRIMARY KEY (ID, VERSION)
205
+ )
206
+ """
207
+ )
208
+
209
+ cur.execute("SHOW PRIMARY KEYS IN test_table")
210
+ pk_result = cur.fetchall()
211
+
212
+ pk_columns = [result[4] for result in pk_result]
213
+ assert pk_columns == ["ID", "VERSION"]
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