Project

General

Profile

Statistics
| Branch: | Revision:

root / env / lib / python2.7 / site-packages / south / db / firebird.py @ d1a4905f

History | View | Annotate | Download (12.7 KB)

1
# firebird
2

    
3
import datetime
4

    
5
from django.db import connection, models
6
from django.core.management.color import no_style
7
from django.db.utils import DatabaseError
8

    
9
from south.db import generic
10

    
11
class DatabaseOperations(generic.DatabaseOperations):
12
    backend_name = 'firebird'
13
    alter_string_set_type = 'ALTER %(column)s TYPE %(type)s'
14
    alter_string_set_default =  'ALTER %(column)s SET DEFAULT %(default)s;'
15
    alter_string_drop_null = ''
16
    add_column_string = 'ALTER TABLE %s ADD %s;'
17
    delete_column_string = 'ALTER TABLE %s DROP %s;'
18
    allows_combined_alters = False
19

    
20
    def _fill_constraint_cache(self, db_name, table_name):
21
        self._constraint_cache.setdefault(db_name, {})
22
        self._constraint_cache[db_name][table_name] = {}
23

    
24
        rows = self.execute("""
25
            SELECT
26
                rc.RDB$CONSTRAINT_NAME,
27
                rc.RDB$CONSTRAINT_TYPE,
28
                cc.RDB$TRIGGER_NAME
29
            FROM rdb$relation_constraints rc
30
            JOIN rdb$check_constraints cc
31
            ON rc.rdb$constraint_name = cc.rdb$constraint_name
32
            WHERE rc.rdb$constraint_type = 'NOT NULL'
33
            AND rc.rdb$relation_name = '%s'
34
            """ % table_name)
35

    
36
        for constraint, kind, column in rows:
37
           self._constraint_cache[db_name][table_name].setdefault(column, set())
38
           self._constraint_cache[db_name][table_name][column].add((kind, constraint))
39
        return
40

    
41
    def _alter_column_set_null(self, table_name, column_name, is_null):
42
        sql = """
43
            UPDATE RDB$RELATION_FIELDS SET RDB$NULL_FLAG = %(null_flag)s
44
            WHERE RDB$FIELD_NAME = '%(column)s'
45
            AND RDB$RELATION_NAME = '%(table_name)s'
46
        """
47
        null_flag = 'NULL' if is_null else '1'
48
        return sql % {
49
            'null_flag': null_flag,
50
            'column': column_name.upper(),
51
            'table_name': table_name.upper()
52
        }
53

    
54
    def _column_has_default(self, params):
55
        sql = """
56
            SELECT a.RDB$DEFAULT_VALUE
57
            FROM RDB$RELATION_FIELDS a
58
            WHERE a.RDB$FIELD_NAME = '%(column)s'
59
            AND a.RDB$RELATION_NAME = '%(table_name)s'
60
        """
61
        value = self.execute(sql % params)
62
        return True if value else False
63

    
64

    
65
    def _alter_set_defaults(self, field, name, params, sqls):
66
        "Subcommand of alter_column that sets default values (overrideable)"
67
        # Next, set any default
68
        if not field.null and field.has_default():
69
            default = field.get_default()
70
            sqls.append(('ALTER COLUMN %s SET DEFAULT %%s ' % (self.quote_name(name),), [default]))
71
        elif self._column_has_default(params):
72
            sqls.append(('ALTER COLUMN %s DROP DEFAULT' % (self.quote_name(name),), []))
73

    
74

    
75
    @generic.invalidate_table_constraints
76
    def create_table(self, table_name, fields):
77
        qn = self.quote_name(table_name)
78
        columns = []
79
        autoinc_sql = ''
80

    
81
        for field_name, field in fields:
82
            col = self.column_sql(table_name, field_name, field)
83
            if not col:
84
                continue
85
            #col = self.adj_column_sql(col)
86

    
87
            columns.append(col)
88
            if isinstance(field, models.AutoField):
89
                field_name = field.db_column or field.column
90
                autoinc_sql = connection.ops.autoinc_sql(table_name, field_name)
91

    
92
        sql = 'CREATE TABLE %s (%s);' % (qn, ', '.join([col for col in columns]))
93
        self.execute(sql)
94
        if autoinc_sql:
95
            self.execute(autoinc_sql[0])
96
            self.execute(autoinc_sql[1])
