fakesnow 0.9.18__py3-none-any.whl → 0.9.20__py3-none-any.whl
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/__init__.py +2 -0
- fakesnow/fakes.py +7 -3
- fakesnow/transforms.py +16 -0
- fakesnow/variables.py +56 -0
- {fakesnow-0.9.18.dist-info → fakesnow-0.9.20.dist-info}/METADATA +1 -1
- {fakesnow-0.9.18.dist-info → fakesnow-0.9.20.dist-info}/RECORD +10 -9
- {fakesnow-0.9.18.dist-info → fakesnow-0.9.20.dist-info}/LICENSE +0 -0
- {fakesnow-0.9.18.dist-info → fakesnow-0.9.20.dist-info}/WHEEL +0 -0
- {fakesnow-0.9.18.dist-info → fakesnow-0.9.20.dist-info}/entry_points.txt +0 -0
- {fakesnow-0.9.18.dist-info → fakesnow-0.9.20.dist-info}/top_level.txt +0 -0
fakesnow/__init__.py
CHANGED
@@ -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
|
fakesnow/fakes.py
CHANGED
@@ -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.
|
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 (
|
fakesnow/transforms.py
CHANGED
@@ -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}
|
fakesnow/variables.py
ADDED
@@ -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,18 +1,19 @@
|
|
1
|
-
fakesnow/__init__.py,sha256=
|
1
|
+
fakesnow/__init__.py,sha256=gEo6Jg6f8tJUwRTsrfb9q-FpDOufelf8Y3XeWtEJ9wg,3897
|
2
2
|
fakesnow/__main__.py,sha256=GDrGyNTvBFuqn_UfDjKs7b3LPtU6gDv1KwosVDrukIM,76
|
3
3
|
fakesnow/checks.py,sha256=-QMvdcrRbhN60rnzxLBJ0IkUBWyLR8gGGKKmCS0w9mA,2383
|
4
4
|
fakesnow/cli.py,sha256=9qfI-Ssr6mo8UmIlXkUAOz2z2YPBgDsrEVaZv9FjGFs,2201
|
5
5
|
fakesnow/expr.py,sha256=CAxuYIUkwI339DQIBzvFF0F-m1tcVGKEPA5rDTzmH9A,892
|
6
|
-
fakesnow/fakes.py,sha256=
|
6
|
+
fakesnow/fakes.py,sha256=MYzj3XqVokPbOdOcU4WFep1CRsuXlBIwaz9EWaNCGo4,30778
|
7
7
|
fakesnow/fixtures.py,sha256=G-NkVeruSQAJ7fvSS2fR2oysUn0Yra1pohHlOvacKEk,455
|
8
8
|
fakesnow/global_database.py,sha256=WTVIP1VhNvdCeX7TQncX1TRpGQU5rBf5Pbxim40zeSU,1399
|
9
9
|
fakesnow/info_schema.py,sha256=CdIcGXHEQ_kmEAzdQKvA-PX41LA6wlK-4p1J45qgKYA,6266
|
10
10
|
fakesnow/macros.py,sha256=pX1YJDnQOkFJSHYUjQ6ErEkYIKvFI6Ncz_au0vv1csA,265
|
11
11
|
fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
|
12
|
-
fakesnow/transforms.py,sha256=
|
13
|
-
fakesnow
|
14
|
-
fakesnow-0.9.
|
15
|
-
fakesnow-0.9.
|
16
|
-
fakesnow-0.9.
|
17
|
-
fakesnow-0.9.
|
18
|
-
fakesnow-0.9.
|
12
|
+
fakesnow/transforms.py,sha256=7XS42Ehv3SRciQLpAL4swCFCkNpOTwarXEIX6jUzO4Y,53230
|
13
|
+
fakesnow/variables.py,sha256=iE8fnyaMnIoWh_ftT2qUq39fCNglwtQoGbC7MkTqHGU,2341
|
14
|
+
fakesnow-0.9.20.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
|
15
|
+
fakesnow-0.9.20.dist-info/METADATA,sha256=RSk43Zbbb8LQsG3G03DwUO8nVbaxs5qNd5VKTHqg4Xw,17839
|
16
|
+
fakesnow-0.9.20.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
17
|
+
fakesnow-0.9.20.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
|
18
|
+
fakesnow-0.9.20.dist-info/top_level.txt,sha256=500evXI1IFX9so82cizGIEMHAb_dJNPaZvd2H9dcKTA,24
|
19
|
+
fakesnow-0.9.20.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|