Commits

Mike Bayer committed e1d19c5

- [feature] Basic support for Oracle added,
courtesy shgoh. #40

Comments (0)

Files changed (7)

 0.3.2
 =====
+- [feature] Basic support for Oracle added, 
+  courtesy shgoh. #40
+
 - [feature] Added support for UniqueConstraint
   in autogenerate, courtesy Atsushi Odagiri
 

alembic/ddl/__init__.py

-import postgresql, mysql, sqlite, mssql
+import postgresql, mysql, sqlite, mssql, oracle
 from impl import DefaultImpl

alembic/ddl/impl.py

     __dialect__ = 'default'
 
     transactional_ddl = False
+    command_terminator = ";"
 
     def __init__(self, dialect, connection, as_sql, 
                     transactional_ddl, output_buffer,
                 raise Exception("Execution arguments not allowed with as_sql")
             self.static_output(unicode(
                     construct.compile(dialect=self.dialect)
-                    ).replace("\t", "    ").strip() + ";")
+                    ).replace("\t", "    ").strip() + self.command_terminator)
         else:
             conn = self.connection
             if execution_options:
         via :meth:`.EnvironmentContext.begin_transaction`.
         
         """
-        self.static_output("BEGIN;")
+        self.static_output("BEGIN" + self.command_terminator)
 
     def emit_commit(self):
         """Emit the string ``COMMIT``, or the backend-specific
         via :meth:`.EnvironmentContext.begin_transaction`.
         
         """
-        self.static_output("COMMIT;")
+        self.static_output("COMMIT" + self.command_terminator)
 
 class _literal_bindparam(_BindParamClause):
     pass

alembic/ddl/mssql.py

             self.static_output(self.batch_separator)
 
     def emit_begin(self):
-        self.static_output("BEGIN TRANSACTION;")
+        self.static_output("BEGIN TRANSACTION" + self.command_terminator)
 
     def alter_column(self, table_name, column_name, 
                         nullable=None,

alembic/ddl/oracle.py

+from alembic.ddl.impl import DefaultImpl
+from alembic.ddl.base import alter_table, AddColumn, ColumnName, \
+    format_table_name, format_column_name, ColumnNullable, \
+    format_server_default,ColumnDefault, format_type, ColumnType
+from alembic import util
+from sqlalchemy.ext.compiler import compiles
+
+class OracleImpl(DefaultImpl):
+    __dialect__ = 'oracle'
+    transactional_ddl = True
+    batch_separator = "/"
+    command_terminator = ""
+
+    def __init__(self, *arg, **kw):
+        super(OracleImpl, self).__init__(*arg, **kw)
+        self.batch_separator = self.context_opts.get(
+                                "oracle_batch_separator", 
+                                self.batch_separator)
+
+    def _exec(self, construct, *args, **kw):
+        super(OracleImpl, self)._exec(construct, *args, **kw)
+        if self.as_sql and self.batch_separator:
+            self.static_output(self.batch_separator)
+
+    def emit_begin(self):
+        self._exec("SET TRANSACTION READ WRITE")
+
+    def emit_commit(self):
+        self._exec("COMMIT")
+
+@compiles(AddColumn, 'oracle')
+def visit_add_column(element, compiler, **kw):
+    return "%s %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        add_column(compiler, element.column, **kw),
+    )
+
+@compiles(ColumnNullable, 'oracle')
+def visit_column_nullable(element, compiler, **kw):
+    return "%s %s %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        alter_column(compiler, element.column_name),
+        "NULL" if element.nullable else "NOT NULL"
+    )
+
+@compiles(ColumnType, 'oracle')
+def visit_column_type(element, compiler, **kw):
+    return "%s %s %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        alter_column(compiler, element.column_name),
+        "%s" % format_type(compiler, element.type_)
+    )
+
+@compiles(ColumnName, 'oracle')
+def visit_column_name(element, compiler, **kw):
+    return "%s RENAME COLUMN %s TO %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        format_column_name(compiler, element.column_name),
+        format_column_name(compiler, element.newname)
+    )
+
+@compiles(ColumnDefault, 'oracle')
+def visit_column_default(element, compiler, **kw):
+    return "%s %s %s" % (
+        alter_table(compiler, element.table_name, element.schema),
+        alter_column(compiler, element.column_name),
+        "DEFAULT %s" % 
+            format_server_default(compiler, element.default)
+        if element.default is not None
+        else "DEFAULT NULL"
+    )
+
+def alter_column(compiler, name):
+    return 'MODIFY %s' % format_column_name(compiler, name)
+
+def add_column(compiler, column, **kw):
+    return "ADD %s" % compiler.get_column_specification(column, **kw)

alembic/environment.py

         Parameters specific to individual backends:
     
         :param mssql_batch_separator: The "batch separator" which will 
-         be placed
-         between each statement when generating offline SQL Server 
+         be placed between each statement when generating offline SQL Server 
          migrations.  Defaults to ``GO``.  Note this is in addition to the 
-         customary
-         semicolon ``;`` at the end of each statement; SQL Server considers
-         the "batch separator" to denote the end of an individual statement
-         execution, and cannot group certain dependent operations in 
-         one step.
+         customary semicolon ``;`` at the end of each statement; SQL Server 
+         considers the "batch separator" to denote the end of an 
+         individual statement execution, and cannot group certain 
+         dependent operations in one step.
+        :param oracle_batch_separator: The "batch separator" which will
+         be placed between each statement when generating offline 
+         Oracle migrations.  Defaults to ``/``.  Oracle doesn't add a
+         semicolon between statements like most other backends.
 
         """
         opts = self.context_opts

