Commits

Anonymous committed 286a7b6

First commit for firebird support

  • Participants
  • Parent commits fbc8b18

Comments (0)

Files changed (2)

File south/db/__init__.py

     'django.db.backends.oracle': 'oracle',
     'sql_server.pyodbc': 'sql_server.pyodbc', #django-pyodbc
     'sqlserver_ado': 'sql_server.pyodbc', #django-mssql
+    'firebird': 'firebird', #django-firebird
     'django.contrib.gis.db.backends.postgis': 'postgresql_psycopg2',
     'django.contrib.gis.db.backends.spatialite': 'sqlite3',
     'django.contrib.gis.db.backends.mysql': 'mysql',

File south/db/firebird.py

+# firebird
+
+import datetime
+
+from django.db import connection, models
+from django.core.management.color import no_style
+from django.db.utils import DatabaseError
+
+from south.db import generic
+
+class DatabaseOperations(generic.DatabaseOperations):
+    backend_name = 'firebird'
+    alter_string_set_type = 'ALTER %(column)s TYPE %(type)s'
+    alter_string_set_default =  'ALTER %(column)s SET DEFAULT %(default)s;'
+    alter_string_drop_null = ''
+    add_column_string = 'ALTER TABLE %s ADD %s;'
+    allows_combined_alters = False
+
+    def _fill_constraint_cache(self, db_name, table_name):
+        self._constraint_cache.setdefault(db_name, {})
+        self._constraint_cache[db_name][table_name] = {}
+
+        rows = self.execute("""
+            SELECT
+                rc.RDB$CONSTRAINT_NAME,
+                rc.RDB$CONSTRAINT_TYPE,
+                cc.RDB$TRIGGER_NAME
+            FROM rdb$relation_constraints rc
+            JOIN rdb$check_constraints cc
+            ON rc.rdb$constraint_name = cc.rdb$constraint_name
+            WHERE rc.rdb$constraint_type = 'NOT NULL'
+            AND rc.rdb$relation_name = '%s'
+            """ % table_name)
+
+        for constraint, kind, column in rows:
+           self._constraint_cache[db_name][table_name].setdefault(column, set())
+           self._constraint_cache[db_name][table_name][column].add((kind, constraint))
+        return
+
+    def _alter_column_set_null(self, table_name, column_name, is_null):
+        sql = """
+            UPDATE RDB$RELATION_FIELDS SET RDB$NULL_FLAG = %(null_flag)s
+            WHERE RDB$FIELD_NAME = '%(column)s'
+            AND RDB$RELATION_NAME = '%(table_name)s'
+        """
+        null_flag = 'NULL' if is_null else '1'
+        return sql % {
+            'null_flag': null_flag,
+            'column': column_name.upper(),
+            'table_name': table_name.upper()
+        }
+
+    def _column_has_default(self, params):
+        sql = """
+            SELECT a.RDB$DEFAULT_VALUE
+            FROM RDB$RELATION_FIELDS a
+            WHERE a.RDB$FIELD_NAME = '%(column)s'
+            AND a.RDB$RELATION_NAME = '%(table_name)s'
+        """
+        value = self.execute(sql % params)
+        return True if value else False
+
+
+    def _alter_set_defaults(self, field, name, params, sqls):
+        "Subcommand of alter_column that sets default values (overrideable)"
+        # Next, set any default
+        if not field.null and field.has_default():
+            default = field.get_default()
+            sqls.append(('ALTER COLUMN %s SET DEFAULT %%s ' % (self.quote_name(name),), [default]))
+        elif self._column_has_default(params):
+            sqls.append(('ALTER COLUMN %s DROP DEFAULT' % (self.quote_name(name),), []))
+
+
+    @generic.invalidate_table_constraints
+    def create_table(self, table_name, fields): 
+        qn = self.quote_name(table_name)
+        columns = []
+        autoinc_sql = ''
+
+        for field_name, field in fields:
+            col = self.column_sql(table_name, field_name, field)
+            if not col:
+                continue
+            #col = self.adj_column_sql(col)
+
+            columns.append(col)
+            if isinstance(field, models.AutoField):
+                autoinc_sql = connection.ops.autoinc_sql(table_name, field_name)
+
+        sql = 'CREATE TABLE %s (%s);' % (qn, ', '.join([col for col in columns]))
+        self.execute(sql)
+        if autoinc_sql:
+            self.execute(autoinc_sql[0])
+            self.execute(autoinc_sql[1])
+
+
+    def column_sql(self, table_name, field_name, field, tablespace='', with_name=True, field_prepared=False):
+        """
+        Creates the SQL snippet for a column. Used by add_column and add_table.
+        """
+
+        # If the field hasn't already been told its attribute name, do so.
+        if not field_prepared:
+            field.set_attributes_from_name(field_name)
+
+        # hook for the field to do any resolution prior to it's attributes being queried
+        if hasattr(field, 'south_init'):
+            field.south_init()
+
+        # Possible hook to fiddle with the fields (e.g. defaults & TEXT on MySQL)
+        field = self._field_sanity(field)
+
+        try:
+            sql = field.db_type(connection=self._get_connection())
+        except TypeError:
+            sql = field.db_type()
+
+        if sql:
+            # Some callers, like the sqlite stuff, just want the extended type.
+            if with_name:
+                field_output = [self.quote_name(field.column), sql]
+            else:
+                field_output = [sql]
+
+            if field.primary_key:
+                field_output.append('NOT NULL PRIMARY KEY')
+            elif field.unique:
+                # Just use UNIQUE (no indexes any more, we have delete_unique)
+                field_output.append('UNIQUE')
+
+            tablespace = field.db_tablespace or tablespace
+            if tablespace and getattr(self._get_connection().features, "supports_tablespaces", False) and field.unique:
+                # We must specify the index tablespace inline, because we
+                # won't be generating a CREATE INDEX statement for this field.
+                field_output.append(self._get_connection().ops.tablespace_sql(tablespace, inline=True))
+
+            sql = ' '.join(field_output)
+
+            sqlparams = ()
+            # if the field is "NOT NULL" and a default value is provided, create the column with it
+            # this allows the addition of a NOT NULL field to a table with existing rows
+            if not getattr(field, '_suppress_default', False):
+                if field.has_default():
+                    default = field.get_default()
+                    # If the default is actually None, don't add a default term
+                    if default is not None:
+                        # If the default is a callable, then call it!
+                        if callable(default):
+                            default = default()
+                        # Now do some very cheap quoting. TODO: Redesign return values to avoid this.
+                        if isinstance(default, basestring):
+                            default = "'%s'" % default.replace("'", "''")
+                        elif isinstance(default, (datetime.date, datetime.time, datetime.datetime)):
+                            default = "'%s'" % default
+                        # Escape any % signs in the output (bug #317)
+                        if isinstance(default, basestring):
+                            default = default.replace("%", "%%")
+                        # Add it in
+                        sql += " DEFAULT %s"
+                        sqlparams = (default)
+                elif (not field.null and field.blank) or (field.get_default() == ''):
+                    if field.empty_strings_allowed and self._get_connection().features.interprets_empty_strings_as_nulls:
+                        sql += " DEFAULT ''"
+                    # Error here would be nice, but doesn't seem to play fair.
+                    #else:
+                    #    raise ValueError("Attempting to add a non null column that isn't character based without an explicit default value.")
+
+            # Firebird need set not null after of default value keyword
+            if not field.null:
+                field_output.append('NOT NULL')
+
+            if field.rel and self.supports_foreign_keys:
+                self.add_deferred_sql(
+                    self.foreign_key_sql(
+                        table_name,
+                        field.column,
+                        field.rel.to._meta.db_table,
+                        field.rel.to._meta.get_field(field.rel.field_name).column
+                    )
+                )
+
+        # Things like the contrib.gis module fields have this in 1.1 and below
+        if hasattr(field, 'post_create_sql'):
+            for stmt in field.post_create_sql(no_style(), table_name):
+                self.add_deferred_sql(stmt)
+
+        # In 1.2 and above, you have to ask the DatabaseCreation stuff for it.
+        # This also creates normal indexes in 1.1.
+        if hasattr(self._get_connection().creation, "sql_indexes_for_field"):
+            # Make a fake model to pass in, with only db_table
+            model = self.mock_model("FakeModelForGISCreation", table_name)
+            for stmt in self._get_connection().creation.sql_indexes_for_field(model, field, no_style()):
+                self.add_deferred_sql(stmt)
+
+        if sql:
+            return sql % sqlparams
+        else:
+            return None
+
+
+    def _drop_constraints(self, table_name, name, field):
+        if self.has_check_constraints:
+            check_constraints = self._constraints_affecting_columns(table_name, [name], "CHECK")
+            for constraint in check_constraints:
+                self.execute(self.delete_check_sql % {
+                    'table': self.quote_name(table_name),
+                    'constraint': self.quote_name(constraint),
+                })
+
+        # Drop or add UNIQUE constraint
+        unique_constraint = list(self._constraints_affecting_columns(table_name, [name], "UNIQUE"))
+        if field.unique and not unique_constraint:
+            self.create_unique(table_name, [name])
+        elif not field.unique and unique_constraint:
+            self.delete_unique(table_name, [name])
+
+        # Drop all foreign key constraints
+        try:
+            self.delete_foreign_key(table_name, name)
+        except ValueError:
+            # There weren't any
+            pass
+
+
+    @generic.invalidate_table_constraints
+    def alter_column(self, table_name, name, field, explicit_name=True, ignore_constraints=False):
+        """
+        Alters the given column name so it will match the given field.
+        Note that conversion between the two by the database must be possible.
+        Will not automatically add _id by default; to have this behavour, pass
+        explicit_name=False.
+
+        @param table_name: The name of the table to add the column to
+        @param name: The name of the column to alter
+        @param field: The new field definition to use
+        """
+
+        if self.dry_run:
+            return
+
+        # hook for the field to do any resolution prior to it's attributes being queried
+        if hasattr(field, 'south_init'):
+            field.south_init()
+
+        # Add _id or whatever if we need to
+        field.set_attributes_from_name(name)
+        if not explicit_name:
+            name = field.column
+        else:
+            field.column = name
+
+        if not ignore_constraints:
+            # Drop all check constraints. Note that constraints will be added back
+            # with self.alter_string_set_type and self.alter_string_drop_null.
+            self._drop_constraints(table_name, name, field)
+
+        # First, change the type
+        params = {
+            "column": self.quote_name(name),
+            "type": self._db_type_for_alter_column(field),
+            "table_name": table_name
+        }
+
+        # SQLs is a list of (SQL, values) pairs.
+        sqls = []
+        sqls_extra = []
+
+        # Only alter the column if it has a type (Geometry ones sometimes don't)
+        if params["type"] is not None:
+            sqls.append((self.alter_string_set_type % params, []))
+
+        # Add any field- and backend- specific modifications
+        self._alter_add_column_mods(field, name, params, sqls)
+
+        # Next, nullity: modified, firebird doesn't support DROP NOT NULL
+        sqls_extra.append(self._alter_column_set_null(table_name, name, field.null))
+
+        # Next, set any default
+        self._alter_set_defaults(field, name, params, sqls)
+
+        # Finally, actually change the column
+        if self.allows_combined_alters:
+            sqls, values = zip(*sqls)
+            self.execute(
+                "ALTER TABLE %s %s;" % (self.quote_name(table_name), ", ".join(sqls)),
+                generic.flatten(values),
+            )
+        else:
+            # Databases like e.g. MySQL don't like more than one alter at once.
+            for sql, values in sqls:
+                try:
+                    self.execute("ALTER TABLE %s %s;" % (self.quote_name(table_name), sql), values)
+                except DatabaseError as e:
+                    print e
+
+
+        # Execute extra sql, which don't need ALTER TABLE statement
+        for sql in sqls_extra:
+            self.execute(sql)
+
+        if not ignore_constraints:
+            # Add back FK constraints if needed
+            if field.rel and self.supports_foreign_keys:
+                self.execute(
+                    self.foreign_key_sql(
+                        table_name,
+                        field.column,
+                        field.rel.to._meta.db_table,
+                        field.rel.to._meta.get_field(field.rel.field_name).column
+                    )
+                )