1"""
2   Oracle database specific implementations of changeset classes.
3"""
4import sqlalchemy as sa
5from sqlalchemy.databases import oracle as sa_base
6
7from migrate import exceptions
8from migrate.changeset import ansisql
9
10
11OracleSchemaGenerator = sa_base.OracleDDLCompiler
12
13
14class OracleColumnGenerator(OracleSchemaGenerator, ansisql.ANSIColumnGenerator):
15    pass
16
17
18class OracleColumnDropper(ansisql.ANSIColumnDropper):
19    pass
20
21
22class OracleSchemaChanger(OracleSchemaGenerator, ansisql.ANSISchemaChanger):
23
24    def get_column_specification(self, column, **kwargs):
25        # Ignore the NOT NULL generated
26        override_nullable = kwargs.pop('override_nullable', None)
27        if override_nullable:
28            orig = column.nullable
29            column.nullable = True
30        ret = super(OracleSchemaChanger, self).get_column_specification(
31            column, **kwargs)
32        if override_nullable:
33            column.nullable = orig
34        return ret
35
36    def visit_column(self, delta):
37        keys = delta.keys()
38
39        if 'name' in keys:
40            self._run_subvisit(delta,
41                               self._visit_column_name,
42                               start_alter=False)
43
44        if len(set(('type', 'nullable', 'server_default')).intersection(keys)):
45            self._run_subvisit(delta,
46                               self._visit_column_change,
47                               start_alter=False)
48
49    def _visit_column_change(self, table, column, delta):
50        # Oracle cannot drop a default once created, but it can set it
51        # to null.  We'll do that if default=None
52        # http://forums.oracle.com/forums/message.jspa?messageID=1273234#1273234
53        dropdefault_hack = (column.server_default is None \
54                                and 'server_default' in delta.keys())
55        # Oracle apparently doesn't like it when we say "not null" if
56        # the column's already not null. Fudge it, so we don't need a
57        # new function
58        notnull_hack = ((not column.nullable) \
59                            and ('nullable' not in delta.keys()))
60        # We need to specify NULL if we're removing a NOT NULL
61        # constraint
62        null_hack = (column.nullable and ('nullable' in delta.keys()))
63
64        if dropdefault_hack:
65            column.server_default = sa.PassiveDefault(sa.sql.null())
66        if notnull_hack:
67            column.nullable = True
68        colspec = self.get_column_specification(column,
69            override_nullable=null_hack)
70        if null_hack:
71            colspec += ' NULL'
72        if notnull_hack:
73            column.nullable = False
74        if dropdefault_hack:
75            column.server_default = None
76
77        self.start_alter_table(table)
78        self.append("MODIFY (")
79        self.append(colspec)
80        self.append(")")
81
82
83class OracleConstraintCommon(object):
84
85    def get_constraint_name(self, cons):
86        # Oracle constraints can't guess their name like other DBs
87        if not cons.name:
88            raise exceptions.NotSupportedError(
89                "Oracle constraint names must be explicitly stated")
90        return cons.name
91
92
93class OracleConstraintGenerator(OracleConstraintCommon,
94                                ansisql.ANSIConstraintGenerator):
95    pass
96
97
98class OracleConstraintDropper(OracleConstraintCommon,
99                              ansisql.ANSIConstraintDropper):
100    pass
101
102
103class OracleDialect(ansisql.ANSIDialect):
104    columngenerator = OracleColumnGenerator
105    columndropper = OracleColumnDropper
106    schemachanger = OracleSchemaChanger
107    constraintgenerator = OracleConstraintGenerator
108    constraintdropper = OracleConstraintDropper
109