diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-04-30 15:17:18 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-04-30 15:17:18 -0400 |
commit | 17b7299729fe1ff05e1638d555233875bc21caab (patch) | |
tree | f6ffbcbac40b280ec2ee691258ac156b7d67d891 | |
parent | 22c36646b82ab1c4d076fc52e4c7e741ca56567c (diff) | |
download | alembic-rel_0_3_2.tar.gz |
- [feature] Basic support for Oracle added,rel_0_3_2
courtesy shgoh. #40
-rw-r--r-- | CHANGES | 3 | ||||
-rw-r--r-- | alembic/ddl/__init__.py | 2 | ||||
-rw-r--r-- | alembic/ddl/impl.py | 7 | ||||
-rw-r--r-- | alembic/ddl/mssql.py | 2 | ||||
-rw-r--r-- | alembic/ddl/oracle.py | 77 | ||||
-rw-r--r-- | alembic/environment.py | 16 | ||||
-rw-r--r-- | tests/test_oracle.py | 156 |
7 files changed, 251 insertions, 12 deletions
@@ -1,5 +1,8 @@ 0.3.2 ===== +- [feature] Basic support for Oracle added, + courtesy shgoh. #40 + - [feature] Added support for UniqueConstraint in autogenerate, courtesy Atsushi Odagiri diff --git a/alembic/ddl/__init__.py b/alembic/ddl/__init__.py index 128b14c..e252fae 100644 --- a/alembic/ddl/__init__.py +++ b/alembic/ddl/__init__.py @@ -1,2 +1,2 @@ -import postgresql, mysql, sqlite, mssql +import postgresql, mysql, sqlite, mssql, oracle from impl import DefaultImpl
\ No newline at end of file diff --git a/alembic/ddl/impl.py b/alembic/ddl/impl.py index 0c5d794..95ead4c 100644 --- a/alembic/ddl/impl.py +++ b/alembic/ddl/impl.py @@ -31,6 +31,7 @@ class DefaultImpl(object): __dialect__ = 'default' transactional_ddl = False + command_terminator = ";" def __init__(self, dialect, connection, as_sql, transactional_ddl, output_buffer, @@ -66,7 +67,7 @@ class DefaultImpl(object): 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: @@ -217,7 +218,7 @@ class DefaultImpl(object): 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 @@ -227,7 +228,7 @@ class DefaultImpl(object): via :meth:`.EnvironmentContext.begin_transaction`. """ - self.static_output("COMMIT;") + self.static_output("COMMIT" + self.command_terminator) class _literal_bindparam(_BindParamClause): pass diff --git a/alembic/ddl/mssql.py b/alembic/ddl/mssql.py index 2f8c9eb..42e8447 100644 --- a/alembic/ddl/mssql.py +++ b/alembic/ddl/mssql.py @@ -22,7 +22,7 @@ class MSSQLImpl(DefaultImpl): 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, diff --git a/alembic/ddl/oracle.py b/alembic/ddl/oracle.py new file mode 100644 index 0000000..bda9578 --- /dev/null +++ b/alembic/ddl/oracle.py @@ -0,0 +1,77 @@ +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) diff --git a/alembic/environment.py b/alembic/environment.py index 52c353a..ab7a7d4 100644 --- a/alembic/environment.py +++ b/alembic/environment.py @@ -373,14 +373,16 @@ class EnvironmentContext(object): 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 diff --git a/tests/test_oracle.py b/tests/test_oracle.py new file mode 100644 index 0000000..a14197d --- /dev/null +++ b/tests/test_oracle.py @@ -0,0 +1,156 @@ +"""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' + # ) + |