From 3a4567a718c2f9f3d8b65acb81f0caefb4f1a2b5 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Thu, 24 Oct 2013 16:13:32 -0400 Subject: [PATCH] - add migration notes for [ticket:2838] - have TypeDecorator use process_bind_param for literal values if no process_literal_param is set --- doc/build/changelog/changelog_09.rst | 4 ++++ doc/build/changelog/migration_09.rst | 26 ++++++++++++++++++++++++++ lib/sqlalchemy/sql/type_api.py | 18 ++++++++++++++++++ test/sql/test_types.py | 16 ++++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst index 24bf6eb095..38ca115e49 100644 --- a/doc/build/changelog/changelog_09.rst +++ b/doc/build/changelog/changelog_09.rst @@ -62,6 +62,10 @@ :meth:`.TypeDecorator.process_literal_param` is added to allow wrapping of a native literal rendering method. + .. seealso:: + + :ref:`change_2838` + .. change:: :tags: feature, sql :tickets: 2716 diff --git a/doc/build/changelog/migration_09.rst b/doc/build/changelog/migration_09.rst index 4bc929f580..b7fa41ac76 100644 --- a/doc/build/changelog/migration_09.rst +++ b/doc/build/changelog/migration_09.rst @@ -372,6 +372,32 @@ to the column being assigned towards will no longer function in that way. :ticket:`2850` +.. _change_2838: + +The typing system now handles the task of rendering "literal bind" values +------------------------------------------------------------------------- + +A new method is added to :class:`.TypeEngine` :meth:`.TypeEngine.literal_processor` +as well as :meth:`.TypeDecorator.process_literal_param` for :class:`.TypeDecorator` +which take on the task of rendering so-called "inline literal paramters" - parameters +that normally render as "bound" values, but are instead being rendered inline +into the SQL statement due to the compiler configuration. This feature is used +when generating DDL for constructs such as :class:`.CheckConstraint`, as well +as by Alembic when using constructs such as ``op.inline_literal()``. Previously, +a simple "isinstance" check checked for a few basic types, and the "bind processor" +was used unconditionally, leading to such issues as strings being encoded into utf-8 +prematurely. + +Custom types written with :class:`.TypeDecorator` should continue to work in +"inline literal" scenarios, as the :meth:`.TypeDecorator.process_literal_param` +falls back to :meth:`.TypeDecorator.process_bind_param` by default, as these methods +usually handle a data manipulation, not as much how the data is presented to the +database. :meth:`.TypeDecorator.process_literal_param` can be specified to +specifically produce a string representing how a value should be rendered +into an inline DDL statement. + +:ticket:`2838` + .. _change_2812: Schema identifiers now carry along their own quoting information diff --git a/lib/sqlalchemy/sql/type_api.py b/lib/sqlalchemy/sql/type_api.py index 698e17472b..5d81e4a0cb 100644 --- a/lib/sqlalchemy/sql/type_api.py +++ b/lib/sqlalchemy/sql/type_api.py @@ -793,11 +793,29 @@ class TypeDecorator(TypeEngine): Subclasses here will typically override :meth:`.TypeDecorator.process_literal_param` instead of this method directly. + By default, this method makes use of :meth:`.TypeDecorator.process_bind_param` + if that method is implemented, where :meth:`.TypeDecorator.process_literal_param` + is not. The rationale here is that :class:`.TypeDecorator` typically deals + with Python conversions of data that are above the layer of database + presentation. With the value converted by :meth:`.TypeDecorator.process_bind_param`, + the underlying type will then handle whether it needs to be presented to the + DBAPI as a bound parameter or to the database as an inline SQL value. + .. versionadded:: 0.9.0 """ if self._has_literal_processor: process_param = self.process_literal_param + elif self._has_bind_processor: + # the bind processor should normally be OK + # for TypeDecorator since it isn't doing DB-level + # handling, the handling here won't be different for bound vs. + # literals. + process_param = self.process_bind_param + else: + process_param = None + + if process_param: impl_processor = self.impl.literal_processor(dialect) if impl_processor: def process(value): diff --git a/test/sql/test_types.py b/test/sql/test_types.py index 30a00ca564..7fc5dc35c5 100644 --- a/test/sql/test_types.py +++ b/test/sql/test_types.py @@ -287,6 +287,22 @@ class UserDefinedTest(fixtures.TablesTest, AssertsCompiledSQL): literal_binds=True ) + def test_typedecorator_literal_render_fallback_bound(self): + # fall back to process_bind_param for literal + # value rendering. + class MyType(types.TypeDecorator): + impl = String + + def process_bind_param(self, value, dialect): + return "HI->%s<-THERE" % value + + self.assert_compile( + select([literal("test", MyType)]), + "SELECT 'HI->test<-THERE' AS anon_1", + dialect='default', + literal_binds=True + ) + def test_typedecorator_impl(self): for impl_, exp, kw in [ (Float, "FLOAT", {}), -- 2.47.3