web/lib/django/db/backends/sqlite3/introspection.py
author ymh <ymh.work@gmail.com>
Wed, 02 Jun 2010 18:57:35 +0200
changeset 38 77b6da96e6f1
permissions -rw-r--r--
update django
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
38
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     1
import re
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     2
from django.db.backends import BaseDatabaseIntrospection
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     3
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     4
# This light wrapper "fakes" a dictionary interface, because some SQLite data
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     5
# types include variables in them -- e.g. "varchar(30)" -- and can't be matched
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     6
# as a simple dictionary lookup.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     7
class FlexibleFieldLookupDict:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     8
    # Maps SQL types to Django Field types. Some of the SQL types have multiple
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     9
    # entries here because SQLite allows for anything and doesn't normalize the
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    10
    # field type; it uses whatever was given.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    11
    base_data_types_reverse = {
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    12
        'bool': 'BooleanField',
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    13
        'boolean': 'BooleanField',
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    14
        'smallint': 'SmallIntegerField',
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    15
        'smallint unsigned': 'PositiveSmallIntegerField',
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    16
        'smallinteger': 'SmallIntegerField',
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    17
        'int': 'IntegerField',
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    18
        'integer': 'IntegerField',
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    19
        'bigint': 'BigIntegerField',
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    20
        'integer unsigned': 'PositiveIntegerField',
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    21
        'decimal': 'DecimalField',
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    22
        'real': 'FloatField',
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    23
        'text': 'TextField',
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    24
        'char': 'CharField',
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    25
        'date': 'DateField',
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    26
        'datetime': 'DateTimeField',
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    27
        'time': 'TimeField',
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    28
    }
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    29
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    30
    def __getitem__(self, key):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    31
        key = key.lower()
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    32
        try:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    33
            return self.base_data_types_reverse[key]
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    34
        except KeyError:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    35
            import re
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    36
            m = re.search(r'^\s*(?:var)?char\s*\(\s*(\d+)\s*\)\s*$', key)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    37
            if m:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    38
                return ('CharField', {'max_length': int(m.group(1))})
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    39
            raise KeyError
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    40
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    41
class DatabaseIntrospection(BaseDatabaseIntrospection):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    42
    data_types_reverse = FlexibleFieldLookupDict()
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    43
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    44
    def get_table_list(self, cursor):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    45
        "Returns a list of table names in the current database."
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    46
        # Skip the sqlite_sequence system table used for autoincrement key
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    47
        # generation.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    48
        cursor.execute("""
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    49
            SELECT name FROM sqlite_master
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    50
            WHERE type='table' AND NOT name='sqlite_sequence'
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    51
            ORDER BY name""")
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    52
        return [row[0] for row in cursor.fetchall()]
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    53
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    54
    def get_table_description(self, cursor, table_name):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    55
        "Returns a description of the table, with the DB-API cursor.description interface."
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    56
        return [(info['name'], info['type'], None, None, None, None,
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    57
                 info['null_ok']) for info in self._table_info(cursor, table_name)]
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    58
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    59
    def get_relations(self, cursor, table_name):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    60
        """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    61
        Returns a dictionary of {field_index: (field_index_other_table, other_table)}
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    62
        representing all relationships to the given table. Indexes are 0-based.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    63
        """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    64
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    65
        # Dictionary of relations to return
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    66
        relations = {}
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    67
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    68
        # Schema for this table
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    69
        cursor.execute("SELECT sql FROM sqlite_master WHERE tbl_name = %s AND type = %s", [table_name, "table"])
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    70
        results = cursor.fetchone()[0].strip()
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    71
        results = results[results.index('(')+1:results.rindex(')')]
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    72
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    73
        # Walk through and look for references to other tables. SQLite doesn't
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    74
        # really have enforced references, but since it echoes out the SQL used
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    75
        # to create the table we can look for REFERENCES statements used there.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    76
        for field_index, field_desc in enumerate(results.split(',')):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    77
            field_desc = field_desc.strip()
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    78
            if field_desc.startswith("UNIQUE"):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    79
                continue
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    80
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    81
            m = re.search('references (.*) \(["|](.*)["|]\)', field_desc, re.I)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    82
            if not m:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    83
                continue
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    84
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    85
            table, column = [s.strip('"') for s in m.groups()]
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    86
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    87
            cursor.execute("SELECT sql FROM sqlite_master WHERE tbl_name = %s", [table])
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    88
            result = cursor.fetchone()
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    89
            if not result:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    90
                continue
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    91
            other_table_results = result[0].strip()
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    92
            li, ri = other_table_results.index('('), other_table_results.rindex(')')
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    93
            other_table_results = other_table_results[li+1:ri]
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    94
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    95
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    96
            for other_index, other_desc in enumerate(other_table_results.split(',')):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    97
                other_desc = other_desc.strip()
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    98
                if other_desc.startswith('UNIQUE'):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    99
                    continue
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   100
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   101
                name = other_desc.split(' ', 1)[0].strip('"')
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   102
                if name == column:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   103
                    relations[field_index] = (other_index, table)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   104
                    break
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   105
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   106
        return relations
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   107
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   108
    def get_indexes(self, cursor, table_name):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   109
        """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   110
        Returns a dictionary of fieldname -> infodict for the given table,
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   111
        where each infodict is in the format:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   112
            {'primary_key': boolean representing whether it's the primary key,
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   113
             'unique': boolean representing whether it's a unique index}
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   114
        """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   115
        indexes = {}
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   116
        for info in self._table_info(cursor, table_name):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   117
            indexes[info['name']] = {'primary_key': info['pk'] != 0,
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   118
                                     'unique': False}
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   119
        cursor.execute('PRAGMA index_list(%s)' % self.connection.ops.quote_name(table_name))
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   120
        # seq, name, unique
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   121
        for index, unique in [(field[1], field[2]) for field in cursor.fetchall()]:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   122
            if not unique:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   123
                continue
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   124
            cursor.execute('PRAGMA index_info(%s)' % self.connection.ops.quote_name(index))
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   125
            info = cursor.fetchall()
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   126
            # Skip indexes across multiple fields
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   127
            if len(info) != 1:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   128
                continue
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   129
            name = info[0][2] # seqno, cid, name
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   130
            indexes[name]['unique'] = True
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   131
        return indexes
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   132
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   133
    def _table_info(self, cursor, name):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   134
        cursor.execute('PRAGMA table_info(%s)' % self.connection.ops.quote_name(name))
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   135
        # cid, name, type, notnull, dflt_value, pk
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   136
        return [{'name': field[1],
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   137
                 'type': field[2],
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   138
                 'null_ok': not field[3],
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   139
                 'pk': field[5]     # undocumented
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   140
                 } for field in cursor.fetchall()]
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   141