snowflake-sqlalchemy 1.7.6__tar.gz → 1.8.0__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.6 → snowflake_sqlalchemy-1.8.0}/DESCRIPTION.md +11 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/PKG-INFO +3 -5
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/README.md +0 -3
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/ci/docker/sqlalchemy_build/Dockerfile +10 -3
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/ci/test_docker.sh +19 -19
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/ci/test_linux.sh +2 -2
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/pyproject.toml +8 -7
- snowflake_sqlalchemy-1.8.0/snyk/requirements.txt +2 -0
- snowflake_sqlalchemy-1.8.0/src/snowflake/sqlalchemy/name_utils.py +36 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/parser/custom_type_parser.py +3 -3
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/snowdialect.py +123 -163
- snowflake_sqlalchemy-1.8.0/src/snowflake/sqlalchemy/structured_type_info_manager.py +142 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/version.py +1 -1
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/conftest.py +26 -34
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/sqlalchemy_test_suite/test_suite_20.py +71 -11
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_core.py +40 -23
- snowflake_sqlalchemy-1.8.0/tests/test_dialect_connect.py +124 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_pandas.py +3 -1
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_qmark.py +3 -1
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_structured_datatypes.py +53 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_transactions.py +3 -2
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tox.ini +10 -4
- snowflake_sqlalchemy-1.7.6/snyk/requirements.txt +0 -2
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/.gitignore +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/.gitmodules +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/.pre-commit-config.yaml +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/LICENSE.txt +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/MANIFEST.in +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/ci/build.sh +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/ci/build_docker.sh +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/ci/docker/sqlalchemy_build/scripts/entrypoint.sh +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/ci/set_base_image.sh +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/ci/test.sh +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/license_header.txt +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/setup.cfg +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/snyk/requiremtnts.txt +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/snyk/update_requirements.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/_constants.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/base.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/compat.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/custom_commands.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/custom_types.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/exc.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/functions.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/provision.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/requirements.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/clustered_table.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/custom_table_base.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/custom_table_prefix.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/iceberg_table.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/as_query_option.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/cluster_by_option.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/identifier_option.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/invalid_table_option.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/keyword_option.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/keywords.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/literal_option.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/table_option.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/target_lag_option.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/table_from_query.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/util.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tested_requirements/requirements_310.reqs +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tested_requirements/requirements_37.reqs +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tested_requirements/requirements_38.reqs +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tested_requirements/requirements_39.reqs +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/README.rst +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/__snapshots__/test_compile_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/__snapshots__/test_core.ambr +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/__snapshots__/test_orm.ambr +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/__snapshots__/test_reflect_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/__snapshots__/test_structured_datatypes.ambr +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/__snapshots__/test_unit_structured_types.ambr +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_compile_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_compile_hybrid_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_compile_iceberg_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_compile_snowflake_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_create_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_create_hybrid_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_create_iceberg_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_create_snowflake_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_generic_options.ambr +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_reflect_hybrid_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_reflect_snowflake_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_compile_dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_compile_hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_compile_iceberg_table.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_compile_snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_create_dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_create_hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_create_iceberg_table.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_create_snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_generic_options.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_reflect_dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_reflect_hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_reflect_snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/data/users.txt +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/sqlalchemy_test_suite/README.md +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/sqlalchemy_test_suite/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/sqlalchemy_test_suite/conftest.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/sqlalchemy_test_suite/test_suite.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_compiler.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_copy.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_create.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_custom_functions.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_custom_types.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_geography.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_geometry.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_imports.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_index_reflection.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_multivalues_insert.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_orm.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_quote.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_quote_identifiers.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_semi_structured_datatypes.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_sequence.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_timestamp.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_unit_core.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_unit_cte.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_unit_structured_types.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_unit_types.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/test_unit_url.py +0 -0
- {snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/tests/util.py +0 -0
|
@@ -6,9 +6,20 @@ Snowflake Documentation is available at:
|
|
|
6
6
|
|
|
7
7
|
Source code is also available at:
|
|
8
8
|
<https://github.com/snowflakedb/snowflake-sqlalchemy>
|
|
9
|
+
|
|
9
10
|
# Unreleased Notes
|
|
10
11
|
|
|
11
12
|
# Release Notes
|
|
13
|
+
|
|
14
|
+
- v1.8.0(December 5, 2025)
|
|
15
|
+
- Add logging of SQLAlchemy version
|
|
16
|
+
- Bump `snowflake-connector-python<5.0.0`
|
|
17
|
+
- Add python up to 3.14
|
|
18
|
+
- Add logging of SQLAlchemy version and pandas (if used)
|
|
19
|
+
|
|
20
|
+
- v1.7.7(September 3, 2025)
|
|
21
|
+
- Fix exception for structured type columns dropped while collecting metadata
|
|
22
|
+
|
|
12
23
|
- v1.7.6(July 10, 2025)
|
|
13
24
|
- Fix get_multi_indexes issue, wrong assign of returned indexes when processing multiple indexes in a table
|
|
14
25
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: snowflake-sqlalchemy
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
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
|
|
@@ -27,6 +27,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
27
27
|
Classifier: Programming Language :: Python :: 3.10
|
|
28
28
|
Classifier: Programming Language :: Python :: 3.11
|
|
29
29
|
Classifier: Programming Language :: Python :: 3.12
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
30
31
|
Classifier: Programming Language :: SQL
|
|
31
32
|
Classifier: Topic :: Database
|
|
32
33
|
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
@@ -35,7 +36,7 @@ Classifier: Topic :: Software Development :: Libraries
|
|
|
35
36
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
36
37
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
37
38
|
Requires-Python: >=3.8
|
|
38
|
-
Requires-Dist: snowflake-connector-python<
|
|
39
|
+
Requires-Dist: snowflake-connector-python<5.0.0
|
|
39
40
|
Requires-Dist: sqlalchemy>=1.4.19
|
|
40
41
|
Provides-Extra: development
|
|
41
42
|
Requires-Dist: mock; extra == 'development'
|
|
@@ -63,9 +64,6 @@ Description-Content-Type: text/markdown
|
|
|
63
64
|
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.
|
|
64
65
|
|
|
65
66
|
|
|
66
|
-
| :exclamation: | Effective May 8th, 2025, Snowflake SQLAlchemy will transition to maintenance mode and will cease active development. Support will be limited to addressing critical bugs and security vulnerabilities. To report such issues, please [create a case with Snowflake Support](https://community.snowflake.com/s/article/How-To-Submit-a-Support-Case-in-Snowflake-Lodge). for individual evaluation. Please note that pull requests from external contributors may not receive action from Snowflake. |
|
|
67
|
-
|---------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
68
|
-
|
|
69
67
|
|
|
70
68
|
## Prerequisites
|
|
71
69
|
|
|
@@ -9,9 +9,6 @@
|
|
|
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
11
|
|
|
12
|
-
| :exclamation: | Effective May 8th, 2025, Snowflake SQLAlchemy will transition to maintenance mode and will cease active development. Support will be limited to addressing critical bugs and security vulnerabilities. To report such issues, please [create a case with Snowflake Support](https://community.snowflake.com/s/article/How-To-Submit-a-Support-Case-in-Snowflake-Lodge). for individual evaluation. Please note that pull requests from external contributors may not receive action from Snowflake. |
|
|
13
|
-
|---------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
14
|
-
|
|
15
12
|
|
|
16
13
|
## Prerequisites
|
|
17
14
|
|
{snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/ci/docker/sqlalchemy_build/Dockerfile
RENAMED
|
@@ -4,9 +4,9 @@ FROM $BASE_IMAGE
|
|
|
4
4
|
|
|
5
5
|
# This is to solve permission issue, read https://denibertovic.com/posts/handling-permissions-with-docker-volumes/
|
|
6
6
|
ARG GOSU_URL=https://github.com/tianon/gosu/releases/download/1.14/gosu-amd64
|
|
7
|
-
ENV GOSU_PATH
|
|
7
|
+
ENV GOSU_PATH=$GOSU_URL
|
|
8
8
|
RUN curl -o /usr/local/bin/gosu -SL $GOSU_PATH \
|
|
9
|
-
|
|
9
|
+
&& chmod +x /usr/local/bin/gosu
|
|
10
10
|
|
|
11
11
|
COPY scripts/entrypoint.sh /usr/local/bin/entrypoint.sh
|
|
12
12
|
RUN chmod +x /usr/local/bin/entrypoint.sh
|
|
@@ -14,6 +14,13 @@ RUN chmod +x /usr/local/bin/entrypoint.sh
|
|
|
14
14
|
WORKDIR /home/user
|
|
15
15
|
RUN chmod 777 /home/user
|
|
16
16
|
|
|
17
|
-
ENV PATH="${PATH}:/opt/python/cp37-
|
|
17
|
+
ENV PATH="${PATH}:/opt/python/cp37-cp37/bin"
|
|
18
|
+
ENV PATH="${PATH}:/opt/python/cp38-cp38/bin"
|
|
19
|
+
ENV PATH="${PATH}:/opt/python/cp39-cp39/bin"
|
|
20
|
+
ENV PATH="${PATH}:/opt/python/cp310-cp310/bin"
|
|
21
|
+
ENV PATH="${PATH}:/opt/python/cp311-cp311/bin"
|
|
22
|
+
ENV PATH="${PATH}:/opt/python/cp312-cp312/bin"
|
|
23
|
+
ENV PATH="${PATH}:/opt/python/cp313-cp313/bin"
|
|
24
|
+
ENV PATH="${PATH}:/opt/python/cp314-cp314/bin"
|
|
18
25
|
|
|
19
26
|
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
set -o pipefail
|
|
8
8
|
|
|
9
9
|
# In case this is ran from dev-vm
|
|
10
|
-
PYTHON_ENV=${1:-3.
|
|
10
|
+
PYTHON_ENV=${1:-3.8}
|
|
11
11
|
|
|
12
12
|
# Set constants
|
|
13
|
-
THIS_DIR="$(
|
|
14
|
-
SQLALCHEMY_DIR="$(
|
|
13
|
+
THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
+
SQLALCHEMY_DIR="$(dirname "${THIS_DIR}")"
|
|
15
15
|
WORKSPACE=${WORKSPACE:-$SQLALCHEMY_DIR}
|
|
16
16
|
source $THIS_DIR/set_base_image.sh
|
|
17
17
|
|
|
@@ -35,19 +35,19 @@ echo "[Info] Start building docker image and testing"
|
|
|
35
35
|
|
|
36
36
|
user_id=$(id -u ${USER})
|
|
37
37
|
docker run \
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
38
|
+
--rm \
|
|
39
|
+
--network=host \
|
|
40
|
+
-e TERM=vt102 \
|
|
41
|
+
-e PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
|
42
|
+
-e OPENSSL_FIPS=1 \
|
|
43
|
+
-e LOCAL_USER_ID=${user_id} \
|
|
44
|
+
-e AWS_ACCESS_KEY_ID \
|
|
45
|
+
-e AWS_SECRET_ACCESS_KEY \
|
|
46
|
+
-e SF_REGRESS_LOGS \
|
|
47
|
+
-e SF_PROJECT_ROOT \
|
|
48
|
+
-e cloud_provider \
|
|
49
|
+
-e JENKINS_HOME \
|
|
50
|
+
-e GITHUB_ACTIONS \
|
|
51
|
+
--mount type=bind,source="${SQLALCHEMY_DIR}",target=/home/user/snowflake-sqlalchemy \
|
|
52
|
+
$(docker build --pull --build-arg BASE_IMAGE=$BASE_IMAGE --build-arg GOSU_URL="$GOSU_URL" -q .) \
|
|
53
|
+
/home/user/snowflake-sqlalchemy/ci/test_linux.sh ${PYTHON_ENV}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# - This script assumes that ../dist/repaired_wheels has the wheel(s) built for all versions to be tested
|
|
7
7
|
# - This is the script that test_docker.sh runs inside of the docker container
|
|
8
8
|
|
|
9
|
-
PYTHON_VERSIONS="${1:-3.8 3.9 3.10 3.11}"
|
|
9
|
+
PYTHON_VERSIONS="${1:-3.8 3.9 3.10 3.11 3.12 3.13 3.14}"
|
|
10
10
|
THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
SQLALCHEMY_DIR="$(dirname "${THIS_DIR}")"
|
|
12
12
|
|
|
@@ -19,7 +19,7 @@ for PYTHON_VERSION in ${PYTHON_VERSIONS}; do
|
|
|
19
19
|
echo "[Info] Testing with ${PYTHON_VERSION}"
|
|
20
20
|
SHORT_VERSION=$(python3 -c "print('${PYTHON_VERSION}'.replace('.', ''))")
|
|
21
21
|
SQLALCHEMY_WHL=$(ls $SQLALCHEMY_DIR/dist/snowflake_sqlalchemy-*-py3-none-any.whl | sort -r | head -n 1)
|
|
22
|
-
TEST_ENVLIST=fix_lint,py${SHORT_VERSION}-ci,py${SHORT_VERSION}-coverage
|
|
22
|
+
TEST_ENVLIST=fix_lint,py${SHORT_VERSION}-ci,py${SHORT_VERSION}-coverage,py${SHORT_VERSION}-pandas-ci,py${SHORT_VERSION}-pandas-coverage
|
|
23
23
|
echo "[Info] Running tox for ${TEST_ENVLIST}"
|
|
24
24
|
python3 -m tox -e ${TEST_ENVLIST} --installpkg ${SQLALCHEMY_WHL}
|
|
25
25
|
done
|
|
@@ -30,6 +30,7 @@ classifiers = [
|
|
|
30
30
|
"Programming Language :: Python :: 3.10",
|
|
31
31
|
"Programming Language :: Python :: 3.11",
|
|
32
32
|
"Programming Language :: Python :: 3.12",
|
|
33
|
+
"Programming Language :: Python :: 3.13",
|
|
33
34
|
"Programming Language :: SQL",
|
|
34
35
|
"Topic :: Database",
|
|
35
36
|
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
@@ -38,7 +39,7 @@ classifiers = [
|
|
|
38
39
|
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
39
40
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
40
41
|
]
|
|
41
|
-
dependencies = ["SQLAlchemy>=1.4.19", "snowflake-connector-python<
|
|
42
|
+
dependencies = ["SQLAlchemy>=1.4.19", "snowflake-connector-python<5.0.0"]
|
|
42
43
|
|
|
43
44
|
[tool.hatch.version]
|
|
44
45
|
path = "src/snowflake/sqlalchemy/version.py"
|
|
@@ -75,17 +76,17 @@ exclude = ["/.github"]
|
|
|
75
76
|
packages = ["src/snowflake"]
|
|
76
77
|
|
|
77
78
|
[tool.hatch.envs.default]
|
|
78
|
-
path = ".venv"
|
|
79
|
-
type = "virtual"
|
|
80
79
|
extra-dependencies = ["SQLAlchemy>=1.4.19,<2.1.0"]
|
|
81
80
|
features = ["development", "pandas"]
|
|
82
|
-
python = "3.
|
|
81
|
+
python = "3.12"
|
|
83
82
|
installer = "uv"
|
|
84
83
|
|
|
85
84
|
[tool.hatch.envs.sa14]
|
|
86
|
-
|
|
85
|
+
installer = "uv"
|
|
86
|
+
builder = true
|
|
87
|
+
extra-dependencies = ["SQLAlchemy>=1.4.19,<2.0.0", "pandas<2.1", "numpy<2"]
|
|
87
88
|
features = ["development", "pandas"]
|
|
88
|
-
python = "3.
|
|
89
|
+
python = "3.12"
|
|
89
90
|
|
|
90
91
|
[tool.hatch.envs.sa14.scripts]
|
|
91
92
|
test-dialect = "pytest --ignore_v20_test -ra -vvv --tb=short --cov snowflake.sqlalchemy --cov-append --junitxml ./junit.xml --ignore=tests/sqlalchemy_test_suite tests/"
|
|
@@ -105,7 +106,7 @@ gh-cache-sum = "python -VV | sha256sum | cut -d' ' -f1"
|
|
|
105
106
|
check-import = "python -c 'import snowflake.sqlalchemy; print(snowflake.sqlalchemy.__version__)'"
|
|
106
107
|
|
|
107
108
|
[[tool.hatch.envs.release.matrix]]
|
|
108
|
-
python = ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
|
109
|
+
python = ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
109
110
|
features = ["development", "pandas"]
|
|
110
111
|
|
|
111
112
|
[tool.hatch.envs.release.scripts]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
from sqlalchemy.sql.compiler import IdentifierPreparer
|
|
5
|
+
from sqlalchemy.sql.elements import quoted_name
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _NameUtils:
|
|
9
|
+
|
|
10
|
+
def __init__(self, identifier_preparer: IdentifierPreparer) -> None:
|
|
11
|
+
self.identifier_preparer = identifier_preparer
|
|
12
|
+
|
|
13
|
+
def normalize_name(self, name):
|
|
14
|
+
if name is None:
|
|
15
|
+
return None
|
|
16
|
+
if name == "":
|
|
17
|
+
return ""
|
|
18
|
+
if name.upper() == name and not self.identifier_preparer._requires_quotes(
|
|
19
|
+
name.lower()
|
|
20
|
+
):
|
|
21
|
+
return name.lower()
|
|
22
|
+
elif name.lower() == name:
|
|
23
|
+
return quoted_name(name, quote=True)
|
|
24
|
+
else:
|
|
25
|
+
return name
|
|
26
|
+
|
|
27
|
+
def denormalize_name(self, name):
|
|
28
|
+
if name is None:
|
|
29
|
+
return None
|
|
30
|
+
if name == "":
|
|
31
|
+
return ""
|
|
32
|
+
elif name.lower() == name and not self.identifier_preparer._requires_quotes(
|
|
33
|
+
name.lower()
|
|
34
|
+
):
|
|
35
|
+
name = name.upper()
|
|
36
|
+
return name
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
|
|
3
3
|
from typing import List
|
|
4
4
|
|
|
5
|
-
import sqlalchemy.
|
|
6
|
-
from sqlalchemy.sql.
|
|
7
|
-
from sqlalchemy.types import (
|
|
5
|
+
import sqlalchemy.sql.sqltypes as sqltypes
|
|
6
|
+
from sqlalchemy.sql.sqltypes import (
|
|
8
7
|
BIGINT,
|
|
9
8
|
BINARY,
|
|
10
9
|
BOOLEAN,
|
|
@@ -21,6 +20,7 @@ from sqlalchemy.types import (
|
|
|
21
20
|
VARCHAR,
|
|
22
21
|
NullType,
|
|
23
22
|
)
|
|
23
|
+
from sqlalchemy.sql.type_api import TypeEngine
|
|
24
24
|
|
|
25
25
|
from ..custom_types import (
|
|
26
26
|
_CUSTOM_DECIMAL,
|
{snowflake_sqlalchemy-1.7.6 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/snowdialect.py
RENAMED
|
@@ -2,28 +2,33 @@
|
|
|
2
2
|
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
|
|
3
3
|
#
|
|
4
4
|
import operator
|
|
5
|
-
import re
|
|
6
5
|
from collections import defaultdict
|
|
7
6
|
from enum import Enum
|
|
8
7
|
from functools import reduce
|
|
9
|
-
from
|
|
8
|
+
from logging import getLogger
|
|
9
|
+
from time import time as time_in_seconds
|
|
10
|
+
from typing import Any, Collection, Optional, cast
|
|
10
11
|
from urllib.parse import unquote_plus
|
|
11
12
|
|
|
12
13
|
import sqlalchemy.sql.sqltypes as sqltypes
|
|
14
|
+
from sqlalchemy import __version__ as SQLALCHEMY_VERSION
|
|
13
15
|
from sqlalchemy import event as sa_vnt
|
|
14
16
|
from sqlalchemy import exc as sa_exc
|
|
15
17
|
from sqlalchemy import util as sa_util
|
|
16
18
|
from sqlalchemy.engine import URL, default, reflection
|
|
17
19
|
from sqlalchemy.schema import Table
|
|
18
20
|
from sqlalchemy.sql import text
|
|
19
|
-
from sqlalchemy.sql.elements import quoted_name
|
|
20
21
|
from sqlalchemy.sql.sqltypes import NullType
|
|
21
22
|
from sqlalchemy.types import FLOAT, Date, DateTime, Float, Time
|
|
22
23
|
|
|
23
24
|
from snowflake.connector import errors as sf_errors
|
|
24
|
-
from snowflake.connector.connection import DEFAULT_CONFIGURATION
|
|
25
|
+
from snowflake.connector.connection import DEFAULT_CONFIGURATION, SnowflakeConnection
|
|
25
26
|
from snowflake.connector.constants import UTF8
|
|
27
|
+
from snowflake.connector.network import SnowflakeRestful
|
|
28
|
+
from snowflake.connector.telemetry import TelemetryClient, TelemetryData, TelemetryField
|
|
26
29
|
from snowflake.sqlalchemy.compat import returns_unicode
|
|
30
|
+
from snowflake.sqlalchemy.name_utils import _NameUtils
|
|
31
|
+
from snowflake.sqlalchemy.structured_type_info_manager import _StructuredTypeInfoManager
|
|
27
32
|
|
|
28
33
|
from ._constants import DIALECT_NAME
|
|
29
34
|
from .base import (
|
|
@@ -42,7 +47,7 @@ from .custom_types import (
|
|
|
42
47
|
)
|
|
43
48
|
from .parser.custom_type_parser import * # noqa
|
|
44
49
|
from .parser.custom_type_parser import _CUSTOM_DECIMAL # noqa
|
|
45
|
-
from .parser.custom_type_parser import ischema_names, parse_index_columns
|
|
50
|
+
from .parser.custom_type_parser import ischema_names, parse_index_columns
|
|
46
51
|
from .sql.custom_schema.custom_table_prefix import CustomTablePrefix
|
|
47
52
|
from .util import (
|
|
48
53
|
_update_connection_application_name,
|
|
@@ -59,6 +64,12 @@ colspecs = {
|
|
|
59
64
|
|
|
60
65
|
_ENABLE_SQLALCHEMY_AS_APPLICATION_NAME = True
|
|
61
66
|
|
|
67
|
+
logger = getLogger(__name__)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TelemetryEvents(Enum):
|
|
71
|
+
NEW_CONNECTION = "sqlalchemy_new_connection"
|
|
72
|
+
|
|
62
73
|
|
|
63
74
|
class SnowflakeIsolationLevel(Enum):
|
|
64
75
|
READ_COMMITTED = "READ COMMITTED"
|
|
@@ -157,6 +168,7 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
157
168
|
super().__init__(isolation_level=isolation_level, **kwargs)
|
|
158
169
|
self.force_div_is_floordiv = force_div_is_floordiv
|
|
159
170
|
self.div_is_floordiv = force_div_is_floordiv
|
|
171
|
+
self.name_utils = _NameUtils(self.identifier_preparer)
|
|
160
172
|
|
|
161
173
|
def initialize(self, connection):
|
|
162
174
|
super().initialize(connection)
|
|
@@ -282,29 +294,10 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
282
294
|
raise
|
|
283
295
|
|
|
284
296
|
def normalize_name(self, name):
|
|
285
|
-
|
|
286
|
-
return None
|
|
287
|
-
if name == "":
|
|
288
|
-
return ""
|
|
289
|
-
if name.upper() == name and not self.identifier_preparer._requires_quotes(
|
|
290
|
-
name.lower()
|
|
291
|
-
):
|
|
292
|
-
return name.lower()
|
|
293
|
-
elif name.lower() == name:
|
|
294
|
-
return quoted_name(name, quote=True)
|
|
295
|
-
else:
|
|
296
|
-
return name
|
|
297
|
+
return self.name_utils.normalize_name(name)
|
|
297
298
|
|
|
298
299
|
def denormalize_name(self, name):
|
|
299
|
-
|
|
300
|
-
return None
|
|
301
|
-
if name == "":
|
|
302
|
-
return ""
|
|
303
|
-
elif name.lower() == name and not self.identifier_preparer._requires_quotes(
|
|
304
|
-
name.lower()
|
|
305
|
-
):
|
|
306
|
-
name = name.upper()
|
|
307
|
-
return name
|
|
300
|
+
return self.name_utils.denormalize_name(name)
|
|
308
301
|
|
|
309
302
|
def _denormalize_quote_join(self, *idents):
|
|
310
303
|
ip = self.identifier_preparer
|
|
@@ -491,53 +484,31 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
491
484
|
)
|
|
492
485
|
return foreign_key_map.get(table_name, [])
|
|
493
486
|
|
|
494
|
-
def table_columns_as_dict(self, columns):
|
|
495
|
-
result = {}
|
|
496
|
-
for column in columns:
|
|
497
|
-
result[column["name"]] = column
|
|
498
|
-
return result
|
|
499
|
-
|
|
500
487
|
@reflection.cache
|
|
501
488
|
def _get_schema_columns(self, connection, schema, **kw):
|
|
502
489
|
"""Get all columns in the schema, if we hit 'Information schema query returned too much data' problem return
|
|
503
490
|
None, as it is cacheable and is an unexpected return type for this function"""
|
|
504
491
|
ans = {}
|
|
505
|
-
|
|
492
|
+
|
|
493
|
+
schema_name = self.denormalize_name(schema)
|
|
494
|
+
|
|
495
|
+
result = self._query_all_columns_info(connection, schema_name, **kw)
|
|
496
|
+
if result is None:
|
|
497
|
+
return None
|
|
498
|
+
|
|
499
|
+
current_database, default_schema = self._current_database_schema(
|
|
500
|
+
connection, **kw
|
|
501
|
+
)
|
|
506
502
|
full_schema_name = self._denormalize_quote_join(current_database, schema)
|
|
507
|
-
full_columns_descriptions = {}
|
|
508
|
-
try:
|
|
509
|
-
schema_primary_keys = self._get_schema_primary_keys(
|
|
510
|
-
connection, full_schema_name, **kw
|
|
511
|
-
)
|
|
512
|
-
schema_name = self.denormalize_name(schema)
|
|
513
503
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
ic.numeric_precision,
|
|
523
|
-
ic.numeric_scale,
|
|
524
|
-
ic.is_nullable,
|
|
525
|
-
ic.column_default,
|
|
526
|
-
ic.is_identity,
|
|
527
|
-
ic.comment,
|
|
528
|
-
ic.identity_start,
|
|
529
|
-
ic.identity_increment
|
|
530
|
-
FROM information_schema.columns ic
|
|
531
|
-
WHERE ic.table_schema=:table_schema
|
|
532
|
-
ORDER BY ic.ordinal_position"""
|
|
533
|
-
),
|
|
534
|
-
{"table_schema": schema_name},
|
|
535
|
-
)
|
|
536
|
-
except sa_exc.ProgrammingError as pe:
|
|
537
|
-
if pe.orig.errno == 90030:
|
|
538
|
-
# This means that there are too many tables in the schema, we need to go more granular
|
|
539
|
-
return None # None triggers _get_table_columns while staying cacheable
|
|
540
|
-
raise
|
|
504
|
+
schema_primary_keys = self._get_schema_primary_keys(
|
|
505
|
+
connection, full_schema_name, **kw
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
structured_type_info_manager = _StructuredTypeInfoManager(
|
|
509
|
+
connection, self.name_utils, default_schema
|
|
510
|
+
)
|
|
511
|
+
|
|
541
512
|
for (
|
|
542
513
|
table_name,
|
|
543
514
|
column_name,
|
|
@@ -572,25 +543,11 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
572
543
|
elif issubclass(col_type, (sqltypes.String, sqltypes.BINARY)):
|
|
573
544
|
col_type_kw["length"] = character_maximum_length
|
|
574
545
|
elif issubclass(col_type, StructuredType):
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
)
|
|
581
|
-
)
|
|
582
|
-
)
|
|
583
|
-
|
|
584
|
-
if (
|
|
585
|
-
(schema_name, table_name) in full_columns_descriptions
|
|
586
|
-
and column_name
|
|
587
|
-
in full_columns_descriptions[(schema_name, table_name)]
|
|
588
|
-
):
|
|
589
|
-
ans[table_name].append(
|
|
590
|
-
full_columns_descriptions[(schema_name, table_name)][
|
|
591
|
-
column_name
|
|
592
|
-
]
|
|
593
|
-
)
|
|
546
|
+
column_info = structured_type_info_manager.get_column_info(
|
|
547
|
+
schema_name, table_name, column_name, **kw
|
|
548
|
+
)
|
|
549
|
+
if column_info:
|
|
550
|
+
ans[table_name].append(column_info)
|
|
594
551
|
continue
|
|
595
552
|
else:
|
|
596
553
|
col_type = NullType
|
|
@@ -628,72 +585,6 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
628
585
|
}
|
|
629
586
|
return ans
|
|
630
587
|
|
|
631
|
-
@reflection.cache
|
|
632
|
-
def _get_table_columns(self, connection, table_name, schema=None, **kw):
|
|
633
|
-
"""Get all columns in a table in a schema"""
|
|
634
|
-
ans = []
|
|
635
|
-
current_database, default_schema = self._current_database_schema(
|
|
636
|
-
connection, **kw
|
|
637
|
-
)
|
|
638
|
-
schema = schema if schema else default_schema
|
|
639
|
-
table_schema = self.denormalize_name(schema)
|
|
640
|
-
table_name = self.denormalize_name(table_name)
|
|
641
|
-
result = connection.execute(
|
|
642
|
-
text(
|
|
643
|
-
"DESC /* sqlalchemy:_get_schema_columns */"
|
|
644
|
-
f" TABLE {table_schema}.{table_name} TYPE = COLUMNS"
|
|
645
|
-
)
|
|
646
|
-
)
|
|
647
|
-
for desc_data in result:
|
|
648
|
-
column_name = desc_data[0]
|
|
649
|
-
coltype = desc_data[1]
|
|
650
|
-
is_nullable = desc_data[3]
|
|
651
|
-
column_default = desc_data[4]
|
|
652
|
-
primary_key = desc_data[5]
|
|
653
|
-
comment = desc_data[9]
|
|
654
|
-
|
|
655
|
-
column_name = self.normalize_name(column_name)
|
|
656
|
-
if column_name.startswith("sys_clustering_column"):
|
|
657
|
-
continue # ignoring clustering column
|
|
658
|
-
type_instance = parse_type(coltype)
|
|
659
|
-
if isinstance(type_instance, NullType):
|
|
660
|
-
sa_util.warn(
|
|
661
|
-
f"Did not recognize type '{coltype}' of column '{column_name}'"
|
|
662
|
-
)
|
|
663
|
-
|
|
664
|
-
identity = None
|
|
665
|
-
match = re.match(
|
|
666
|
-
r"IDENTITY START (?P<start>\d+) INCREMENT (?P<increment>\d+) (?P<order_type>ORDER|NOORDER)",
|
|
667
|
-
column_default if column_default else "",
|
|
668
|
-
)
|
|
669
|
-
if match:
|
|
670
|
-
identity = {
|
|
671
|
-
"start": int(match.group("start")),
|
|
672
|
-
"increment": int(match.group("increment")),
|
|
673
|
-
"order_type": match.group("order_type"),
|
|
674
|
-
}
|
|
675
|
-
is_identity = identity is not None
|
|
676
|
-
|
|
677
|
-
ans.append(
|
|
678
|
-
{
|
|
679
|
-
"name": column_name,
|
|
680
|
-
"type": type_instance,
|
|
681
|
-
"nullable": is_nullable == "Y",
|
|
682
|
-
"default": None if is_identity else column_default,
|
|
683
|
-
"autoincrement": is_identity,
|
|
684
|
-
"comment": comment if comment != "" else None,
|
|
685
|
-
"primary_key": primary_key == "Y",
|
|
686
|
-
}
|
|
687
|
-
)
|
|
688
|
-
|
|
689
|
-
if is_identity:
|
|
690
|
-
ans[-1]["identity"] = identity
|
|
691
|
-
|
|
692
|
-
# If we didn't find any columns for the table, the table doesn't exist.
|
|
693
|
-
if len(ans) == 0:
|
|
694
|
-
raise sa_exc.NoSuchTableError()
|
|
695
|
-
return ans
|
|
696
|
-
|
|
697
588
|
def get_columns(self, connection, table_name, schema=None, **kw):
|
|
698
589
|
"""
|
|
699
590
|
Gets all column info given the table info
|
|
@@ -704,8 +595,11 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
704
595
|
|
|
705
596
|
schema_columns = self._get_schema_columns(connection, schema, **kw)
|
|
706
597
|
if schema_columns is None:
|
|
598
|
+
column_info_manager = _StructuredTypeInfoManager(
|
|
599
|
+
connection, self.name_utils, self.default_schema_name
|
|
600
|
+
)
|
|
707
601
|
# Too many results, fall back to only query about single table
|
|
708
|
-
return
|
|
602
|
+
return column_info_manager.get_table_columns(table_name, schema)
|
|
709
603
|
normalized_table_name = self.normalize_name(table_name)
|
|
710
604
|
if normalized_table_name not in schema_columns:
|
|
711
605
|
raise sa_exc.NoSuchTableError()
|
|
@@ -719,6 +613,37 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
719
613
|
prefixes_found.append(valid_prefix.name)
|
|
720
614
|
return prefixes_found
|
|
721
615
|
|
|
616
|
+
@reflection.cache
|
|
617
|
+
def _query_all_columns_info(self, connection, schema_name, **kw):
|
|
618
|
+
try:
|
|
619
|
+
return connection.execute(
|
|
620
|
+
text(
|
|
621
|
+
"""
|
|
622
|
+
SELECT /* sqlalchemy:_get_schema_columns */
|
|
623
|
+
ic.table_name,
|
|
624
|
+
ic.column_name,
|
|
625
|
+
ic.data_type,
|
|
626
|
+
ic.character_maximum_length,
|
|
627
|
+
ic.numeric_precision,
|
|
628
|
+
ic.numeric_scale,
|
|
629
|
+
ic.is_nullable,
|
|
630
|
+
ic.column_default,
|
|
631
|
+
ic.is_identity,
|
|
632
|
+
ic.comment,
|
|
633
|
+
ic.identity_start,
|
|
634
|
+
ic.identity_increment
|
|
635
|
+
FROM information_schema.columns ic
|
|
636
|
+
WHERE ic.table_schema=:table_schema
|
|
637
|
+
ORDER BY ic.ordinal_position"""
|
|
638
|
+
),
|
|
639
|
+
{"table_schema": schema_name},
|
|
640
|
+
)
|
|
641
|
+
except sa_exc.ProgrammingError as pe:
|
|
642
|
+
if pe.orig.errno == 90030:
|
|
643
|
+
# This means that there are too many tables in the schema, we need to go more granular
|
|
644
|
+
return None # None triggers get_table_columns while staying cacheable
|
|
645
|
+
raise
|
|
646
|
+
|
|
722
647
|
@reflection.cache
|
|
723
648
|
def _get_schema_tables_info(self, connection, schema=None, **kw):
|
|
724
649
|
"""
|
|
@@ -982,18 +907,53 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
982
907
|
return self._value_or_default(data, table_name, schema)
|
|
983
908
|
|
|
984
909
|
def connect(self, *cargs, **cparams):
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
910
|
+
if _ENABLE_SQLALCHEMY_AS_APPLICATION_NAME:
|
|
911
|
+
cparams = _update_connection_application_name(**cparams)
|
|
912
|
+
|
|
913
|
+
connection = super().connect(*cargs, **cparams)
|
|
914
|
+
self._log_new_connection_event(connection)
|
|
915
|
+
|
|
916
|
+
return connection
|
|
917
|
+
|
|
918
|
+
def _log_new_connection_event(self, connection):
|
|
919
|
+
try:
|
|
920
|
+
snowflake_connection = cast(SnowflakeConnection, cast(object, connection))
|
|
921
|
+
snowflake_rest_client = SnowflakeRestful(
|
|
922
|
+
host=snowflake_connection.host,
|
|
923
|
+
port=snowflake_connection.port,
|
|
924
|
+
protocol="https",
|
|
925
|
+
connection=snowflake_connection,
|
|
926
|
+
)
|
|
927
|
+
snowflake_telemetry_client = TelemetryClient(rest=snowflake_rest_client)
|
|
928
|
+
|
|
929
|
+
telemetry_value = {
|
|
930
|
+
"SQLAlchemy": SQLALCHEMY_VERSION,
|
|
931
|
+
}
|
|
932
|
+
try:
|
|
933
|
+
from pandas import __version__ as PANDAS_VERSION
|
|
934
|
+
|
|
935
|
+
telemetry_value["pandas"] = PANDAS_VERSION
|
|
936
|
+
except ImportError:
|
|
937
|
+
pass
|
|
938
|
+
|
|
939
|
+
snowflake_telemetry_client.add_log_to_batch(
|
|
940
|
+
TelemetryData.from_telemetry_data_dict(
|
|
941
|
+
from_dict={
|
|
942
|
+
TelemetryField.KEY_TYPE.value: TelemetryEvents.NEW_CONNECTION.value,
|
|
943
|
+
TelemetryField.KEY_VALUE.value: str(telemetry_value),
|
|
944
|
+
},
|
|
945
|
+
timestamp=int(time_in_seconds() * 1000),
|
|
946
|
+
connection=snowflake_connection,
|
|
947
|
+
)
|
|
948
|
+
)
|
|
949
|
+
snowflake_telemetry_client.send_batch()
|
|
950
|
+
except Exception as e:
|
|
951
|
+
logger.debug(
|
|
952
|
+
"Failed to send telemetry data for %s event: %s: %s",
|
|
953
|
+
TelemetryEvents.NEW_CONNECTION.value,
|
|
954
|
+
type(e).__name__,
|
|
955
|
+
str(e),
|
|
993
956
|
)
|
|
994
|
-
if _ENABLE_SQLALCHEMY_AS_APPLICATION_NAME
|
|
995
|
-
else super().connect(*cargs, **cparams)
|
|
996
|
-
)
|
|
997
957
|
|
|
998
958
|
|
|
999
959
|
@sa_vnt.listens_for(Table, "before_create")
|