diff --git a/DESCRIPTION.md b/DESCRIPTION.md index c178a593..77b3f6aa 100644 --- a/DESCRIPTION.md +++ b/DESCRIPTION.md @@ -10,7 +10,10 @@ Source code is also available at: # Release Notes - (Unreleased) - - Fix flag `div_is_floordiv` to `False` in `SnowflakeDialect` to avoid division wrong CAST when trying to do a integer division when using `/` + - Added `force_div_is_floordiv` flag to override `div_is_floordiv` new default value `False` in `SnowflakeDialect`. + - With the flag in `False`, the `/` division operator will be treated as a float division and `//` as a floor division. + - This flag is added to maintain backward compatibility with the previous behavior of Snowflake Dialect division. + - This flag will be removed in the future and Snowflake Dialect will use `div_is_floor_div` as `False`. - v1.7.0(November 21, 2024) diff --git a/pyproject.toml b/pyproject.toml index 84e64faf..8a011d1b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,6 +84,11 @@ extra-dependencies = ["SQLAlchemy>=1.4.19,<2.0.0"] features = ["development", "pandas"] python = "3.8" +[tool.hatch.envs.sa14.scripts] +test-dialect = "pytest --ignore_v20_test -ra -vvv --tb=short --cov snowflake.sqlalchemy --cov-append --junitxml ./junit.xml --ignore=tests/sqlalchemy_test_suite tests/" +test-dialect-compatibility = "pytest --ignore_v20_test -ra -vvv --tb=short --cov snowflake.sqlalchemy --cov-append --junitxml ./junit.xml tests/sqlalchemy_test_suite" +test-dialect-aws = "pytest --ignore_v20_test -m \"aws\" -ra -vvv --tb=short --cov snowflake.sqlalchemy --cov-append --junitxml ./junit.xml --ignore=tests/sqlalchemy_test_suite tests/" + [tool.hatch.envs.default.env-vars] COVERAGE_FILE = "coverage.xml" SQLACHEMY_WARN_20 = "1" @@ -131,4 +136,5 @@ markers = [ "requires_external_volume: tests that needs a external volume to be executed", "external: tests that could but should only run on our external CI", "feature_max_lob_size: tests that could but should only run on our external CI", + "feature_v2: tests that could but should only run on SqlAlchemy v20", ] diff --git a/src/snowflake/sqlalchemy/snowdialect.py b/src/snowflake/sqlalchemy/snowdialect.py index 6f17e323..d24d0e0b 100644 --- a/src/snowflake/sqlalchemy/snowdialect.py +++ b/src/snowflake/sqlalchemy/snowdialect.py @@ -140,6 +140,18 @@ class SnowflakeDialect(default.DefaultDialect): supports_identity_columns = True + def __init__( + self, + force_div_is_floordiv_behavior: bool = True, + **kwargs, + ): + default.DefaultDialect.__init__(self, **kwargs) + self.div_is_floordiv = force_div_is_floordiv_behavior + + def initialize(self, connection): + super().initialize(connection) + self.div_is_floordiv = self.force_div_is_floordiv + @classmethod def dbapi(cls): return cls.import_dbapi() diff --git a/tests/conftest.py b/tests/conftest.py index a91521b9..8cd3beca 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -46,6 +46,26 @@ TEST_SCHEMA = f"sqlalchemy_tests_{str(uuid.uuid4()).replace('-', '_')}" +def pytest_addoption(parser): + parser.addoption( + "--ignore_v20_test", + action="store_true", + default=False, + help="skip sqlalchemy 2.0 exclusive tests", + ) + + +def pytest_collection_modifyitems(config, items): + if config.getoption("--ignore_v20_test"): + # --ignore_v20_test given in cli: skip sqlalchemy 2.0 tests + skip_feature_v2 = pytest.mark.skip( + reason="need remove --ignore_v20_test option to run" + ) + for item in items: + if "feature_v20" in item.keywords: + item.add_marker(skip_feature_v2) + + @pytest.fixture(scope="session") def on_travis(): return os.getenv("TRAVIS", "").lower() == "true" diff --git a/tests/test_compiler.py b/tests/test_compiler.py index f3de088c..b79fd944 100644 --- a/tests/test_compiler.py +++ b/tests/test_compiler.py @@ -2,12 +2,14 @@ # Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. # +import pytest from sqlalchemy import Integer, String, and_, func, select from sqlalchemy.schema import DropColumnComment, DropTableComment from sqlalchemy.sql import column, quoted_name, table from sqlalchemy.testing.assertions import AssertsCompiledSQL from snowflake.sqlalchemy import snowdialect +from src.snowflake.sqlalchemy.snowdialect import SnowflakeDialect table1 = table( "table1", column("id", Integer), column("name", String), column("value", Integer) @@ -122,31 +124,51 @@ def test_outer_lateral_join(): ) -def test_division_operator(): +def test_division_operator_with_force_div_is_floordiv_behavior_false(): col1 = column("col1") col2 = column("col2") stmt = col1 / col2 - assert str(stmt.compile(dialect=snowdialect.dialect())) == "col1 / col2" + assert ( + str( + stmt.compile(dialect=SnowflakeDialect(force_div_is_floordiv_behavior=False)) + ) + == "col1 / col2" + ) -def test_division_operator_with_denominator_expr(): +def test_division_operator_with_denominator_expr_force_div_is_floordiv_behavior_false(): col1 = column("col1") col2 = column("col2") stmt = col1 / func.sqrt(col2) - assert str(stmt.compile(dialect=snowdialect.dialect())) == "col1 / sqrt(col2)" + assert ( + str( + stmt.compile(dialect=SnowflakeDialect(force_div_is_floordiv_behavior=False)) + ) + == "col1 / sqrt(col2)" + ) -def test_floor_division_operator(): +@pytest.mark.feature_v20 +def test_floor_division_operator_force_div_is_floordiv_behavior_false(): col1 = column("col1") col2 = column("col2") stmt = col1 // col2 - assert str(stmt.compile(dialect=snowdialect.dialect())) == "FLOOR(col1 / col2)" + assert ( + str( + stmt.compile(dialect=SnowflakeDialect(force_div_is_floordiv_behavior=False)) + ) + == "FLOOR(col1 / col2)" + ) -def test_floor_division_operator_with_denominator_expr(): +@pytest.mark.feature_v20 +def test_floor_division_operator_with_denominator_expr_force_div_is_floordiv_behavior_false(): col1 = column("col1") col2 = column("col2") stmt = col1 // func.sqrt(col2) assert ( - str(stmt.compile(dialect=snowdialect.dialect())) == "FLOOR(col1 / sqrt(col2))" + str( + stmt.compile(dialect=SnowflakeDialect(force_div_is_floordiv_behavior=False)) + ) + == "FLOOR(col1 / sqrt(col2))" )