fakesnow 0.9.28__py3-none-any.whl → 0.9.29__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/conn.py +14 -8
- fakesnow/cursor.py +25 -19
- fakesnow/info_schema.py +48 -19
- fakesnow/instance.py +3 -0
- fakesnow/server.py +7 -2
- fakesnow/transforms.py +58 -30
- {fakesnow-0.9.28.dist-info → fakesnow-0.9.29.dist-info}/METADATA +3 -3
- {fakesnow-0.9.28.dist-info → fakesnow-0.9.29.dist-info}/RECORD +12 -12
- {fakesnow-0.9.28.dist-info → fakesnow-0.9.29.dist-info}/LICENSE +0 -0
- {fakesnow-0.9.28.dist-info → fakesnow-0.9.29.dist-info}/WHEEL +0 -0
- {fakesnow-0.9.28.dist-info → fakesnow-0.9.29.dist-info}/entry_points.txt +0 -0
- {fakesnow-0.9.28.dist-info → fakesnow-0.9.29.dist-info}/top_level.txt +0 -0
fakesnow/conn.py
CHANGED
@@ -42,13 +42,15 @@ class FakeSnowflakeConnection:
|
|
42
42
|
# information_schema.schemata below we use upper-case to match any existing duckdb
|
43
43
|
# catalog or schemas like "information_schema"
|
44
44
|
self.database = database and database.upper()
|
45
|
-
self.
|
45
|
+
self._schema = schema and (
|
46
|
+
"_FS_INFORMATION_SCHEMA" if schema.upper() == "INFORMATION_SCHEMA" else schema.upper()
|
47
|
+
)
|
46
48
|
|
47
49
|
self.database_set = False
|
48
50
|
self.schema_set = False
|
49
51
|
self.db_path = Path(db_path) if db_path else None
|
50
52
|
self.nop_regexes = nop_regexes
|
51
|
-
self._paramstyle = snowflake.connector.paramstyle
|
53
|
+
self._paramstyle = kwargs.get("paramstyle", snowflake.connector.paramstyle)
|
52
54
|
self.variables = Variables()
|
53
55
|
|
54
56
|
# create database if needed
|
@@ -69,24 +71,24 @@ class FakeSnowflakeConnection:
|
|
69
71
|
if (
|
70
72
|
create_schema
|
71
73
|
and self.database
|
72
|
-
and self.
|
74
|
+
and self._schema
|
73
75
|
and not duck_conn.execute(
|
74
76
|
f"""select * from information_schema.schemata
|
75
|
-
where upper(catalog_name) = '{self.database}' and upper(schema_name) = '{self.
|
77
|
+
where upper(catalog_name) = '{self.database}' and upper(schema_name) = '{self._schema}'"""
|
76
78
|
).fetchone()
|
77
79
|
):
|
78
|
-
duck_conn.execute(f"CREATE SCHEMA {self.database}.{self.
|
80
|
+
duck_conn.execute(f"CREATE SCHEMA {self.database}.{self._schema}")
|
79
81
|
|
80
82
|
# set database and schema if both exist
|
81
83
|
if (
|
82
84
|
self.database
|
83
|
-
and self.
|
85
|
+
and self._schema
|
84
86
|
and duck_conn.execute(
|
85
87
|
f"""select * from information_schema.schemata
|
86
|
-
where upper(catalog_name) = '{self.database}' and upper(schema_name) = '{self.
|
88
|
+
where upper(catalog_name) = '{self.database}' and upper(schema_name) = '{self._schema}'"""
|
87
89
|
).fetchone()
|
88
90
|
):
|
89
|
-
duck_conn.execute(f"SET schema='{self.database}.{self.
|
91
|
+
duck_conn.execute(f"SET schema='{self.database}.{self._schema}'")
|
90
92
|
self.database_set = True
|
91
93
|
self.schema_set = True
|
92
94
|
# set database if only that exists
|
@@ -149,3 +151,7 @@ class FakeSnowflakeConnection:
|
|
149
151
|
|
150
152
|
def rollback(self) -> None:
|
151
153
|
self.cursor().execute("ROLLBACK")
|
154
|
+
|
155
|
+
@property
|
156
|
+
def schema(self) -> str | None:
|
157
|
+
return "INFORMATION_SCHEMA" if self._schema == "_FS_INFORMATION_SCHEMA" else self._schema
|
fakesnow/cursor.py
CHANGED
@@ -145,6 +145,8 @@ class FakeSnowflakeCursor:
|
|
145
145
|
return self
|
146
146
|
|
147
147
|
expression = parse_one(command, read="snowflake")
|
148
|
+
self.check_db_and_schema(expression)
|
149
|
+
|
148
150
|
for exp in self._transform_explode(expression):
|
149
151
|
transformed = self._transform(exp)
|
150
152
|
self._execute(transformed, params)
|
@@ -154,6 +156,24 @@ class FakeSnowflakeCursor:
|
|
154
156
|
self._sqlstate = e.sqlstate
|
155
157
|
raise e
|
156
158
|
|
159
|
+
def check_db_and_schema(self, expression: exp.Expression) -> None:
|
160
|
+
no_database, no_schema = checks.is_unqualified_table_expression(expression)
|
161
|
+
|
162
|
+
if no_database and not self._conn.database_set:
|
163
|
+
cmd = expr.key_command(expression)
|
164
|
+
raise snowflake.connector.errors.ProgrammingError(
|
165
|
+
msg=f"Cannot perform {cmd}. This session does not have a current database. Call 'USE DATABASE', or use a qualified name.", # noqa: E501
|
166
|
+
errno=90105,
|
167
|
+
sqlstate="22000",
|
168
|
+
)
|
169
|
+
elif no_schema and not self._conn.schema_set:
|
170
|
+
cmd = expr.key_command(expression)
|
171
|
+
raise snowflake.connector.errors.ProgrammingError(
|
172
|
+
msg=f"Cannot perform {cmd}. This session does not have a current schema. Call 'USE SCHEMA', or use a qualified name.", # noqa: E501
|
173
|
+
errno=90106,
|
174
|
+
sqlstate="22000",
|
175
|
+
)
|
176
|
+
|
157
177
|
def _transform(self, expression: exp.Expression) -> exp.Expression:
|
158
178
|
return (
|
159
179
|
expression.transform(transforms.upper_case_unquoted_identifiers)
|
@@ -163,7 +183,8 @@ class FakeSnowflakeCursor:
|
|
163
183
|
.transform(transforms.extract_comment_on_table)
|
164
184
|
.transform(transforms.extract_comment_on_columns)
|
165
185
|
.transform(transforms.information_schema_fs_columns_snowflake)
|
166
|
-
.transform(transforms.
|
186
|
+
.transform(transforms.information_schema_databases, current_schema=self._conn.schema)
|
187
|
+
.transform(transforms.information_schema_fs_tables)
|
167
188
|
.transform(transforms.information_schema_fs_views)
|
168
189
|
.transform(transforms.drop_schema_cascade)
|
169
190
|
.transform(transforms.tag)
|
@@ -228,21 +249,6 @@ class FakeSnowflakeCursor:
|
|
228
249
|
|
229
250
|
cmd = expr.key_command(transformed)
|
230
251
|
|
231
|
-
no_database, no_schema = checks.is_unqualified_table_expression(transformed)
|
232
|
-
|
233
|
-
if no_database and not self._conn.database_set:
|
234
|
-
raise snowflake.connector.errors.ProgrammingError(
|
235
|
-
msg=f"Cannot perform {cmd}. This session does not have a current database. Call 'USE DATABASE', or use a qualified name.", # noqa: E501
|
236
|
-
errno=90105,
|
237
|
-
sqlstate="22000",
|
238
|
-
)
|
239
|
-
elif no_schema and not self._conn.schema_set:
|
240
|
-
raise snowflake.connector.errors.ProgrammingError(
|
241
|
-
msg=f"Cannot perform {cmd}. This session does not have a current schema. Call 'USE SCHEMA', or use a qualified name.", # noqa: E501
|
242
|
-
errno=90106,
|
243
|
-
sqlstate="22000",
|
244
|
-
)
|
245
|
-
|
246
252
|
sql = transformed.sql(dialect="duckdb")
|
247
253
|
|
248
254
|
if transformed.find(exp.Select) and (seed := transformed.args.get("seed")):
|
@@ -278,7 +284,7 @@ class FakeSnowflakeCursor:
|
|
278
284
|
self._conn.database_set = True
|
279
285
|
|
280
286
|
elif set_schema := transformed.args.get("set_schema"):
|
281
|
-
self._conn.
|
287
|
+
self._conn._schema = set_schema # noqa: SLF001
|
282
288
|
self._conn.schema_set = True
|
283
289
|
|
284
290
|
elif create_db_name := transformed.args.get("create_db_name"):
|
@@ -328,10 +334,10 @@ class FakeSnowflakeCursor:
|
|
328
334
|
# if dropping the current database/schema then reset conn metadata
|
329
335
|
if cmd == "DROP DATABASE" and ident == self._conn.database:
|
330
336
|
self._conn.database = None
|
331
|
-
self._conn.
|
337
|
+
self._conn._schema = None # noqa: SLF001
|
332
338
|
|
333
339
|
elif cmd == "DROP SCHEMA" and ident == self._conn.schema:
|
334
|
-
self._conn.
|
340
|
+
self._conn._schema = None # noqa: SLF001
|
335
341
|
|
336
342
|
if table_comment := cast(tuple[exp.Table, str], transformed.args.get("table_comment")):
|
337
343
|
# record table comment
|
fakesnow/info_schema.py
CHANGED
@@ -4,10 +4,11 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
from string import Template
|
6
6
|
|
7
|
+
from fakesnow.instance import GLOBAL_DATABASE_NAME
|
8
|
+
|
7
9
|
# use ext prefix in columns to disambiguate when joining with information_schema.tables
|
8
|
-
SQL_CREATE_INFORMATION_SCHEMA_TABLES_EXT =
|
9
|
-
|
10
|
-
create table if not exists ${catalog}.information_schema._fs_tables_ext (
|
10
|
+
SQL_CREATE_INFORMATION_SCHEMA_TABLES_EXT = f"""
|
11
|
+
create table if not exists {GLOBAL_DATABASE_NAME}.main._fs_tables_ext (
|
11
12
|
ext_table_catalog varchar,
|
12
13
|
ext_table_schema varchar,
|
13
14
|
ext_table_name varchar,
|
@@ -15,11 +16,10 @@ create table if not exists ${catalog}.information_schema._fs_tables_ext (
|
|
15
16
|
PRIMARY KEY(ext_table_catalog, ext_table_schema, ext_table_name)
|
16
17
|
)
|
17
18
|
"""
|
18
|
-
)
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
create table if not exists
|
20
|
+
|
21
|
+
SQL_CREATE_INFORMATION_SCHEMA_COLUMNS_EXT = f"""
|
22
|
+
create table if not exists {GLOBAL_DATABASE_NAME}.main._fs_columns_ext (
|
23
23
|
ext_table_catalog varchar,
|
24
24
|
ext_table_schema varchar,
|
25
25
|
ext_table_name varchar,
|
@@ -29,13 +29,19 @@ create table if not exists ${catalog}.information_schema._fs_columns_ext (
|
|
29
29
|
PRIMARY KEY(ext_table_catalog, ext_table_schema, ext_table_name, ext_column_name)
|
30
30
|
)
|
31
31
|
"""
|
32
|
+
|
33
|
+
SQL_CREATE_FS_INFORMATION_SCHEMA = Template(
|
34
|
+
"""
|
35
|
+
create schema if not exists ${catalog}._fs_information_schema
|
36
|
+
"""
|
32
37
|
)
|
33
38
|
|
39
|
+
|
34
40
|
# only include fields applicable to snowflake (as mentioned by describe table information_schema.columns)
|
35
41
|
# snowflake integers are 38 digits, base 10, See https://docs.snowflake.com/en/sql-reference/data-types-numeric
|
36
42
|
SQL_CREATE_INFORMATION_SCHEMA_COLUMNS_VIEW = Template(
|
37
43
|
"""
|
38
|
-
create view if not exists ${catalog}.
|
44
|
+
create view if not exists ${catalog}._fs_information_schema._fs_columns_snowflake AS
|
39
45
|
select
|
40
46
|
columns.table_catalog AS table_catalog,
|
41
47
|
columns.table_schema AS table_schema,
|
@@ -64,8 +70,8 @@ collation_name, is_identity, identity_generation, identity_cycle,
|
|
64
70
|
ddb_columns.comment as comment,
|
65
71
|
null::VARCHAR as identity_start,
|
66
72
|
null::VARCHAR as identity_increment,
|
67
|
-
from
|
68
|
-
left join
|
73
|
+
from system.information_schema.columns columns
|
74
|
+
left join _fs_global.main._fs_columns_ext ext
|
69
75
|
on ext_table_catalog = columns.table_catalog
|
70
76
|
AND ext_table_schema = columns.table_schema
|
71
77
|
AND ext_table_name = columns.table_name
|
@@ -75,6 +81,8 @@ LEFT JOIN duckdb_columns ddb_columns
|
|
75
81
|
AND ddb_columns.schema_name = columns.table_schema
|
76
82
|
AND ddb_columns.table_name = columns.table_name
|
77
83
|
AND ddb_columns.column_name = columns.column_name
|
84
|
+
where database_name = '${catalog}'
|
85
|
+
and schema_name != '_fs_information_schema'
|
78
86
|
"""
|
79
87
|
)
|
80
88
|
|
@@ -82,7 +90,7 @@ LEFT JOIN duckdb_columns ddb_columns
|
|
82
90
|
# replicates https://docs.snowflake.com/sql-reference/info-schema/databases
|
83
91
|
SQL_CREATE_INFORMATION_SCHEMA_DATABASES_VIEW = Template(
|
84
92
|
"""
|
85
|
-
create view if not exists ${catalog}.
|
93
|
+
create view if not exists ${catalog}._fs_information_schema.databases AS
|
86
94
|
select
|
87
95
|
catalog_name as database_name,
|
88
96
|
'SYSADMIN' as database_owner,
|
@@ -92,17 +100,31 @@ select
|
|
92
100
|
to_timestamp(0)::timestamptz as last_altered,
|
93
101
|
1 as retention_time,
|
94
102
|
'STANDARD' as type
|
95
|
-
from information_schema.schemata
|
103
|
+
from system.information_schema.schemata
|
96
104
|
where catalog_name not in ('memory', 'system', 'temp', '_fs_global')
|
97
|
-
and schema_name = '
|
105
|
+
and schema_name = 'main'
|
98
106
|
"""
|
99
107
|
)
|
100
108
|
|
109
|
+
# replicates https://docs.snowflake.com/sql-reference/info-schema/tables
|
110
|
+
SQL_CREATE_INFORMATION_SCHEMA_TABLES_VIEW = Template(
|
111
|
+
"""
|
112
|
+
create view if not exists ${catalog}._fs_information_schema._fs_tables AS
|
113
|
+
select *
|
114
|
+
from system.information_schema.tables tables
|
115
|
+
left join _fs_global.main._fs_tables_ext on
|
116
|
+
tables.table_catalog = _fs_tables_ext.ext_table_catalog AND
|
117
|
+
tables.table_schema = _fs_tables_ext.ext_table_schema AND
|
118
|
+
tables.table_name = _fs_tables_ext.ext_table_name
|
119
|
+
where table_catalog = '${catalog}'
|
120
|
+
and table_schema != '_fs_information_schema'
|
121
|
+
"""
|
122
|
+
)
|
101
123
|
|
102
124
|
# replicates https://docs.snowflake.com/sql-reference/info-schema/views
|
103
125
|
SQL_CREATE_INFORMATION_SCHEMA_VIEWS_VIEW = Template(
|
104
126
|
"""
|
105
|
-
create view if not exists ${catalog}.
|
127
|
+
create view if not exists ${catalog}._fs_information_schema._fs_views AS
|
106
128
|
select
|
107
129
|
database_name as table_catalog,
|
108
130
|
schema_name as table_schema,
|
@@ -120,24 +142,31 @@ select
|
|
120
142
|
null::VARCHAR as comment
|
121
143
|
from duckdb_views
|
122
144
|
where database_name = '${catalog}'
|
123
|
-
and schema_name != '
|
145
|
+
and schema_name != '_fs_information_schema'
|
124
146
|
"""
|
125
147
|
)
|
126
148
|
|
127
149
|
|
128
150
|
def creation_sql(catalog: str) -> str:
|
129
151
|
return f"""
|
130
|
-
{
|
131
|
-
{SQL_CREATE_INFORMATION_SCHEMA_COLUMNS_EXT.substitute(catalog=catalog)};
|
152
|
+
{SQL_CREATE_FS_INFORMATION_SCHEMA.substitute(catalog=catalog)};
|
132
153
|
{SQL_CREATE_INFORMATION_SCHEMA_COLUMNS_VIEW.substitute(catalog=catalog)};
|
133
154
|
{SQL_CREATE_INFORMATION_SCHEMA_DATABASES_VIEW.substitute(catalog=catalog)};
|
155
|
+
{SQL_CREATE_INFORMATION_SCHEMA_TABLES_VIEW.substitute(catalog=catalog)};
|
134
156
|
{SQL_CREATE_INFORMATION_SCHEMA_VIEWS_VIEW.substitute(catalog=catalog)};
|
135
157
|
"""
|
136
158
|
|
137
159
|
|
160
|
+
def fs_global_creation_sql(catalog: str) -> str:
|
161
|
+
return f"""
|
162
|
+
{SQL_CREATE_INFORMATION_SCHEMA_TABLES_EXT};
|
163
|
+
{SQL_CREATE_INFORMATION_SCHEMA_COLUMNS_EXT};
|
164
|
+
"""
|
165
|
+
|
166
|
+
|
138
167
|
def insert_table_comment_sql(catalog: str, schema: str, table: str, comment: str) -> str:
|
139
168
|
return f"""
|
140
|
-
INSERT INTO {
|
169
|
+
INSERT INTO {GLOBAL_DATABASE_NAME}.main._fs_tables_ext
|
141
170
|
values ('{catalog}', '{schema}', '{table}', '{comment}')
|
142
171
|
ON CONFLICT (ext_table_catalog, ext_table_schema, ext_table_name)
|
143
172
|
DO UPDATE SET comment = excluded.comment
|
@@ -151,7 +180,7 @@ def insert_text_lengths_sql(catalog: str, schema: str, table: str, text_lengths:
|
|
151
180
|
)
|
152
181
|
|
153
182
|
return f"""
|
154
|
-
INSERT INTO {
|
183
|
+
INSERT INTO {GLOBAL_DATABASE_NAME}.main._fs_columns_ext
|
155
184
|
values {values}
|
156
185
|
ON CONFLICT (ext_table_catalog, ext_table_schema, ext_table_name, ext_column_name)
|
157
186
|
DO UPDATE SET ext_character_maximum_length = excluded.ext_character_maximum_length,
|
fakesnow/instance.py
CHANGED
@@ -6,6 +6,7 @@ from typing import Any
|
|
6
6
|
import duckdb
|
7
7
|
|
8
8
|
import fakesnow.fakes as fakes
|
9
|
+
from fakesnow import info_schema
|
9
10
|
|
10
11
|
GLOBAL_DATABASE_NAME = "_fs_global"
|
11
12
|
USERS_TABLE_FQ_NAME = f"{GLOBAL_DATABASE_NAME}._fs_users_ext"
|
@@ -71,6 +72,8 @@ class FakeSnow:
|
|
71
72
|
# create a "global" database for storing objects which span databases.
|
72
73
|
self.duck_conn.execute(f"ATTACH IF NOT EXISTS ':memory:' AS {GLOBAL_DATABASE_NAME}")
|
73
74
|
self.duck_conn.execute(SQL_CREATE_INFORMATION_SCHEMA_USERS_TABLE_EXT)
|
75
|
+
# create the info schema extensions
|
76
|
+
self.duck_conn.execute(info_schema.fs_global_creation_sql(GLOBAL_DATABASE_NAME))
|
74
77
|
|
75
78
|
def connect(
|
76
79
|
self, database: str | None = None, schema: str | None = None, **kwargs: Any
|
fakesnow/server.py
CHANGED
@@ -34,7 +34,9 @@ async def login_request(request: Request) -> JSONResponse:
|
|
34
34
|
database = request.query_params.get("databaseName")
|
35
35
|
schema = request.query_params.get("schemaName")
|
36
36
|
body = await request.body()
|
37
|
-
|
37
|
+
if request.headers.get("Content-Encoding") == "gzip":
|
38
|
+
body = gzip.decompress(body)
|
39
|
+
body_json = json.loads(body)
|
38
40
|
session_params: dict[str, Any] = body_json["data"]["SESSION_PARAMETERS"]
|
39
41
|
if db_path := session_params.get("FAKESNOW_DB_PATH"):
|
40
42
|
# isolated creates a new in-memory database, rather than using the shared in-memory database
|
@@ -53,7 +55,10 @@ async def query_request(request: Request) -> JSONResponse:
|
|
53
55
|
conn = to_conn(request)
|
54
56
|
|
55
57
|
body = await request.body()
|
56
|
-
|
58
|
+
if request.headers.get("Content-Encoding") == "gzip":
|
59
|
+
body = gzip.decompress(body)
|
60
|
+
|
61
|
+
body_json = json.loads(body)
|
57
62
|
|
58
63
|
sql_text = body_json["sqlText"]
|
59
64
|
|
fakesnow/transforms.py
CHANGED
@@ -11,7 +11,6 @@ from fakesnow import transforms_merge
|
|
11
11
|
from fakesnow.instance import USERS_TABLE_FQ_NAME
|
12
12
|
from fakesnow.variables import Variables
|
13
13
|
|
14
|
-
MISSING_DATABASE = "missing_database"
|
15
14
|
SUCCESS_NOP = sqlglot.parse_one("SELECT 'Statement executed successfully.' as status")
|
16
15
|
|
17
16
|
|
@@ -167,7 +166,7 @@ SELECT
|
|
167
166
|
NULL::VARCHAR AS "comment",
|
168
167
|
NULL::VARCHAR AS "policy name",
|
169
168
|
NULL::JSON AS "privacy domain",
|
170
|
-
FROM
|
169
|
+
FROM _fs_information_schema._fs_columns_snowflake
|
171
170
|
WHERE table_catalog = '${catalog}' AND table_schema = '${schema}' AND table_name = '${table}'
|
172
171
|
ORDER BY ordinal_position
|
173
172
|
"""
|
@@ -188,7 +187,7 @@ SELECT
|
|
188
187
|
NULL::VARCHAR AS "comment",
|
189
188
|
NULL::VARCHAR AS "policy name",
|
190
189
|
NULL::JSON AS "privacy domain",
|
191
|
-
FROM (DESCRIBE
|
190
|
+
FROM (DESCRIBE ${view})
|
192
191
|
"""
|
193
192
|
)
|
194
193
|
|
@@ -211,9 +210,17 @@ def describe_table(
|
|
211
210
|
catalog = table.catalog or current_database
|
212
211
|
schema = table.db or current_schema
|
213
212
|
|
213
|
+
# TODO - move this after information_schema_fs_columns_snowflake
|
214
214
|
if schema and schema.upper() == "INFORMATION_SCHEMA":
|
215
215
|
# information schema views don't exist in _fs_columns_snowflake
|
216
|
-
return sqlglot.parse_one(
|
216
|
+
return sqlglot.parse_one(
|
217
|
+
SQL_DESCRIBE_INFO_SCHEMA.substitute(view=f"system.information_schema.{table.name}"), read="duckdb"
|
218
|
+
)
|
219
|
+
elif table.name.upper() == "_FS_COLUMNS_SNOWFLAKE":
|
220
|
+
# information schema views don't exist in _fs_columns_snowflake
|
221
|
+
return sqlglot.parse_one(
|
222
|
+
SQL_DESCRIBE_INFO_SCHEMA.substitute(view="_fs_information_schema._FS_COLUMNS_SNOWFLAKE"), read="duckdb"
|
223
|
+
)
|
217
224
|
|
218
225
|
return sqlglot.parse_one(
|
219
226
|
SQL_DESCRIBE_TABLE.substitute(catalog=catalog, schema=schema, table=table.name),
|
@@ -596,7 +603,7 @@ def indices_to_json_extract(expression: exp.Expression) -> exp.Expression:
|
|
596
603
|
|
597
604
|
|
598
605
|
def information_schema_fs_columns_snowflake(expression: exp.Expression) -> exp.Expression:
|
599
|
-
"""Redirect to the
|
606
|
+
"""Redirect to the _FS_COLUMNS_SNOWFLAKE view which has metadata that matches snowflake.
|
600
607
|
|
601
608
|
Because duckdb doesn't store character_maximum_length or character_octet_length.
|
602
609
|
"""
|
@@ -609,44 +616,58 @@ def information_schema_fs_columns_snowflake(expression: exp.Expression) -> exp.E
|
|
609
616
|
and expression.name.upper() == "COLUMNS"
|
610
617
|
):
|
611
618
|
expression.set("this", exp.Identifier(this="_FS_COLUMNS_SNOWFLAKE", quoted=False))
|
619
|
+
expression.set("db", exp.Identifier(this="_FS_INFORMATION_SCHEMA", quoted=False))
|
612
620
|
|
613
621
|
return expression
|
614
622
|
|
615
623
|
|
616
|
-
def
|
617
|
-
|
624
|
+
def information_schema_databases(
|
625
|
+
expression: exp.Expression,
|
626
|
+
current_schema: str | None = None,
|
627
|
+
) -> exp.Expression:
|
628
|
+
if (
|
629
|
+
isinstance(expression, exp.Table)
|
630
|
+
and (
|
631
|
+
expression.db.upper() == "INFORMATION_SCHEMA"
|
632
|
+
or (current_schema and current_schema.upper() == "INFORMATION_SCHEMA")
|
633
|
+
)
|
634
|
+
and expression.name.upper() == "DATABASES"
|
635
|
+
):
|
636
|
+
return exp.Table(
|
637
|
+
this=exp.Identifier(this="DATABASES", quoted=False),
|
638
|
+
db=exp.Identifier(this="_FS_INFORMATION_SCHEMA", quoted=False),
|
639
|
+
)
|
640
|
+
return expression
|
641
|
+
|
642
|
+
|
643
|
+
def information_schema_fs_tables(
|
644
|
+
expression: exp.Expression,
|
645
|
+
) -> exp.Expression:
|
646
|
+
"""Use _FS_TABLES to access additional metadata columns (eg: comment)."""
|
618
647
|
|
619
648
|
if (
|
620
649
|
isinstance(expression, exp.Select)
|
621
|
-
and (
|
622
|
-
and
|
623
|
-
and
|
650
|
+
and (tbl := expression.find(exp.Table))
|
651
|
+
and tbl.db.upper() == "INFORMATION_SCHEMA"
|
652
|
+
and tbl.name.upper() == "TABLES"
|
624
653
|
):
|
625
|
-
|
626
|
-
|
627
|
-
on=(
|
628
|
-
"""
|
629
|
-
tables.table_catalog = _fs_tables_ext.ext_table_catalog AND
|
630
|
-
tables.table_schema = _fs_tables_ext.ext_table_schema AND
|
631
|
-
tables.table_name = _fs_tables_ext.ext_table_name
|
632
|
-
"""
|
633
|
-
),
|
634
|
-
join_type="left",
|
635
|
-
)
|
654
|
+
tbl.set("this", exp.Identifier(this="_FS_TABLES", quoted=False))
|
655
|
+
tbl.set("db", exp.Identifier(this="_FS_INFORMATION_SCHEMA", quoted=False))
|
636
656
|
|
637
657
|
return expression
|
638
658
|
|
639
659
|
|
640
660
|
def information_schema_fs_views(expression: exp.Expression) -> exp.Expression:
|
641
|
-
"""Use
|
661
|
+
"""Use _FS_VIEWS to return Snowflake's version instead of duckdb's."""
|
642
662
|
|
643
663
|
if (
|
644
664
|
isinstance(expression, exp.Select)
|
645
|
-
and (
|
646
|
-
and
|
647
|
-
and
|
665
|
+
and (tbl := expression.find(exp.Table))
|
666
|
+
and tbl.db.upper() == "INFORMATION_SCHEMA"
|
667
|
+
and tbl.name.upper() == "VIEWS"
|
648
668
|
):
|
649
|
-
|
669
|
+
tbl.set("this", exp.Identifier(this="_FS_VIEWS", quoted=False))
|
670
|
+
tbl.set("db", exp.Identifier(this="_FS_INFORMATION_SCHEMA", quoted=False))
|
650
671
|
|
651
672
|
return expression
|
652
673
|
|
@@ -921,7 +942,10 @@ def set_schema(expression: exp.Expression, current_database: str | None) -> exp.
|
|
921
942
|
db_name = db.name
|
922
943
|
else:
|
923
944
|
# isn't qualified with a database
|
924
|
-
db_name = current_database
|
945
|
+
db_name = current_database
|
946
|
+
|
947
|
+
# assertion always true because check_db_schema is called before this
|
948
|
+
assert db_name
|
925
949
|
|
926
950
|
schema = expression.this.name
|
927
951
|
return exp.Command(
|
@@ -960,7 +984,7 @@ def show_objects_tables(expression: exp.Expression, current_database: str | None
|
|
960
984
|
schema = None
|
961
985
|
|
962
986
|
tables_only = "table_type = 'BASE TABLE' and " if show == "TABLES" else ""
|
963
|
-
exclude_fakesnow_tables = "not (table_schema == '
|
987
|
+
exclude_fakesnow_tables = "not (table_schema == '_fs_information_schema')"
|
964
988
|
# without a database will show everything in the "account"
|
965
989
|
table_catalog = f" and table_catalog = '{catalog}'" if catalog else ""
|
966
990
|
schema = f" and table_schema = '{schema}'" if schema else ""
|
@@ -991,12 +1015,16 @@ def show_objects_tables(expression: exp.Expression, current_database: str | None
|
|
991
1015
|
SQL_SHOW_SCHEMAS = """
|
992
1016
|
select
|
993
1017
|
to_timestamp(0)::timestamptz as 'created_on',
|
994
|
-
|
1018
|
+
case
|
1019
|
+
when schema_name = '_fs_information_schema' then 'information_schema'
|
1020
|
+
else schema_name
|
1021
|
+
end as 'name',
|
995
1022
|
NULL as 'kind',
|
996
1023
|
catalog_name as 'database_name',
|
997
1024
|
NULL as 'schema_name'
|
998
1025
|
from information_schema.schemata
|
999
|
-
where catalog_name
|
1026
|
+
where not catalog_name in ('memory', 'system', 'temp')
|
1027
|
+
and not schema_name in ('main', 'pg_catalog')
|
1000
1028
|
"""
|
1001
1029
|
|
1002
1030
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: fakesnow
|
3
|
-
Version: 0.9.
|
3
|
+
Version: 0.9.29
|
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
|
@@ -210,10 +210,10 @@ Classifier: License :: OSI Approved :: MIT License
|
|
210
210
|
Requires-Python: >=3.9
|
211
211
|
Description-Content-Type: text/markdown
|
212
212
|
License-File: LICENSE
|
213
|
-
Requires-Dist: duckdb~=1.
|
213
|
+
Requires-Dist: duckdb~=1.2.0
|
214
214
|
Requires-Dist: pyarrow
|
215
215
|
Requires-Dist: snowflake-connector-python
|
216
|
-
Requires-Dist: sqlglot~=26.
|
216
|
+
Requires-Dist: sqlglot~=26.6.0
|
217
217
|
Provides-Extra: dev
|
218
218
|
Requires-Dist: build~=1.0; extra == "dev"
|
219
219
|
Requires-Dist: dirty-equals; extra == "dev"
|
@@ -3,24 +3,24 @@ fakesnow/__main__.py,sha256=GDrGyNTvBFuqn_UfDjKs7b3LPtU6gDv1KwosVDrukIM,76
|
|
3
3
|
fakesnow/arrow.py,sha256=MwatkdZX5AFADzXvxhBFmcRJVxbW4D39VoqLyhpTbl0,5057
|
4
4
|
fakesnow/checks.py,sha256=N8sXldhS3u1gG32qvZ4VFlsKgavRKrQrxLiQU8am1lw,2691
|
5
5
|
fakesnow/cli.py,sha256=9qfI-Ssr6mo8UmIlXkUAOz2z2YPBgDsrEVaZv9FjGFs,2201
|
6
|
-
fakesnow/conn.py,sha256=
|
7
|
-
fakesnow/cursor.py,sha256=
|
6
|
+
fakesnow/conn.py,sha256=da9ln_covsyKgdNdPXLzMTUBr72P0rRGadIDVt-kaeI,5737
|
7
|
+
fakesnow/cursor.py,sha256=1BP1rZ28JfIfJkIR_8yEFDq2FrUf93JFrrYLJoKJr14,20587
|
8
8
|
fakesnow/expr.py,sha256=CAxuYIUkwI339DQIBzvFF0F-m1tcVGKEPA5rDTzmH9A,892
|
9
9
|
fakesnow/fakes.py,sha256=JQTiUkkwPeQrJ8FDWhPFPK6pGwd_aR2oiOrNzCWznlM,187
|
10
10
|
fakesnow/fixtures.py,sha256=G-NkVeruSQAJ7fvSS2fR2oysUn0Yra1pohHlOvacKEk,455
|
11
|
-
fakesnow/info_schema.py,sha256=
|
12
|
-
fakesnow/instance.py,sha256=
|
11
|
+
fakesnow/info_schema.py,sha256=_4YWnpuOFuyACr9k4iYdf2vLN7GDMG8X_pEBlC-8OmM,7269
|
12
|
+
fakesnow/instance.py,sha256=7xHJv-5-KKAI3Qm7blcvkXgkGg7WYtXEm3nUS4jLyFs,3299
|
13
13
|
fakesnow/macros.py,sha256=pX1YJDnQOkFJSHYUjQ6ErEkYIKvFI6Ncz_au0vv1csA,265
|
14
14
|
fakesnow/pandas_tools.py,sha256=wI203UQHC8JvDzxE_VjE1NeV4rThek2P-u52oTg2foo,3481
|
15
15
|
fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
|
16
16
|
fakesnow/rowtype.py,sha256=QUp8EaXD5LT0Xv8BXk5ze4WseEn52xoJ6R05pJjs5mM,2729
|
17
|
-
fakesnow/server.py,sha256=
|
18
|
-
fakesnow/transforms.py,sha256=
|
17
|
+
fakesnow/server.py,sha256=VpM-ZjFS4JekLESEoQTndvXqnqz8bH4ZO8lq_66-c6s,4387
|
18
|
+
fakesnow/transforms.py,sha256=dAoFFRFkJG8kcQdBPnE5w2eed4AZkh4NV3ajx1vu3A8,56444
|
19
19
|
fakesnow/transforms_merge.py,sha256=Pg7_rwbAT_vr1U4ocBofUSyqaK8_e3qdIz_2SDm2S3s,8320
|
20
20
|
fakesnow/variables.py,sha256=WXyPnkeNwD08gy52yF66CVe2twiYC50tztNfgXV4q1k,3032
|
21
|
-
fakesnow-0.9.
|
22
|
-
fakesnow-0.9.
|
23
|
-
fakesnow-0.9.
|
24
|
-
fakesnow-0.9.
|
25
|
-
fakesnow-0.9.
|
26
|
-
fakesnow-0.9.
|
21
|
+
fakesnow-0.9.29.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
|
22
|
+
fakesnow-0.9.29.dist-info/METADATA,sha256=sx4qqMsuxOaaDMKR5U8A4ZT0djn9wHSrOUjCqthUoWQ,18107
|
23
|
+
fakesnow-0.9.29.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
24
|
+
fakesnow-0.9.29.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
|
25
|
+
fakesnow-0.9.29.dist-info/top_level.txt,sha256=500evXI1IFX9so82cizGIEMHAb_dJNPaZvd2H9dcKTA,24
|
26
|
+
fakesnow-0.9.29.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|