fakesnow 0.9.27__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/arrow.py +5 -1
- fakesnow/conn.py +19 -9
- fakesnow/cursor.py +32 -22
- fakesnow/info_schema.py +49 -20
- fakesnow/instance.py +3 -0
- fakesnow/pandas_tools.py +2 -2
- fakesnow/{types.py → rowtype.py} +2 -0
- fakesnow/server.py +10 -4
- fakesnow/transforms.py +59 -31
- fakesnow/transforms_merge.py +7 -7
- {fakesnow-0.9.27.dist-info → fakesnow-0.9.29.dist-info}/METADATA +5 -5
- fakesnow-0.9.29.dist-info/RECORD +26 -0
- {fakesnow-0.9.27.dist-info → fakesnow-0.9.29.dist-info}/WHEEL +1 -1
- fakesnow-0.9.27.dist-info/RECORD +0 -26
- {fakesnow-0.9.27.dist-info → fakesnow-0.9.29.dist-info}/LICENSE +0 -0
- {fakesnow-0.9.27.dist-info → fakesnow-0.9.29.dist-info}/entry_points.txt +0 -0
- {fakesnow-0.9.27.dist-info → fakesnow-0.9.29.dist-info}/top_level.txt +0 -0
fakesnow/arrow.py
CHANGED
@@ -5,7 +5,7 @@ from typing import cast
|
|
5
5
|
import pyarrow as pa
|
6
6
|
import pyarrow.compute as pc
|
7
7
|
|
8
|
-
from fakesnow.
|
8
|
+
from fakesnow.rowtype import ColumnInfo
|
9
9
|
|
10
10
|
|
11
11
|
def to_sf_schema(schema: pa.Schema, rowtype: list[ColumnInfo]) -> pa.Schema:
|
@@ -27,6 +27,10 @@ def to_sf_schema(schema: pa.Schema, rowtype: list[ColumnInfo]) -> pa.Schema:
|
|
27
27
|
field = field.with_type(pa.struct(fields))
|
28
28
|
elif isinstance(field.type, pa.Time64Type):
|
29
29
|
field = field.with_type(pa.int64())
|
30
|
+
elif pa.types.is_uint64(field.type):
|
31
|
+
# snowflake-python-connector expects signed ints
|
32
|
+
# see https://github.com/snowflakedb/snowflake-connector-python/blob/5d7064c7f3f756792c1f6252bf5c9d807e4307e8/src/snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.cpp#L187
|
33
|
+
field = field.with_type(pa.int64())
|
30
34
|
|
31
35
|
return field.with_metadata(
|
32
36
|
{
|
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
|
@@ -60,7 +62,7 @@ class FakeSnowflakeConnection:
|
|
60
62
|
where upper(catalog_name) = '{self.database}'"""
|
61
63
|
).fetchone()
|
62
64
|
):
|
63
|
-
db_file = f"{self.db_path/self.database}.db" if self.db_path else ":memory:"
|
65
|
+
db_file = f"{self.db_path / self.database}.db" if self.db_path else ":memory:"
|
64
66
|
duck_conn.execute(f"ATTACH DATABASE '{db_file}' AS {self.database}")
|
65
67
|
duck_conn.execute(info_schema.creation_sql(self.database))
|
66
68
|
duck_conn.execute(macros.creation_sql(self.database))
|
@@ -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
|
@@ -114,6 +116,10 @@ class FakeSnowflakeConnection:
|
|
114
116
|
) -> None:
|
115
117
|
pass
|
116
118
|
|
119
|
+
def autocommit(self, _mode: bool) -> None:
|
120
|
+
# autcommit is always on in duckdb
|
121
|
+
pass
|
122
|
+
|
117
123
|
def close(self, retry: bool = True) -> None:
|
118
124
|
self._duck_conn.close()
|
119
125
|
self._is_closed = True
|
@@ -145,3 +151,7 @@ class FakeSnowflakeConnection:
|
|
145
151
|
|
146
152
|
def rollback(self) -> None:
|
147
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
@@ -3,13 +3,14 @@ from __future__ import annotations
|
|
3
3
|
import os
|
4
4
|
import re
|
5
5
|
import sys
|
6
|
+
import uuid
|
6
7
|
from collections.abc import Iterator, Sequence
|
7
8
|
from string import Template
|
8
9
|
from types import TracebackType
|
9
10
|
from typing import TYPE_CHECKING, Any, cast
|
10
11
|
|
11
12
|
import duckdb
|
12
|
-
import pyarrow
|
13
|
+
import pyarrow # needed by fetch_arrow_table()
|
13
14
|
import snowflake.connector.converter
|
14
15
|
import snowflake.connector.errors
|
15
16
|
import sqlglot
|
@@ -23,7 +24,7 @@ import fakesnow.checks as checks
|
|
23
24
|
import fakesnow.expr as expr
|
24
25
|
import fakesnow.info_schema as info_schema
|
25
26
|
import fakesnow.transforms as transforms
|
26
|
-
from fakesnow.
|
27
|
+
from fakesnow.rowtype import describe_as_result_metadata
|
27
28
|
|
28
29
|
if TYPE_CHECKING:
|
29
30
|
# don't require pandas at import time
|
@@ -71,6 +72,7 @@ class FakeSnowflakeCursor:
|
|
71
72
|
self._arrow_table = None
|
72
73
|
self._arrow_table_fetch_index = None
|
73
74
|
self._rowcount = None
|
75
|
+
self._sfqid = None
|
74
76
|
self._converter = snowflake.connector.converter.SnowflakeConverter()
|
75
77
|
|
76
78
|
def __enter__(self) -> Self:
|
@@ -143,6 +145,8 @@ class FakeSnowflakeCursor:
|
|
143
145
|
return self
|
144
146
|
|
145
147
|
expression = parse_one(command, read="snowflake")
|
148
|
+
self.check_db_and_schema(expression)
|
149
|
+
|
146
150
|
for exp in self._transform_explode(expression):
|
147
151
|
transformed = self._transform(exp)
|
148
152
|
self._execute(transformed, params)
|
@@ -152,6 +156,24 @@ class FakeSnowflakeCursor:
|
|
152
156
|
self._sqlstate = e.sqlstate
|
153
157
|
raise e
|
154
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
|
+
|
155
177
|
def _transform(self, expression: exp.Expression) -> exp.Expression:
|
156
178
|
return (
|
157
179
|
expression.transform(transforms.upper_case_unquoted_identifiers)
|
@@ -161,7 +183,8 @@ class FakeSnowflakeCursor:
|
|
161
183
|
.transform(transforms.extract_comment_on_table)
|
162
184
|
.transform(transforms.extract_comment_on_columns)
|
163
185
|
.transform(transforms.information_schema_fs_columns_snowflake)
|
164
|
-
.transform(transforms.
|
186
|
+
.transform(transforms.information_schema_databases, current_schema=self._conn.schema)
|
187
|
+
.transform(transforms.information_schema_fs_tables)
|
165
188
|
.transform(transforms.information_schema_fs_views)
|
166
189
|
.transform(transforms.drop_schema_cascade)
|
167
190
|
.transform(transforms.tag)
|
@@ -222,24 +245,10 @@ class FakeSnowflakeCursor:
|
|
222
245
|
self._arrow_table = None
|
223
246
|
self._arrow_table_fetch_index = None
|
224
247
|
self._rowcount = None
|
248
|
+
self._sfqid = None
|
225
249
|
|
226
250
|
cmd = expr.key_command(transformed)
|
227
251
|
|
228
|
-
no_database, no_schema = checks.is_unqualified_table_expression(transformed)
|
229
|
-
|
230
|
-
if no_database and not self._conn.database_set:
|
231
|
-
raise snowflake.connector.errors.ProgrammingError(
|
232
|
-
msg=f"Cannot perform {cmd}. This session does not have a current database. Call 'USE DATABASE', or use a qualified name.", # noqa: E501
|
233
|
-
errno=90105,
|
234
|
-
sqlstate="22000",
|
235
|
-
)
|
236
|
-
elif no_schema and not self._conn.schema_set:
|
237
|
-
raise snowflake.connector.errors.ProgrammingError(
|
238
|
-
msg=f"Cannot perform {cmd}. This session does not have a current schema. Call 'USE SCHEMA', or use a qualified name.", # noqa: E501
|
239
|
-
errno=90106,
|
240
|
-
sqlstate="22000",
|
241
|
-
)
|
242
|
-
|
243
252
|
sql = transformed.sql(dialect="duckdb")
|
244
253
|
|
245
254
|
if transformed.find(exp.Select) and (seed := transformed.args.get("seed")):
|
@@ -275,7 +284,7 @@ class FakeSnowflakeCursor:
|
|
275
284
|
self._conn.database_set = True
|
276
285
|
|
277
286
|
elif set_schema := transformed.args.get("set_schema"):
|
278
|
-
self._conn.
|
287
|
+
self._conn._schema = set_schema # noqa: SLF001
|
279
288
|
self._conn.schema_set = True
|
280
289
|
|
281
290
|
elif create_db_name := transformed.args.get("create_db_name"):
|
@@ -325,10 +334,10 @@ class FakeSnowflakeCursor:
|
|
325
334
|
# if dropping the current database/schema then reset conn metadata
|
326
335
|
if cmd == "DROP DATABASE" and ident == self._conn.database:
|
327
336
|
self._conn.database = None
|
328
|
-
self._conn.
|
337
|
+
self._conn._schema = None # noqa: SLF001
|
329
338
|
|
330
339
|
elif cmd == "DROP SCHEMA" and ident == self._conn.schema:
|
331
|
-
self._conn.
|
340
|
+
self._conn._schema = None # noqa: SLF001
|
332
341
|
|
333
342
|
if table_comment := cast(tuple[exp.Table, str], transformed.args.get("table_comment")):
|
334
343
|
# record table comment
|
@@ -353,6 +362,7 @@ class FakeSnowflakeCursor:
|
|
353
362
|
|
354
363
|
self._arrow_table = self._duck_conn.fetch_arrow_table()
|
355
364
|
self._rowcount = affected_count or self._arrow_table.num_rows
|
365
|
+
self._sfqid = str(uuid.uuid4())
|
356
366
|
|
357
367
|
self._last_sql = result_sql or sql
|
358
368
|
self._last_params = params
|
@@ -423,7 +433,7 @@ class FakeSnowflakeCursor:
|
|
423
433
|
|
424
434
|
@property
|
425
435
|
def sfqid(self) -> str | None:
|
426
|
-
return
|
436
|
+
return self._sfqid
|
427
437
|
|
428
438
|
@property
|
429
439
|
def sqlstate(self) -> str | None:
|
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
|
@@ -146,12 +175,12 @@ def insert_table_comment_sql(catalog: str, schema: str, table: str, comment: str
|
|
146
175
|
|
147
176
|
def insert_text_lengths_sql(catalog: str, schema: str, table: str, text_lengths: list[tuple[str, int]]) -> str:
|
148
177
|
values = ", ".join(
|
149
|
-
f"('{catalog}', '{schema}', '{table}', '{col_name}', {size}, {min(size*4,16777216)})"
|
178
|
+
f"('{catalog}', '{schema}', '{table}', '{col_name}', {size}, {min(size * 4, 16777216)})"
|
150
179
|
for (col_name, size) in 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/pandas_tools.py
CHANGED
@@ -4,13 +4,13 @@ import json
|
|
4
4
|
from collections.abc import Sequence
|
5
5
|
from typing import TYPE_CHECKING, Any, Literal, Optional
|
6
6
|
|
7
|
-
import numpy as np
|
8
7
|
from duckdb import DuckDBPyConnection
|
9
8
|
|
10
9
|
from fakesnow.conn import FakeSnowflakeConnection
|
11
10
|
|
12
11
|
if TYPE_CHECKING:
|
13
|
-
# don't require pandas at import time
|
12
|
+
# don't require pandas or numpy at import time
|
13
|
+
import numpy as np
|
14
14
|
import pandas as pd
|
15
15
|
|
16
16
|
|
fakesnow/{types.py → rowtype.py}
RENAMED
@@ -25,12 +25,14 @@ duckdb_to_sf_type = {
|
|
25
25
|
"DATE": "date",
|
26
26
|
"DECIMAL": "fixed",
|
27
27
|
"DOUBLE": "real",
|
28
|
+
"HUGEINT": "fixed",
|
28
29
|
"INTEGER": "fixed",
|
29
30
|
"JSON": "variant",
|
30
31
|
"TIME": "time",
|
31
32
|
"TIMESTAMP WITH TIME ZONE": "timestamp_tz",
|
32
33
|
"TIMESTAMP_NS": "timestamp_ntz",
|
33
34
|
"TIMESTAMP": "timestamp_ntz",
|
35
|
+
"UBIGINT": "fixed",
|
34
36
|
"VARCHAR": "text",
|
35
37
|
}
|
36
38
|
|
fakesnow/server.py
CHANGED
@@ -17,7 +17,7 @@ from starlette.routing import Route
|
|
17
17
|
from fakesnow.arrow import to_ipc, to_sf
|
18
18
|
from fakesnow.fakes import FakeSnowflakeConnection
|
19
19
|
from fakesnow.instance import FakeSnow
|
20
|
-
from fakesnow.
|
20
|
+
from fakesnow.rowtype import describe_as_rowtype
|
21
21
|
|
22
22
|
shared_fs = FakeSnow()
|
23
23
|
sessions: dict[str, FakeSnowflakeConnection] = {}
|
@@ -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
|
|
@@ -87,7 +92,8 @@ async def query_request(request: Request) -> JSONResponse:
|
|
87
92
|
"data": {
|
88
93
|
"rowtype": rowtype,
|
89
94
|
"rowsetBase64": rowset_b64,
|
90
|
-
"total":
|
95
|
+
"total": cur._rowcount, # noqa: SLF001
|
96
|
+
"queryId": cur.sfqid,
|
91
97
|
"queryResultFormat": "arrow",
|
92
98
|
},
|
93
99
|
"success": True,
|
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
|
|
@@ -133,7 +132,7 @@ def create_database(expression: exp.Expression, db_path: Path | None = None) ->
|
|
133
132
|
ident = expression.find(exp.Identifier)
|
134
133
|
assert ident, f"No identifier in {expression.sql}"
|
135
134
|
db_name = ident.this
|
136
|
-
db_file = f"{db_path/db_name}.db" if db_path else ":memory:"
|
135
|
+
db_file = f"{db_path / db_name}.db" if db_path else ":memory:"
|
137
136
|
|
138
137
|
if_not_exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
|
139
138
|
|
@@ -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
|
|
fakesnow/transforms_merge.py
CHANGED
@@ -48,7 +48,7 @@ def _create_merge_candidates(merge_expr: exp.Merge) -> exp.Expression:
|
|
48
48
|
)
|
49
49
|
|
50
50
|
# Iterate through the WHEN clauses to build up the CASE WHEN clauses
|
51
|
-
for w_idx, w in enumerate(merge_expr.
|
51
|
+
for w_idx, w in enumerate(merge_expr.args["whens"]):
|
52
52
|
assert isinstance(w, exp.When), f"Expected When expression, got {w}"
|
53
53
|
|
54
54
|
predicate = join_expr.copy()
|
@@ -83,9 +83,9 @@ def _create_merge_candidates(merge_expr: exp.Merge) -> exp.Expression:
|
|
83
83
|
sql = f"""
|
84
84
|
CREATE OR REPLACE TEMPORARY TABLE merge_candidates AS
|
85
85
|
SELECT
|
86
|
-
{
|
86
|
+
{", ".join(sorted(values))},
|
87
87
|
CASE
|
88
|
-
{
|
88
|
+
{" ".join(case_when_clauses)}
|
89
89
|
ELSE NULL
|
90
90
|
END AS MERGE_OP
|
91
91
|
FROM {target_tbl}
|
@@ -109,7 +109,7 @@ def _mutations(merge_expr: exp.Merge) -> list[exp.Expression]:
|
|
109
109
|
statements: list[exp.Expression] = []
|
110
110
|
|
111
111
|
# Iterate through the WHEN clauses to generate delete/update/insert statements
|
112
|
-
for w_idx, w in enumerate(merge_expr.
|
112
|
+
for w_idx, w in enumerate(merge_expr.args["whens"]):
|
113
113
|
assert isinstance(w, exp.When), f"Expected When expression, got {w}"
|
114
114
|
|
115
115
|
matched = w.args.get("matched")
|
@@ -173,7 +173,7 @@ def _counts(merge_expr: exp.Merge) -> exp.Expression:
|
|
173
173
|
operations = {"inserted": [], "updated": [], "deleted": []}
|
174
174
|
|
175
175
|
# Iterate through the WHEN clauses to categorize operations
|
176
|
-
for w_idx, w in enumerate(merge_expr.
|
176
|
+
for w_idx, w in enumerate(merge_expr.args["whens"]):
|
177
177
|
assert isinstance(w, exp.When), f"Expected When expression, got {w}"
|
178
178
|
|
179
179
|
matched = w.args.get("matched")
|
@@ -191,12 +191,12 @@ def _counts(merge_expr: exp.Merge) -> exp.Expression:
|
|
191
191
|
operations["inserted"].append(w_idx)
|
192
192
|
|
193
193
|
count_statements = [
|
194
|
-
f"""COUNT_IF(merge_op in ({
|
194
|
+
f"""COUNT_IF(merge_op in ({",".join(map(str, indices))})) as \"number of rows {op}\""""
|
195
195
|
for op, indices in operations.items()
|
196
196
|
if indices
|
197
197
|
]
|
198
198
|
sql = f"""
|
199
|
-
SELECT {
|
199
|
+
SELECT {", ".join(count_statements)}
|
200
200
|
FROM merge_candidates
|
201
201
|
"""
|
202
202
|
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
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~=
|
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"
|
@@ -223,7 +223,7 @@ Requires-Dist: pre-commit~=4.0; extra == "dev"
|
|
223
223
|
Requires-Dist: pyarrow-stubs==10.0.1.9; extra == "dev"
|
224
224
|
Requires-Dist: pytest~=8.0; extra == "dev"
|
225
225
|
Requires-Dist: pytest-asyncio; extra == "dev"
|
226
|
-
Requires-Dist: ruff~=0.
|
226
|
+
Requires-Dist: ruff~=0.9.4; extra == "dev"
|
227
227
|
Requires-Dist: twine~=6.0; extra == "dev"
|
228
228
|
Requires-Dist: snowflake-sqlalchemy~=1.7.0; extra == "dev"
|
229
229
|
Provides-Extra: notebook
|
@@ -0,0 +1,26 @@
|
|
1
|
+
fakesnow/__init__.py,sha256=qUfgucQYPdELrJaxczalhJgWAWQ6cfTCUAHx6nUqRaI,3528
|
2
|
+
fakesnow/__main__.py,sha256=GDrGyNTvBFuqn_UfDjKs7b3LPtU6gDv1KwosVDrukIM,76
|
3
|
+
fakesnow/arrow.py,sha256=MwatkdZX5AFADzXvxhBFmcRJVxbW4D39VoqLyhpTbl0,5057
|
4
|
+
fakesnow/checks.py,sha256=N8sXldhS3u1gG32qvZ4VFlsKgavRKrQrxLiQU8am1lw,2691
|
5
|
+
fakesnow/cli.py,sha256=9qfI-Ssr6mo8UmIlXkUAOz2z2YPBgDsrEVaZv9FjGFs,2201
|
6
|
+
fakesnow/conn.py,sha256=da9ln_covsyKgdNdPXLzMTUBr72P0rRGadIDVt-kaeI,5737
|
7
|
+
fakesnow/cursor.py,sha256=1BP1rZ28JfIfJkIR_8yEFDq2FrUf93JFrrYLJoKJr14,20587
|
8
|
+
fakesnow/expr.py,sha256=CAxuYIUkwI339DQIBzvFF0F-m1tcVGKEPA5rDTzmH9A,892
|
9
|
+
fakesnow/fakes.py,sha256=JQTiUkkwPeQrJ8FDWhPFPK6pGwd_aR2oiOrNzCWznlM,187
|
10
|
+
fakesnow/fixtures.py,sha256=G-NkVeruSQAJ7fvSS2fR2oysUn0Yra1pohHlOvacKEk,455
|
11
|
+
fakesnow/info_schema.py,sha256=_4YWnpuOFuyACr9k4iYdf2vLN7GDMG8X_pEBlC-8OmM,7269
|
12
|
+
fakesnow/instance.py,sha256=7xHJv-5-KKAI3Qm7blcvkXgkGg7WYtXEm3nUS4jLyFs,3299
|
13
|
+
fakesnow/macros.py,sha256=pX1YJDnQOkFJSHYUjQ6ErEkYIKvFI6Ncz_au0vv1csA,265
|
14
|
+
fakesnow/pandas_tools.py,sha256=wI203UQHC8JvDzxE_VjE1NeV4rThek2P-u52oTg2foo,3481
|
15
|
+
fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
|
16
|
+
fakesnow/rowtype.py,sha256=QUp8EaXD5LT0Xv8BXk5ze4WseEn52xoJ6R05pJjs5mM,2729
|
17
|
+
fakesnow/server.py,sha256=VpM-ZjFS4JekLESEoQTndvXqnqz8bH4ZO8lq_66-c6s,4387
|
18
|
+
fakesnow/transforms.py,sha256=dAoFFRFkJG8kcQdBPnE5w2eed4AZkh4NV3ajx1vu3A8,56444
|
19
|
+
fakesnow/transforms_merge.py,sha256=Pg7_rwbAT_vr1U4ocBofUSyqaK8_e3qdIz_2SDm2S3s,8320
|
20
|
+
fakesnow/variables.py,sha256=WXyPnkeNwD08gy52yF66CVe2twiYC50tztNfgXV4q1k,3032
|
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,,
|
fakesnow-0.9.27.dist-info/RECORD
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
fakesnow/__init__.py,sha256=qUfgucQYPdELrJaxczalhJgWAWQ6cfTCUAHx6nUqRaI,3528
|
2
|
-
fakesnow/__main__.py,sha256=GDrGyNTvBFuqn_UfDjKs7b3LPtU6gDv1KwosVDrukIM,76
|
3
|
-
fakesnow/arrow.py,sha256=EGAYeuCnRuvmWBEGqw2YOcgQR4zcCsZBu85kSRl70dQ,4698
|
4
|
-
fakesnow/checks.py,sha256=N8sXldhS3u1gG32qvZ4VFlsKgavRKrQrxLiQU8am1lw,2691
|
5
|
-
fakesnow/cli.py,sha256=9qfI-Ssr6mo8UmIlXkUAOz2z2YPBgDsrEVaZv9FjGFs,2201
|
6
|
-
fakesnow/conn.py,sha256=Gy_Z7BZRm5yMjV3x6hR4iegDQFdG9aJBjqWdc3iWYFU,5353
|
7
|
-
fakesnow/cursor.py,sha256=8wWtRCxzrM1yiHmH2C-9CT0b98nTzr23ygeaEAkumRE,20086
|
8
|
-
fakesnow/expr.py,sha256=CAxuYIUkwI339DQIBzvFF0F-m1tcVGKEPA5rDTzmH9A,892
|
9
|
-
fakesnow/fakes.py,sha256=JQTiUkkwPeQrJ8FDWhPFPK6pGwd_aR2oiOrNzCWznlM,187
|
10
|
-
fakesnow/fixtures.py,sha256=G-NkVeruSQAJ7fvSS2fR2oysUn0Yra1pohHlOvacKEk,455
|
11
|
-
fakesnow/info_schema.py,sha256=nsDceFtjiSXrvkksKziVvqrefskaSyOmAspBwMAsaDg,6307
|
12
|
-
fakesnow/instance.py,sha256=3cJvPRuFy19dMKXbtBLl6imzO48pEw8uTYhZyFDuwhk,3133
|
13
|
-
fakesnow/macros.py,sha256=pX1YJDnQOkFJSHYUjQ6ErEkYIKvFI6Ncz_au0vv1csA,265
|
14
|
-
fakesnow/pandas_tools.py,sha256=WjyjTV8QUCQQaCGboaEOvx2uo4BkknpWYjtLwkeCY6U,3468
|
15
|
-
fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
|
16
|
-
fakesnow/server.py,sha256=SO5xKZ4rvySsuKDsoSPSCZcFuIX_K7d1XJYhRRJ-7Bk,4150
|
17
|
-
fakesnow/transforms.py,sha256=h35WHWxhlXpfe03f3FCF64QVmiCLMdDZF7oZmaAOS60,55451
|
18
|
-
fakesnow/transforms_merge.py,sha256=7rq-UPjfFNRrFsqR8xx3otwP6-k4eslLVLhfuqSXq1A,8314
|
19
|
-
fakesnow/types.py,sha256=9Tt83Z7ctc9_v6SYyayXYz4MEI4RZo4zq_uqdj4g3Dk,2681
|
20
|
-
fakesnow/variables.py,sha256=WXyPnkeNwD08gy52yF66CVe2twiYC50tztNfgXV4q1k,3032
|
21
|
-
fakesnow-0.9.27.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
|
22
|
-
fakesnow-0.9.27.dist-info/METADATA,sha256=k7PwECuxM55u-Dx_WyieDaH1p2BrHlcmu9INMsxGtzQ,18108
|
23
|
-
fakesnow-0.9.27.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
24
|
-
fakesnow-0.9.27.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
|
25
|
-
fakesnow-0.9.27.dist-info/top_level.txt,sha256=500evXI1IFX9so82cizGIEMHAb_dJNPaZvd2H9dcKTA,24
|
26
|
-
fakesnow-0.9.27.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|