snowflake-sqlalchemy 1.7.5__tar.gz → 1.7.7__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.5 → snowflake_sqlalchemy-1.7.7}/DESCRIPTION.md +6 -1
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/PKG-INFO +1 -1
- snowflake_sqlalchemy-1.7.7/src/snowflake/sqlalchemy/name_utils.py +36 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/parser/custom_type_parser.py +3 -3
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/snowdialect.py +65 -153
- snowflake_sqlalchemy-1.7.7/src/snowflake/sqlalchemy/structured_type_info_manager.py +142 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/version.py +1 -1
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/conftest.py +4 -1
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/sqlalchemy_test_suite/test_suite_20.py +71 -11
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_core.py +37 -20
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_structured_datatypes.py +53 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_transactions.py +3 -2
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/.gitignore +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/.gitmodules +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/.pre-commit-config.yaml +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/LICENSE.txt +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/MANIFEST.in +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/README.md +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/ci/build.sh +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/ci/build_docker.sh +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/ci/docker/sqlalchemy_build/Dockerfile +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/ci/docker/sqlalchemy_build/scripts/entrypoint.sh +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/ci/set_base_image.sh +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/ci/test.sh +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/ci/test_docker.sh +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/ci/test_linux.sh +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/license_header.txt +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/pyproject.toml +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/setup.cfg +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/snyk/requirements.txt +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/snyk/requiremtnts.txt +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/snyk/update_requirements.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/_constants.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/base.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/compat.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/custom_commands.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/custom_types.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/exc.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/functions.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/provision.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/requirements.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/sql/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/sql/custom_schema/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/sql/custom_schema/clustered_table.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/sql/custom_schema/custom_table_base.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/sql/custom_schema/custom_table_prefix.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/sql/custom_schema/dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/sql/custom_schema/hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/sql/custom_schema/iceberg_table.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/sql/custom_schema/options/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/sql/custom_schema/options/as_query_option.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/sql/custom_schema/options/cluster_by_option.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/sql/custom_schema/options/identifier_option.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/sql/custom_schema/options/invalid_table_option.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/sql/custom_schema/options/keyword_option.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/sql/custom_schema/options/keywords.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/sql/custom_schema/options/literal_option.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/sql/custom_schema/options/table_option.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/sql/custom_schema/options/target_lag_option.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/sql/custom_schema/snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/sql/custom_schema/table_from_query.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/util.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tested_requirements/requirements_310.reqs +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tested_requirements/requirements_37.reqs +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tested_requirements/requirements_38.reqs +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tested_requirements/requirements_39.reqs +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/README.rst +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/__snapshots__/test_compile_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/__snapshots__/test_core.ambr +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/__snapshots__/test_orm.ambr +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/__snapshots__/test_reflect_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/__snapshots__/test_structured_datatypes.ambr +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/__snapshots__/test_unit_structured_types.ambr +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/__snapshots__/test_compile_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/__snapshots__/test_compile_hybrid_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/__snapshots__/test_compile_iceberg_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/__snapshots__/test_compile_snowflake_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/__snapshots__/test_create_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/__snapshots__/test_create_hybrid_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/__snapshots__/test_create_iceberg_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/__snapshots__/test_create_snowflake_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/__snapshots__/test_generic_options.ambr +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/__snapshots__/test_reflect_hybrid_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/__snapshots__/test_reflect_snowflake_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/test_compile_dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/test_compile_hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/test_compile_iceberg_table.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/test_compile_snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/test_create_dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/test_create_hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/test_create_iceberg_table.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/test_create_snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/test_generic_options.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/test_reflect_dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/test_reflect_hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/custom_tables/test_reflect_snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/data/users.txt +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/sqlalchemy_test_suite/README.md +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/sqlalchemy_test_suite/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/sqlalchemy_test_suite/conftest.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/sqlalchemy_test_suite/test_suite.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_compiler.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_copy.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_create.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_custom_functions.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_custom_types.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_geography.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_geometry.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_imports.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_index_reflection.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_multivalues_insert.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_orm.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_pandas.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_qmark.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_quote.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_quote_identifiers.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_semi_structured_datatypes.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_sequence.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_timestamp.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_unit_core.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_unit_cte.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_unit_structured_types.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_unit_types.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/test_unit_url.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tests/util.py +0 -0
- {snowflake_sqlalchemy-1.7.5 → snowflake_sqlalchemy-1.7.7}/tox.ini +0 -0
|
@@ -7,9 +7,14 @@ Snowflake Documentation is available at:
|
|
|
7
7
|
Source code is also available at:
|
|
8
8
|
<https://github.com/snowflakedb/snowflake-sqlalchemy>
|
|
9
9
|
# Unreleased Notes
|
|
10
|
-
- Compiling Merge and Copy Into
|
|
11
10
|
|
|
12
11
|
# Release Notes
|
|
12
|
+
- v1.7.7(September 3, 2025)
|
|
13
|
+
- Fix exception for structured type columns dropped while collecting meetadata
|
|
14
|
+
|
|
15
|
+
- v1.7.6(July 10, 2025)
|
|
16
|
+
- Fix get_multi_indexes issue, wrong assign of returned indexes when processing multiple indexes in a table
|
|
17
|
+
|
|
13
18
|
- v1.7.5(June 20, 2025)
|
|
14
19
|
- Fix compilation of Merge and Copy Into was not working
|
|
15
20
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: snowflake-sqlalchemy
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.7
|
|
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
|
|
@@ -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.5 → snowflake_sqlalchemy-1.7.7}/src/snowflake/sqlalchemy/snowdialect.py
RENAMED
|
@@ -2,7 +2,6 @@
|
|
|
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
|
|
@@ -16,7 +15,6 @@ from sqlalchemy import util as sa_util
|
|
|
16
15
|
from sqlalchemy.engine import URL, default, reflection
|
|
17
16
|
from sqlalchemy.schema import Table
|
|
18
17
|
from sqlalchemy.sql import text
|
|
19
|
-
from sqlalchemy.sql.elements import quoted_name
|
|
20
18
|
from sqlalchemy.sql.sqltypes import NullType
|
|
21
19
|
from sqlalchemy.types import FLOAT, Date, DateTime, Float, Time
|
|
22
20
|
|
|
@@ -24,6 +22,8 @@ from snowflake.connector import errors as sf_errors
|
|
|
24
22
|
from snowflake.connector.connection import DEFAULT_CONFIGURATION
|
|
25
23
|
from snowflake.connector.constants import UTF8
|
|
26
24
|
from snowflake.sqlalchemy.compat import returns_unicode
|
|
25
|
+
from snowflake.sqlalchemy.name_utils import _NameUtils
|
|
26
|
+
from snowflake.sqlalchemy.structured_type_info_manager import _StructuredTypeInfoManager
|
|
27
27
|
|
|
28
28
|
from ._constants import DIALECT_NAME
|
|
29
29
|
from .base import (
|
|
@@ -42,7 +42,7 @@ from .custom_types import (
|
|
|
42
42
|
)
|
|
43
43
|
from .parser.custom_type_parser import * # noqa
|
|
44
44
|
from .parser.custom_type_parser import _CUSTOM_DECIMAL # noqa
|
|
45
|
-
from .parser.custom_type_parser import ischema_names, parse_index_columns
|
|
45
|
+
from .parser.custom_type_parser import ischema_names, parse_index_columns
|
|
46
46
|
from .sql.custom_schema.custom_table_prefix import CustomTablePrefix
|
|
47
47
|
from .util import (
|
|
48
48
|
_update_connection_application_name,
|
|
@@ -157,6 +157,7 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
157
157
|
super().__init__(isolation_level=isolation_level, **kwargs)
|
|
158
158
|
self.force_div_is_floordiv = force_div_is_floordiv
|
|
159
159
|
self.div_is_floordiv = force_div_is_floordiv
|
|
160
|
+
self.name_utils = _NameUtils(self.identifier_preparer)
|
|
160
161
|
|
|
161
162
|
def initialize(self, connection):
|
|
162
163
|
super().initialize(connection)
|
|
@@ -282,29 +283,10 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
282
283
|
raise
|
|
283
284
|
|
|
284
285
|
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
|
|
286
|
+
return self.name_utils.normalize_name(name)
|
|
297
287
|
|
|
298
288
|
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
|
|
289
|
+
return self.name_utils.denormalize_name(name)
|
|
308
290
|
|
|
309
291
|
def _denormalize_quote_join(self, *idents):
|
|
310
292
|
ip = self.identifier_preparer
|
|
@@ -491,53 +473,31 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
491
473
|
)
|
|
492
474
|
return foreign_key_map.get(table_name, [])
|
|
493
475
|
|
|
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
476
|
@reflection.cache
|
|
501
477
|
def _get_schema_columns(self, connection, schema, **kw):
|
|
502
478
|
"""Get all columns in the schema, if we hit 'Information schema query returned too much data' problem return
|
|
503
479
|
None, as it is cacheable and is an unexpected return type for this function"""
|
|
504
480
|
ans = {}
|
|
505
|
-
|
|
481
|
+
|
|
482
|
+
schema_name = self.denormalize_name(schema)
|
|
483
|
+
|
|
484
|
+
result = self._query_all_columns_info(connection, schema_name, **kw)
|
|
485
|
+
if result is None:
|
|
486
|
+
return None
|
|
487
|
+
|
|
488
|
+
current_database, default_schema = self._current_database_schema(
|
|
489
|
+
connection, **kw
|
|
490
|
+
)
|
|
506
491
|
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
492
|
|
|
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
|
|
493
|
+
schema_primary_keys = self._get_schema_primary_keys(
|
|
494
|
+
connection, full_schema_name, **kw
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
structured_type_info_manager = _StructuredTypeInfoManager(
|
|
498
|
+
connection, self.name_utils, default_schema
|
|
499
|
+
)
|
|
500
|
+
|
|
541
501
|
for (
|
|
542
502
|
table_name,
|
|
543
503
|
column_name,
|
|
@@ -572,25 +532,11 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
572
532
|
elif issubclass(col_type, (sqltypes.String, sqltypes.BINARY)):
|
|
573
533
|
col_type_kw["length"] = character_maximum_length
|
|
574
534
|
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
|
-
)
|
|
535
|
+
column_info = structured_type_info_manager.get_column_info(
|
|
536
|
+
schema_name, table_name, column_name, **kw
|
|
537
|
+
)
|
|
538
|
+
if column_info:
|
|
539
|
+
ans[table_name].append(column_info)
|
|
594
540
|
continue
|
|
595
541
|
else:
|
|
596
542
|
col_type = NullType
|
|
@@ -628,72 +574,6 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
628
574
|
}
|
|
629
575
|
return ans
|
|
630
576
|
|
|
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
577
|
def get_columns(self, connection, table_name, schema=None, **kw):
|
|
698
578
|
"""
|
|
699
579
|
Gets all column info given the table info
|
|
@@ -704,8 +584,11 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
704
584
|
|
|
705
585
|
schema_columns = self._get_schema_columns(connection, schema, **kw)
|
|
706
586
|
if schema_columns is None:
|
|
587
|
+
column_info_manager = _StructuredTypeInfoManager(
|
|
588
|
+
connection, self.name_utils, self.default_schema_name
|
|
589
|
+
)
|
|
707
590
|
# Too many results, fall back to only query about single table
|
|
708
|
-
return
|
|
591
|
+
return column_info_manager.get_table_columns(table_name, schema)
|
|
709
592
|
normalized_table_name = self.normalize_name(table_name)
|
|
710
593
|
if normalized_table_name not in schema_columns:
|
|
711
594
|
raise sa_exc.NoSuchTableError()
|
|
@@ -719,6 +602,37 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
719
602
|
prefixes_found.append(valid_prefix.name)
|
|
720
603
|
return prefixes_found
|
|
721
604
|
|
|
605
|
+
@reflection.cache
|
|
606
|
+
def _query_all_columns_info(self, connection, schema_name, **kw):
|
|
607
|
+
try:
|
|
608
|
+
return connection.execute(
|
|
609
|
+
text(
|
|
610
|
+
"""
|
|
611
|
+
SELECT /* sqlalchemy:_get_schema_columns */
|
|
612
|
+
ic.table_name,
|
|
613
|
+
ic.column_name,
|
|
614
|
+
ic.data_type,
|
|
615
|
+
ic.character_maximum_length,
|
|
616
|
+
ic.numeric_precision,
|
|
617
|
+
ic.numeric_scale,
|
|
618
|
+
ic.is_nullable,
|
|
619
|
+
ic.column_default,
|
|
620
|
+
ic.is_identity,
|
|
621
|
+
ic.comment,
|
|
622
|
+
ic.identity_start,
|
|
623
|
+
ic.identity_increment
|
|
624
|
+
FROM information_schema.columns ic
|
|
625
|
+
WHERE ic.table_schema=:table_schema
|
|
626
|
+
ORDER BY ic.ordinal_position"""
|
|
627
|
+
),
|
|
628
|
+
{"table_schema": schema_name},
|
|
629
|
+
)
|
|
630
|
+
except sa_exc.ProgrammingError as pe:
|
|
631
|
+
if pe.orig.errno == 90030:
|
|
632
|
+
# This means that there are too many tables in the schema, we need to go more granular
|
|
633
|
+
return None # None triggers get_table_columns while staying cacheable
|
|
634
|
+
raise
|
|
635
|
+
|
|
722
636
|
@reflection.cache
|
|
723
637
|
def _get_schema_tables_info(self, connection, schema=None, **kw):
|
|
724
638
|
"""
|
|
@@ -955,9 +869,7 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
955
869
|
}
|
|
956
870
|
|
|
957
871
|
if (schema, table_name) in indexes:
|
|
958
|
-
indexes[(schema, table_name)]
|
|
959
|
-
index
|
|
960
|
-
)
|
|
872
|
+
indexes[(schema, table_name)].append(index)
|
|
961
873
|
else:
|
|
962
874
|
indexes[(schema, table_name)] = [index]
|
|
963
875
|
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
from sqlalchemy import util as sa_util
|
|
7
|
+
from sqlalchemy.sql import text
|
|
8
|
+
|
|
9
|
+
from snowflake.sqlalchemy.name_utils import _NameUtils
|
|
10
|
+
from snowflake.sqlalchemy.parser.custom_type_parser import NullType, parse_type
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class _StructuredTypeInfoManager:
|
|
14
|
+
"""
|
|
15
|
+
Manager for handling structured type information in Snowflake tables.
|
|
16
|
+
This class is responsible for retrieving, caching, and providing
|
|
17
|
+
column information for structured types in Snowflake tables. It maintains
|
|
18
|
+
a cache of column descriptions to avoid repeated database queries.
|
|
19
|
+
Attributes:
|
|
20
|
+
connection: The database connection to use for queries
|
|
21
|
+
full_columns_descriptions (dict): Cache of column descriptions by schema and table
|
|
22
|
+
name_utils (_NameUtils): Utility for normalizing and denormalizing names
|
|
23
|
+
default_schema (str): The default schema to use when none is specified
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, connection, name_utils: _NameUtils, default_schema: str):
|
|
27
|
+
self.connection = connection
|
|
28
|
+
self.full_columns_descriptions = {}
|
|
29
|
+
self.name_utils = name_utils
|
|
30
|
+
self.default_schema = default_schema
|
|
31
|
+
|
|
32
|
+
def get_column_info(
|
|
33
|
+
self, schema_name: str, table_name: str, column_name: str, **kwargs
|
|
34
|
+
):
|
|
35
|
+
self._load_structured_type_info(schema_name, table_name)
|
|
36
|
+
if (
|
|
37
|
+
(schema_name, table_name) in self.full_columns_descriptions
|
|
38
|
+
and column_name in self.full_columns_descriptions[(schema_name, table_name)]
|
|
39
|
+
):
|
|
40
|
+
return self.full_columns_descriptions[(schema_name, table_name)][
|
|
41
|
+
column_name
|
|
42
|
+
]
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
def _load_structured_type_info(self, schema_name: str, table_name: str):
|
|
46
|
+
"""Get column information for a structured type"""
|
|
47
|
+
if (schema_name, table_name) not in self.full_columns_descriptions:
|
|
48
|
+
|
|
49
|
+
column_definitions = self.get_table_columns(table_name, schema_name)
|
|
50
|
+
if not column_definitions:
|
|
51
|
+
self.full_columns_descriptions[(schema_name, table_name)] = {}
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
self.full_columns_descriptions[(schema_name, table_name)] = (
|
|
55
|
+
self._table_columns_as_dict(column_definitions)
|
|
56
|
+
)
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
def _table_columns_as_dict(self, columns: list):
|
|
60
|
+
result = {}
|
|
61
|
+
for column in columns:
|
|
62
|
+
result[column["name"]] = column
|
|
63
|
+
return result
|
|
64
|
+
|
|
65
|
+
def get_table_columns(self, table_name: str, schema: str = None):
|
|
66
|
+
"""Get all columns in a table in a schema"""
|
|
67
|
+
ans = []
|
|
68
|
+
|
|
69
|
+
schema = schema if schema else self.default_schema
|
|
70
|
+
|
|
71
|
+
table_schema = self.name_utils.denormalize_name(schema)
|
|
72
|
+
table_name = self.name_utils.denormalize_name(table_name)
|
|
73
|
+
result = self._execute_desc(table_schema, table_name)
|
|
74
|
+
if not result:
|
|
75
|
+
return []
|
|
76
|
+
|
|
77
|
+
for desc_data in result:
|
|
78
|
+
column_name = desc_data[0]
|
|
79
|
+
coltype = desc_data[1]
|
|
80
|
+
is_nullable = desc_data[3]
|
|
81
|
+
column_default = desc_data[4]
|
|
82
|
+
primary_key = desc_data[5]
|
|
83
|
+
comment = desc_data[9]
|
|
84
|
+
|
|
85
|
+
column_name = self.name_utils.normalize_name(column_name)
|
|
86
|
+
if column_name.startswith("sys_clustering_column"):
|
|
87
|
+
continue # ignoring clustering column
|
|
88
|
+
type_instance = parse_type(coltype)
|
|
89
|
+
if isinstance(type_instance, NullType):
|
|
90
|
+
sa_util.warn(
|
|
91
|
+
f"Did not recognize type '{coltype}' of column '{column_name}'"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
identity = None
|
|
95
|
+
match = re.match(
|
|
96
|
+
r"IDENTITY START (?P<start>\d+) INCREMENT (?P<increment>\d+) (?P<order_type>ORDER|NOORDER)",
|
|
97
|
+
column_default if column_default else "",
|
|
98
|
+
)
|
|
99
|
+
if match:
|
|
100
|
+
identity = {
|
|
101
|
+
"start": int(match.group("start")),
|
|
102
|
+
"increment": int(match.group("increment")),
|
|
103
|
+
"order_type": match.group("order_type"),
|
|
104
|
+
}
|
|
105
|
+
is_identity = identity is not None
|
|
106
|
+
|
|
107
|
+
ans.append(
|
|
108
|
+
{
|
|
109
|
+
"name": column_name,
|
|
110
|
+
"type": type_instance,
|
|
111
|
+
"nullable": is_nullable == "Y",
|
|
112
|
+
"default": None if is_identity else column_default,
|
|
113
|
+
"autoincrement": is_identity,
|
|
114
|
+
"comment": comment if comment != "" else None,
|
|
115
|
+
"primary_key": primary_key == "Y",
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if is_identity:
|
|
120
|
+
ans[-1]["identity"] = identity
|
|
121
|
+
|
|
122
|
+
# If we didn't find any columns for the table, the table doesn't exist.
|
|
123
|
+
if len(ans) == 0:
|
|
124
|
+
return []
|
|
125
|
+
return ans
|
|
126
|
+
|
|
127
|
+
def _execute_desc(self, table_schema: str, table_name: str):
|
|
128
|
+
"""Execute a DESC command handling a possible exception.
|
|
129
|
+
Exception can be caused by another session dropping the table while
|
|
130
|
+
once this process has started"""
|
|
131
|
+
try:
|
|
132
|
+
return self.connection.execute(
|
|
133
|
+
text(
|
|
134
|
+
"DESC /* sqlalchemy:_get_schema_columns */"
|
|
135
|
+
f" TABLE {table_schema}.{table_name} TYPE = COLUMNS"
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
except Exception:
|
|
139
|
+
sa_util.warn(
|
|
140
|
+
f"Failed to reflect '{table_schema}' .'{table_name}' table using sqlalchemy:_get_schema_columns"
|
|
141
|
+
)
|
|
142
|
+
return None
|
|
@@ -232,8 +232,11 @@ def assert_text_in_buf():
|
|
|
232
232
|
def go(expected, occurrences=1):
|
|
233
233
|
assert buf.buffer
|
|
234
234
|
buflines = [rec.getMessage() for rec in buf.buffer]
|
|
235
|
+
ocurrences_found = 0
|
|
236
|
+
for line in buflines:
|
|
237
|
+
if line.find(expected) != -1:
|
|
238
|
+
ocurrences_found += 1
|
|
235
239
|
|
|
236
|
-
ocurrences_found = buflines.count(expected)
|
|
237
240
|
assert occurrences == ocurrences_found, (
|
|
238
241
|
f"Expected {occurrences} of {expected}, got {ocurrences_found} "
|
|
239
242
|
f"occurrences in {buflines}."
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
#
|
|
4
4
|
import pytest
|
|
5
5
|
from sqlalchemy import Integer, testing
|
|
6
|
+
from sqlalchemy import types as sql_types
|
|
6
7
|
from sqlalchemy.schema import Column, Sequence, Table
|
|
7
8
|
from sqlalchemy.testing import config
|
|
8
9
|
from sqlalchemy.testing.assertions import eq_
|
|
10
|
+
from sqlalchemy.testing.suite import BizarroCharacterTest as _BizarroCharacterTest
|
|
9
11
|
from sqlalchemy.testing.suite import (
|
|
10
|
-
|
|
12
|
+
ComponentReflectionTestExtra as _ComponentReflectionTestExtra,
|
|
11
13
|
)
|
|
12
14
|
from sqlalchemy.testing.suite import (
|
|
13
15
|
CompositeKeyReflectionTest as _CompositeKeyReflectionTest,
|
|
@@ -190,16 +192,74 @@ class CompositeKeyReflectionTest(_CompositeKeyReflectionTest):
|
|
|
190
192
|
super().test_pk_column_order()
|
|
191
193
|
|
|
192
194
|
|
|
193
|
-
class
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
195
|
+
class BizarroCharacterTest(_BizarroCharacterTest):
|
|
196
|
+
|
|
197
|
+
def column_names_without_id():
|
|
198
|
+
return testing.combinations(
|
|
199
|
+
("(3)",),
|
|
200
|
+
("col%p",),
|
|
201
|
+
("[brack]",),
|
|
202
|
+
argnames="columnname",
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
def column_names():
|
|
206
|
+
return testing.combinations(
|
|
207
|
+
("id",),
|
|
208
|
+
("(3)",),
|
|
209
|
+
("col%p",),
|
|
210
|
+
("[brack]",),
|
|
211
|
+
argnames="columnname",
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def table_names():
|
|
215
|
+
return testing.combinations(
|
|
216
|
+
("plain",),
|
|
217
|
+
("(2)",),
|
|
218
|
+
("[brackets]",),
|
|
219
|
+
argnames="tablename",
|
|
220
|
+
)
|
|
221
|
+
|
|
197
222
|
@testing.variation("use_composite", [True, False])
|
|
198
|
-
@
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
("[brackets]",),
|
|
202
|
-
argnames="tablename",
|
|
203
|
-
)
|
|
223
|
+
@column_names()
|
|
224
|
+
@table_names()
|
|
225
|
+
@testing.requires.foreign_key_constraint_reflection
|
|
204
226
|
def test_fk_ref(self, connection, metadata, use_composite, tablename, columnname):
|
|
205
227
|
super().test_fk_ref(connection, metadata, use_composite, tablename, columnname)
|
|
228
|
+
|
|
229
|
+
@column_names()
|
|
230
|
+
@table_names()
|
|
231
|
+
@testing.requires.identity_columns
|
|
232
|
+
def test_reflect_identity(self, tablename, columnname, connection, metadata):
|
|
233
|
+
super().test_reflect_identity(tablename, columnname, connection, metadata)
|
|
234
|
+
|
|
235
|
+
@column_names_without_id()
|
|
236
|
+
@table_names()
|
|
237
|
+
@testing.requires.comment_reflection
|
|
238
|
+
def test_reflect_comments(self, tablename, columnname, connection, metadata):
|
|
239
|
+
super().test_reflect_comments(tablename, columnname, connection, metadata)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class ComponentReflectionTestExtra(_ComponentReflectionTestExtra):
|
|
243
|
+
|
|
244
|
+
@testing.requires.table_reflection
|
|
245
|
+
@testing.combinations(
|
|
246
|
+
sql_types.String,
|
|
247
|
+
sql_types.VARCHAR,
|
|
248
|
+
sql_types.CHAR,
|
|
249
|
+
(sql_types.NVARCHAR, testing.requires.nvarchar_types),
|
|
250
|
+
(sql_types.NCHAR, testing.requires.nvarchar_types),
|
|
251
|
+
argnames="type_",
|
|
252
|
+
)
|
|
253
|
+
def test_string_length_reflection(self, connection, metadata, type_):
|
|
254
|
+
typ = self._type_round_trip(connection, metadata, type_(52))[0]
|
|
255
|
+
if issubclass(type_, sql_types.VARCHAR):
|
|
256
|
+
assert isinstance(typ, sql_types.VARCHAR)
|
|
257
|
+
elif issubclass(
|
|
258
|
+
type_, sql_types.CHAR
|
|
259
|
+
): # char is not supported then mapped to VARCHAR
|
|
260
|
+
assert isinstance(typ, sql_types.VARCHAR)
|
|
261
|
+
else:
|
|
262
|
+
assert isinstance(typ, sql_types.String)
|
|
263
|
+
|
|
264
|
+
eq_(typ.length, 52)
|
|
265
|
+
assert isinstance(typ.length, int)
|