97

    
98

    
99
    def column_sql(self, table_name, field_name, field, tablespace='', with_name=True, field_prepared=False):
100
        """
101
        Creates the SQL snippet for a column. Used by add_column and add_table.
102
        """
103

    
104
        # If the field hasn't already been told its attribute name, do so.
105
        if not field_prepared:
106
            field.set_attributes_from_name(field_name)
107

    
108
        # hook for the field to do any resolution prior to it's attributes being queried
109
        if hasattr(field, 'south_init'):
110
            field.south_init()
111

    
112
        # Possible hook to fiddle with the fields (e.g. defaults & TEXT on MySQL)
113
        field = self._field_sanity(field)
114

    
115
        try:
116
            sql = field.db_type(connection=self._get_connection())
117
        except TypeError:
118
            sql = field.db_type()
119

    
120
        if sql:
121
            # Some callers, like the sqlite stuff, just want the extended type.
122
            if with_name:
123
                field_output = [self.quote_name(field.column), sql]
124
            else:
125
                field_output = [sql]
126

    
127
            if field.primary_key:
128
                field_output.append('NOT NULL PRIMARY KEY')
129
            elif field.unique:
130
                # Just use UNIQUE (no indexes any more, we have delete_unique)
131
                field_output.append('UNIQUE')
132

    
133
            sql = ' '.join(field_output)
134
            sqlparams = ()
135

    
136
            # if the field is "NOT NULL" and a default value is provided, create the column with it
137
            # this allows the addition of a NOT NULL field to a table with existing rows
138
            if not getattr(field, '_suppress_default', False):
139
                if field.has_default():
140
                    default = field.get_default()
141
                    # If the default is actually None, don't add a default term
142
                    if default is not None:
143
                        # If the default is a callable, then call it!
144
                        if callable(default):
145
                            default = default()
146
                        # Now do some very cheap quoting. TODO: Redesign return values to avoid this.
147
                        if isinstance(default, basestring):
148
                            default = "'%s'" % default.replace("'", "''")
149
                        elif isinstance(default, (datetime.date, datetime.time, datetime.datetime)):
150
                            default = "'%s'" % default
151
                        elif isinstance(default, bool):
152
                            default = int(default)
153
                        # Escape any % signs in the output (bug #317)
154
                        if isinstance(default, basestring):
155
                            default = default.replace("%", "%%")
156
                        # Add it in
157
                        sql += " DEFAULT %s"
158
                        sqlparams = (default)
159
                elif (not field.null and field.blank) or (field.get_default() == ''):
160
                    if field.empty_strings_allowed and self._get_connection().features.interprets_empty_strings_as_nulls:
161
                        sql += " DEFAULT ''"
162
                    # Error here would be nice, but doesn't seem to play fair.
163
                    #else:
164
                    #    raise ValueError("Attempting to add a non null column that isn't character based without an explicit default value.")
165

    
166
            # Firebird need set not null after of default value keyword
167
            if not field.primary_key and not field.null:
168
                sql += ' NOT NULL'
169

    
170
            if field.rel and self.supports_foreign_keys:
171
                self.add_deferred_sql(
172
                    self.foreign_key_sql(
173
                        table_name,
174
                        field.column,
175
                        field.rel.to._meta.db_table,
176
                        field.rel.to._meta.get_field(field.rel.field_name).column
177
                    )
178
                )
179

    
180
        # Things like the contrib.gis module fields have this in 1.1 and below
181
        if hasattr(field, 'post_create_sql'):
182
            for stmt in field.post_create_sql(no_style(), table_name):
183
                self.add_deferred_sql(stmt)
184

    
185
        # In 1.2 and above, you have to ask the DatabaseCreation stuff for it.
186
        # This also creates normal indexes in 1.1.
187
        if hasattr(self._get_connection().creation, "sql_indexes_for_field"):
188
            # Make a fake model to pass in, with only db_table
189
            model = self.mock_model("FakeModelForGISCreation", table_name)
190
            for stmt in self._get_connection().creation.sql_indexes_for_field(model, field, no_style()):
191
                self.add_deferred_sql(stmt)
192

    
193
        if sql:
194
            return sql % sqlparams
195
        else:
196
            return None
197

    
198

    
199
    def _drop_constraints(self, table_name, name, field):
200
        if self.has_check_constraints:
201
            check_constraints = self._constraints_affecting_columns(table_name, [name], "CHECK")
