web/lib/django_extensions/management/commands/sqldiff.py
changeset 3 526ebd3988b0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/sqldiff.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,572 @@
+"""
+sqldiff.py - Prints the (approximated) difference between models and database
+
+TODO:
+ - better support for relations
+ - better support for constraints (mainly postgresql?)
+ - support for table spaces with postgresql
+ 
+KNOWN ISSUES:
+ - MySQL has by far the most problems with introspection. Please be
+   carefull when using MySQL with sqldiff. 
+   - Booleans are reported back as Integers, so there's know way to know if
+     there was a real change.
+   - Varchar sizes are reported back without unicode support so there size
+     may change in comparison to the real length of the varchar.   
+   - Some of the 'fixes' to counter these problems might create false 
+     positives or false negatives.
+"""
+
+from django.core.management.base import BaseCommand
+from django.core.management import sql as _sql
+from django.core.management import CommandError
+from django.core.management.color import no_style
+from django.db import transaction, connection
+from django.db.models.fields import IntegerField
+from optparse import make_option
+
+ORDERING_FIELD = IntegerField('_order', null=True)
+
+def flatten(l, ltypes=(list, tuple)):
+    ltype = type(l)
+    l = list(l)
+    i = 0
+    while i < len(l):
+        while isinstance(l[i], ltypes):
+            if not l[i]:
+                l.pop(i)
+                i -= 1
+                break
+            else:
+                l[i:i + 1] = l[i]
+        i += 1
+    return ltype(l)
+
+class SQLDiff(object):
+    DATA_TYPES_REVERSE_OVERRIDE = {
+    }
+    
+    DIFF_TYPES = [
+        'comment',
+        'table-missing-in-db',
+        'field-missing-in-db',
+        'field-missing-in-model',
+        'index-missing-in-db',
+        'index-missing-in-model',
+        'unique-missing-in-db',
+        'unique-missing-in-model',
+        'field-type-differ',
+        'field-parameter-differ',
+    ]
+    DIFF_TEXTS = {
+        'comment': 'comment: %(0)s',
+        'table-missing-in-db': "table '%(0)s' missing in database",
+        'field-missing-in-db' : "field '%(1)s' defined in model but missing in database",
+        'field-missing-in-model' : "field '%(1)s' defined in database but missing in model",
+        'index-missing-in-db' : "field '%(1)s' INDEX defined in model but missing in database",
+        'index-missing-in-model' : "field '%(1)s' INDEX defined in database schema but missing in model",
+        'unique-missing-in-db' : "field '%(1)s' UNIQUE defined in model but missing in database",
+        'unique-missing-in-model' : "field '%(1)s' UNIQUE defined in database schema but missing in model",
+        'field-type-differ' : "field '%(1)s' not of same type: db='%(3)s', model='%(2)s'",
+        'field-parameter-differ' : "field '%(1)s' parameters differ: db='%(3)s', model='%(2)s'",
+    }
+
+    SQL_FIELD_MISSING_IN_DB = lambda self, style, qn, args: "%s %s\n\t%s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('ADD'), style.SQL_FIELD(qn(args[1])), style.SQL_COLTYPE(args[2]))
+    SQL_FIELD_MISSING_IN_MODEL = lambda self, style, qn, args: "%s %s\n\t%s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('DROP COLUMN'), style.SQL_FIELD(qn(args[1])))
+    SQL_INDEX_MISSING_IN_DB = lambda self, style, qn, args: "%s %s\n\t%s %s (%s);" % (style.SQL_KEYWORD('CREATE INDEX'), style.SQL_TABLE(qn("%s_idx" % '_'.join(args[0:2]))), style.SQL_KEYWORD('ON'), style.SQL_TABLE(qn(args[0])), style.SQL_FIELD(qn(args[1])))
+    # FIXME: need to lookup index name instead of just appending _idx to table + fieldname
+    SQL_INDEX_MISSING_IN_MODEL = lambda self, style, qn, args: "%s %s;" % (style.SQL_KEYWORD('DROP INDEX'), style.SQL_TABLE(qn("%s_idx" % '_'.join(args[0:2]))))
+    SQL_UNIQUE_MISSING_IN_DB = lambda self, style, qn, args: "%s %s\n\t%s %s (%s);" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('ADD'), style.SQL_KEYWORD('UNIQUE'), style.SQL_FIELD(qn(args[1])))
+    # FIXME: need to lookup unique constraint name instead of appending _key to table + fieldname
+    SQL_UNIQUE_MISSING_IN_MODEL = lambda self, style, qn, args: "%s %s\n\t%s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('DROP'), style.SQL_KEYWORD('CONSTRAINT'), style.SQL_TABLE(qn("%s_key" % ('_'.join(args[:2])))))
+    SQL_FIELD_TYPE_DIFFER = lambda self, style, qn, args:  "%s %s\n\t%s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD("MODIFY"), style.SQL_FIELD(qn(args[1])), style.SQL_COLTYPE(args[2]))
+    SQL_FIELD_PARAMETER_DIFFER = lambda self, style, qn, args:  "%s %s\n\t%s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD("MODIFY"), style.SQL_FIELD(qn(args[1])), style.SQL_COLTYPE(args[2]))
+    SQL_COMMENT = lambda self, style, qn, args: style.NOTICE('-- Comment: %s' % style.SQL_TABLE(args[0]))
+    SQL_TABLE_MISSING_IN_DB = lambda self, style, qn, args: style.NOTICE('-- Table missing: %s' % args[0])
+
+    def __init__(self, app_models, options):
+        self.app_models = app_models
+        self.options = options
+        self.dense = options.get('dense_output', False)
+
+        try:
+            self.introspection = connection.introspection
+        except AttributeError:
+            from django.db import get_introspection_module
+            self.introspection = get_introspection_module()
+
+        self.cursor = connection.cursor()
+        self.django_tables = self.get_django_tables(options.get('only_existing', True))
+        self.db_tables = self.introspection.get_table_list(self.cursor)
+        self.differences = []
+        self.unknown_db_fields = {}
+        
+        self.DIFF_SQL = {
+            'comment': self.SQL_COMMENT,
+            'table-missing-in-db': self.SQL_TABLE_MISSING_IN_DB,
+            'field-missing-in-db': self.SQL_FIELD_MISSING_IN_DB,
+            'field-missing-in-model': self.SQL_FIELD_MISSING_IN_MODEL,
+            'index-missing-in-db': self.SQL_INDEX_MISSING_IN_DB,
+            'index-missing-in-model': self.SQL_INDEX_MISSING_IN_MODEL,
+            'unique-missing-in-db': self.SQL_UNIQUE_MISSING_IN_DB,
+            'unique-missing-in-model': self.SQL_UNIQUE_MISSING_IN_MODEL,
+            'field-type-differ': self.SQL_FIELD_TYPE_DIFFER,
+            'field-parameter-differ': self.SQL_FIELD_PARAMETER_DIFFER,
+        }
+
+
+    def add_app_model_marker(self, app_label, model_name):
+        self.differences.append((app_label, model_name, []))
+        
+    def add_difference(self, diff_type, *args):
+        assert diff_type in self.DIFF_TYPES, 'Unknown difference type'
+        self.differences[-1][-1].append((diff_type, args))
+
+    def get_django_tables(self, only_existing):
+        try:
+            django_tables = self.introspection.django_table_names(only_existing=only_existing)
+        except AttributeError:
+            # backwards compatibility for before introspection refactoring (r8296)
+            try:
+                django_tables = _sql.django_table_names(only_existing=only_existing)
+            except AttributeError:
+                # backwards compatibility for before svn r7568
+                django_tables = _sql.django_table_list(only_existing=only_existing)
+        return django_tables
+
+    def sql_to_dict(self, query,param):
+        """ sql_to_dict(query, param) -> list of dicts
+
+        code from snippet at http://www.djangosnippets.org/snippets/1383/
+        """
+        cursor = connection.cursor()
+        cursor.execute(query,param)
+        fieldnames = [name[0] for name in cursor.description]
+        result = []
+        for row in cursor.fetchall():
+            rowset = []
+            for field in zip(fieldnames, row):
+                rowset.append(field)
+            result.append(dict(rowset))
+        return result
+
+    def get_field_model_type(self, field):
+        return field.db_type()
+
+    def get_field_db_type(self, description, field=None, table_name=None):
+        from django.db import models
+        # DB-API cursor.description
+        #(name, type_code, display_size, internal_size, precision, scale, null_ok) = description
+        type_code = description[1]
+        if type_code in self.DATA_TYPES_REVERSE_OVERRIDE:
+            reverse_type = self.DATA_TYPES_REVERSE_OVERRIDE[type_code]
+        else:
+            try:
+                try:
+                    reverse_type = self.introspection.data_types_reverse[type_code]
+                except AttributeError:
+                    # backwards compatibility for before introspection refactoring (r8296)
+                    reverse_type = self.introspection.DATA_TYPES_REVERSE.get(type_code)
+            except KeyError:
+                # type_code not found in data_types_reverse map
+                key = (self.differences[-1][:2], description[:2])
+                if key not in self.unknown_db_fields:
+                    self.unknown_db_fields[key] = 1
+                    self.add_difference('comment', "Unknown database type for field '%s' (%s)" % (description[0], type_code))
+                return None
+        
+        kwargs = {}
+        if isinstance(reverse_type, tuple):
+            kwargs.update(reverse_type[1])
+            reverse_type = reverse_type[0]
+
+        if reverse_type == "CharField" and description[3]:
+            kwargs['max_length'] = description[3]
+
+        if reverse_type == "DecimalField":
+            kwargs['max_digits'] = description[4]
+            kwargs['decimal_places'] = description[5]
+
+        if description[6]:
+            kwargs['blank'] = True
+            if not reverse_type in ('TextField', 'CharField'):
+                kwargs['null'] = True
+
+        field_db_type = getattr(models, reverse_type)(**kwargs).db_type()
+        return field_db_type
+
+    def strip_parameters(self, field_type):
+        if field_type:
+            return field_type.split(" ")[0].split("(")[0]
+        return field_type
+
+    def find_unique_missing_in_db(self, meta, table_indexes, table_name):
+        for field in meta.fields:
+            if field.unique:
+                attname = field.db_column or field.attname
+                if attname in table_indexes and table_indexes[attname]['unique']:
+                    continue
+                self.add_difference('unique-missing-in-db', table_name, attname)
+
+    def find_unique_missing_in_model(self, meta, table_indexes, table_name):
+        # TODO: Postgresql does not list unique_togethers in table_indexes
+        #       MySQL does
+        fields = dict([(field.db_column or field.name, field.unique) for field in meta.fields])
+        for att_name, att_opts in table_indexes.iteritems():
+            if att_opts['unique'] and att_name in fields and not fields[att_name]:
+                if att_name in flatten(meta.unique_together): continue
+                self.add_difference('unique-missing-in-model', table_name, att_name)
+
+    def find_index_missing_in_db(self, meta, table_indexes, table_name):
+        for field in meta.fields:
+            if field.db_index:
+                attname = field.db_column or field.attname
+                if not attname in table_indexes:
+                    self.add_difference('index-missing-in-db', table_name, attname)
+
+    def find_index_missing_in_model(self, meta, table_indexes, table_name):
+        fields = dict([(field.name, field) for field in meta.fields])
+        for att_name, att_opts in table_indexes.iteritems():
+            if att_name in fields:
+                field = fields[att_name]
+                if field.db_index: continue
+                if att_opts['primary_key'] and field.primary_key: continue
+                if att_opts['unique'] and field.unique: continue
+                if att_opts['unique'] and att_name in flatten(meta.unique_together): continue
+                self.add_difference('index-missing-in-model', table_name, att_name)
+
+    def find_field_missing_in_model(self, fieldmap, table_description, table_name):
+        for row in table_description:
+            if row[0] not in fieldmap:
+                self.add_difference('field-missing-in-model', table_name, row[0])
+
+    def find_field_missing_in_db(self, fieldmap, table_description, table_name):
+        db_fields = [row[0] for row in table_description]
+        for field_name, field in fieldmap.iteritems():
+            if field_name not in db_fields:
+                self.add_difference('field-missing-in-db', table_name, field_name, field.db_type())
+
+    def find_field_type_differ(self, meta, table_description, table_name, func=None):
+        db_fields = dict([(row[0], row) for row in table_description])
+        for field in meta.fields:
+            if field.name not in db_fields: continue
+            description = db_fields[field.name]
+
+            model_type = self.strip_parameters(self.get_field_model_type(field))
+            db_type = self.strip_parameters(self.get_field_db_type(description, field))
+
+            # use callback function if defined
+            if func:
+                model_type, db_type = func(field, description, model_type, db_type)
+
+            if not model_type==db_type:
+                self.add_difference('field-type-differ', table_name, field.name, model_type, db_type)
+
+    def find_field_parameter_differ(self, meta, table_description, table_name, func=None):
+        db_fields = dict([(row[0], row) for row in table_description])
+        for field in meta.fields:
+            if field.name not in db_fields: continue
+            description = db_fields[field.name]
+
+            model_type = self.get_field_model_type(field)
+            db_type = self.get_field_db_type(description, field, table_name)
+
+            if not self.strip_parameters(model_type)==self.strip_parameters(db_type):
+                continue
+            
+            # use callback function if defined
+            if func:
+                model_type, db_type = func(field, description, model_type, db_type)
+
+            if not model_type==db_type:
+                self.add_difference('field-parameter-differ', table_name, field.name, model_type, db_type)
+    
+    @transaction.commit_manually
+    def find_differences(self):
+        cur_app_label = None
+        for app_model in self.app_models:
+            meta = app_model._meta
+            table_name = meta.db_table
+            app_label = meta.app_label
+
+            if cur_app_label!=app_label:
+                # Marker indicating start of difference scan for this table_name
+                self.add_app_model_marker(app_label, app_model.__name__)
+
+            #if not table_name in self.django_tables:
+            if not table_name in self.db_tables:
+                # Table is missing from database
+                self.add_difference('table-missing-in-db', table_name)
+                continue
+            
+            table_indexes = self.introspection.get_indexes(self.cursor, table_name)
+            fieldmap = dict([(field.db_column or field.get_attname(), field) for field in meta.fields])
+            
+            # add ordering field if model uses order_with_respect_to
+            if meta.order_with_respect_to:
+                fieldmap['_order'] = ORDERING_FIELD
+
+            try:
+                table_description = self.introspection.get_table_description(self.cursor, table_name)
+            except Exception, e:
+                model_diffs.append((app_model.__name__, [str(e).strip()]))
+                transaction.rollback() # reset transaction
+                continue
+            
+            # Fields which are defined in database but not in model
+            # 1) find: 'unique-missing-in-model'
+            self.find_unique_missing_in_model(meta, table_indexes, table_name)
+            # 2) find: 'index-missing-in-model'
+            self.find_index_missing_in_model(meta, table_indexes, table_name)
+            # 3) find: 'field-missing-in-model'
+            self.find_field_missing_in_model(fieldmap, table_description, table_name)
+
+            # Fields which are defined in models but not in database
+            # 4) find: 'field-missing-in-db'
+            self.find_field_missing_in_db(fieldmap, table_description, table_name)
+            # 5) find: 'unique-missing-in-db'
+            self.find_unique_missing_in_db(meta, table_indexes, table_name)
+            # 6) find: 'index-missing-in-db'
+            self.find_index_missing_in_db(meta, table_indexes, table_name)
+
+            # Fields which have a different type or parameters
+            # 7) find: 'type-differs'
+            self.find_field_type_differ(meta, table_description, table_name)
+            # 8) find: 'type-parameter-differs'
+            self.find_field_parameter_differ(meta, table_description, table_name)
+
+    def print_diff(self, style=no_style()):
+        """ print differences to stdout """
+        if self.options.get('sql', True):
+            self.print_diff_sql(style)
+        else:
+            self.print_diff_text(style)
+
+    def print_diff_text(self, style):
+        cur_app_label = None
+        for app_label, model_name, diffs in self.differences:
+            if not diffs: continue
+            if not self.dense and cur_app_label != app_label:
+                print style.NOTICE("+ Application:"), style.SQL_TABLE(app_label)
+                cur_app_label = app_label
+            if not self.dense:
+                print style.NOTICE("|-+ Differences for model:"), style.SQL_TABLE(model_name)
+            for diff in diffs:
+                diff_type, diff_args = diff
+                text = self.DIFF_TEXTS[diff_type] % dict((str(i), style.SQL_TABLE(e)) for i, e in enumerate(diff_args))
+                text = "'".join(i%2==0 and style.ERROR_OUTPUT(e) or e for i, e in enumerate(text.split("'")))
+                if not self.dense:
+                    print style.NOTICE("|--+"), text
+                else:
+                    print style.NOTICE("App"), style.SQL_TABLE(app_name), style.NOTICE('Model'), style.SQL_TABLE(model_name), text
+
+    def print_diff_sql(self, style):
+        cur_app_label = None
+        qn = connection.ops.quote_name
+        print style.SQL_KEYWORD("BEGIN;")
+        for app_label, model_name, diffs in self.differences:
+            if not diffs: continue
+            if not self.dense and cur_app_label != app_label:
+                print style.NOTICE("-- Application: %s" % style.SQL_TABLE(app_label))
+                cur_app_label = app_label
+            if not self.dense:
+                print style.NOTICE("-- Model: %s" % style.SQL_TABLE(model_name))
+            for diff in diffs:
+                diff_type, diff_args = diff
+                text = self.DIFF_SQL[diff_type](style, qn, diff_args)
+                if self.dense:
+                    text = text.replace("\n\t", " ")
+                print text
+        print style.SQL_KEYWORD("COMMIT;")
+        
+class GenericSQLDiff(SQLDiff):
+    pass
+
+class MySQLDiff(SQLDiff):
+    # All the MySQL hacks together create something of a problem
+    # Fixing one bug in MySQL creates another issue. So just keep in mind
+    # that this is way unreliable for MySQL atm.
+    def get_field_db_type(self, description, field=None, table_name=None):
+        from MySQLdb.constants import FIELD_TYPE
+        # weird bug? in mysql db-api where it returns three times the correct value for field length
+        # if i remember correctly it had something todo with unicode strings
+        # TODO: Fix this is a more meaningful and better understood manner
+        description = list(description)
+        if description[1] not in [FIELD_TYPE.TINY, FIELD_TYPE.SHORT]: # exclude tinyints from conversion.
+            description[3] = description[3]/3
+            description[4] = description[4]/3
+        db_type = super(MySQLDiff, self).get_field_db_type(description)
+        if not db_type:
+            return
+        if field:
+            if field.primary_key and db_type=='integer':
+                db_type += ' AUTO_INCREMENT'
+            # MySQL isn't really sure about char's and varchar's like sqlite
+            field_type = self.get_field_model_type(field)
+            # Fix char/varchar inconsistencies
+            if self.strip_parameters(field_type)=='char' and self.strip_parameters(db_type)=='varchar':
+                db_type = db_type.lstrip("var")
+            # They like to call 'bool's 'tinyint(1)' and introspection makes that a integer
+            # just convert it back to it's proper type, a bool is a bool and nothing else.
+            if db_type=='integer' and description[1]==FIELD_TYPE.TINY and description[4]==1:
+                db_type = 'bool'
+            if db_type=='integer' and description[1]==FIELD_TYPE.SHORT:
+                db_type = 'smallint UNSIGNED' # FIXME: what about if it's not UNSIGNED ?
+        return db_type
+
+class SqliteSQLDiff(SQLDiff):
+    # Unique does not seem to be implied on Sqlite for Primary_key's
+    # if this is more generic among databases this might be usefull
+    # to add to the superclass's find_unique_missing_in_db method
+    def find_unique_missing_in_db(self, meta, table_indexes, table_name):
+        for field in meta.fields:
+            if field.unique:
+                attname = field.attname
+                if attname in table_indexes and table_indexes[attname]['unique']:
+                    continue
+                if table_indexes[attname]['primary_key']:
+                    continue
+                self.add_difference('unique-missing-in-db', table_name, attname)
+
+    # Finding Indexes by using the get_indexes dictionary doesn't seem to work
+    # for sqlite.
+    def find_index_missing_in_db(self, meta, table_indexes, table_name):
+        pass
+
+    def find_index_missing_in_model(self, meta, table_indexes, table_name):
+        pass
+
+    def get_field_db_type(self, description, field=None, table_name=None):
+        db_type = super(SqliteSQLDiff, self).get_field_db_type(description)
+        if not db_type:
+            return
+        if field:
+            field_type = self.get_field_model_type(field)
+            # Fix char/varchar inconsistencies
+            if self.strip_parameters(field_type)=='char' and self.strip_parameters(db_type)=='varchar':
+                db_type = db_type.lstrip("var")
+        return db_type
+
+class PostgresqlSQLDiff(SQLDiff):
+    DATA_TYPES_REVERSE_OVERRIDE = {
+        20: 'IntegerField',
+        1042: 'CharField',
+    }
+
+    # Hopefully in the future we can add constraint checking and other more
+    # advanced checks based on this database.
+    SQL_LOAD_CONSTRAINTS = """
+    SELECT nspname, relname, conname, attname, pg_get_constraintdef(pg_constraint.oid)
+    FROM pg_constraint
+    INNER JOIN pg_attribute ON pg_constraint.conrelid = pg_attribute.attrelid AND pg_attribute.attnum = any(pg_constraint.conkey)
+    INNER JOIN pg_class ON conrelid=pg_class.oid
+    INNER JOIN pg_namespace ON pg_namespace.oid=pg_class.relnamespace
+    ORDER BY CASE WHEN contype='f' THEN 0 ELSE 1 END,contype,nspname,relname,conname;
+    """
+
+    SQL_FIELD_TYPE_DIFFER = lambda self, style, qn, args:  "%s %s\n\t%s %s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('ALTER'), style.SQL_FIELD(qn(args[1])), style.SQL_KEYWORD("TYPE"), style.SQL_COLTYPE(args[2]))
+    SQL_FIELD_PARAMETER_DIFFER = lambda self, style, qn, args:  "%s %s\n\t%s %s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('ALTER'), style.SQL_FIELD(qn(args[1])), style.SQL_KEYWORD("TYPE"), style.SQL_COLTYPE(args[2]))
+
+    def __init__(self, app_models, options):
+        SQLDiff.__init__(self, app_models, options)
+        self.check_constraints = {}
+        self.load_constraints()
+
+    def load_constraints(self):
+        for dct in self.sql_to_dict(self.SQL_LOAD_CONSTRAINTS, []):
+            key = (dct['nspname'], dct['relname'], dct['attname'])
+            if 'CHECK' in dct['pg_get_constraintdef']:
+                self.check_constraints[key] = dct
+
+    def get_field_db_type(self, description, field=None, table_name=None):
+        db_type = super(PostgresqlSQLDiff, self).get_field_db_type(description)
+        if not db_type:
+            return
+        if field:
+            if field.primary_key and db_type=='integer':
+                db_type = 'serial'
+            if table_name:
+                tablespace = field.db_tablespace
+                if tablespace=="":
+                    tablespace = "public"
+                check_constraint = self.check_constraints.get((tablespace, table_name, field.attname),{}).get('pg_get_constraintdef', None)
+                if check_constraint:
+                    check_constraint = check_constraint.replace("((", "(")
+                    check_constraint = check_constraint.replace("))", ")")
+                    check_constraint = '("'.join([')' in e and '" '.join(e.split(" ", 1)) or e for e in check_constraint.split("(")])
+                    # TODO: might be more then one constraint in definition ?
+                    db_type += ' '+check_constraint
+        return db_type
+
+    """
+    def find_field_type_differ(self, meta, table_description, table_name):
+        def callback(field, description, model_type, db_type):
+            if field.primary_key and db_type=='integer':
+                db_type = 'serial'
+            return model_type, db_type
+        super(PostgresqlSQLDiff, self).find_field_type_differs(meta, table_description, table_name, callback)
+    """
+
+DATABASE_SQLDIFF_CLASSES = {
+    'postgresql_psycopg2' : PostgresqlSQLDiff,
+    'postgresql': PostgresqlSQLDiff,
+    'mysql': MySQLDiff,
+    'sqlite3': SqliteSQLDiff,
+    'oracle': GenericSQLDiff
+}
+
+class Command(BaseCommand):
+    option_list = BaseCommand.option_list + (
+        make_option('--all-applications', '-a', action='store_true', dest='all_applications',
+                    help="Automaticly include all application from INSTALLED_APPS."),
+        make_option('--not-only-existing', '-e', action='store_false', dest='only_existing',
+                    help="Check all tables that exist in the database, not only tables that should exist based on models."),
+        make_option('--dense-output', '-d', action='store_true', dest='dense_output',
+                    help="Shows the output in dense format, normally output is spreaded over multiple lines."),
+        make_option('--output_text', '-t', action='store_false', dest='sql', default=True,
+                    help="Outputs the differences as descriptive text instead of SQL"),
+    )
+    
+    help = """Prints the (approximated) difference between models and fields in the database for the given app name(s).
+
+It indicates how columns in the database are different from the sql that would
+be generated by Django. This command is not a database migration tool. (Though
+it can certainly help) It's purpose is to show the current differences as a way
+to check/debug ur models compared to the real database tables and columns."""
+
+    output_transaction = False
+    args = '<appname appname ...>'
+
+    def handle(self, *app_labels, **options):
+        from django.db import models
+        from django.conf import settings
+
+        if settings.DATABASE_ENGINE =='dummy':
+            # This must be the "dummy" database backend, which means the user
+            # hasn't set DATABASE_ENGINE.
+            raise CommandError("Django doesn't know which syntax to use for your SQL statements,\n" +
+                "because you haven't specified the DATABASE_ENGINE setting.\n" +
+                "Edit your settings file and change DATABASE_ENGINE to something like 'postgresql' or 'mysql'.")
+
+        if options.get('all_applications', False):
+            app_models = models.get_models()
+        else:
+            if not app_labels:
+                raise CommandError('Enter at least one appname.')
+            try:
+                app_list = [models.get_app(app_label) for app_label in app_labels]
+            except (models.ImproperlyConfigured, ImportError), e:
+                raise CommandError("%s. Are you sure your INSTALLED_APPS setting is correct?" % e)
+
+            app_models = []
+            for app in app_list:
+                app_models.extend(models.get_models(app))
+
+        if not app_models:
+            raise CommandError('Unable to execute sqldiff no models founds.')
+
+        cls = DATABASE_SQLDIFF_CLASSES.get(settings.DATABASE_ENGINE, GenericSQLDiff)
+        sqldiff_instance = cls(app_models, options)
+        sqldiff_instance.find_differences()
+        sqldiff_instance.print_diff(self.style)
+        return