From 81aa5b376eb80793e3734eb420b82872d2941d6f Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Mon, 9 Feb 2015 14:58:26 -0500 Subject: [PATCH] - Literal values within a :class:`.DefaultClause`, which is invoked when using the :paramref:`.Column.server_default` parameter, will now be rendered using the "inline" compiler, so that they are rendered as-is, rather than as bound parameters. fixes #3087 --- doc/build/changelog/changelog_10.rst | 17 ++++++- doc/build/changelog/migration_10.rst | 35 +++++++++++++ lib/sqlalchemy/sql/compiler.py | 3 +- test/sql/test_defaults.py | 73 ++++++++++++++++++++++++++-- 4 files changed, 122 insertions(+), 6 deletions(-) diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index 10d6dc786d..277fe28f8d 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -23,12 +23,25 @@ series as well. For changes that are specific to 1.0 with an emphasis on compatibility concerns, see :doc:`/changelog/migration_10`. + .. change:: + :tags: feature, sql + :tickets: 3087 + + Literal values within a :class:`.DefaultClause`, which is invoked + when using the :paramref:`.Column.server_default` parameter, will + now be rendered using the "inline" compiler, so that they are rendered + as-is, rather than as bound parameters. + + .. seealso:: + + :ref:`change_3087` + .. change:: :tags: feature, oracle :pullreq: github:152 - Added support for cx_oracle connections to a specific service - name, as opposed to a tns name, by passing ``?service_name=`` + Added support for cx_oracle connections to a specific service + name, as opposed to a tns name, by passing ``?service_name=`` to the URL. Pull request courtesy Sławomir Ehlert. .. change:: diff --git a/doc/build/changelog/migration_10.rst b/doc/build/changelog/migration_10.rst index efb4b26e59..651679dfdc 100644 --- a/doc/build/changelog/migration_10.rst +++ b/doc/build/changelog/migration_10.rst @@ -669,6 +669,41 @@ Will render:: The feature can be disabled using :paramref:`.Insert.from_select.include_defaults`. +.. _change_3087: + +Column server defaults now render literal values +------------------------------------------------ + +The "literal binds" compiler flag is switched on when a +:class:`.DefaultClause`, set up by :paramref:`.Column.server_default` +is present as a SQL expression to be compiled. This allows literals +embedded in SQL to render correctly, such as:: + + from sqlalchemy import Table, Column, MetaData, Text + from sqlalchemy.schema import CreateTable + from sqlalchemy.dialects.postgresql import ARRAY, array + from sqlalchemy.dialects import postgresql + + metadata = MetaData() + + tbl = Table("derp", metadata, + Column("arr", ARRAY(Text), + server_default=array(["foo", "bar", "baz"])), + ) + + print(CreateTable(tbl).compile(dialect=postgresql.dialect())) + +Now renders:: + + CREATE TABLE derp ( + arr TEXT[] DEFAULT ARRAY['foo', 'bar', 'baz'] + ) + +Previously, the literal values ``"foo", "bar", "baz"`` would render as +bound parameters, which are useless in DDL. + +:ticket:`3087` + .. _feature_3184: UniqueConstraint is now part of the Table reflection process diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index da62b14348..f8f4d1dda9 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -2279,7 +2279,8 @@ class DDLCompiler(Compiled): if isinstance(column.server_default.arg, util.string_types): return "'%s'" % column.server_default.arg else: - return self.sql_compiler.process(column.server_default.arg) + return self.sql_compiler.process( + column.server_default.arg, literal_binds=True) else: return None diff --git a/test/sql/test_defaults.py b/test/sql/test_defaults.py index 48505dd8cb..ecf3dcc4d2 100644 --- a/test/sql/test_defaults.py +++ b/test/sql/test_defaults.py @@ -1,7 +1,8 @@ -from sqlalchemy.testing import eq_, assert_raises_message, assert_raises +from sqlalchemy.testing import eq_, assert_raises_message, \ + assert_raises, AssertsCompiledSQL import datetime -from sqlalchemy.schema import CreateSequence, DropSequence -from sqlalchemy.sql import select, text +from sqlalchemy.schema import CreateSequence, DropSequence, CreateTable +from sqlalchemy.sql import select, text, literal_column import sqlalchemy as sa from sqlalchemy import testing from sqlalchemy.testing import engines @@ -19,6 +20,72 @@ import itertools t = f = f2 = ts = currenttime = metadata = default_generator = None +class DDLTest(fixtures.TestBase, AssertsCompiledSQL): + __dialect__ = 'default' + + def test_string(self): + m = MetaData() + t = Table('t', m, Column('x', Integer, server_default='5')) + self.assert_compile( + CreateTable(t), + "CREATE TABLE t (x INTEGER DEFAULT '5')" + ) + + def test_text(self): + m = MetaData() + t = Table('t', m, Column('x', Integer, server_default=text('5 + 8'))) + self.assert_compile( + CreateTable(t), + "CREATE TABLE t (x INTEGER DEFAULT 5 + 8)" + ) + + def test_text_literal_binds(self): + m = MetaData() + t = Table( + 't', m, + Column( + 'x', Integer, server_default=text('q + :x1').bindparams(x1=7))) + self.assert_compile( + CreateTable(t), + "CREATE TABLE t (x INTEGER DEFAULT q + 7)" + ) + + def test_sqlexpr(self): + m = MetaData() + t = Table('t', m, Column( + 'x', Integer, + server_default=literal_column('a') + literal_column('b')) + ) + self.assert_compile( + CreateTable(t), + "CREATE TABLE t (x INTEGER DEFAULT a + b)" + ) + + def test_literal_binds_plain(self): + m = MetaData() + t = Table('t', m, Column( + 'x', Integer, + server_default=literal('a') + literal('b')) + ) + self.assert_compile( + CreateTable(t), + "CREATE TABLE t (x INTEGER DEFAULT 'a' || 'b')" + ) + + def test_literal_binds_pgarray(self): + from sqlalchemy.dialects.postgresql import ARRAY, array + m = MetaData() + t = Table('t', m, Column( + 'x', ARRAY(Integer), + server_default=array([1, 2, 3])) + ) + self.assert_compile( + CreateTable(t), + "CREATE TABLE t (x INTEGER[] DEFAULT ARRAY[1, 2, 3])", + dialect='postgresql' + ) + + class DefaultTest(fixtures.TestBase): __backend__ = True -- 2.47.2