202
            for constraint in check_constraints:
203
                self.execute(self.delete_check_sql % {
204
                    'table': self.quote_name(table_name),
205
                    'constraint': self.quote_name(constraint),
206
                })
207

    
208
        # Drop or add UNIQUE constraint
209
        unique_constraint = list(self._constraints_affecting_columns(table_name, [name], "UNIQUE"))
210
        if field.unique and not unique_constraint:
211
            self.create_unique(table_name, [name])
212
        elif not field.unique and unique_constraint:
213
            self.delete_unique(table_name, [name])
214

    
215
        # Drop all foreign key constraints
216
        try:
217
            self.delete_foreign_key(table_name, name)
218
        except ValueError:
219
            # There weren't any
220
            pass
221

    
222

    
223
    @generic.invalidate_table_constraints
224
    def alter_column(self, table_name, name, field, explicit_name=True, ignore_constraints=False):
225
        """
226
        Alters the given column name so it will match the given field.
227
        Note that conversion between the two by the database must be possible.
228
        Will not automatically add _id by default; to have this behavour, pass
229
        explicit_name=False.
230

231
        @param table_name: The name of the table to add the column to
232
        @param name: The name of the column to alter
233
        @param field: The new field definition to use
234
        """
235

    
236
        if self.dry_run:
237
            return
238

    
239
        # hook for the field to do any resolution prior to it's attributes being queried
240
        if hasattr(field, 'south_init'):
241
            field.south_init()
242

    
243
        # Add _id or whatever if we need to
244
        field.set_attributes_from_name(name)
245
        if not explicit_name:
246
            name = field.column
247
        else:
248
            field.column = name
249

    
250
        if not ignore_constraints:
251
            # Drop all check constraints. Note that constraints will be added back
252
            # with self.alter_string_set_type and self.alter_string_drop_null.
253
            self._drop_constraints(table_name, name, field)
254

    
255
        # First, change the type
256
        params = {
257
            "column": self.quote_name(name),
258
            "type": self._db_type_for_alter_column(field),
259
            "table_name": table_name
260
        }
261

    
262
        # SQLs is a list of (SQL, values) pairs.
263
        sqls = []
264
        sqls_extra = []
265

    
266
        # Only alter the column if it has a type (Geometry ones sometimes don't)
267
        if params["type"] is not None:
268
            sqls.append((self.alter_string_set_type % params, []))
269

    
270
        # Add any field- and backend- specific modifications
271
        self._alter_add_column_mods(field, name, params, sqls)
272

    
273
        # Next, nullity: modified, firebird doesn't support DROP NOT NULL
274
        sqls_extra.append(self._alter_column_set_null(table_name, name, field.null))
275

    
276
        # Next, set any default
277
        self._alter_set_defaults(field, name, params, sqls)
278

    
279
        # Finally, actually change the column
280
        if self.allows_combined_alters:
281
            sqls, values = zip(*sqls)
282
            self.execute(
283
                "ALTER TABLE %s %s;" % (self.quote_name(table_name), ", ".join(sqls)),
284
                generic.flatten(values),
285
            )
286
        else:
287
            # Databases like e.g. MySQL don't like more than one alter at once.
288
            for sql, values in sqls:
289
                try:
290
                    self.execute("ALTER TABLE %s %s;" % (self.quote_name(table_name), sql), values)
291
                except DatabaseError, e:
292
                    print e
293

    
294

    
295
        # Execute extra sql, which don't need ALTER TABLE statement
296
        for sql in sqls_extra:
297
            self.execute(sql)
298

    
299
        if not ignore_constraints:
300
            # Add back FK constraints if needed
301
            if field.rel and self.supports_foreign_keys:
302
                self.execute(
303
                    self.foreign_key_sql(
304
                        table_name,
305
                        field.column,
306
                        field.rel.to._meta.db_table,
307
                        field.rel.to._meta.get_field(field.rel.field_name).column
308
                    )
309
                )
310

    
311
    @generic.copy_column_constraints
312
    @generic.delete_column_constraints
313
    def rename_column(self, table_name, old, new):
314
        if old == new:
315
            # Short-circuit out
316
            return []
317

    
318
        self.execute('ALTER TABLE %s ALTER %s TO %s;' % (
319
            self.quote_name(table_name),
320
            self.quote_name(old),
321
            self.quote_name(new),
322
        ))