fakesnow 0.9.20__py3-none-any.whl → 0.9.21__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 CHANGED
@@ -8,12 +8,11 @@ import unittest.mock as mock
8
8
  from collections.abc import Iterator, Sequence
9
9
  from contextlib import contextmanager
10
10
 
11
- import duckdb
12
11
  import snowflake.connector
13
12
  import snowflake.connector.pandas_tools
14
13
 
15
14
  import fakesnow.fakes as fakes
16
- from fakesnow.global_database import create_global_database
15
+ from fakesnow.instance import FakeSnow
17
16
 
18
17
 
19
18
  @contextmanager
@@ -52,20 +51,15 @@ def patch(
52
51
  # won't be able to patch extra targets
53
52
  assert not isinstance(snowflake.connector.connect, mock.MagicMock), "Snowflake connector is already patched"
54
53
 
55
- duck_conn = duckdb.connect(database=":memory:")
56
- create_global_database(duck_conn)
54
+ fs = FakeSnow(
55
+ create_database_on_connect=create_database_on_connect,
56
+ create_schema_on_connect=create_schema_on_connect,
57
+ db_path=db_path,
58
+ nop_regexes=nop_regexes,
59
+ )
57
60
 
58
61
  fake_fns = {
59
- # every time we connect, create a new cursor (ie: connection) so we can isolate each connection's
60
- # schema setting, see https://duckdb.org/docs/api/python/overview.html#startup--shutdown
61
- snowflake.connector.connect: lambda **kwargs: fakes.FakeSnowflakeConnection(
62
- duck_conn.cursor(),
63
- create_database=create_database_on_connect,
64
- create_schema=create_schema_on_connect,
65
- db_path=db_path,
66
- nop_regexes=nop_regexes,
67
- **kwargs,
68
- ),
62
+ snowflake.connector.connect: fs.connect,
69
63
  snowflake.connector.pandas_tools.write_pandas: fakes.write_pandas,
70
64
  }
71
65
 
fakesnow/arrow.py ADDED
@@ -0,0 +1,32 @@
1
+ import pyarrow as pa
2
+
3
+
4
+ def with_sf_metadata(schema: pa.Schema) -> pa.Schema:
5
+ # see https://github.com/snowflakedb/snowflake-connector-python/blob/e9393a6/src/snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.cpp#L32
6
+ # and https://github.com/snowflakedb/snowflake-connector-python/blob/e9393a6/src/snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.cpp#L10
7
+ fms = []
8
+ for i, t in enumerate(schema.types):
9
+ f = schema.field(i)
10
+
11
+ if isinstance(t, pa.Decimal128Type):
12
+ fm = f.with_metadata({"logicalType": "FIXED", "precision": str(t.precision), "scale": str(t.scale)})
13
+ elif t == pa.string():
14
+ fm = f.with_metadata({"logicalType": "TEXT"})
15
+ else:
16
+ raise NotImplementedError(f"Unsupported Arrow type: {t}")
17
+ fms.append(fm)
18
+ return pa.schema(fms)
19
+
20
+
21
+ def to_ipc(table: pa.Table) -> pa.Buffer:
22
+ batches = table.to_batches()
23
+ if len(batches) != 1:
24
+ raise NotImplementedError(f"{len(batches)} batches")
25
+ batch = batches[0]
26
+
27
+ sink = pa.BufferOutputStream()
28
+
29
+ with pa.ipc.new_stream(sink, with_sf_metadata(batch.schema)) as writer:
30
+ writer.write_batch(batch)
31
+
32
+ return sink.getvalue()
fakesnow/fakes.py CHANGED
@@ -203,6 +203,7 @@ class FakeSnowflakeCursor:
203
203
  .transform(transforms.sha256)
204
204
  .transform(transforms.create_clone)
205
205
  .transform(transforms.alias_in_join)
206
+ .transform(transforms.alter_table_strip_cluster_by)
206
207
  )
207
208
 
208
209
  def _execute(
@@ -522,9 +523,14 @@ class FakeSnowflakeConnection:
522
523
  ):
523
524
  self._duck_conn = duck_conn
524
525
  # upper case database and schema like snowflake unquoted identifiers
