]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
- [bug] implement 'tablename' parameter on
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 28 Feb 2012 16:21:07 +0000 (11:21 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 28 Feb 2012 16:21:07 +0000 (11:21 -0500)
  drop_index() as this is needed by some
  backends.

- [feature] Added execution_options parameter
  to op.execute(), will call execution_options()
  on the Connection before executing.

  The immediate use case here is to allow
  access to the new no_parameters option
  in SQLAlchemy 0.7.6, which allows
  some DBAPIs (psycopg2, MySQLdb) to allow
  percent signs straight through without
  escaping, thus providing cross-compatible
  operation with DBAPI execution and
  static script generation.

CHANGES
alembic/ddl/impl.py
alembic/environment.py
alembic/operations.py
tests/test_mssql.py
tests/test_postgresql.py

diff --git a/CHANGES b/CHANGES
index e1cd2bf87e7a0f05b5de4ca661d51ec65ee485de..6efa99745e15ba01166da9a93c37ac2e570f8ea9 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -2,7 +2,24 @@
 =====
 - [feature] Informative error message when op.XYZ
   directives are invoked at module import time.
-  
+
+- [bug] implement 'tablename' parameter on 
+  drop_index() as this is needed by some 
+  backends.
+
+- [feature] Added execution_options parameter
+  to op.execute(), will call execution_options()
+  on the Connection before executing.
+
+  The immediate use case here is to allow
+  access to the new no_parameters option
+  in SQLAlchemy 0.7.6, which allows
+  some DBAPIs (psycopg2, MySQLdb) to allow
+  percent signs straight through without
+  escaping, thus providing cross-compatible
+  operation with DBAPI execution and 
+  static script generation.
+
 - [bug] setup.py won't install argparse if on
   Python 2.7/3.2
 
index 08134b581db70d9161c3a5114ab9ddc2491a1edc..32f4860e83f65bc5edaf4b570103b5f7b2c4bde0 100644 (file)
@@ -5,6 +5,7 @@ from sqlalchemy import schema
 from alembic.ddl import base
 from alembic import util
 from sqlalchemy import types as sqltypes
+from sqlalchemy import util as sqla_util
 
 class ImplMeta(type):
     def __init__(cls, classname, bases, dict_):
@@ -55,21 +56,26 @@ class DefaultImpl(object):
     def bind(self):
         return self.connection
 
-    def _exec(self, construct, *args, **kw):
+    def _exec(self, construct, execution_options=None, 
+                            multiparams=(), 
+                            params=sqla_util.immutabledict()):
         if isinstance(construct, basestring):
             construct = text(construct)
         if self.as_sql:
-            if args or kw:
+            if multiparams or params:
                 # TODO: coverage
                 raise Exception("Execution arguments not allowed with as_sql")
             self.static_output(unicode(
                     construct.compile(dialect=self.dialect)
                     ).replace("\t", "    ").strip() + ";")
         else:
-            self.connection.execute(construct, *args, **kw)
+            conn = self.connection
+            if execution_options:
+                conn = conn.execution_options(**execution_options)
+            conn.execute(construct, *multiparams, **params)
 
-    def execute(self, sql):
-        self._exec(sql)
+    def execute(self, sql, execution_options=None):
+        self._exec(sql, execution_options)
 
     def alter_column(self, table_name, column_name, 
                         nullable=None,
index b58cb1e85534b48b0016b9eb9c95a1f503258521..231acebe1e39241e6c4fcbf3ebe403963d9cdac6 100644 (file)
@@ -383,7 +383,7 @@ class EnvironmentContext(object):
         with Operations.context(self._migration_context):
             self.get_context().run_migrations(**kw)
 
-    def execute(self, sql):
+    def execute(self, sql, execution_options=None):
         """Execute the given SQL using the current change context.
 
         The behavior of :meth:`.execute` is the same
@@ -395,7 +395,8 @@ class EnvironmentContext(object):
         first been made available via :meth:`.configure`.
 
         """
-        self.get_context().execute(sql)
+        self.get_context().execute(sql, 
+                execution_options=execution_options)
 
     def static_output(self, text):
         """Emit text directly to the "offline" SQL stream.
index 53b65dca01fd783776ca330369750e15228bbd60..f3150395d6fd592e336c5a4850280d8a0b020fc0 100644 (file)
@@ -98,7 +98,7 @@ class Operations(object):
         return schema.Column(name, type_, **kw)
 
     def _index(self, name, tablename, columns, **kw):
-        t = schema.Table(tablename, schema.MetaData(),
+        t = schema.Table(tablename or 'no_table', schema.MetaData(),
             *[schema.Column(n, NULLTYPE) for n in columns]
         )
         return schema.Index(name, *list(t.c), **kw)
@@ -512,7 +512,7 @@ class Operations(object):
             self._index(name, tablename, *columns, **kw)
         )
 
-    def drop_index(self, name):
+    def drop_index(self, name, tablename=None):
         """Issue a "drop index" instruction using the current 
         migration context.
 
@@ -520,11 +520,14 @@ class Operations(object):
         e.g.::
 
             drop_index("accounts")
+            
+        :param tablename: name of the owning table.  Some
+         backends such as Microsoft SQL Server require this.
 
         """
         # need a dummy column name here since SQLAlchemy
         # 0.7.6 and further raises on Index with no columns
-        self.impl.drop_index(self._index(name, 'foo', ['x']))
+        self.impl.drop_index(self._index(name, tablename, ['x']))
 
     def drop_constraint(self, name, tablename):
         """Drop a constraint of the given name"""
@@ -603,7 +606,7 @@ class Operations(object):
         """
         return impl._literal_bindparam(None, value, type_=type_)
 
-    def execute(self, sql):
+    def execute(self, sql, execution_options=None):
         """Execute the given SQL using the current migration context.
 
         In a SQL script context, the statement is emitted directly to the 
@@ -661,9 +664,12 @@ class Operations(object):
         * Pretty much anything that's "executable" as described
           in :ref:`sqlexpression_toplevel`.
 
-
+        :param execution_options: Optional dictionary of 
+         execution options, will be passed to 
+         :meth:`sqlalchemy.engine.base.Connection.execution_options`.
         """
-        self.migration_context.impl.execute(sql)
+        self.migration_context.impl.execute(sql, 
+                    execution_options=execution_options)
 
     def get_bind(self):
         """Return the current 'bind'.
index ed4751ee30e7906ac7ea4bea455240683381496d..9f78530887e0de7da135b2c6429d27ed468467d8 100644 (file)
@@ -65,6 +65,12 @@ class OpTest(TestCase):
             'ALTER TABLE t ALTER COLUMN c INTEGER'
         )
 
+    def test_drop_index(self):
+        context = op_fixture('mssql')
+        op.drop_index('my_idx', 'my_table')
+        # TODO: annoying that SQLA escapes unconditionally
+        context.assert_contains("DROP INDEX [my_table].my_idx")
+
     def test_drop_column_w_default(self):
         context = op_fixture('mssql')
         op.drop_column('t1', 'c1', mssql_drop_default=True)
index b1602375030500599b9cf6bcbb899a3e45699eba..3eea778d303378e5d0a9c7a181bfa6aa07c21482 100644 (file)
@@ -61,7 +61,7 @@ def upgrade():
 def downgrade():
     op.drop_table("sometable")
     ENUM(name="pgenum").drop(op.get_bind(), checkfirst=False)
-    
+
 """ % self.rid)
 
     def test_offline_inline_enum_create(self):
@@ -93,7 +93,51 @@ def downgrade():
         assert "DROP TABLE sometable" in buf.getvalue()
         assert "DROP TYPE pgenum" in buf.getvalue()
 
+from alembic.migration import MigrationContext
+from alembic.operations import Operations
+from sqlalchemy.sql import table, column
+
+class PostgresqlInlineLiteralTest(TestCase):
+    @classmethod
+    def setup_class(cls):
+        cls.bind = db_for_dialect("postgresql")
+        cls.bind.execute("""
+            create table tab (
+                col varchar(50)
+            )
+        """)
+        cls.bind.execute("""
+            insert into tab (col) values 
+                ('old data 1'),
+                ('old data 2.1'),
+                ('old data 3')
+        """)
+
+    @classmethod
+    def teardown_class(cls):
+        cls.bind.execute("drop table tab")
+
+    def setUp(self):
+        self.conn = self.bind.connect()
+        ctx = MigrationContext.configure(self.conn)
+        self.op = Operations(ctx)
 
+    def tearDown(self):
+        self.conn.close()
+
+    def test_inline_percent(self):
+        # TODO: here's the issue, you need to escape this.
+        tab = table('tab', column('col'))
+        self.op.execute(
+            tab.update().where(
+                tab.c.col.like(self.op.inline_literal('%.%'))
+            ).values(col=self.op.inline_literal('new data')),
+            execution_options={'no_parameters':True}
+        )
+        eq_(
+            self.conn.execute("select count(*) from tab where col='new data'").scalar(),
+            1,
+        )
 
 class PostgresqlDefaultCompareTest(TestCase):
     @classmethod