1# firebird
2
3from __future__ import print_function
4
5import datetime
6
7from django.db import connection, models
8from django.core.management.color import no_style
9from django.db.utils import DatabaseError
10
11from south.db import generic
12from south.utils.py3 import string_types
13
14class DatabaseOperations(generic.DatabaseOperations):
15    backend_name = 'firebird'
16    alter_string_set_type = 'ALTER %(column)s TYPE %(type)s'
17    alter_string_set_default =  'ALTER %(column)s SET DEFAULT %(default)s;'
18    alter_string_drop_null = ''
19    add_column_string = 'ALTER TABLE %s ADD %s;'
20    delete_column_string = 'ALTER TABLE %s DROP %s;'
21    rename_table_sql = ''
22
23    # Features
24    allows_combined_alters = False
25    has_booleans = False
26
27    def _fill_constraint_cache(self, db_name, table_name):
28        self._constraint_cache.setdefault(db_name, {})
29        self._constraint_cache[db_name][table_name] = {}
30
31        rows = self.execute("""
32            SELECT
33                rc.RDB$CONSTRAINT_NAME,
34                rc.RDB$CONSTRAINT_TYPE,
35                cc.RDB$TRIGGER_NAME
36            FROM rdb$relation_constraints rc
37            JOIN rdb$check_constraints cc
38            ON rc.rdb$constraint_name = cc.rdb$constraint_name
39            WHERE rc.rdb$constraint_type = 'NOT NULL'
40            AND rc.rdb$relation_name = '%s'
41            """ % table_name)
42
43        for constraint, kind, column in rows:
44           self._constraint_cache[db_name][table_name].setdefault(column, set())
45           self._constraint_cache[db_name][table_name][column].add((kind, constraint))
46        return
47
48    def _alter_column_set_null(self, table_name, column_name, is_null):
49        sql = """
50            UPDATE RDB$RELATION_FIELDS SET RDB$NULL_FLAG = %(null_flag)s
51            WHERE RDB$FIELD_NAME = '%(column)s'
52            AND RDB$RELATION_NAME = '%(table_name)s'
53        """
54        null_flag = 'NULL' if is_null else '1'
55        return sql % {
56            'null_flag': null_flag,
57            'column': column_name.upper(),
58            'table_name': table_name.upper()
59        }
60
61    def _column_has_default(self, params):
62        sql = """
63            SELECT a.RDB$DEFAULT_VALUE
64            FROM RDB$RELATION_FIELDS a
65            WHERE a.RDB$FIELD_NAME = '%(column)s'
66            AND a.RDB$RELATION_NAME = '%(table_name)s'
67        """
68        value = self.execute(sql % params)
69        return True if value else False
70
71
72    def _alter_set_defaults(self, field, name, params, sqls):
73        "Subcommand of alter_column that sets default values (overrideable)"
74        # Historically, we used to set defaults here.
75        # But since South 0.8, we don't ever set defaults on alter-column -- we only
76        # use database-level defaults as scaffolding when adding columns.
77        # However, we still sometimes need to remove defaults in alter-column.
78        if self._column_has_default(params):
79            sqls.append(('ALTER COLUMN %s DROP DEFAULT' % (self.quote_name(name),), []))
80
81
82    @generic.invalidate_table_constraints
83    def create_table(self, table_name, fields):
84        columns = []
85        autoinc_sql = ''
86
87        for field_name, field in fields:
88            # avoid default values in CREATE TABLE statements (#925)
89            field._suppress_default = True
90
91            col = self.column_sql(table_name, field_name, field)
92            if not col:
93                continue
94
95            columns.append(col)
96            if isinstance(field, models.AutoField):
97                field_name = field.db_column or field.column
98                autoinc_sql = connection.ops.autoinc_sql(table_name, field_name)
99
100        self.execute(self.create_table_sql % {
101            "table": self.quote_name(table_name),
102            "columns": ', '.join([col for col in columns if col]),
103        })
104
105        if autoinc_sql:
106            self.execute(autoinc_sql[0])
107            self.execute(autoinc_sql[1])
108
109    def rename_table(self, old_table_name, table_name):
110        """
111        Renames table is not supported by firebird.
112        This involve recreate all related objects (store procedure, views, triggers, etc)
113        """
114        pass
115
116    @generic.invalidate_table_constraints
117    def delete_table(self, table_name, cascade=False):
118        """
119        Deletes the table 'table_name'.
120        Firebird will also delete any triggers associated with the table.
121        """
122        super(DatabaseOperations, self).delete_table(table_name, cascade=False)
123
124        # Also, drop sequence if exists
125        sql = connection.ops.drop_sequence_sql(table_name)
126        if sql:
127            try:
128                self.execute(sql)
129            except:
130                pass
131
132    def column_sql(self, table_name, field_name, field, tablespace='', with_name=True, field_prepared=False):
133        """
134        Creates the SQL snippet for a column. Used by add_column and add_table.
135        """
136
137        # If the field hasn't already been told its attribute name, do so.
138        if not field_prepared:
139            field.set_attributes_from_name(field_name)
140
141        # hook for the field to do any resolution prior to it's attributes being queried
142        if hasattr(field, 'south_init'):
143            field.south_init()
144
145        # Possible hook to fiddle with the fields (e.g. defaults & TEXT on MySQL)
146        field = self._field_sanity(field)
147
148        try:
149            sql = field.db_type(connection=self._get_connection())
150        except TypeError:
151            sql = field.db_type()
152
153        if sql:
154            # Some callers, like the sqlite stuff, just want the extended type.
155            if with_name:
156                field_output = [self.quote_name(field.column), sql]
157            else:
158                field_output = [sql]
159
160            if field.primary_key:
161                field_output.append('NOT NULL PRIMARY KEY')
162            elif field.unique:
163                # Just use UNIQUE (no indexes any more, we have delete_unique)
164                field_output.append('UNIQUE')
165
166            sql = ' '.join(field_output)
167            sqlparams = ()
168
169            # if the field is "NOT NULL" and a default value is provided, create the column with it
170            # this allows the addition of a NOT NULL field to a table with existing rows
171            if not getattr(field, '_suppress_default', False):
172                if field.has_default():
173                    default = field.get_default()
174                    # If the default is actually None, don't add a default term
175                    if default is not None:
176                        # If the default is a callable, then call it!
177                        if callable(default):
178                            default = default()
179                        # Now do some very cheap quoting. TODO: Redesign return values to avoid this.
180                        if isinstance(default, string_types):
181                            default = "'%s'" % default.replace("'", "''")
182                        elif isinstance(default, (datetime.date, datetime.time, datetime.datetime)):
183                            default = "'%s'" % default
184                        elif isinstance(default, bool):
185                            default = int(default)
186                        # Escape any % signs in the output (bug #317)
187                        if isinstance(default, string_types):
188                            default = default.replace("%", "%%")
189                        # Add it in
190                        sql += " DEFAULT %s"
191                        sqlparams = (default)
192                elif (not field.null and field.blank) or (field.get_default() == ''):
193                    if field.empty_strings_allowed and self._get_connection().features.interprets_empty_strings_as_nulls:
194                        sql += " DEFAULT ''"
195                    # Error here would be nice, but doesn't seem to play fair.
196                    #else:
197                    #    raise ValueError("Attempting to add a non null column that isn't character based without an explicit default value.")
198
199            # Firebird need set not null after of default value keyword
200            if not field.primary_key and not field.null:
201                sql += ' NOT NULL'
202
203            if field.rel and self.supports_foreign_keys:
204                self.add_deferred_sql(
205                    self.foreign_key_sql(
206                        table_name,
207                        field.column,
208                        field.rel.to._meta.db_table,
209                        field.rel.to._meta.get_field(field.rel.field_name).column
210                    )
211                )
212
213        # Things like the contrib.gis module fields have this in 1.1 and below
214        if hasattr(field, 'post_create_sql'):
215            for stmt in field.post_create_sql(no_style(), table_name):
216                self.add_deferred_sql(stmt)
217
218        # Avoid double index creation (#1317)
219        # Firebird creates an index implicity for each foreign key field
220        # sql_indexes_for_field tries to create an index for that field too
221        if not field.rel:
222            # In 1.2 and above, you have to ask the DatabaseCreation stuff for it.
223            # This also creates normal indexes in 1.1.
224            if hasattr(self._get_connection().creation, "sql_indexes_for_field"):
225                # Make a fake model to pass in, with only db_table
226                model = self.mock_model("FakeModelForGISCreation", table_name)
227                for stmt in self._get_connection().creation.sql_indexes_for_field(model, field, no_style()):
228                    self.add_deferred_sql(stmt)
229
230        if sql:
231            return sql % sqlparams
232        else:
233            return None
234
235
236    def _drop_constraints(self, table_name, name, field):
237        if self.has_check_constraints:
238            check_constraints = self._constraints_affecting_columns(table_name, [name], "CHECK")
239            for constraint in check_constraints:
240                self.execute(self.delete_check_sql % {
241                    'table': self.quote_name(table_name),
242                    'constraint': self.quote_name(constraint),
243                })
244
245        # Drop or add UNIQUE constraint
246        unique_constraint = list(self._constraints_affecting_columns(table_name, [name], "UNIQUE"))
247        if field.unique and not unique_constraint:
248            self.create_unique(table_name, [name])
249        elif not field.unique and unique_constraint:
250            self.delete_unique(table_name, [name])
251
252        # Drop all foreign key constraints
253        try:
254            self.delete_foreign_key(table_name, name)
255        except ValueError:
256            # There weren't any
257            pass
258
259
260    @generic.invalidate_table_constraints
261    def alter_column(self, table_name, name, field, explicit_name=True, ignore_constraints=False):
262        """
263        Alters the given column name so it will match the given field.
264        Note that conversion between the two by the database must be possible.
265        Will not automatically add _id by default; to have this behavour, pass
266        explicit_name=False.
267
268        @param table_name: The name of the table to add the column to
269        @param name: The name of the column to alter
270        @param field: The new field definition to use
271        """
272
273        if self.dry_run:
274            if self.debug:
275                print('   - no dry run output for alter_column() due to dynamic DDL, sorry')
276            return
277
278
279        # hook for the field to do any resolution prior to it's attributes being queried
280        if hasattr(field, 'south_init'):
281            field.south_init()
282
283        # Add _id or whatever if we need to
284        field.set_attributes_from_name(name)
285        if not explicit_name:
286            name = field.column
287        else:
288            field.column = name
289
290        if not ignore_constraints:
291            # Drop all check constraints. Note that constraints will be added back
292            # with self.alter_string_set_type and self.alter_string_drop_null.
293            self._drop_constraints(table_name, name, field)
294
295        # First, change the type
296        params = {
297            "column": self.quote_name(name),
298            "type": self._db_type_for_alter_column(field),
299            "table_name": table_name
300        }
301
302        # SQLs is a list of (SQL, values) pairs.
303        sqls = []
304        sqls_extra = []
305
306        # Only alter the column if it has a type (Geometry ones sometimes don't)
307        if params["type"] is not None:
308            sqls.append((self.alter_string_set_type % params, []))
309
310        # Add any field- and backend- specific modifications
311        self._alter_add_column_mods(field, name, params, sqls)
312
313        # Next, nullity: modified, firebird doesn't support DROP NOT NULL
314        sqls_extra.append(self._alter_column_set_null(table_name, name, field.null))
315
316        # Next, set any default
317        self._alter_set_defaults(field, name, params, sqls)
318
319        # Finally, actually change the column
320        if self.allows_combined_alters:
321            sqls, values = list(zip(*sqls))
322            self.execute(
323                "ALTER TABLE %s %s;" % (self.quote_name(table_name), ", ".join(sqls)),
324                generic.flatten(values),
325            )
326        else:
327            # Databases like e.g. MySQL don't like more than one alter at once.
328            for sql, values in sqls:
329                try:
330                    self.execute("ALTER TABLE %s %s;" % (self.quote_name(table_name), sql), values)
331                except DatabaseError as e:
332                    print(e)
333
334
335        # Execute extra sql, which don't need ALTER TABLE statement
336        for sql in sqls_extra:
337            self.execute(sql)
338
339        if not ignore_constraints:
340            # Add back FK constraints if needed
341            if field.rel and self.supports_foreign_keys:
342                self.execute(
343                    self.foreign_key_sql(
344                        table_name,
345                        field.column,
346                        field.rel.to._meta.db_table,
347                        field.rel.to._meta.get_field(field.rel.field_name).column
348                    )
349                )
350
351    @generic.copy_column_constraints
352    @generic.delete_column_constraints
353    def rename_column(self, table_name, old, new):
354        if old == new:
355            # Short-circuit out
356            return []
357
358        self.execute('ALTER TABLE %s ALTER %s TO %s;' % (
359            self.quote_name(table_name),
360            self.quote_name(old),
361            self.quote_name(new),
362        ))
363