525
- # NB: catalog names are not case-sensitive in duckdb but stored as cased in information_schema.schemata
526
+ # so they appear as upper-cased in information_schema
527
+ # catalog and schema names are not actually case-sensitive in duckdb even though
528
+ # they are as cased in information_schema.schemata, so when selecting from
529
+ # information_schema.schemata below we use upper-case to match any existing duckdb
530
+ # catalog or schemas like "information_schema"
526
531
  self.database = database and database.upper()
527
532
  self.schema = schema and schema.upper()
533
+
528
534
  self.database_set = False
529
535
  self.schema_set = False
530
536
  self.db_path = Path(db_path) if db_path else None
@@ -538,7 +544,7 @@ class FakeSnowflakeConnection:
538
544
  and self.database
539
545
  and not duck_conn.execute(
540
546
  f"""select * from information_schema.schemata
541
- where catalog_name = '{self.database}'"""
547
+ where upper(catalog_name) = '{self.database}'"""
542
548
  ).fetchone()
543
549
  ):
544
550
  db_file = f"{self.db_path/self.database}.db" if self.db_path else ":memory:"
@@ -553,7 +559,7 @@ class FakeSnowflakeConnection:
553
559
  and self.schema
554
560
  and not duck_conn.execute(
555
561
  f"""select * from information_schema.schemata
556
- where catalog_name = '{self.database}' and schema_name = '{self.schema}'"""
562
+ where upper(catalog_name) = '{self.database}' and upper(schema_name) = '{self.schema}'"""
557
563
  ).fetchone()
558
564
  ):
559
565
  duck_conn.execute(f"CREATE SCHEMA {self.database}.{self.schema}")
@@ -564,7 +570,7 @@ class FakeSnowflakeConnection:
564
570
  and self.schema
565
571
  and duck_conn.execute(
566
572
  f"""select * from information_schema.schemata
567
- where catalog_name = '{self.database}' and schema_name = '{self.schema}'"""
573
+ where upper(catalog_name) = '{self.database}' and upper(schema_name) = '{self.schema}'"""
568
574
  ).fetchone()
569
575
  ):
570
576
  duck_conn.execute(f"SET schema='{self.database}.{self.schema}'")
@@ -575,7 +581,7 @@ class FakeSnowflakeConnection:
575
581
  self.database
576
582
  and duck_conn.execute(
577
583
  f"""select * from information_schema.schemata
578
- where catalog_name = '{self.database}'"""
584
+ where upper(catalog_name) = '{self.database}'"""
579
585
  ).fetchone()
580
586
  ):
581
587
  duck_conn.execute(f"SET schema='{self.database}.main'")
@@ -602,6 +608,7 @@ class FakeSnowflakeConnection:
602
608
  self.cursor().execute("COMMIT")
603
609
 
604
610
  def cursor(self, cursor_class: type[SnowflakeCursor] = SnowflakeCursor) -> FakeSnowflakeCursor:
611
+ # TODO: use duck_conn cursor for thread-safety
605
612
  return FakeSnowflakeCursor(conn=self, duck_conn=self._duck_conn, use_dict_result=cursor_class == DictCursor)
606
613
 
