fakesnow 0.9.0__py3-none-any.whl → 0.9.2__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/fakes.py CHANGED
@@ -30,6 +30,7 @@ import fakesnow.expr as expr
30
30
  import fakesnow.info_schema as info_schema
31
31
  import fakesnow.macros as macros
32
32
  import fakesnow.transforms as transforms
33
+ from fakesnow.global_database import create_global_database
33
34
 
34
35
  SCHEMA_UNSET = "schema_unset"
35
36
  SQL_SUCCESS = "SELECT 'Statement executed successfully.' as 'status'"
@@ -39,6 +40,7 @@ SQL_CREATED_TABLE = Template("SELECT 'Table ${name} successfully created.' as 's
39
40
  SQL_DROPPED = Template("SELECT '${name} successfully dropped.' as 'status'")
40
41
  SQL_INSERTED_ROWS = Template("SELECT ${count} as 'number of rows inserted'")
41
42
  SQL_UPDATED_ROWS = Template("SELECT ${count} as 'number of rows updated', 0 as 'number of multi-joined rows updated'")
43
+ SQL_DELETED_ROWS = Template("SELECT ${count} as 'number of rows deleted'")
42
44
 
43
45
 
44
46
  class FakeSnowflakeCursor:
@@ -194,6 +196,8 @@ class FakeSnowflakeCursor:
194
196
  .transform(transforms.identifier)
195
197
  .transform(lambda e: transforms.show_schemas(e, self._conn.database))
196
198
  .transform(lambda e: transforms.show_objects_tables(e, self._conn.database))
199
+ .transform(transforms.show_users)
200
+ .transform(transforms.create_user)
197
201
  )
198
202
  sql = transformed.sql(dialect="duckdb")
199
203
  result_sql = None
@@ -265,6 +269,10 @@ class FakeSnowflakeCursor:
265
269
  (affected_count,) = self._duck_conn.fetchall()[0]
266
270
  result_sql = SQL_UPDATED_ROWS.substitute(count=affected_count)
267
271
 
272
+ elif cmd == "DELETE":
273
+ (affected_count,) = self._duck_conn.fetchall()[0]
274
+ result_sql = SQL_DELETED_ROWS.substitute(count=affected_count)
275
+
268
276
  elif cmd == "DESCRIBE TABLE":
269
277
  # DESCRIBE TABLE has already been run above to detect and error if the table exists
270
278
  # We now rerun DESCRIBE TABLE but transformed with columns to match Snowflake
@@ -480,6 +488,8 @@ class FakeSnowflakeConnection:
480
488
  self.db_path = db_path
481
489
  self._paramstyle = "pyformat"
482
490
 
491
+ create_global_database(duck_conn)
492
+
483
493
  # create database if needed
484
494
  if (
485
495
  create_database
@@ -0,0 +1,46 @@
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)
fakesnow/info_schema.py CHANGED
@@ -74,7 +74,8 @@ select
74
74
  1 as retention_time,
75
75
  'STANDARD' as type
76
76
  from information_schema.schemata
