From: Nick Crews Date: Tue, 9 May 2023 18:51:42 +0000 (-0800) Subject: fixup: try_cast v2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8161ac24008f75f5ecc6f8d69795688671c8df06;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git fixup: try_cast v2 - update changelog entry - don't deprecate accessing try_cast from mssql. Revert my test change to be consistent. - re-export try_cast from the root place in mssql/__init__.py - Move visit_try_cast() to base compiler Still not sure exactly about where the test should live, since it only should work for mssql, so I'm not sure how to add it to the generic tests but specify it to run only for mssql. --- diff --git a/doc/build/changelog/unreleased_20/9752.rst b/doc/build/changelog/unreleased_20/9752.rst index 519c75f070..0c095fcedc 100644 --- a/doc/build/changelog/unreleased_20/9752.rst +++ b/doc/build/changelog/unreleased_20/9752.rst @@ -3,12 +3,12 @@ :tickets: 9752 - Added new :func:`.try_cast` factory function and corresponding - :class:`.TryCast` SQL Element, which implements a cast where + Generalized the MSSQL :func:`.try_cast` function to any dialect. + This implements a cast where un-castable values are returned as NULL, instead of raising an error. + Now this function can be taken advantage of by third party dialects + that also support this function, such as - This is currently implemented as ``TRY_CAST`` in Microsoft SQL Server. - and could be implemented in other backends: - * ``SAFE_CAST`` in Google BigQuery, and - * ``TRY_CAST`` in DuckDB. - * ``TRY_CAST`` in Snowflake. + * ``SAFE_CAST`` in Google BigQuery + * ``TRY_CAST`` in DuckDB + * ``TRY_CAST`` in Snowflake diff --git a/lib/sqlalchemy/dialects/mssql/__init__.py b/lib/sqlalchemy/dialects/mssql/__init__.py index 3bbfad344c..997896a504 100644 --- a/lib/sqlalchemy/dialects/mssql/__init__.py +++ b/lib/sqlalchemy/dialects/mssql/__init__.py @@ -6,7 +6,7 @@ # the MIT License: https://www.opensource.org/licenses/mit-license.php # mypy: ignore-errors - +from ...sql._elements_constructors import try_cast from . import base # noqa from . import pymssql # noqa from . import pyodbc # noqa @@ -39,7 +39,6 @@ from .base import TEXT from .base import TIME from .base import TIMESTAMP from .base import TINYINT -from .base import try_cast from .base import UNIQUEIDENTIFIER from .base import VARBINARY from .base import VARCHAR diff --git a/lib/sqlalchemy/dialects/mssql/base.py b/lib/sqlalchemy/dialects/mssql/base.py index e37f92af33..6c019d2907 100644 --- a/lib/sqlalchemy/dialects/mssql/base.py +++ b/lib/sqlalchemy/dialects/mssql/base.py @@ -939,6 +939,7 @@ from ...sql import quoted_name from ...sql import roles from ...sql import sqltypes from ...sql import util as sql_util +from ...sql._elements_constructors import try_cast from ...sql._typing import is_sql_compiler from ...sql.compiler import InsertmanyvaluesSentinelOpts from ...sql.elements import TryCast @@ -1604,15 +1605,6 @@ class SQL_VARIANT(sqltypes.TypeEngine): __visit_name__ = "SQL_VARIANT" -def try_cast(*arg, **kw): - util.warn_deprecated( - "`sqlalchemy.dialects.mssql.base.try_cast` is deprecated. " - "Use directly from sqlalchemy instead, i.e. `sa.try_cast(...)`", - "2.1", - ) - return TryCast(*arg, **kw) - - # old names. MSDateTime = _MSDateTime MSDate = _MSDate @@ -2153,12 +2145,6 @@ class MSSQLCompiler(compiler.SQLCompiler): else: return "" - def visit_try_cast(self, element, **kw): - return "TRY_CAST (%s AS %s)" % ( - self.process(element.clause, **kw), - self.process(element.typeclause, **kw), - ) - def translate_select_structure(self, select_stmt, **kwargs): """Look for ``LIMIT`` and OFFSET in a select statement, and if so tries to wrap it in a subquery with ``row_number()`` criterion. diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 554a841120..4299377044 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -2777,6 +2777,12 @@ class SQLCompiler(Compiled): cast.typeclause._compiler_dispatch(self, **kwargs), ) + def visit_try_cast(self, cast, **kwargs): + return "TRY_CAST(%s AS %s)" % ( + cast.clause._compiler_dispatch(self, **kwargs), + cast.typeclause._compiler_dispatch(self, **kwargs), + ) + def _format_frame_clause(self, range_, **kw): return "%s AND %s" % ( diff --git a/test/dialect/mssql/test_compiler.py b/test/dialect/mssql/test_compiler.py index 3dbd436d3c..b4b6862c24 100644 --- a/test/dialect/mssql/test_compiler.py +++ b/test/dialect/mssql/test_compiler.py @@ -26,7 +26,6 @@ from sqlalchemy import UniqueConstraint from sqlalchemy import update from sqlalchemy.dialects import mssql from sqlalchemy.dialects.mssql import base as mssql_base -from sqlalchemy.dialects.mssql.base import try_cast from sqlalchemy.sql import column from sqlalchemy.sql import quoted_name from sqlalchemy.sql import table @@ -1478,15 +1477,10 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL): metadata = MetaData() t1 = Table("t1", metadata, Column("id", Integer, primary_key=True)) - def call(func): - self.assert_compile( - select(func(t1.c.id, Integer)), - "SELECT TRY_CAST (t1.id AS INTEGER) AS id FROM t1", - ) - - with testing.expect_deprecated(".*try_cast.*"): - call(mssql_base.try_cast) - call(try_cast) + self.assert_compile( + select(try_cast(t1.c.id, Integer)), + "SELECT TRY_CAST (t1.id AS INTEGER) AS id FROM t1", + ) @testing.combinations( ("no_persisted", "", "ignore"),