tests/test_oracle.py

+"""Test op functions against ORACLE."""
+from __future__ import with_statement
+from tests import op_fixture, capture_context_buffer, \
+    _no_sql_testing_config, assert_raises_message, staging_env, \
+    three_rev_fixture, clear_staging_env
+from alembic import op, command, util
+from sqlalchemy import Integer, Column, ForeignKey, \
+            UniqueConstraint, Table, MetaData, String
+from sqlalchemy.sql import table
+from unittest import TestCase
+
+
+class FullEnvironmentTests(TestCase):
+    @classmethod
+    def setup_class(cls):
+        env = staging_env()
+        cls.cfg = cfg = _no_sql_testing_config("oracle")
+
+        cls.a, cls.b, cls.c = \
+            three_rev_fixture(cfg)
+
+    @classmethod
+    def teardown_class(cls):
+        clear_staging_env()
+
+    def test_begin_comit(self):
+        with capture_context_buffer(transactional_ddl=True) as buf:
+            command.upgrade(self.cfg, self.a, sql=True)
+        assert "SET TRANSACTION READ WRITE\n\n/" in buf.getvalue()
+        assert "COMMIT\n\n/" in buf.getvalue()
+
+    def test_batch_separator_default(self):
+        with capture_context_buffer() as buf:
+            command.upgrade(self.cfg, self.a, sql=True)
+        assert "/" in buf.getvalue()
+        assert ";" not in buf.getvalue()
+
+    def test_batch_separator_custom(self):
+        with capture_context_buffer(oracle_batch_separator="BYE") as buf:
+            command.upgrade(self.cfg, self.a, sql=True)
+        assert "BYE" in buf.getvalue()
+
+class OpTest(TestCase):
+    def test_add_column(self):
+        context = op_fixture('oracle')
+        op.add_column('t1', Column('c1', Integer, nullable=False))
+        context.assert_("ALTER TABLE t1 ADD c1 INTEGER NOT NULL")
+
+
+    def test_add_column_with_default(self):
+        context = op_fixture("oracle")
+        op.add_column('t1', Column('c1', Integer, nullable=False, server_default="12"))
+        context.assert_("ALTER TABLE t1 ADD c1 INTEGER DEFAULT '12' NOT NULL")
+
+    def test_alter_column_rename_oracle(self):
+        context = op_fixture('oracle')
+        op.alter_column("t", "c", name="x")
+        context.assert_(
+            "ALTER TABLE t RENAME COLUMN c TO x"
+        )
+
+    def test_alter_column_new_type(self):
+        context = op_fixture('oracle')
+        op.alter_column("t", "c", type_=Integer)
+        context.assert_(
+            'ALTER TABLE t MODIFY c INTEGER'
+        )
+
+    def test_drop_index(self):
+        context = op_fixture('oracle')
+        op.drop_index('my_idx', 'my_table')
+        context.assert_contains("DROP INDEX my_idx")
+
+    def test_drop_column_w_default(self):
+        context = op_fixture('oracle')
+        op.drop_column('t1', 'c1')
+        context.assert_(
+            "ALTER TABLE t1 DROP COLUMN c1"
+        )
+
+    def test_drop_column_w_check(self):
+        context = op_fixture('oracle')
+        op.drop_column('t1', 'c1')
+        context.assert_(
+            "ALTER TABLE t1 DROP COLUMN c1"
+        )
+
+    def test_alter_column_nullable_w_existing_type(self):
+        context = op_fixture('oracle')
+        op.alter_column("t", "c", nullable=True, existing_type=Integer)
+        context.assert_(
+            "ALTER TABLE t MODIFY c NULL"
+        )
+
+    def test_alter_column_not_nullable_w_existing_type(self):
+        context = op_fixture('oracle')
+        op.alter_column("t", "c", nullable=False, existing_type=Integer)
+        context.assert_(
+            "ALTER TABLE t MODIFY c NOT NULL"
+        )
+
+    def test_alter_column_nullable_w_new_type(self):
+        context = op_fixture('oracle')
+        op.alter_column("t", "c", nullable=True, type_=Integer)
+        context.assert_(
+            "ALTER TABLE t MODIFY c NULL",
+            'ALTER TABLE t MODIFY c INTEGER'
+        )
+
+    def test_alter_column_not_nullable_w_new_type(self):
+        context = op_fixture('oracle')
+        op.alter_column("t", "c", nullable=False, type_=Integer)
+        context.assert_(
+            "ALTER TABLE t MODIFY c NOT NULL",
+            "ALTER TABLE t MODIFY c INTEGER"
+        )
+
+    def test_alter_add_server_default(self):
+        context = op_fixture('oracle')
+        op.alter_column("t", "c", server_default="5")
+        context.assert_(
+            "ALTER TABLE t MODIFY c DEFAULT '5'"
+        )
+
+    def test_alter_replace_server_default(self):
+        context = op_fixture('oracle')
+        op.alter_column("t", "c", server_default="5", existing_server_default="6")
+        context.assert_(
+            "ALTER TABLE t MODIFY c DEFAULT '5'"
+        )
+
+    def test_alter_remove_server_default(self):
+        context = op_fixture('oracle')
+        op.alter_column("t", "c", server_default=None)
+        context.assert_(
+            "ALTER TABLE t MODIFY c DEFAULT NULL"
+        )
+
+    def test_alter_do_everything(self):
+        context = op_fixture('oracle')
+        op.alter_column("t", "c", name="c2", nullable=True, type_=Integer, server_default="5")
+        context.assert_(
+            'ALTER TABLE t MODIFY c NULL',
+            "ALTER TABLE t MODIFY c DEFAULT '5'",
+            'ALTER TABLE t MODIFY c INTEGER',
+            'ALTER TABLE t RENAME COLUMN c TO c2'
+        )
+
+    # TODO: when we add schema support
+    #def test_alter_column_rename_oracle_schema(self):
+    #    context = op_fixture('oracle')
+    #    op.alter_column("t", "c", name="x", schema="y")
+    #    context.assert_(
+    #        'ALTER TABLE y.t RENAME COLUMN c TO c2'
+    #    )
+