607
614
  def execute_string(
fakesnow/info_schema.py CHANGED
@@ -62,8 +62,8 @@ case when columns.data_type='BIGINT' then 10
62
62
  case when columns.data_type='DOUBLE' then NULL else columns.numeric_scale end as numeric_scale,
63
63
  collation_name, is_identity, identity_generation, identity_cycle,
64
64
  ddb_columns.comment as comment,
65
- null as identity_start,
66
- null as identity_increment,
65
+ null::VARCHAR as identity_start,
66
+ null::VARCHAR as identity_increment,
67
67
  from ${catalog}.information_schema.columns columns
68
68
  left join ${catalog}.information_schema._fs_columns_ext ext
69
69
  on ext_table_catalog = columns.table_catalog
@@ -86,7 +86,7 @@ select
86
86
  catalog_name as database_name,
87
87
  'SYSADMIN' as database_owner,
88
88
  'NO' as is_transient,
89
- null as comment,
89
+ null::VARCHAR as comment,
90
90
  to_timestamp(0)::timestamptz as created,
91
91
  to_timestamp(0)::timestamptz as last_altered,
92
92
  1 as retention_time,
@@ -116,7 +116,7 @@ select
116
116
  to_timestamp(0)::timestamptz as last_altered,
117
117
  to_timestamp(0)::timestamptz as last_ddl,
118
118
  'SYSADMIN' as last_ddl_by,
119
- null as comment
119
+ null::VARCHAR as comment
120
120
  from duckdb_views
121
121
  where database_name = '${catalog}'
122
122
  and schema_name != 'information_schema'
fakesnow/instance.py ADDED
@@ -0,0 +1,92 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from typing import Any
5
+
6
+ import duckdb
7
+
8
+ import fakesnow.fakes as fakes
9
+
10
+ GLOBAL_DATABASE_NAME = "_fs_global"
11
+ USERS_TABLE_FQ_NAME = f"{GLOBAL_DATABASE_NAME}._fs_users_ext"
12
+
13
+ # replicates the output structure of https://docs.snowflake.com/en/sql-reference/sql/show-users
14
+ SQL_CREATE_INFORMATION_SCHEMA_USERS_TABLE_EXT = f"""
15
+ create table if not exists {USERS_TABLE_FQ_NAME} (
16
+ name varchar,
17
+ created_on TIMESTAMPTZ,
18
+ login_name varchar,
19
+ display_name varchar,
20
+ first_name varchar,
21
+ last_name varchar,
22
+ email varchar,
23
+ mins_to_unlock varchar,
24
+ days_to_expiry varchar,
25
+ comment varchar,
26
+ disabled varchar,
27
+ must_change_password varchar,
28
+ snowflake_lock varchar,
29
+ default_warehouse varchar,
30
+ default_namespace varchar,
31
+ default_role varchar,
32
+ default_secondary_roles varchar,
33
+ ext_authn_duo varchar,
34
+ ext_authn_uid varchar,
35
+ mins_to_bypass_mfa varchar,
36
+ owner varchar,
37
+ last_success_login TIMESTAMPTZ,
38
+ expires_at_time TIMESTAMPTZ,
39
+ locked_until_time TIMESTAMPTZ,
40
+ has_password varchar,
41
+ has_rsa_public_key varchar,
42
+ )
43
+ """
44
+
45
+
46
+ def create_global_database(conn: duckdb.DuckDBPyConnection) -> None:
47
+ """Create a "global" database for storing objects which span databases.
48
+
49
+ Including (but not limited to):
50
+ - Users
51
+ """
52
+ conn.execute(f"ATTACH IF NOT EXISTS ':memory:' AS {GLOBAL_DATABASE_NAME}")
53
+ conn.execute(SQL_CREATE_INFORMATION_SCHEMA_USERS_TABLE_EXT)
54
+
55
+
56
+ class FakeSnow:
57
+ def __init__(
58
+ self,
59
+ create_database_on_connect: bool = True,
60
+ create_schema_on_connect: bool = True,
61
+ db_path: str | os.PathLike | None = None,
62
+ nop_regexes: list[str] | None = None,
63
+ ):
64
+ self.create_database_on_connect = create_database_on_connect
65
+ self.create_schema_on_connect = create_schema_on_connect
66
+ self.db_path = db_path
67
+ self.nop_regexes = nop_regexes
68
+
69
+ self.duck_conn = duckdb.connect(database=":memory:")
70
+
71
+ # create a "global" database for storing objects which span databases.
72
+ self.duck_conn.execute(f"ATTACH IF NOT EXISTS ':memory:' AS {GLOBAL_DATABASE_NAME}")
73
+ self.duck_conn.execute(SQL_CREATE_INFORMATION_SCHEMA_USERS_TABLE_EXT)
74
+
75
+ def connect(
76
+ self, database: str | None = None, schema: str | None = None, **kwargs: Any
77
+ ) -> fakes.FakeSnowflakeConnection:
78
+ # every time we connect, create a new cursor (ie: connection) so we can isolate each connection's
79
+ # schema setting see
80
+ # https://github.com/duckdb/duckdb/blob/18254ec/tools/pythonpkg/src/pyconnection.cpp#L1440
81
+ # and to make connections thread-safe see
82
+ # https://duckdb.org/docs/api/python/overview.html#using-connections-in-parallel-python-programs
83
+ return fakes.FakeSnowflakeConnection(
84
+ self.duck_conn.cursor(),
85
+ database,
86
+ schema,
87
+ create_database=self.create_database_on_connect,
88
+ create_schema=self.create_schema_on_connect,
89
+ db_path=self.db_path,
90
+ nop_regexes=self.nop_regexes,
91
+ **kwargs,
92
+ )
fakesnow/server.py ADDED
@@ -0,0 +1,108 @@
1
+ from __future__ import annotations
2
+
3
+ import gzip
4
+ import json
5
+ import secrets
6
+ from base64 import b64encode
7
+ from dataclasses import dataclass
8
+
9
+ from starlette.applications import Starlette
10
+ from starlette.concurrency import run_in_threadpool
11
+ from starlette.requests import Request
12
+ from starlette.responses import JSONResponse
13
+ from starlette.routing import Route
14
+
15
+ from fakesnow.arrow import to_ipc
16
+ from fakesnow.fakes import FakeSnowflakeConnection
17
+ from fakesnow.instance import FakeSnow
18
+
19
+ fs = FakeSnow()
20
+ sessions = {}
21
+
22
+
23
+ @dataclass
24
+ class ServerError(Exception):
25
+ status_code: int
26
+ code: str
27
+ message: str
28
+
29
+
30
+ def login_request(request: Request) -> JSONResponse:
31
+ database = request.query_params.get("databaseName")
32
+ schema = request.query_params.get("schemaName")
33
+ token = secrets.token_urlsafe(32)
34
+ sessions[token] = fs.connect(database, schema)
35
+ return JSONResponse({"data": {"token": token}, "success": True})
36
+
37
+
38
+ async def query_request(request: Request) -> JSONResponse:
39
+ try:
40
+ conn = to_conn(request)
41
+
42
+ body = await request.body()
43
+ body_json = json.loads(gzip.decompress(body))
44
+
45
+ sql_text = body_json["sqlText"]
46
+
47
+ # only a single sql statement is sent at a time by the python snowflake connector
48
+ cur = await run_in_threadpool(conn.cursor().execute, sql_text)
49
+
50
+ assert cur._arrow_table, "No result set" # noqa: SLF001
51
+
52
+ batch_bytes = to_ipc(cur._arrow_table) # noqa: SLF001
53
+ rowset_b64 = b64encode(batch_bytes).decode("utf-8")
54
+
55
+ return JSONResponse(
56
+ {
57
+ "data": {
58
+ "rowtype": [
59
+ {
60
+ "name": "'HELLO WORLD'",
61
+ "nullable": False,
62
+ "type": "text",
63
+ "length": 11,
64
+ "scale": None,
65
+ "precision": None,
66
+ }
67
+ ],
68
+ "rowsetBase64": rowset_b64,
69
+ "total": 1,
70
+ "queryResultFormat": "arrow",
71
+ },
72
+ "success": True,
73
+ }
74
+ )
75
+
76
+ except ServerError as e:
77
+ return JSONResponse(
78
+ {"data": None, "code": e.code, "message": e.message, "success": False, "headers": None},
79
+ status_code=e.status_code,
80
+ )
81
+
82
+
83
+ def to_conn(request: Request) -> FakeSnowflakeConnection:
84
+ if not (auth := request.headers.get("Authorization")):
85
+ raise ServerError(status_code=401, code="390103", message="Session token not found in the request data.")
86
+
87
+ token = auth[17:-1]
88
+
89
+ if not (conn := sessions.get(token)):
90
+ raise ServerError(status_code=401, code="390104", message="User must login again to access the service.")
91
+
92
+ return conn
93
+
94
+
95
+ routes = [
96
+ Route(
97
+ "/session/v1/login-request",
98
+ login_request,
99
+ methods=["POST"],
100
+ ),
101
+ Route(
102
+ "/queries/v1/query-request",
103
+ query_request,
104
+ methods=["POST"],
105
+ ),
106
+ ]
107
+
108
+ app = Starlette(debug=True, routes=routes)
fakesnow/transforms.py CHANGED
@@ -7,11 +7,11 @@ from typing import ClassVar, Literal, cast
7
7
  import sqlglot
8
8
  from sqlglot import exp
9
9
 
10
- from fakesnow.global_database import USERS_TABLE_FQ_NAME
10
+ from fakesnow.instance import USERS_TABLE_FQ_NAME
11
11
  from fakesnow.variables import Variables
12
12
 
13
13
  MISSING_DATABASE = "missing_database"
14
- SUCCESS_NOP = sqlglot.parse_one("SELECT 'Statement executed successfully.'")
14
+ SUCCESS_NOP = sqlglot.parse_one("SELECT 'Statement executed successfully.' as status")
15
15
 
16
16
 
17
17
  def alias_in_join(expression: exp.Expression) -> exp.Expression:
@@ -33,6 +33,18 @@ def alias_in_join(expression: exp.Expression) -> exp.Expression:
33
33
  return expression
34
34
 
35
35
 
36
+ def alter_table_strip_cluster_by(expression: exp.Expression) -> exp.Expression:
37
+ """Turn alter table cluster by into a no-op"""
38
+ if (
39
+ isinstance(expression, exp.AlterTable)
40
+ and (actions := expression.args.get("actions"))
41
+ and len(actions) == 1
42
+ and (isinstance(actions[0], exp.Cluster))
43
+ ):
44
+ return SUCCESS_NOP
45
+ return expression
46
+
47
+
36
48
  def array_size(expression: exp.Expression) -> exp.Expression:
37
49
  if isinstance(expression, exp.ArraySize):
38
50
  # case is used to convert 0 to null, because null is returned by duckdb when no case matches
@@ -551,12 +563,13 @@ def information_schema_fs_columns_snowflake(expression: exp.Expression) -> exp.E
551
563
  """
552
564
 
553
565
  if (
554
- isinstance(expression, exp.Select)
555
- and (tbl_exp := expression.find(exp.Table))
556
- and tbl_exp.name.upper() == "COLUMNS"
557
- and tbl_exp.db.upper() == "INFORMATION_SCHEMA"
566
+ isinstance(expression, exp.Table)
567
+ and expression.db
568
+ and expression.db.upper() == "INFORMATION_SCHEMA"
569
+ and expression.name
570
+ and expression.name.upper() == "COLUMNS"
558
571
  ):
559
- tbl_exp.set("this", exp.Identifier(this="_FS_COLUMNS_SNOWFLAKE", quoted=False))
572
+ expression.set("this", exp.Identifier(this="_FS_COLUMNS_SNOWFLAKE", quoted=False))
560
573
 
561
574
  return expression
562
575
 
fakesnow/variables.py CHANGED
@@ -5,10 +5,23 @@ from sqlglot import exp
5
5
 
6
6
 
7
7
  # Implements snowflake variables: https://docs.snowflake.com/en/sql-reference/session-variables#using-variables-in-sql
8
+ # [ ] Add support for setting multiple variables in a single statement
8
9
  class Variables:
9
10
  @classmethod
10
11
  def is_variable_modifier(cls, expr: exp.Expression) -> bool:
11
- return isinstance(expr, exp.Set) or cls._is_unset_expression(expr)
12
+ return cls._is_set_expression(expr) or cls._is_unset_expression(expr)
13
+
14
+ @classmethod
15
+ def _is_set_expression(cls, expr: exp.Expression) -> bool:
16
+ if isinstance(expr, exp.Set):
17
+ is_set = not expr.args.get("unset")
18
+ if is_set: # SET varname = value;
19
+ set_expressions = expr.args.get("expressions")
20
+ assert set_expressions, "SET without values in expression(s) is unexpected."
21
+ # Avoids mistakenly setting variables for statements that use SET in a different context.
22
+ # (eg. WHEN MATCHED THEN UPDATE SET x=7)
23
+ return isinstance(set_expressions[0], exp.SetItem)
24
+ return False
12
25
 
13
26
  @classmethod
14
27
  def _is_unset_expression(cls, expr: exp.Expression) -> bool:
@@ -22,11 +35,11 @@ class Variables:
22
35
 
23
36
  def update_variables(self, expr: exp.Expression) -> None:
24
37
  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
38
+ is_set = not expr.args.get("unset")
39
+ if is_set: # SET varname = value;
40
+ set_expressions = expr.args.get("expressions")
41
+ assert set_expressions, "SET without values in expression(s) is unexpected."
42
+ eq = set_expressions[0].this
30
43
  name = eq.this.sql()
31
44
  value = eq.args.get("expression").sql()
32
45
  self._set(name, value)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fakesnow
3
- Version: 0.9.20
3
+ Version: 0.9.21
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
@@ -213,20 +213,25 @@ License-File: LICENSE
213
213
  Requires-Dist: duckdb ~=1.0.0
214
214
  Requires-Dist: pyarrow
215
215
  Requires-Dist: snowflake-connector-python
216
- Requires-Dist: sqlglot ~=25.3.0
216
+ Requires-Dist: sqlglot ~=25.5.1
217
217
  Provides-Extra: dev
218
218
  Requires-Dist: build ~=1.0 ; extra == 'dev'
219
219
  Requires-Dist: pandas-stubs ; extra == 'dev'
220
220
  Requires-Dist: snowflake-connector-python[pandas,secure-local-storage] ; extra == 'dev'
221
221
  Requires-Dist: pre-commit ~=3.4 ; extra == 'dev'
222
+ Requires-Dist: pyarrow-stubs ; extra == 'dev'
222
223
  Requires-Dist: pytest ~=8.0 ; extra == 'dev'
223
- Requires-Dist: ruff ~=0.4.2 ; extra == 'dev'
224
+ Requires-Dist: pytest-asyncio ; extra == 'dev'
225
+ Requires-Dist: ruff ~=0.5.1 ; extra == 'dev'
224
226
  Requires-Dist: twine ~=5.0 ; extra == 'dev'
225
227
  Requires-Dist: snowflake-sqlalchemy ~=1.5.0 ; extra == 'dev'
226
228
  Provides-Extra: notebook
227
229
  Requires-Dist: duckdb-engine ; extra == 'notebook'
228
230
  Requires-Dist: ipykernel ; extra == 'notebook'
229
231
  Requires-Dist: jupysql ; extra == 'notebook'
232
+ Provides-Extra: server
233
+ Requires-Dist: starlette ; extra == 'server'
234
+ Requires-Dist: uvicorn ; extra == 'server'
230
235
 
231
236
  # fakesnow ❄️
232
237
 
@@ -0,0 +1,21 @@
1
+ fakesnow/__init__.py,sha256=9tFJJKvowKNW3vfnlmza6hOLN1I52DwChgNc5Ew6CcA,3499
2
+ fakesnow/__main__.py,sha256=GDrGyNTvBFuqn_UfDjKs7b3LPtU6gDv1KwosVDrukIM,76
3
+ fakesnow/arrow.py,sha256=1ypCsf-r2Ven6CuSm-bTLoeq1G31kBD6JnaLvDxpwhU,1218
4
+ fakesnow/checks.py,sha256=-QMvdcrRbhN60rnzxLBJ0IkUBWyLR8gGGKKmCS0w9mA,2383
5
+ fakesnow/cli.py,sha256=9qfI-Ssr6mo8UmIlXkUAOz2z2YPBgDsrEVaZv9FjGFs,2201
6
+ fakesnow/expr.py,sha256=CAxuYIUkwI339DQIBzvFF0F-m1tcVGKEPA5rDTzmH9A,892
7
+ fakesnow/fakes.py,sha256=wLSjKrNI8wxe3MuUAa97jpUHd5vZTzvrlF1-Hf0FC0M,31208
8
+ fakesnow/fixtures.py,sha256=G-NkVeruSQAJ7fvSS2fR2oysUn0Yra1pohHlOvacKEk,455
9
+ fakesnow/info_schema.py,sha256=LjS_-8YXBtCSvkdU5uL0aJdFcZEsBa6o5zf-Q_aV9i0,6302
10
+ fakesnow/instance.py,sha256=3cJvPRuFy19dMKXbtBLl6imzO48pEw8uTYhZyFDuwhk,3133
11
+ fakesnow/macros.py,sha256=pX1YJDnQOkFJSHYUjQ6ErEkYIKvFI6Ncz_au0vv1csA,265
12
+ fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
13
+ fakesnow/server.py,sha256=HwCAZ5AhU4nRbFGIqDBs2rdwoK70dYZDyw1XfE1cHqU,3082
14
+ fakesnow/transforms.py,sha256=GCpczoFdvnffQMvSG59PNiiTVTld9kROnnR5dWNXQvY,53624
15
+ fakesnow/variables.py,sha256=WXyPnkeNwD08gy52yF66CVe2twiYC50tztNfgXV4q1k,3032
16
+ fakesnow-0.9.21.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
17
+ fakesnow-0.9.21.dist-info/METADATA,sha256=6EzLZbGBTS0VEYCHdG4sNNZT-w5SiespKcitL_XEtM4,18043
18
+ fakesnow-0.9.21.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
19
+ fakesnow-0.9.21.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
20
+ fakesnow-0.9.21.dist-info/top_level.txt,sha256=500evXI1IFX9so82cizGIEMHAb_dJNPaZvd2H9dcKTA,24
21
+ fakesnow-0.9.21.dist-info/RECORD,,
@@ -1,46 +0,0 @@
1
- from duckdb import DuckDBPyConnection
2
-
3
- GLOBAL_DATABASE_NAME = "_fs_global"
4
- USERS_TABLE_FQ_NAME = f"{GLOBAL_DATABASE_NAME}._fs_users_ext"
5
-
6
- # replicates the output structure of https://docs.snowflake.com/en/sql-reference/sql/show-users
7
- SQL_CREATE_INFORMATION_SCHEMA_USERS_TABLE_EXT = f"""
8
- create table if not exists {USERS_TABLE_FQ_NAME} (
9
- name varchar,
10
- created_on TIMESTAMPTZ,
11
- login_name varchar,
12
- display_name varchar,
13
- first_name varchar,
14
- last_name varchar,
15
- email varchar,
16
- mins_to_unlock varchar,
17
- days_to_expiry varchar,
18
- comment varchar,
19
- disabled varchar,
20
- must_change_password varchar,
21
- snowflake_lock varchar,
22
- default_warehouse varchar,
23
- default_namespace varchar,
24
- default_role varchar,
25
- default_secondary_roles varchar,
26
- ext_authn_duo varchar,
27
- ext_authn_uid varchar,
28
- mins_to_bypass_mfa varchar,
29
- owner varchar,
30
- last_success_login TIMESTAMPTZ,
31
- expires_at_time TIMESTAMPTZ,
32
- locked_until_time TIMESTAMPTZ,
33
- has_password varchar,
34
- has_rsa_public_key varchar,
35
- )
36
- """
37
-
38
-
39
- def create_global_database(conn: DuckDBPyConnection) -> None:
40
- """Create a "global" database for storing objects which span database.
41
-
42
- Including (but not limited to):
43
- - Users
44
- """
45
- conn.execute(f"ATTACH IF NOT EXISTS ':memory:' AS {GLOBAL_DATABASE_NAME}")
46
- conn.execute(SQL_CREATE_INFORMATION_SCHEMA_USERS_TABLE_EXT)
@@ -1,19 +0,0 @@
1
- fakesnow/__init__.py,sha256=gEo6Jg6f8tJUwRTsrfb9q-FpDOufelf8Y3XeWtEJ9wg,3897
2
- fakesnow/__main__.py,sha256=GDrGyNTvBFuqn_UfDjKs7b3LPtU6gDv1KwosVDrukIM,76
3
- fakesnow/checks.py,sha256=-QMvdcrRbhN60rnzxLBJ0IkUBWyLR8gGGKKmCS0w9mA,2383
4
- fakesnow/cli.py,sha256=9qfI-Ssr6mo8UmIlXkUAOz2z2YPBgDsrEVaZv9FjGFs,2201
5
- fakesnow/expr.py,sha256=CAxuYIUkwI339DQIBzvFF0F-m1tcVGKEPA5rDTzmH9A,892
6
- fakesnow/fakes.py,sha256=MYzj3XqVokPbOdOcU4WFep1CRsuXlBIwaz9EWaNCGo4,30778
7
- fakesnow/fixtures.py,sha256=G-NkVeruSQAJ7fvSS2fR2oysUn0Yra1pohHlOvacKEk,455
8
- fakesnow/global_database.py,sha256=WTVIP1VhNvdCeX7TQncX1TRpGQU5rBf5Pbxim40zeSU,1399
9
- fakesnow/info_schema.py,sha256=CdIcGXHEQ_kmEAzdQKvA-PX41LA6wlK-4p1J45qgKYA,6266
10
- fakesnow/macros.py,sha256=pX1YJDnQOkFJSHYUjQ6ErEkYIKvFI6Ncz_au0vv1csA,265
11
- fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
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,,