77
- where catalog_name not in ('memory', 'system', 'temp') and schema_name = 'information_schema'
77
+ where catalog_name not in ('memory', 'system', 'temp', '_fs_global')
78
+ and schema_name = 'information_schema'
78
79
  """
79
80
  )
80
81
 
fakesnow/transforms.py CHANGED
@@ -7,6 +7,8 @@ from typing import cast
7
7
  import sqlglot
8
8
  from sqlglot import exp
9
9
 
10
+ from fakesnow.global_database import USERS_TABLE_FQ_NAME
11
+
10
12
  MISSING_DATABASE = "missing_database"
11
13
  SUCCESS_NOP = sqlglot.parse_one("SELECT 'Statement executed successfully.'")
12
14
 
@@ -271,22 +273,15 @@ def flatten(expression: exp.Expression) -> exp.Expression:
271
273
  return exp.Lateral(
272
274
  this=exp.Unnest(
273
275
  expressions=[
274
- exp.Anonymous(
275
- # duckdb unnests in reserve, so we reverse the list to match
276
- # the order of the original array (and snowflake)
277
- this="list_reverse",
278
- expressions=[
279
- exp.Cast(
280
- this=explode_expression,
281
- to=exp.DataType(
282
- this=exp.DataType.Type.ARRAY,
283
- expressions=[exp.DataType(this=exp.DataType.Type.JSON, nested=False, prefix=False)],
284
- nested=True,
285
- ),
286
- )
287
- ],
276
+ exp.Cast(
277
+ this=explode_expression,
278
+ to=exp.DataType(
279
+ this=exp.DataType.Type.ARRAY,
280
+ expressions=[exp.DataType(this=exp.DataType.Type.JSON, nested=False, prefix=False)],
281
+ nested=True,
282
+ ),
288
283
  )
289
- ]
284
+ ],
290
285
  ),
291
286
  alias=exp.TableAlias(this=alias.this, columns=[exp.Identifier(this="VALUE", quoted=False)]),
292
287
  )
@@ -963,3 +958,32 @@ def values_columns(expression: exp.Expression) -> exp.Expression:
963
958
  expression.set("alias", exp.TableAlias(this=exp.Identifier(this="_", quoted=False), columns=columns))
964
959
 
965
960
  return expression
961
+
962
+
963
+ def show_users(expression: exp.Expression) -> exp.Expression:
964
+ """Transform SHOW USERS to a query against the global database's information_schema._fs_users table.
965
+
966
+ https://docs.snowflake.com/en/sql-reference/sql/show-users
967
+ """
968
+ if isinstance(expression, exp.Show) and isinstance(expression.this, str) and expression.this.upper() == "USERS":
969
+ return sqlglot.parse_one(f"SELECT * FROM {USERS_TABLE_FQ_NAME}", read="duckdb")
970
+
971
+ return expression
972
+
973
+
974
+ def create_user(expression: exp.Expression) -> exp.Expression:
975
+ """Transform CREATE USER to a query against the global database's information_schema._fs_users table.
976
+
977
+ https://docs.snowflake.com/en/sql-reference/sql/create-user
978
+ """
979
+ # XXX: this is a placeholder. We need to implement the full CREATE USER syntax, but
980
+ # sqlglot doesnt yet support Create for snowflake.
981
+ if isinstance(expression, exp.Command) and expression.this == "CREATE":
982
+ sub_exp = expression.expression.strip()
983
+ if sub_exp.upper().startswith("USER"):
984
+ _, name, *ignored = sub_exp.split(" ")
985
+ if ignored:
986
+ raise NotImplementedError(f"`CREATE USER` with {ignored} not yet supported")
987
+ return sqlglot.parse_one(f"INSERT INTO {USERS_TABLE_FQ_NAME} (name) VALUES ('{name}')", read="duckdb")
988
+
989
+ return expression
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fakesnow
3
- Version: 0.9.0
3
+ Version: 0.9.2
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 ~=0.9.2
213
+ Requires-Dist: duckdb ~=0.10.0
214
214
  Requires-Dist: pyarrow
215
215
  Requires-Dist: snowflake-connector-python
216
- Requires-Dist: sqlglot ~=21.0.1
216
+ Requires-Dist: sqlglot ~=21.1.0
217
217
  Provides-Extra: dev
218
218
  Requires-Dist: black ~=23.9 ; extra == 'dev'
219
219
  Requires-Dist: build ~=1.0 ; extra == 'dev'
@@ -349,6 +349,7 @@ Partial support
349
349
  - [x] regex functions
350
350
  - [x] semi-structured data
351
351
  - [x] tags
352
+ - [x] user management (See [tests/test_users.py](tests/test_users.py))
352
353
 
353
354
  For more detail see [tests/test_fakes.py](tests/test_fakes.py)
354
355
 
@@ -3,15 +3,16 @@ 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=aRVnMMtqe2GM2YFEmoj8JEDEofb_iv6BrmVBf5zxiA0,27492
6
+ fakesnow/fakes.py,sha256=iF9IN7nG74DNMDNTZ9CBUGqsYsqicZ7PgRNe4B_zAFs,27931
7
7
  fakesnow/fixtures.py,sha256=G-NkVeruSQAJ7fvSS2fR2oysUn0Yra1pohHlOvacKEk,455
8
- fakesnow/info_schema.py,sha256=TyFR4YfZkJhoPXqgbZr0J2xDo7mK9WMpbn-O1XHVhl4,4685
8
+ fakesnow/global_database.py,sha256=WTVIP1VhNvdCeX7TQncX1TRpGQU5rBf5Pbxim40zeSU,1399
9
+ fakesnow/info_schema.py,sha256=eiKEVBbaSGfsuPD8USuhHF9BDC8pU9sPiDHgy0orTN8,4701
9
10
  fakesnow/macros.py,sha256=pX1YJDnQOkFJSHYUjQ6ErEkYIKvFI6Ncz_au0vv1csA,265
10
11
  fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
11
- fakesnow/transforms.py,sha256=8-QKw47_TwCamSai1kndiB-JhrKjlYcWP4y8-6X_m0U,35293
12
- fakesnow-0.9.0.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
13
- fakesnow-0.9.0.dist-info/METADATA,sha256=V9B-3KdQ5AmjluhsGnDyPzn0IxNHYQ3t972eIQIYV9M,17652
14
- fakesnow-0.9.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
15
- fakesnow-0.9.0.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
16
- fakesnow-0.9.0.dist-info/top_level.txt,sha256=500evXI1IFX9so82cizGIEMHAb_dJNPaZvd2H9dcKTA,24
17
- fakesnow-0.9.0.dist-info/RECORD,,
12
+ fakesnow/transforms.py,sha256=z9tZDOtvQDAoYmQDvDegUOwxgBFvjnSfKCo7kTc-ryA,36300
13
+ fakesnow-0.9.2.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
14
+ fakesnow-0.9.2.dist-info/METADATA,sha256=7rSjgiC1nVjW_38DFx5I6OfyFz71f_TbjapFAIZ-kkI,17724
15
+ fakesnow-0.9.2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
16
+ fakesnow-0.9.2.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
17
+ fakesnow-0.9.2.dist-info/top_level.txt,sha256=500evXI1IFX9so82cizGIEMHAb_dJNPaZvd2H9dcKTA,24
18
+ fakesnow-0.9.2.dist-info/RECORD,,