snowflake-sqlalchemy 1.7.0__tar.gz → 1.7.1__tar.gz
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.
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/DESCRIPTION.md +5 -1
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/PKG-INFO +152 -4
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/README.md +151 -3
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/base.py +20 -6
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/custom_commands.py +7 -2
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/snowdialect.py +2 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/version.py +1 -1
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_copy.py +48 -17
- snowflake_sqlalchemy-1.7.1/tests/test_imports.py +64 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/.gitignore +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/.gitmodules +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/.pre-commit-config.yaml +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/LICENSE.txt +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/MANIFEST.in +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/ci/build.sh +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/ci/build_docker.sh +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/ci/docker/sqlalchemy_build/Dockerfile +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/ci/docker/sqlalchemy_build/scripts/entrypoint.sh +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/ci/set_base_image.sh +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/ci/test.sh +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/ci/test_docker.sh +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/ci/test_linux.sh +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/license_header.txt +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/pyproject.toml +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/setup.cfg +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/snyk/requirements.txt +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/snyk/requiremtnts.txt +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/snyk/update_requirements.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/_constants.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/compat.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/custom_types.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/exc.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/functions.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/parser/custom_type_parser.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/provision.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/requirements.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/custom_schema/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/custom_schema/clustered_table.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/custom_schema/custom_table_base.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/custom_schema/custom_table_prefix.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/custom_schema/dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/custom_schema/hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/custom_schema/iceberg_table.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/custom_schema/options/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/custom_schema/options/as_query_option.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/custom_schema/options/cluster_by_option.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/custom_schema/options/identifier_option.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/custom_schema/options/invalid_table_option.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/custom_schema/options/keyword_option.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/custom_schema/options/keywords.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/custom_schema/options/literal_option.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/custom_schema/options/table_option.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/custom_schema/options/target_lag_option.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/custom_schema/snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/custom_schema/table_from_query.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/util.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tested_requirements/requirements_310.reqs +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tested_requirements/requirements_37.reqs +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tested_requirements/requirements_38.reqs +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tested_requirements/requirements_39.reqs +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/README.rst +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/__snapshots__/test_compile_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/__snapshots__/test_core.ambr +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/__snapshots__/test_orm.ambr +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/__snapshots__/test_reflect_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/__snapshots__/test_structured_datatypes.ambr +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/__snapshots__/test_unit_structured_types.ambr +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/conftest.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/__snapshots__/test_compile_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/__snapshots__/test_compile_hybrid_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/__snapshots__/test_compile_iceberg_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/__snapshots__/test_compile_snowflake_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/__snapshots__/test_create_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/__snapshots__/test_create_hybrid_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/__snapshots__/test_create_iceberg_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/__snapshots__/test_create_snowflake_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/__snapshots__/test_generic_options.ambr +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/__snapshots__/test_reflect_hybrid_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/__snapshots__/test_reflect_snowflake_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/test_compile_dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/test_compile_hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/test_compile_iceberg_table.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/test_compile_snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/test_create_dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/test_create_hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/test_create_iceberg_table.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/test_create_snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/test_generic_options.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/test_reflect_dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/test_reflect_hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/custom_tables/test_reflect_snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/data/users.txt +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/sqlalchemy_test_suite/README.md +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/sqlalchemy_test_suite/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/sqlalchemy_test_suite/conftest.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/sqlalchemy_test_suite/test_suite.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/sqlalchemy_test_suite/test_suite_20.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_compiler.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_core.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_create.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_custom_functions.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_custom_types.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_geography.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_geometry.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_index_reflection.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_multivalues_insert.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_orm.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_pandas.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_qmark.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_quote.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_semi_structured_datatypes.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_sequence.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_structured_datatypes.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_timestamp.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_unit_core.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_unit_cte.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_unit_structured_types.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_unit_types.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_unit_url.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/util.py +0 -0
- {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tox.ini +0 -0
|
@@ -9,7 +9,11 @@ Source code is also available at:
|
|
|
9
9
|
|
|
10
10
|
# Release Notes
|
|
11
11
|
|
|
12
|
-
- v1.7.
|
|
12
|
+
- v1.7.1(December 02, 2024)
|
|
13
|
+
- Add support for partition by to copy into <location>
|
|
14
|
+
- Fix BOOLEAN type not found in snowdialect
|
|
15
|
+
|
|
16
|
+
- v1.7.0(November 21, 2024)
|
|
13
17
|
|
|
14
18
|
- Add support for dynamic tables and required options
|
|
15
19
|
- Add support for hybrid tables
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: snowflake-sqlalchemy
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.1
|
|
4
4
|
Summary: Snowflake SQLAlchemy Dialect
|
|
5
5
|
Project-URL: Changelog, https://github.com/snowflakedb/snowflake-sqlalchemy/blob/main/DESCRIPTION.md
|
|
6
6
|
Project-URL: Documentation, https://docs.snowflake.com/en/user-guide/sqlalchemy.html
|
|
@@ -60,6 +60,11 @@ Description-Content-Type: text/markdown
|
|
|
60
60
|
|
|
61
61
|
Snowflake SQLAlchemy runs on the top of the Snowflake Connector for Python as a [dialect](http://docs.sqlalchemy.org/en/latest/dialects/) to bridge a Snowflake database and SQLAlchemy applications.
|
|
62
62
|
|
|
63
|
+
|
|
64
|
+
| :exclamation: | For production-affecting or urgent issues related to the connector, please [create a case with Snowflake Support](https://community.snowflake.com/s/article/How-To-Submit-a-Support-Case-in-Snowflake-Lodge). |
|
|
65
|
+
|---------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
66
|
+
|
|
67
|
+
|
|
63
68
|
## Prerequisites
|
|
64
69
|
|
|
65
70
|
### Snowflake Connector for Python
|
|
@@ -256,7 +261,7 @@ finally:
|
|
|
256
261
|
|
|
257
262
|
# Best
|
|
258
263
|
try:
|
|
259
|
-
with engine.
|
|
264
|
+
with engine.connect() as connection:
|
|
260
265
|
connection.execute(text(<SQL>))
|
|
261
266
|
# or
|
|
262
267
|
connection.exec_driver_sql(<SQL>)
|
|
@@ -277,11 +282,43 @@ t = Table('mytable', metadata,
|
|
|
277
282
|
|
|
278
283
|
### Object Name Case Handling
|
|
279
284
|
|
|
280
|
-
Snowflake stores all case-insensitive object names in uppercase text. In contrast, SQLAlchemy considers all lowercase object names to be case-insensitive. Snowflake SQLAlchemy converts the object name case during schema-level communication, i.e. during table and index reflection. If you use uppercase object names, SQLAlchemy assumes they are case-sensitive and encloses the names with quotes. This behavior will cause mismatches
|
|
285
|
+
Snowflake stores all case-insensitive object names in uppercase text. In contrast, SQLAlchemy considers all lowercase object names to be case-insensitive. Snowflake SQLAlchemy converts the object name case during schema-level communication, i.e. during table and index reflection. If you use uppercase object names, SQLAlchemy assumes they are case-sensitive and encloses the names with quotes. This behavior will cause mismatches against data dictionary data received from Snowflake, so unless identifier names have been truly created as case sensitive using quotes, e.g., `"TestDb"`, all lowercase names should be used on the SQLAlchemy side.
|
|
281
286
|
|
|
282
287
|
### Index Support
|
|
283
288
|
|
|
284
|
-
|
|
289
|
+
Indexes are supported only for Hybrid Tables in Snowflake SQLAlchemy. For more details on limitations and use cases, refer to the [Create Index documentation](https://docs.snowflake.com/en/sql-reference/constraints-indexes.html). You can create an index using the following methods:
|
|
290
|
+
|
|
291
|
+
#### Single Column Index
|
|
292
|
+
|
|
293
|
+
You can create a single column index by setting the `index=True` parameter on the column or by explicitly defining an `Index` object.
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
hybrid_test_table_1 = HybridTable(
|
|
297
|
+
"table_name",
|
|
298
|
+
metadata,
|
|
299
|
+
Column("column1", Integer, primary_key=True),
|
|
300
|
+
Column("column2", String, index=True),
|
|
301
|
+
Index("index_1", "column1", "column2")
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
metadata.create_all(engine_testaccount)
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
#### Multi-Column Index
|
|
308
|
+
|
|
309
|
+
For multi-column indexes, you define the `Index` object specifying the columns that should be indexed.
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
hybrid_test_table_1 = HybridTable(
|
|
313
|
+
"table_name",
|
|
314
|
+
metadata,
|
|
315
|
+
Column("column1", Integer, primary_key=True),
|
|
316
|
+
Column("column2", String),
|
|
317
|
+
Index("index_1", "column1", "column2")
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
metadata.create_all(engine_testaccount)
|
|
321
|
+
```
|
|
285
322
|
|
|
286
323
|
### Numpy Data Type Support
|
|
287
324
|
|
|
@@ -508,6 +545,117 @@ copy_into = CopyIntoStorage(from_=users,
|
|
|
508
545
|
connection.execute(copy_into)
|
|
509
546
|
```
|
|
510
547
|
|
|
548
|
+
### Iceberg Table with Snowflake Catalog support
|
|
549
|
+
|
|
550
|
+
Snowflake SQLAlchemy supports Iceberg Tables with the Snowflake Catalog, along with various related parameters. For detailed information about Iceberg Tables, refer to the Snowflake [CREATE ICEBERG](https://docs.snowflake.com/en/sql-reference/sql/create-iceberg-table-snowflake) documentation.
|
|
551
|
+
|
|
552
|
+
To create an Iceberg Table using Snowflake SQLAlchemy, you can define the table using the SQLAlchemy Core syntax as follows:
|
|
553
|
+
|
|
554
|
+
```python
|
|
555
|
+
table = IcebergTable(
|
|
556
|
+
"myuser",
|
|
557
|
+
metadata,
|
|
558
|
+
Column("id", Integer, primary_key=True),
|
|
559
|
+
Column("name", String),
|
|
560
|
+
external_volume=external_volume_name,
|
|
561
|
+
base_location="my_iceberg_table",
|
|
562
|
+
as_query="SELECT * FROM table"
|
|
563
|
+
)
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
Alternatively, you can define the table using a declarative approach:
|
|
567
|
+
|
|
568
|
+
```python
|
|
569
|
+
class MyUser(Base):
|
|
570
|
+
__tablename__ = "myuser"
|
|
571
|
+
|
|
572
|
+
@classmethod
|
|
573
|
+
def __table_cls__(cls, name, metadata, *arg, **kw):
|
|
574
|
+
return IcebergTable(name, metadata, *arg, **kw)
|
|
575
|
+
|
|
576
|
+
__table_args__ = {
|
|
577
|
+
"external_volume": "my_external_volume",
|
|
578
|
+
"base_location": "my_iceberg_table",
|
|
579
|
+
"as_query": "SELECT * FROM table",
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
id = Column(Integer, primary_key=True)
|
|
583
|
+
name = Column(String)
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### Hybrid Table support
|
|
587
|
+
|
|
588
|
+
Snowflake SQLAlchemy supports Hybrid Tables with indexes. For detailed information, refer to the Snowflake [CREATE HYBRID TABLE](https://docs.snowflake.com/en/sql-reference/sql/create-hybrid-table) documentation.
|
|
589
|
+
|
|
590
|
+
To create a Hybrid Table and add an index, you can use the SQLAlchemy Core syntax as follows:
|
|
591
|
+
|
|
592
|
+
```python
|
|
593
|
+
table = HybridTable(
|
|
594
|
+
"myuser",
|
|
595
|
+
metadata,
|
|
596
|
+
Column("id", Integer, primary_key=True),
|
|
597
|
+
Column("name", String),
|
|
598
|
+
Index("idx_name", "name")
|
|
599
|
+
)
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
Alternatively, you can define the table using the declarative approach:
|
|
603
|
+
|
|
604
|
+
```python
|
|
605
|
+
class MyUser(Base):
|
|
606
|
+
__tablename__ = "myuser"
|
|
607
|
+
|
|
608
|
+
@classmethod
|
|
609
|
+
def __table_cls__(cls, name, metadata, *arg, **kw):
|
|
610
|
+
return HybridTable(name, metadata, *arg, **kw)
|
|
611
|
+
|
|
612
|
+
__table_args__ = (
|
|
613
|
+
Index("idx_name", "name"),
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
id = Column(Integer, primary_key=True)
|
|
617
|
+
name = Column(String)
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### Dynamic Tables support
|
|
621
|
+
|
|
622
|
+
Snowflake SQLAlchemy supports Dynamic Tables. For detailed information, refer to the Snowflake [CREATE DYNAMIC TABLE](https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table) documentation.
|
|
623
|
+
|
|
624
|
+
To create a Dynamic Table, you can use the SQLAlchemy Core syntax as follows:
|
|
625
|
+
|
|
626
|
+
```python
|
|
627
|
+
dynamic_test_table_1 = DynamicTable(
|
|
628
|
+
"dynamic_MyUser",
|
|
629
|
+
metadata,
|
|
630
|
+
Column("id", Integer),
|
|
631
|
+
Column("name", String),
|
|
632
|
+
target_lag=(1, TimeUnit.HOURS), # Additionally, you can use SnowflakeKeyword.DOWNSTREAM
|
|
633
|
+
warehouse='test_wh',
|
|
634
|
+
refresh_mode=SnowflakeKeyword.FULL,
|
|
635
|
+
as_query="SELECT id, name from MyUser;"
|
|
636
|
+
)
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
Alternatively, you can define a table without columns using the SQLAlchemy `select()` construct:
|
|
640
|
+
|
|
641
|
+
```python
|
|
642
|
+
dynamic_test_table_1 = DynamicTable(
|
|
643
|
+
"dynamic_MyUser",
|
|
644
|
+
metadata,
|
|
645
|
+
target_lag=(1, TimeUnit.HOURS),
|
|
646
|
+
warehouse='test_wh',
|
|
647
|
+
refresh_mode=SnowflakeKeyword.FULL,
|
|
648
|
+
as_query=select(MyUser.id, MyUser.name)
|
|
649
|
+
)
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
### Notes
|
|
653
|
+
|
|
654
|
+
- Defining a primary key in a Dynamic Table is not supported, meaning declarative tables don’t support Dynamic Tables.
|
|
655
|
+
- When using the `as_query` parameter with a string, you must explicitly define the columns. However, if you use the SQLAlchemy `select()` construct, you don’t need to explicitly define the columns.
|
|
656
|
+
- Direct data insertion into Dynamic Tables is not supported.
|
|
657
|
+
|
|
658
|
+
|
|
511
659
|
## Support
|
|
512
660
|
|
|
513
661
|
Feel free to file an issue or submit a PR here for general cases. For official support, contact Snowflake support at:
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
Snowflake SQLAlchemy runs on the top of the Snowflake Connector for Python as a [dialect](http://docs.sqlalchemy.org/en/latest/dialects/) to bridge a Snowflake database and SQLAlchemy applications.
|
|
10
10
|
|
|
11
|
+
|
|
12
|
+
| :exclamation: | For production-affecting or urgent issues related to the connector, please [create a case with Snowflake Support](https://community.snowflake.com/s/article/How-To-Submit-a-Support-Case-in-Snowflake-Lodge). |
|
|
13
|
+
|---------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
14
|
+
|
|
15
|
+
|
|
11
16
|
## Prerequisites
|
|
12
17
|
|
|
13
18
|
### Snowflake Connector for Python
|
|
@@ -204,7 +209,7 @@ finally:
|
|
|
204
209
|
|
|
205
210
|
# Best
|
|
206
211
|
try:
|
|
207
|
-
with engine.
|
|
212
|
+
with engine.connect() as connection:
|
|
208
213
|
connection.execute(text(<SQL>))
|
|
209
214
|
# or
|
|
210
215
|
connection.exec_driver_sql(<SQL>)
|
|
@@ -225,11 +230,43 @@ t = Table('mytable', metadata,
|
|
|
225
230
|
|
|
226
231
|
### Object Name Case Handling
|
|
227
232
|
|
|
228
|
-
Snowflake stores all case-insensitive object names in uppercase text. In contrast, SQLAlchemy considers all lowercase object names to be case-insensitive. Snowflake SQLAlchemy converts the object name case during schema-level communication, i.e. during table and index reflection. If you use uppercase object names, SQLAlchemy assumes they are case-sensitive and encloses the names with quotes. This behavior will cause mismatches
|
|
233
|
+
Snowflake stores all case-insensitive object names in uppercase text. In contrast, SQLAlchemy considers all lowercase object names to be case-insensitive. Snowflake SQLAlchemy converts the object name case during schema-level communication, i.e. during table and index reflection. If you use uppercase object names, SQLAlchemy assumes they are case-sensitive and encloses the names with quotes. This behavior will cause mismatches against data dictionary data received from Snowflake, so unless identifier names have been truly created as case sensitive using quotes, e.g., `"TestDb"`, all lowercase names should be used on the SQLAlchemy side.
|
|
229
234
|
|
|
230
235
|
### Index Support
|
|
231
236
|
|
|
232
|
-
|
|
237
|
+
Indexes are supported only for Hybrid Tables in Snowflake SQLAlchemy. For more details on limitations and use cases, refer to the [Create Index documentation](https://docs.snowflake.com/en/sql-reference/constraints-indexes.html). You can create an index using the following methods:
|
|
238
|
+
|
|
239
|
+
#### Single Column Index
|
|
240
|
+
|
|
241
|
+
You can create a single column index by setting the `index=True` parameter on the column or by explicitly defining an `Index` object.
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
hybrid_test_table_1 = HybridTable(
|
|
245
|
+
"table_name",
|
|
246
|
+
metadata,
|
|
247
|
+
Column("column1", Integer, primary_key=True),
|
|
248
|
+
Column("column2", String, index=True),
|
|
249
|
+
Index("index_1", "column1", "column2")
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
metadata.create_all(engine_testaccount)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### Multi-Column Index
|
|
256
|
+
|
|
257
|
+
For multi-column indexes, you define the `Index` object specifying the columns that should be indexed.
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
hybrid_test_table_1 = HybridTable(
|
|
261
|
+
"table_name",
|
|
262
|
+
metadata,
|
|
263
|
+
Column("column1", Integer, primary_key=True),
|
|
264
|
+
Column("column2", String),
|
|
265
|
+
Index("index_1", "column1", "column2")
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
metadata.create_all(engine_testaccount)
|
|
269
|
+
```
|
|
233
270
|
|
|
234
271
|
### Numpy Data Type Support
|
|
235
272
|
|
|
@@ -456,6 +493,117 @@ copy_into = CopyIntoStorage(from_=users,
|
|
|
456
493
|
connection.execute(copy_into)
|
|
457
494
|
```
|
|
458
495
|
|
|
496
|
+
### Iceberg Table with Snowflake Catalog support
|
|
497
|
+
|
|
498
|
+
Snowflake SQLAlchemy supports Iceberg Tables with the Snowflake Catalog, along with various related parameters. For detailed information about Iceberg Tables, refer to the Snowflake [CREATE ICEBERG](https://docs.snowflake.com/en/sql-reference/sql/create-iceberg-table-snowflake) documentation.
|
|
499
|
+
|
|
500
|
+
To create an Iceberg Table using Snowflake SQLAlchemy, you can define the table using the SQLAlchemy Core syntax as follows:
|
|
501
|
+
|
|
502
|
+
```python
|
|
503
|
+
table = IcebergTable(
|
|
504
|
+
"myuser",
|
|
505
|
+
metadata,
|
|
506
|
+
Column("id", Integer, primary_key=True),
|
|
507
|
+
Column("name", String),
|
|
508
|
+
external_volume=external_volume_name,
|
|
509
|
+
base_location="my_iceberg_table",
|
|
510
|
+
as_query="SELECT * FROM table"
|
|
511
|
+
)
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
Alternatively, you can define the table using a declarative approach:
|
|
515
|
+
|
|
516
|
+
```python
|
|
517
|
+
class MyUser(Base):
|
|
518
|
+
__tablename__ = "myuser"
|
|
519
|
+
|
|
520
|
+
@classmethod
|
|
521
|
+
def __table_cls__(cls, name, metadata, *arg, **kw):
|
|
522
|
+
return IcebergTable(name, metadata, *arg, **kw)
|
|
523
|
+
|
|
524
|
+
__table_args__ = {
|
|
525
|
+
"external_volume": "my_external_volume",
|
|
526
|
+
"base_location": "my_iceberg_table",
|
|
527
|
+
"as_query": "SELECT * FROM table",
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
id = Column(Integer, primary_key=True)
|
|
531
|
+
name = Column(String)
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Hybrid Table support
|
|
535
|
+
|
|
536
|
+
Snowflake SQLAlchemy supports Hybrid Tables with indexes. For detailed information, refer to the Snowflake [CREATE HYBRID TABLE](https://docs.snowflake.com/en/sql-reference/sql/create-hybrid-table) documentation.
|
|
537
|
+
|
|
538
|
+
To create a Hybrid Table and add an index, you can use the SQLAlchemy Core syntax as follows:
|
|
539
|
+
|
|
540
|
+
```python
|
|
541
|
+
table = HybridTable(
|
|
542
|
+
"myuser",
|
|
543
|
+
metadata,
|
|
544
|
+
Column("id", Integer, primary_key=True),
|
|
545
|
+
Column("name", String),
|
|
546
|
+
Index("idx_name", "name")
|
|
547
|
+
)
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
Alternatively, you can define the table using the declarative approach:
|
|
551
|
+
|
|
552
|
+
```python
|
|
553
|
+
class MyUser(Base):
|
|
554
|
+
__tablename__ = "myuser"
|
|
555
|
+
|
|
556
|
+
@classmethod
|
|
557
|
+
def __table_cls__(cls, name, metadata, *arg, **kw):
|
|
558
|
+
return HybridTable(name, metadata, *arg, **kw)
|
|
559
|
+
|
|
560
|
+
__table_args__ = (
|
|
561
|
+
Index("idx_name", "name"),
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
id = Column(Integer, primary_key=True)
|
|
565
|
+
name = Column(String)
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### Dynamic Tables support
|
|
569
|
+
|
|
570
|
+
Snowflake SQLAlchemy supports Dynamic Tables. For detailed information, refer to the Snowflake [CREATE DYNAMIC TABLE](https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table) documentation.
|
|
571
|
+
|
|
572
|
+
To create a Dynamic Table, you can use the SQLAlchemy Core syntax as follows:
|
|
573
|
+
|
|
574
|
+
```python
|
|
575
|
+
dynamic_test_table_1 = DynamicTable(
|
|
576
|
+
"dynamic_MyUser",
|
|
577
|
+
metadata,
|
|
578
|
+
Column("id", Integer),
|
|
579
|
+
Column("name", String),
|
|
580
|
+
target_lag=(1, TimeUnit.HOURS), # Additionally, you can use SnowflakeKeyword.DOWNSTREAM
|
|
581
|
+
warehouse='test_wh',
|
|
582
|
+
refresh_mode=SnowflakeKeyword.FULL,
|
|
583
|
+
as_query="SELECT id, name from MyUser;"
|
|
584
|
+
)
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
Alternatively, you can define a table without columns using the SQLAlchemy `select()` construct:
|
|
588
|
+
|
|
589
|
+
```python
|
|
590
|
+
dynamic_test_table_1 = DynamicTable(
|
|
591
|
+
"dynamic_MyUser",
|
|
592
|
+
metadata,
|
|
593
|
+
target_lag=(1, TimeUnit.HOURS),
|
|
594
|
+
warehouse='test_wh',
|
|
595
|
+
refresh_mode=SnowflakeKeyword.FULL,
|
|
596
|
+
as_query=select(MyUser.id, MyUser.name)
|
|
597
|
+
)
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### Notes
|
|
601
|
+
|
|
602
|
+
- Defining a primary key in a Dynamic Table is not supported, meaning declarative tables don’t support Dynamic Tables.
|
|
603
|
+
- When using the `as_query` parameter with a string, you must explicitly define the columns. However, if you use the SQLAlchemy `select()` construct, you don’t need to explicitly define the columns.
|
|
604
|
+
- Direct data insertion into Dynamic Tables is not supported.
|
|
605
|
+
|
|
606
|
+
|
|
459
607
|
## Support
|
|
460
608
|
|
|
461
609
|
Feel free to file an issue or submit a PR here for general cases. For official support, contact Snowflake support at:
|
|
@@ -16,7 +16,8 @@ from sqlalchemy.orm.context import _MapperEntity
|
|
|
16
16
|
from sqlalchemy.schema import Sequence, Table
|
|
17
17
|
from sqlalchemy.sql import compiler, expression, functions
|
|
18
18
|
from sqlalchemy.sql.base import CompileState
|
|
19
|
-
from sqlalchemy.sql.elements import quoted_name
|
|
19
|
+
from sqlalchemy.sql.elements import BindParameter, quoted_name
|
|
20
|
+
from sqlalchemy.sql.expression import Executable
|
|
20
21
|
from sqlalchemy.sql.selectable import Lateral, SelectState
|
|
21
22
|
|
|
22
23
|
from snowflake.sqlalchemy._constants import DIALECT_NAME
|
|
@@ -563,9 +564,8 @@ class SnowflakeCompiler(compiler.SQLCompiler):
|
|
|
563
564
|
if isinstance(copy_into.into, Table)
|
|
564
565
|
else copy_into.into._compiler_dispatch(self, **kw)
|
|
565
566
|
)
|
|
566
|
-
from_ = None
|
|
567
567
|
if isinstance(copy_into.from_, Table):
|
|
568
|
-
from_ = copy_into.from_
|
|
568
|
+
from_ = copy_into.from_.name
|
|
569
569
|
# this is intended to catch AWSBucket and AzureContainer
|
|
570
570
|
elif (
|
|
571
571
|
isinstance(copy_into.from_, AWSBucket)
|
|
@@ -576,6 +576,21 @@ class SnowflakeCompiler(compiler.SQLCompiler):
|
|
|
576
576
|
# everything else (selects, etc.)
|
|
577
577
|
else:
|
|
578
578
|
from_ = f"({copy_into.from_._compiler_dispatch(self, **kw)})"
|
|
579
|
+
|
|
580
|
+
partition_by_value = None
|
|
581
|
+
if isinstance(copy_into.partition_by, (BindParameter, Executable)):
|
|
582
|
+
partition_by_value = copy_into.partition_by.compile(
|
|
583
|
+
compile_kwargs={"literal_binds": True}
|
|
584
|
+
)
|
|
585
|
+
elif copy_into.partition_by is not None:
|
|
586
|
+
partition_by_value = copy_into.partition_by
|
|
587
|
+
|
|
588
|
+
partition_by = (
|
|
589
|
+
f"PARTITION BY {partition_by_value}"
|
|
590
|
+
if partition_by_value is not None and partition_by_value != ""
|
|
591
|
+
else ""
|
|
592
|
+
)
|
|
593
|
+
|
|
579
594
|
credentials, encryption = "", ""
|
|
580
595
|
if isinstance(into, tuple):
|
|
581
596
|
into, credentials, encryption = into
|
|
@@ -586,8 +601,7 @@ class SnowflakeCompiler(compiler.SQLCompiler):
|
|
|
586
601
|
options_list.sort(key=operator.itemgetter(0))
|
|
587
602
|
options = (
|
|
588
603
|
(
|
|
589
|
-
" "
|
|
590
|
-
+ " ".join(
|
|
604
|
+
" ".join(
|
|
591
605
|
[
|
|
592
606
|
"{} = {}".format(
|
|
593
607
|
n,
|
|
@@ -608,7 +622,7 @@ class SnowflakeCompiler(compiler.SQLCompiler):
|
|
|
608
622
|
options += f" {credentials}"
|
|
609
623
|
if encryption:
|
|
610
624
|
options += f" {encryption}"
|
|
611
|
-
return f"COPY INTO {into} FROM {from_
|
|
625
|
+
return f"COPY INTO {into} FROM {' '.join([from_, partition_by, formatter, options])}"
|
|
612
626
|
|
|
613
627
|
def visit_copy_formatter(self, formatter, **kw):
|
|
614
628
|
options_list = list(formatter.options.items())
|
|
@@ -115,18 +115,23 @@ class CopyInto(UpdateBase):
|
|
|
115
115
|
__visit_name__ = "copy_into"
|
|
116
116
|
_bind = None
|
|
117
117
|
|
|
118
|
-
def __init__(self, from_, into, formatter=None):
|
|
118
|
+
def __init__(self, from_, into, partition_by=None, formatter=None):
|
|
119
119
|
self.from_ = from_
|
|
120
120
|
self.into = into
|
|
121
121
|
self.formatter = formatter
|
|
122
122
|
self.copy_options = {}
|
|
123
|
+
self.partition_by = partition_by
|
|
123
124
|
|
|
124
125
|
def __repr__(self):
|
|
125
126
|
"""
|
|
126
127
|
repr for debugging / logging purposes only. For compilation logic, see
|
|
127
128
|
the corresponding visitor in base.py
|
|
128
129
|
"""
|
|
129
|
-
|
|
130
|
+
val = f"COPY INTO {self.into} FROM {repr(self.from_)}"
|
|
131
|
+
if self.partition_by is not None:
|
|
132
|
+
val += f" PARTITION BY {self.partition_by}"
|
|
133
|
+
|
|
134
|
+
return val + f" {repr(self.formatter)} ({self.copy_options})"
|
|
130
135
|
|
|
131
136
|
def bind(self):
|
|
132
137
|
return None
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/snowdialect.py
RENAMED
|
@@ -39,6 +39,8 @@ from .custom_types import (
|
|
|
39
39
|
_CUSTOM_Float,
|
|
40
40
|
_CUSTOM_Time,
|
|
41
41
|
)
|
|
42
|
+
from .parser.custom_type_parser import * # noqa
|
|
43
|
+
from .parser.custom_type_parser import _CUSTOM_DECIMAL # noqa
|
|
42
44
|
from .parser.custom_type_parser import ischema_names, parse_type
|
|
43
45
|
from .sql.custom_schema.custom_table_prefix import CustomTablePrefix
|
|
44
46
|
from .util import (
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
6
|
from sqlalchemy import Column, Integer, MetaData, Sequence, String, Table
|
|
7
|
-
from sqlalchemy.sql import select, text
|
|
7
|
+
from sqlalchemy.sql import functions, select, text
|
|
8
8
|
|
|
9
9
|
from snowflake.sqlalchemy import (
|
|
10
10
|
AWSBucket,
|
|
@@ -58,8 +58,8 @@ def test_copy_into_location(engine_testaccount, sql_compiler):
|
|
|
58
58
|
)
|
|
59
59
|
assert (
|
|
60
60
|
sql_compiler(copy_stmt_1)
|
|
61
|
-
== "COPY INTO 's3://backup' FROM python_tests_foods
|
|
62
|
-
"ESCAPE=None NULL_IF=('null', 'Null') RECORD_DELIMITER='|')
|
|
61
|
+
== "COPY INTO 's3://backup' FROM python_tests_foods FILE_FORMAT=(TYPE=csv "
|
|
62
|
+
"ESCAPE=None NULL_IF=('null', 'Null') RECORD_DELIMITER='|') ENCRYPTION="
|
|
63
63
|
"(KMS_KEY_ID='1234abcd-12ab-34cd-56ef-1234567890ab' TYPE='AWS_SSE_KMS')"
|
|
64
64
|
)
|
|
65
65
|
copy_stmt_2 = CopyIntoStorage(
|
|
@@ -73,8 +73,8 @@ def test_copy_into_location(engine_testaccount, sql_compiler):
|
|
|
73
73
|
sql_compiler(copy_stmt_2)
|
|
74
74
|
== "COPY INTO 's3://backup' FROM (SELECT python_tests_foods.id, "
|
|
75
75
|
"python_tests_foods.name, python_tests_foods.quantity FROM python_tests_foods "
|
|
76
|
-
"WHERE python_tests_foods.id = 1)
|
|
77
|
-
"FILE_EXTENSION='json')
|
|
76
|
+
"WHERE python_tests_foods.id = 1) FILE_FORMAT=(TYPE=json COMPRESSION='zstd' "
|
|
77
|
+
"FILE_EXTENSION='json') CREDENTIALS=(AWS_ROLE='some_iam_role') "
|
|
78
78
|
"ENCRYPTION=(TYPE='AWS_SSE_S3')"
|
|
79
79
|
)
|
|
80
80
|
copy_stmt_3 = CopyIntoStorage(
|
|
@@ -87,7 +87,7 @@ def test_copy_into_location(engine_testaccount, sql_compiler):
|
|
|
87
87
|
assert (
|
|
88
88
|
sql_compiler(copy_stmt_3)
|
|
89
89
|
== "COPY INTO 'azure://snowflake.blob.core.windows.net/snowpile/backup' "
|
|
90
|
-
"FROM python_tests_foods
|
|
90
|
+
"FROM python_tests_foods FILE_FORMAT=(TYPE=parquet SNAPPY_COMPRESSION=true) "
|
|
91
91
|
"CREDENTIALS=(AZURE_SAS_TOKEN='token')"
|
|
92
92
|
)
|
|
93
93
|
|
|
@@ -95,7 +95,7 @@ def test_copy_into_location(engine_testaccount, sql_compiler):
|
|
|
95
95
|
assert (
|
|
96
96
|
sql_compiler(copy_stmt_3)
|
|
97
97
|
== "COPY INTO 'azure://snowflake.blob.core.windows.net/snowpile/backup' "
|
|
98
|
-
"FROM python_tests_foods
|
|
98
|
+
"FROM python_tests_foods FILE_FORMAT=(TYPE=parquet SNAPPY_COMPRESSION=true) "
|
|
99
99
|
"MAX_FILE_SIZE = 50000000 "
|
|
100
100
|
"CREDENTIALS=(AZURE_SAS_TOKEN='token')"
|
|
101
101
|
)
|
|
@@ -112,8 +112,8 @@ def test_copy_into_location(engine_testaccount, sql_compiler):
|
|
|
112
112
|
)
|
|
113
113
|
assert (
|
|
114
114
|
sql_compiler(copy_stmt_4)
|
|
115
|
-
== "COPY INTO python_tests_foods FROM 's3://backup'
|
|
116
|
-
"ESCAPE=None NULL_IF=('null', 'Null') RECORD_DELIMITER='|')
|
|
115
|
+
== "COPY INTO python_tests_foods FROM 's3://backup' FILE_FORMAT=(TYPE=csv "
|
|
116
|
+
"ESCAPE=None NULL_IF=('null', 'Null') RECORD_DELIMITER='|') ENCRYPTION="
|
|
117
117
|
"(KMS_KEY_ID='1234abcd-12ab-34cd-56ef-1234567890ab' TYPE='AWS_SSE_KMS')"
|
|
118
118
|
)
|
|
119
119
|
|
|
@@ -126,8 +126,8 @@ def test_copy_into_location(engine_testaccount, sql_compiler):
|
|
|
126
126
|
)
|
|
127
127
|
assert (
|
|
128
128
|
sql_compiler(copy_stmt_5)
|
|
129
|
-
== "COPY INTO python_tests_foods FROM 's3://backup'
|
|
130
|
-
"FIELD_DELIMITER=',')
|
|
129
|
+
== "COPY INTO python_tests_foods FROM 's3://backup' FILE_FORMAT=(TYPE=csv "
|
|
130
|
+
"FIELD_DELIMITER=',') ENCRYPTION="
|
|
131
131
|
"(KMS_KEY_ID='1234abcd-12ab-34cd-56ef-1234567890ab' TYPE='AWS_SSE_KMS')"
|
|
132
132
|
)
|
|
133
133
|
|
|
@@ -138,7 +138,7 @@ def test_copy_into_location(engine_testaccount, sql_compiler):
|
|
|
138
138
|
)
|
|
139
139
|
assert (
|
|
140
140
|
sql_compiler(copy_stmt_6)
|
|
141
|
-
== "COPY INTO @stage_name FROM python_tests_foods
|
|
141
|
+
== "COPY INTO @stage_name FROM python_tests_foods FILE_FORMAT=(TYPE=csv) "
|
|
142
142
|
)
|
|
143
143
|
|
|
144
144
|
copy_stmt_7 = CopyIntoStorage(
|
|
@@ -148,7 +148,38 @@ def test_copy_into_location(engine_testaccount, sql_compiler):
|
|
|
148
148
|
)
|
|
149
149
|
assert (
|
|
150
150
|
sql_compiler(copy_stmt_7)
|
|
151
|
-
== "COPY INTO @name.stage_name/prefix/file FROM python_tests_foods
|
|
151
|
+
== "COPY INTO @name.stage_name/prefix/file FROM python_tests_foods FILE_FORMAT=(TYPE=csv) "
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
copy_stmt_8 = CopyIntoStorage(
|
|
155
|
+
from_=food_items,
|
|
156
|
+
into=ExternalStage(name="stage_name"),
|
|
157
|
+
partition_by=text("('YEAR=' || year)"),
|
|
158
|
+
)
|
|
159
|
+
assert (
|
|
160
|
+
sql_compiler(copy_stmt_8)
|
|
161
|
+
== "COPY INTO @stage_name FROM python_tests_foods PARTITION BY ('YEAR=' || year) "
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
copy_stmt_9 = CopyIntoStorage(
|
|
165
|
+
from_=food_items,
|
|
166
|
+
into=ExternalStage(name="stage_name"),
|
|
167
|
+
partition_by=functions.concat(
|
|
168
|
+
text("'YEAR='"), text(food_items.columns["name"].name)
|
|
169
|
+
),
|
|
170
|
+
)
|
|
171
|
+
assert (
|
|
172
|
+
sql_compiler(copy_stmt_9)
|
|
173
|
+
== "COPY INTO @stage_name FROM python_tests_foods PARTITION BY concat('YEAR=', name) "
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
copy_stmt_10 = CopyIntoStorage(
|
|
177
|
+
from_=food_items,
|
|
178
|
+
into=ExternalStage(name="stage_name"),
|
|
179
|
+
partition_by="",
|
|
180
|
+
)
|
|
181
|
+
assert (
|
|
182
|
+
sql_compiler(copy_stmt_10) == "COPY INTO @stage_name FROM python_tests_foods "
|
|
152
183
|
)
|
|
153
184
|
|
|
154
185
|
# NOTE Other than expect known compiled text, submit it to RegressionTests environment and expect them to fail, but
|
|
@@ -231,7 +262,7 @@ def test_copy_into_storage_csv_extended(sql_compiler):
|
|
|
231
262
|
result = sql_compiler(copy_into)
|
|
232
263
|
expected = (
|
|
233
264
|
r"COPY INTO TEST_IMPORT "
|
|
234
|
-
r"FROM @ML_POC.PUBLIC.AZURE_STAGE/testdata
|
|
265
|
+
r"FROM @ML_POC.PUBLIC.AZURE_STAGE/testdata "
|
|
235
266
|
r"FILE_FORMAT=(TYPE=csv COMPRESSION='auto' DATE_FORMAT='AUTO' "
|
|
236
267
|
r"ERROR_ON_COLUMN_COUNT_MISMATCH=True ESCAPE=None "
|
|
237
268
|
r"ESCAPE_UNENCLOSED_FIELD='\134' FIELD_DELIMITER=',' "
|
|
@@ -288,7 +319,7 @@ def test_copy_into_storage_parquet_named_format(sql_compiler):
|
|
|
288
319
|
expected = (
|
|
289
320
|
"COPY INTO TEST_IMPORT "
|
|
290
321
|
"FROM (SELECT $1:COL1::number, $1:COL2::varchar "
|
|
291
|
-
"FROM @ML_POC.PUBLIC.AZURE_STAGE/testdata/out.parquet)
|
|
322
|
+
"FROM @ML_POC.PUBLIC.AZURE_STAGE/testdata/out.parquet) "
|
|
292
323
|
"FILE_FORMAT=(format_name = parquet_file_format) force = TRUE"
|
|
293
324
|
)
|
|
294
325
|
assert result == expected
|
|
@@ -350,7 +381,7 @@ def test_copy_into_storage_parquet_files(sql_compiler):
|
|
|
350
381
|
"COPY INTO TEST_IMPORT "
|
|
351
382
|
"FROM (SELECT $1:COL1::number, $1:COL2::varchar "
|
|
352
383
|
"FROM @ML_POC.PUBLIC.AZURE_STAGE/testdata/out.parquet "
|
|
353
|
-
"(file_format => parquet_file_format))
|
|
384
|
+
"(file_format => parquet_file_format)) FILES = ('foo.txt','bar.txt') "
|
|
354
385
|
"FORCE = true"
|
|
355
386
|
)
|
|
356
387
|
assert result == expected
|
|
@@ -412,6 +443,6 @@ def test_copy_into_storage_parquet_pattern(sql_compiler):
|
|
|
412
443
|
"COPY INTO TEST_IMPORT "
|
|
413
444
|
"FROM (SELECT $1:COL1::number, $1:COL2::varchar "
|
|
414
445
|
"FROM @ML_POC.PUBLIC.AZURE_STAGE/testdata/out.parquet "
|
|
415
|
-
"(file_format => parquet_file_format))
|
|
446
|
+
"(file_format => parquet_file_format)) FORCE = true PATTERN = '.*csv'"
|
|
416
447
|
)
|
|
417
448
|
assert result == expected
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import inspect
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_classes_from_module(module_name):
|
|
12
|
+
"""Returns a set of class names from a given module."""
|
|
13
|
+
try:
|
|
14
|
+
module = importlib.import_module(module_name)
|
|
15
|
+
members = inspect.getmembers(module)
|
|
16
|
+
return {name for name, obj in members if inspect.isclass(obj)}
|
|
17
|
+
|
|
18
|
+
except ImportError:
|
|
19
|
+
print(f"Module '{module_name}' could not be imported.")
|
|
20
|
+
return set()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_types_in_snowdialect():
|
|
24
|
+
classes_a = get_classes_from_module(
|
|
25
|
+
"snowflake.sqlalchemy.parser.custom_type_parser"
|
|
26
|
+
)
|
|
27
|
+
classes_b = get_classes_from_module("snowflake.sqlalchemy.snowdialect")
|
|
28
|
+
assert classes_a.issubset(classes_b), str(classes_a - classes_b)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.mark.parametrize(
|
|
32
|
+
"type_class_name",
|
|
33
|
+
[
|
|
34
|
+
"BIGINT",
|
|
35
|
+
"BINARY",
|
|
36
|
+
"BOOLEAN",
|
|
37
|
+
"CHAR",
|
|
38
|
+
"DATE",
|
|
39
|
+
"DATETIME",
|
|
40
|
+
"DECIMAL",
|
|
41
|
+
"FLOAT",
|
|
42
|
+
"INTEGER",
|
|
43
|
+
"REAL",
|
|
44
|
+
"SMALLINT",
|
|
45
|
+
"TIME",
|
|
46
|
+
"TIMESTAMP",
|
|
47
|
+
"VARCHAR",
|
|
48
|
+
"NullType",
|
|
49
|
+
"_CUSTOM_DECIMAL",
|
|
50
|
+
"ARRAY",
|
|
51
|
+
"DOUBLE",
|
|
52
|
+
"GEOGRAPHY",
|
|
53
|
+
"GEOMETRY",
|
|
54
|
+
"MAP",
|
|
55
|
+
"OBJECT",
|
|
56
|
+
"TIMESTAMP_LTZ",
|
|
57
|
+
"TIMESTAMP_NTZ",
|
|
58
|
+
"TIMESTAMP_TZ",
|
|
59
|
+
"VARIANT",
|
|
60
|
+
],
|
|
61
|
+
)
|
|
62
|
+
def test_snowflake_data_types_instance(type_class_name):
|
|
63
|
+
classes_b = get_classes_from_module("snowflake.sqlalchemy.snowdialect")
|
|
64
|
+
assert type_class_name in classes_b, type_class_name
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/ci/docker/sqlalchemy_build/Dockerfile
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/__init__.py
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/_constants.py
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/compat.py
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/custom_types.py
RENAMED
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/functions.py
RENAMED
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/provision.py
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/requirements.py
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/sql/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tested_requirements/requirements_310.reqs
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tested_requirements/requirements_37.reqs
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tested_requirements/requirements_38.reqs
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tested_requirements/requirements_39.reqs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/__snapshots__/test_core.ambr
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/sqlalchemy_test_suite/README.md
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/sqlalchemy_test_suite/__init__.py
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/sqlalchemy_test_suite/conftest.py
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/sqlalchemy_test_suite/test_suite.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_semi_structured_datatypes.py
RENAMED
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_structured_datatypes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.1}/tests/test_unit_structured_types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|