Code

Ticket #1261: release-0.96-firebird.diff

File release-0.96-firebird.diff, 31.8 KB (added by david@…, 7 years ago)

Patch against 0.96 with backend included

Line 
1Index: django/test/utils.py
2===================================================================
3--- django/test/utils.py        (revision 5080)
4+++ django/test/utils.py        (working copy)
5@@ -1,6 +1,6 @@
6 import sys, time
7 from django.conf import settings
8-from django.db import connection, transaction, backend
9+from django.db import connection, transaction, backend, get_creation_module
10 from django.core import management
11 from django.dispatch import dispatcher
12 from django.test import signals
13@@ -44,6 +44,12 @@
14         connection.connection.set_isolation_level(0)
15 
16 def create_test_db(verbosity=1, autoclobber=False):
17+    # If the database backend wants to create the test DB itself, let it
18+    creation_module = get_creation_module()
19+    if hasattr(creation_module, "create_test_db"):
20+        creation_module.create_test_db(settings, connection, backend, verbosity, autoclobber)
21+        return
22+   
23     if verbosity >= 1:
24         print "Creating test database..."
25     # If we're using SQLite, it's more convenient to test against an
26@@ -92,6 +98,12 @@
27     cursor = connection.cursor()
28 
29 def destroy_test_db(old_database_name, verbosity=1):
30+    # If the database wants to drop the test DB itself, let it
31+    creation_module = get_creation_module()
32+    if hasattr(creation_module, "destroy_test_db"):
33+        creation_module.destroy_test_db(settings, connection, backend, old_database_name, verbosity)
34+        return
35+   
36     # Unless we're using SQLite, remove the test database to clean up after
37     # ourselves. Connect to the previous database (not the test database)
38     # to do so, because it's not allowed to delete a database while being
39Index: django/db/models/base.py
40===================================================================
41--- django/db/models/base.py    (revision 5080)
42+++ django/db/models/base.py    (working copy)
43@@ -205,7 +205,10 @@
44         record_exists = True
45         if pk_set:
46             # Determine whether a record with the primary key already exists.
47-            cursor.execute("SELECT 1 FROM %s WHERE %s=%%s LIMIT 1" % \
48+            check_sql = 'SELECT 1 FROM %s WHERE %s=%%s LIMIT 1'
49+            if settings.DATABASE_ENGINE == 'firebird':
50+                check_sql = 'SELECT FIRST 1 1 FROM %s WHERE %s=%%s'
51+            cursor.execute(check_sql % \
52                 (backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)), [pk_val])
53             # If it does already exist, do an UPDATE.
54             if cursor.fetchone():
55Index: django/db/models/fields/related.py
56===================================================================
57--- django/db/models/fields/related.py  (revision 5080)
58+++ django/db/models/fields/related.py  (working copy)
59@@ -335,8 +335,9 @@
60                     (target_col_name, self.join_table, source_col_name,
61                     target_col_name, ",".join(['%s'] * len(new_ids))),
62                     [self._pk_val] + list(new_ids))
63-                if cursor.rowcount is not None and cursor.rowcount != 0:
64-                    existing_ids = set([row[0] for row in cursor.fetchmany(cursor.rowcount)])
65+                rows = cursor.fetchall()
66+                if rows:
67+                    existing_ids = set([row[0] for row in rows])
68                 else:
69                     existing_ids = set()
70 
71Index: django/db/models/query.py
72===================================================================
73--- django/db/models/query.py   (revision 5080)
74+++ django/db/models/query.py   (working copy)
75@@ -2,6 +2,7 @@
76 from django.db.models.fields import DateField, FieldDoesNotExist
77 from django.db.models.fields.generic import GenericRelation
78 from django.db.models import signals
79+from django.conf import settings
80 from django.dispatch import dispatcher
81 from django.utils.datastructures import SortedDict
82 import operator
83@@ -179,8 +180,15 @@
84         # undefined, so we convert it to a list of tuples.
85         extra_select = self._select.items()
86 
87+        pre_columns = ""
88+        if settings.DATABASE_ENGINE == 'firebird' and self._limit is not None:
89+            pre_columns += "FIRST %s " % self._limit
90+            if self._offset:
91+                pre_columns += "SKIP %s " % self._offset
92+        if self._distinct:
93+            pre_columns += "DISTINCT "
94         cursor = connection.cursor()
95-        cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
96+        cursor.execute("SELECT " + pre_columns + ",".join(select) + sql, params)
97         fill_cache = self._select_related
98         index_end = len(self.model._meta.fields)
99         while 1:
100@@ -502,7 +510,7 @@
101 
102         # Compose the join dictionary into SQL describing the joins.
103         if joins:
104-            sql.append(" ".join(["%s %s AS %s ON %s" % (join_type, table, alias, condition)
105+            sql.append(" ".join(["%s %s %s ON %s" % (join_type, table, alias, condition)
106                             for (alias, (table, join_type, condition)) in joins.items()]))
107 
108         # Compose the tables clause into SQL.
109@@ -717,7 +725,10 @@
110         table_prefix = backend.quote_name(table_prefix[:-1])+'.'
111     field_name = backend.quote_name(field_name)
112     try:
113-        return '%s%s %s' % (table_prefix, field_name, (backend.OPERATOR_MAPPING[lookup_type] % '%s'))
114+        lookup_sql = '%s%s %s'
115+        if settings.DATABASE_ENGINE == 'firebird' and lookup_type in ('iexact', 'icontains', 'istartswith', 'iendswith'):
116+            lookup_sql = 'UPPER(%s%s) %s'
117+        return lookup_sql % (table_prefix, field_name, (backend.OPERATOR_MAPPING[lookup_type] % '%s'))
118     except KeyError:
119         pass
120     if lookup_type == 'in':
121Index: django/db/backends/util.py
122===================================================================
123--- django/db/backends/util.py  (revision 5080)
124+++ django/db/backends/util.py  (working copy)
125@@ -1,4 +1,4 @@
126-import datetime
127+import datetime, md5
128 from time import time
129 
130 class CursorDebugWrapper(object):
131@@ -38,6 +38,14 @@
132         else:
133             return getattr(self.cursor, attr)
134 
135+def truncate_name(name, length=None):
136+    """Shortens a string to a repeatable mangled version with the given length.
137+    """
138+    if length is None or len(name) <= length:
139+        return name
140+    hash = md5.md5(name).hexdigest()[:4]
141+    return '%s%s' % (name[:length-4], hash)
142+
143 ###############################################
144 # Converters from database (string) to Python #
145 ###############################################
146Index: django/db/backends/firebird/base.py
147===================================================================
148--- django/db/backends/firebird/base.py (revision 0)
149+++ django/db/backends/firebird/base.py (revision 0)
150@@ -0,0 +1,294 @@
151+"""
152+Firebird database backend for Django.
153+
154+Requires kinterbasdb: http://kinterbasdb.sourceforge.net/
155+"""
156+
157+from django.conf import settings
158+from django.db.backends import util
159+import re
160+try:
161+    import kinterbasdb as Database
162+    import kinterbasdb.typeconv_datetime_stdlib as typeconv_datetime
163+except ImportError, e:
164+    from django.core.exceptions import ImproperlyConfigured
165+    raise ImproperlyConfigured, "Error loading kinterbasdb module: %s" % e
166+
167+DatabaseError = Database.DatabaseError
168+Database.init(type_conv=199)
169+
170+try:
171+    # Only exists in Python 2.4+
172+    from threading import local
173+except ImportError:
174+    # Import copy of _thread_local.py from Python 2.4
175+    from django.utils._threading_local import local
176+
177+server_version = None
178+
179+def unicode_conv_in(text):
180+    #Type converter for columns with charsets NONE, OCTETS, or ASCII
181+    if isinstance(text, unicode):
182+        return text.encode(settings.DEFAULT_CHARSET)
183+    return text
184+
185+def timestamp_conv_in(datetime):
186+    #Type converter for datetime casted strings.
187+    #Replaces 6 digits microseconds to 4 digits allowed in Firebird
188+    if isinstance(datetime, basestring) and datetime.find('.') > 0 and len(datetime) == 26:
189+        datetime = datetime[0:-2]
190+    return typeconv_datetime.timestamp_conv_in(datetime)
191+
192+class DatabaseWrapper(local):
193+    def __init__(self, **kwargs):
194+        self.connection = None
195+        self.queries = []
196+        self.options = kwargs
197+
198+    def cursor(self):
199+        if self.connection is None:
200+            if settings.DATABASE_NAME == '':
201+                from django.core.exceptions import ImproperlyConfigured
202+                raise ImproperlyConfigured, "You need to specify DATABASE_NAME in your Django settings file."
203+            kwargs = {'database': settings.DATABASE_NAME}
204+            if settings.DATABASE_HOST:
205+                kwargs['host'] = settings.DATABASE_HOST
206+            if settings.DATABASE_USER:
207+                kwargs['user'] = settings.DATABASE_USER
208+            if settings.DATABASE_PASSWORD:
209+                kwargs['password'] = settings.DATABASE_PASSWORD
210+            self.connection = Database.connect(**kwargs)
211+            self.connection.set_type_trans_in({
212+                'TEXT': unicode_conv_in,
213+                'BLOB': unicode_conv_in,
214+                'TIMESTAMP': timestamp_conv_in
215+            })
216+            global server_version
217+            if not server_version:
218+                import re
219+                version_re = re.compile('.*Firebird\s([\d\.]+)')
220+                m = version_re.match(self.connection.server_version)
221+                if not m:
222+                    raise Exception('Unable to determine Firebird version from version string %r' % self.connection.server_version)
223+                server_version = m.groups()[0]
224+        cursor = FirebirdCursorWrapper(self.connection)
225+        if settings.DEBUG:
226+            return util.CursorDebugWrapper(cursor, self)
227+        return cursor
228+
229+    def _commit(self):
230+        if self.connection is not None:
231+            return self.connection.commit()
232+
233+    def _rollback(self):
234+        if self.connection is not None:
235+            return self.connection.rollback()
236+
237+    def close(self):
238+        if self.connection is not None:
239+            self.connection.close()
240+            self.connection = None
241+
242+class FirebirdCursorWrapper(Database.Cursor):
243+    """
244+    Django uses "format" ('%s') style placeholders, but firebird uses "qmark" ('?') style.
245+    This fixes it -- but note that if you want to use a literal "%s" in a query,
246+    you'll need to use "%%s".
247+    """
248+    def execute(self, query, params=()):
249+        query = self._convert_query(query, len(params))
250+        #print "%s %s" % (query, params)
251+        return Database.Cursor.execute(self, query, params)
252+
253+    def executemany(self, query, params):
254+        query = self._convert_query(query, len(params[0]))
255+        return Database.Cursor.executemany(self, query, params)
256+
257+    def _convert_query(self, query, num_params):
258+        return query % tuple("?" * num_params)
259+
260+supports_constraints = True
261+
262+def quote_name(name):
263+    # the standard for firebird is not to quote names but in django
264+    # it will quote all names uppercased so we can write sql without quotes
265+    # because all names without quotes will defualt to uppercased,
266+    # like oracle truncate names bigger than 30 chars
267+    if not name.startswith('"') and not name.endswith('"'):
268+        name = '"%s"' % util.truncate_name(name, get_max_name_length())
269+    return name.upper()
270+
271+_quote_sequence_name = lambda n: '"%s_SQ"' % util.truncate_name(n.upper(), get_max_name_length()-3)
272+_quote_trigger_name = lambda n: '"%s_TR"' % util.truncate_name(n.upper(), get_max_name_length()-3)
273+
274+def dictfetchone(cursor):
275+    "Returns a row from the cursor as a dict"
276+    return cursor.fetchonemap()
277+
278+def dictfetchmany(cursor, number):
279+    "Returns a certain number of rows from a cursor as a dict"
280+    return cursor.fetchmanymap(number)
281+
282+def dictfetchall(cursor):
283+    "Returns all rows from a cursor as a dict"
284+    return cursor.fetchallmap()
285+
286+def get_last_insert_id(cursor, table_name, pk_name):
287+    stmt = server_version < '2' and 'SELECT GEN_ID(%s, 0) FROM RDB$DATABASE' or 'NEXT VALUE FOR %s'
288+    cursor.execute(stmt % _quote_sequence_name(table_name))
289+    return cursor.fetchone()[0]
290+
291+def get_date_extract_sql(lookup_type, column_name):
292+    # lookup_type is 'year', 'month', 'day'
293+    return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), column_name)
294+
295+def get_date_trunc_sql(lookup_type, column_name):
296+    if lookup_type == 'year':
297+         sql = "EXTRACT(year FROM %s)||'-01-01 00:00:00'" % column_name
298+    elif lookup_type == 'month':
299+        sql = "EXTRACT(year FROM %s)||'-'||EXTRACT(month FROM %s)||'-01 00:00:00'" % (column_name, column_name)
300+    elif lookup_type == 'day':
301+        sql = "EXTRACT(year FROM %s)||'-'||EXTRACT(month FROM %s)||'-'||EXTRACT(day FROM %s)||' 00:00:00'" % (column_name, column_name, column_name)
302+    return "CAST(%s AS TIMESTAMP)" % sql
303+
304+def get_datetime_cast_sql():
305+    return None
306+
307+def get_limit_offset_sql(limit, offset=None):
308+    return ""
309+
310+def get_random_function_sql():
311+    return "RAND()"
312+
313+def get_deferrable_sql():
314+    # Not supported, maybe in Firebird 3.0
315+    return ""
316+
317+def get_fulltext_search_sql(field_name):
318+    # maybe in Firebird 3.0
319+    raise NotImplementedError
320+
321+def get_drop_foreignkey_sql():
322+    return "DROP CONSTRAINT"
323+
324+def get_pk_default_value():
325+    return "NULL"
326+
327+def get_max_name_length():
328+    return 30
329+
330+def get_sequence_sql(style, table_name, column_name):
331+    """To simulate auto-incrementing primary keys in Firebird, we have to
332+    create a generator (sequence in Firebird 2) and a trigger.
333+
334+    Create the sequences and triggers names based only on table name
335+    since django only support one auto field per model"""
336+    KEYWORD = style.SQL_KEYWORD
337+    TABLE = style.SQL_TABLE
338+    FIELD = style.SQL_FIELD
339+
340+    sequence_name = _quote_sequence_name(table_name)
341+    column_name = quote_name(column_name)
342+    return ["%s %s;" % ( \
343+            KEYWORD(server_version < '2' and 'CREATE GENERATOR' or 'CREATE SEQUENCE'),
344+            TABLE(sequence_name)),
345+        "\n".join(["%s %s %s %s" % ( \
346+            KEYWORD('CREATE TRIGGER'),
347+            TABLE(_quote_trigger_name(table_name)),
348+            KEYWORD('FOR'),
349+            TABLE(quote_name(table_name))),
350+        "%s 0 %s" % ( \
351+            KEYWORD('ACTIVE BEFORE INSERT POSITION'),
352+            KEYWORD('AS')),
353+        KEYWORD('BEGIN'),
354+        "  %s ((%s.%s %s) %s (%s.%s = 0)) %s" % ( \
355+            KEYWORD('IF'),
356+            KEYWORD('NEW'),
357+            FIELD(column_name),
358+            KEYWORD('IS NULL'),
359+            KEYWORD('OR'),
360+            KEYWORD('NEW'),
361+            FIELD(column_name),
362+            KEYWORD('THEN')),
363+        "  %s" % KEYWORD('BEGIN'),
364+        "    %s.%s = %s(%s, 1);" % ( \
365+            KEYWORD('NEW'),
366+            FIELD(column_name),
367+            KEYWORD('GEN_ID'),
368+            TABLE(sequence_name)),
369+        "  %s" % KEYWORD('END'),
370+        KEYWORD('END')])]
371+
372+def get_sql_flush(style, tables, sequences):
373+    """Return a list of SQL statements required to remove all data from
374+    all tables in the database (without actually removing the tables
375+    themselves) and put the database in an empty 'initial' state
376+
377+    For Firebird we gonna take a dangerous workaround:
378+        - first create a temporary table 'django_flush$constraints' that
379+        will contain all Foreignkey definitions for this database.
380+        - next import this definitions into the new table and delete them
381+        from system table 'rdb$relation_constraints'.
382+        - after the flush is done import the definitions back to the system table
383+        and delete the temporary table.
384+    """
385+    KEYWORD = style.SQL_KEYWORD
386+    TABLE = style.SQL_TABLE
387+    FIELD = style.SQL_FIELD
388+
389+    if tables:
390+        temp_table_name = quote_name('django_flush$constraints')
391+        orig_table_name = 'RDB$RELATION_CONSTRAINTS'
392+        sql = ["\n".join([
393+            "%s %s (" % (KEYWORD('CREATE TABLE'), TABLE(temp_table_name)),
394+            "  %s %s(31) %s," % (FIELD(quote_name('rdb$constraint_name')), KEYWORD('CHAR'), KEYWORD('CHARACTER SET UNICODE_FSS')),
395+            "  %s %s(11)," % (FIELD(quote_name('rdb$constraint_type')), KEYWORD('CHAR')),
396+            "  %s %s(31) %s," % (FIELD(quote_name('rdb$relation_name')), KEYWORD('CHAR'), KEYWORD('CHARACTER SET UNICODE_FSS')),
397+            "  %s %s(3)," % (FIELD(quote_name('rdb$deferrable')), KEYWORD('CHAR')),
398+            "  %s %s(3)," % (FIELD(quote_name('rdb$initially_deferred')), KEYWORD('CHAR')),
399+            "  %s %s(31) %s" % (FIELD(quote_name('rdb$index_name')), KEYWORD('CHAR'), KEYWORD('CHARACTER SET UNICODE_FSS')),
400+            ");"]),
401+            "%s;" % KEYWORD('COMMIT'),
402+            "%s %s %s %s %s %s %s %s = 'FOREIGN KEY';" % ( \
403+                KEYWORD('INSERT INTO'), TABLE(temp_table_name),
404+                KEYWORD('SELECT'), FIELD('*'), KEYWORD('FROM'), TABLE(orig_table_name),
405+                KEYWORD('WHERE'), FIELD('rdb$constraint_type')),
406+            "%s %s %s %s = 'FOREIGN KEY';" % ( \
407+                KEYWORD('DELETE FROM'), TABLE(orig_table_name),
408+                KEYWORD('WHERE'), FIELD('rdb$constraint_type')),
409+            "%s;" % KEYWORD('COMMIT')]
410+
411+        sql += ['%s %s;' % \
412+                (KEYWORD('DELETE FROM'),
413+                 TABLE(quote_name(table))
414+                 ) for table in tables]
415+
416+        sql += ["%s %s %s 0;" % \
417+                (KEYWORD(server_version < '2' and 'SET GENERATOR' or 'ALTER SEQUENCE'),
418+                 TABLE(_quote_sequence_name(sequence['table'])),
419+                 KEYWORD(server_version < '2' and 'TO' or 'RESTART WITH')
420+                 ) for sequence in sequences]
421+        return sql + [
422+            "%s %s %s %s %s %s;" % ( \
423+                KEYWORD('INSERT INTO'), TABLE(orig_table_name),
424+                KEYWORD('SELECT'), FIELD('*'), KEYWORD('FROM'), TABLE(temp_table_name)),
425+            "%s %s;" % (KEYWORD('DROP TABLE'), TABLE(temp_table_name)),
426+            "%s;" % KEYWORD('COMMIT')]
427+    return []
428+
429+OPERATOR_MAPPING = {
430+    'exact': '= %s',
431+    'iexact': "= UPPER(%s)",
432+    'contains': "LIKE %s ESCAPE '\\'",
433+    'icontains': "LIKE UPPER(%s) ESCAPE '\\'",
434+    'gt': '> %s',
435+    'gte': '>= %s',
436+    'lt': '< %s',
437+    'lte': '<= %s',
438+    'startswith': "LIKE %s ESCAPE '\\'",
439+    'endswith': "LIKE %s ESCAPE '\\'",
440+    'istartswith': "LIKE UPPER(%s) ESCAPE '\\'",
441+    'iendswith': "LIKE UPPER(%s) ESCAPE '\\'",
442+
443+    'text_icontains': "CONTAINING %s",
444+}
445Index: django/db/backends/firebird/client.py
446===================================================================
447--- django/db/backends/firebird/client.py       (revision 0)
448+++ django/db/backends/firebird/client.py       (revision 0)
449@@ -0,0 +1,11 @@
450+from django.conf import settings
451+import os
452+
453+def runshell():
454+    args = [settings.DATABASE_NAME]
455+    args += ["-u %s" % settings.DATABASE_USER]
456+    if settings.DATABASE_PASSWORD:
457+        args += ["-p %s" % settings.DATABASE_PASSWORD]
458+    if 'FIREBIRD' not in os.environ:
459+        path = '/opt/firebird/bin/'
460+    os.system(path + 'isql ' + ' '.join(args))
461Index: django/db/backends/firebird/__init__.py
462===================================================================
463Index: django/db/backends/firebird/introspection.py
464===================================================================
465--- django/db/backends/firebird/introspection.py        (revision 0)
466+++ django/db/backends/firebird/introspection.py        (revision 0)
467@@ -0,0 +1,91 @@
468+from django.db import transaction
469+from django.db.backends.firebird.base import quote_name
470+
471+def get_table_list(cursor):
472+    "Returns a list of table names in the current database."
473+    cursor.execute("""
474+        SELECT rdb$relation_name FROM rdb$relations
475+        WHERE rdb$system_flag = 0 AND rdb$view_blr IS NULL ORDER BY rdb$relation_name""")
476+    return [str(row[0].strip().lower()) for row in cursor.fetchall()]
477+
478+def get_table_description(cursor, table_name):
479+    "Returns a description of the table, with the DB-API cursor.description interface."
480+    #cursor.execute("SELECT FIRST 1 * FROM %s" % quote_name(table_name))
481+    #return cursor.description
482+    # (name, type_code, display_size, internal_size, precision, scale, null_ok)
483+    cursor.execute("""
484+        SELECT DISTINCT R.RDB$FIELD_NAME AS FNAME,
485+                  F.RDB$FIELD_TYPE AS FTYPE,
486+                  F.RDB$FIELD_LENGTH AS FLENGTH,
487+                  F.RDB$FIELD_PRECISION AS FPRECISION,
488+                  F.RDB$FIELD_SCALE AS FSCALE,
489+                  R.RDB$NULL_FLAG AS NULL_FLAG,
490+                  R.RDB$FIELD_POSITION
491+        FROM RDB$RELATION_FIELDS R
492+             JOIN RDB$FIELDS F ON R.RDB$FIELD_SOURCE=F.RDB$FIELD_NAME
493+        WHERE F.RDB$SYSTEM_FLAG=0 and R.RDB$RELATION_NAME= %s
494+        ORDER BY R.RDB$FIELD_POSITION
495+    """, (table_name,))
496+    return [(row[0].lower().rstrip(), row[1], row[2], row[2] or 0, row[3], row[4], row[5] and True or False) for row in cursor.fetchall()]
497+
498+
499+def get_relations(cursor, table_name):
500+    """
501+    Returns a dictionary of {field_index: (field_index_other_table, other_table)}
502+    representing all relationships to the given table. Indexes are 0-based.
503+    """
504+    cursor.execute("""
505+        SELECT seg.rdb$field_name, seg_ref.rdb$field_name, idx_ref.rdb$relation_name
506+        FROM rdb$indices idx
507+        INNER JOIN rdb$index_segments seg
508+            ON seg.rdb$index_name = idx.rdb$index_name
509+        INNER JOIN rdb$indices idx_ref
510+            ON idx_ref.rdb$index_name = idx.rdb$foreign_key
511+        INNER JOIN rdb$index_segments seg_ref
512+            ON seg_ref.rdb$index_name = idx_ref.rdb$index_name
513+        WHERE idx.rdb$relation_name = %s
514+            AND idx.rdb$foreign_key IS NOT NULL""", [table_name])
515+
516+    relations = {}
517+    for row in cursor.fetchall():
518+        relations[row[0].rstrip()] = (row[1].strip(), row[2].strip())
519+    return relations
520+
521+def get_indexes(cursor, table_name):
522+    """
523+    Returns a dictionary of fieldname -> infodict for the given table,
524+    where each infodict is in the format:
525+        {'primary_key': boolean representing whether it's the primary key,
526+         'unique': boolean representing whether it's a unique index}
527+    """
528+
529+    # This query retrieves each field name and index type on the given table.
530+    cursor.execute("""
531+        SELECT seg.rdb$field_name, const.rdb$constraint_type
532+        FROM rdb$relation_constraints const
533+        LEFT JOIN rdb$index_segments seg
534+            ON seg.rdb$index_name = const.rdb$index_name
535+        WHERE const.rdb$relation_name = %s
536+            AND (const.rdb$constraint_type = 'PRIMARY KEY'
537+                OR const.rdb$constraint_type = 'UNIQUE')""", [table_name])
538+    indexes = {}
539+    for row in cursor.fetchall():
540+        indexes[row[0].strip()] = {
541+            'primary_key': ('PRIMARY KEY' == row[1].strip()),
542+            'unique': ('UNIQUE' == row[1].strip())}
543+    return indexes
544+
545+# Maps type codes to Django Field types.
546+# !todo
547+DATA_TYPES_REVERSE = {
548+    7: 'BooleanField',
549+    7: 'SmallIntegerField',
550+    8: 'IntegerField',
551+    261: 'TextField',
552+    37: 'IPAddressField',
553+    37: 'CharField',
554+    12: 'DateField',
555+    13: 'TimeField',
556+    35: 'DateTimeField',
557+    10: 'FloatField',
558+}
559Index: django/db/backends/firebird/creation.py
560===================================================================
561--- django/db/backends/firebird/creation.py     (revision 0)
562+++ django/db/backends/firebird/creation.py     (revision 0)
563@@ -0,0 +1,120 @@
564+from django.core import management
565+import sys, os, time
566+
567+# This dictionary maps Field objects to their associated PostgreSQL column
568+# types, as strings. Column-type strings can contain format strings; they'll
569+# be interpolated against the values of Field.__dict__ before being output.
570+# If a column type is set to None, it won't be included in the output.
571+
572+DATA_TYPES = {
573+    'AutoField':                     'INTEGER',
574+    'BooleanField':                  'SMALLINT',
575+    'CharField':                     'VARCHAR(%(maxlength)s)',
576+    'CommaSeparatedIntegerField':    'VARCHAR(%(maxlength)s)',
577+    'DateField':                     'DATE',
578+    'DateTimeField':                 'TIMESTAMP',
579+    'FileField':                     'VARCHAR(100)',
580+    'FilePathField':                 'VARCHAR(100)',
581+    'FloatField':                    'NUMERIC(%(max_digits)s, %(decimal_places)s)',
582+    'ImageField':                    'VARCHAR(100)',
583+    'IntegerField':                  'INTEGER',
584+    'IPAddressField':                'VARCHAR(15)',
585+    'ManyToManyField':               None,
586+    'NullBooleanField':              'SMALLINT',
587+    'OneToOneField':                 'INTEGER',
588+    'PhoneNumberField':              'VARCHAR(20)',
589+    'PositiveIntegerField':          'INTEGER',
590+    'PositiveSmallIntegerField':     'SMALLINT',
591+    'SlugField':                     'VARCHAR(%(maxlength)s)',
592+    'SmallIntegerField':             'SMALLINT',
593+    'TextField':                     'BLOB SUB_TYPE TEXT',
594+    'TimeField':                     'TIME',
595+    'URLField':                      'VARCHAR(200)',
596+    'USStateField':                  'VARCHAR(2)',
597+}
598+
599+TEST_DATABASE_PREFIX = 'test_'
600+
601+def create_test_db(settings, connection, backend, verbosity, autoclobber):
602+
603+    if verbosity >= 1:
604+        print "Creating test database..."
605+
606+    if settings.TEST_DATABASE_NAME:
607+        if os.path.isfile(settings.DATABASE_NAME):
608+            TEST_DATABASE_NAME = os.path.join(
609+                os.path.dirname(settings.DATABASE_NAME),
610+                settings.TEST_DATABASE_NAME)
611+        else:
612+            import tempfile
613+            tempfile.gettempdir()
614+            TEST_DATABASE_NAME = os.path.join(
615+                tempfile.gettempdir(),
616+                settings.TEST_DATABASE_NAME)
617+    else:
618+        if os.path.isfile(settings.DATABASE_NAME):
619+            TEST_DATABASE_NAME = os.path.join(
620+                os.path.dirname(settings.DATABASE_NAME),
621+                TEST_DATABASE_PREFIX + os.path.basename(settings.DATABASE_NAME))
622+        else:
623+            import tempfile
624+            tempfile.gettempdir()
625+            TEST_DATABASE_NAME = os.path.join(
626+                tempfile.gettempdir(),
627+                TEST_DATABASE_PREFIX + settings.DATABASE_NAME)
628+
629+    settings.DATABASE_NAME = TEST_DATABASE_NAME
630+
631+    try:
632+        _create_test_db(backend, settings)
633+    except Exception, e:
634+        sys.stderr.write("Got an error creating the test database: %s\n" % e)
635+        if not autoclobber:
636+            confirm = raw_input("It appears the test database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_NAME)
637+        if autoclobber or confirm == 'yes':
638+            try:
639+                if verbosity >= 1:
640+                    print "Destroying old test database..."
641+                _destroy_test_db(connection)
642+                if verbosity >= 1:
643+                    print "Creating test database..."
644+                _create_test_db(backend, settings)
645+            except Exception, e:
646+                sys.stderr.write("Got an error recreating the test database: %s\n" % e)
647+                sys.exit(2)
648+        else:
649+            print "Tests cancelled."
650+            sys.exit(1)
651+
652+    management.syncdb(verbosity, interactive=False)
653+
654+    # Get a cursor (even though we don't need one yet). This has
655+    # the side effect of initializing the test database.
656+    cursor = connection.cursor()
657+
658+
659+def destroy_test_db(settings, connection, backend, old_database_name, verbosity):
660+    if verbosity >= 1:
661+        print "Destroying test database..."
662+
663+    # To avoid "database is being accessed by other users" errors.
664+    time.sleep(1)
665+    _destroy_test_db(connection)
666+
667+def _create_test_db(backend, settings):
668+    connection = backend.Database.create_database("""
669+        CREATE DATABASE '%s' user '%s' password '%s'""" % \
670+        (settings.DATABASE_NAME, settings.DATABASE_USER, settings.DATABASE_PASSWORD))
671+    cursor = connection.cursor()
672+    cursor.execute("""
673+    DECLARE EXTERNAL FUNCTION rand
674+        RETURNS DOUBLE PRECISION
675+        BY VALUE ENTRY_POINT 'IB_UDF_rand' MODULE_NAME 'ib_udf';
676+    """)
677+    connection.commit()
678+    connection.close()
679+
680+def _destroy_test_db(connection):
681+    cursor = connection.cursor()
682+    connection.connection.drop_database()
683+    connection.connection = None
684\ No newline at end of file
685Index: django/core/management.py
686===================================================================
687--- django/core/management.py   (revision 5080)
688+++ django/core/management.py   (working copy)
689@@ -159,6 +159,7 @@
690     Returns list_of_sql, pending_references_dict
691     """
692     from django.db import backend, get_creation_module, models
693+    from django.conf import settings
694     data_types = get_creation_module().DATA_TYPES
695 
696     opts = model._meta
697@@ -177,7 +178,11 @@
698             # Make the definition (e.g. 'foo VARCHAR(30)') for this field.
699             field_output = [style.SQL_FIELD(backend.quote_name(f.column)),
700                 style.SQL_COLTYPE(col_type % rel_field.__dict__)]
701-            field_output.append(style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or '')))
702+            column_null_sql = '%sNULL' % (not f.null and 'NOT ' or '')
703+            if settings.DATABASE_ENGINE == 'firebird' and f.null:
704+                column_null_sql = (not f.null or f.primary_key) and 'NOT NULL' or ''
705+            if column_null_sql:
706+                field_output.append(style.SQL_KEYWORD(column_null_sql))
707             if f.unique:
708                 field_output.append(style.SQL_KEYWORD('UNIQUE'))
709             if f.primary_key:
710@@ -207,6 +212,9 @@
711         full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
712     full_statement.append(');')
713     final_output.append('\n'.join(full_statement))
714+   
715+    if opts.has_auto_field and hasattr(backend, 'get_sequence_sql'):
716+        final_output += backend.get_sequence_sql(style, opts.db_table, opts.pk.column)
717 
718     return final_output, pending_references
719 
720@@ -231,7 +239,7 @@
721                 # So we are careful with character usage here.
722                 r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table))))
723                 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
724-                    (backend.quote_name(r_table), r_name,
725+                    (backend.quote_name(r_table), backend.quote_name(r_name),
726                     backend.quote_name(r_col), backend.quote_name(table), backend.quote_name(col),
727                     backend.get_deferrable_sql()))
728             del pending_references[model]
729@@ -273,6 +281,8 @@
730                 style.SQL_FIELD(backend.quote_name(f.m2m_reverse_name()))))
731             table_output.append(');')
732             final_output.append('\n'.join(table_output))
733+            if hasattr(backend, 'get_sequence_sql'):
734+                final_output += backend.get_sequence_sql(style, f.m2m_db_table(), 'id')
735     return final_output
736 
737 def get_sql_delete(app):
738@@ -456,7 +466,7 @@
739             unique = f.unique and 'UNIQUE ' or ''
740             output.append(
741                 style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \
742-                style.SQL_TABLE('%s_%s' % (model._meta.db_table, f.column)) + ' ' + \
743+                style.SQL_TABLE(backend.quote_name('%s_%s' % (model._meta.db_table, f.column))) + ' ' + \
744                 style.SQL_KEYWORD('ON') + ' ' + \
745                 style.SQL_TABLE(backend.quote_name(model._meta.db_table)) + ' ' + \
746                 "(%s);" % style.SQL_FIELD(backend.quote_name(f.column))
747Index: django/contrib/redirects/models.py
748===================================================================
749--- django/contrib/redirects/models.py  (revision 5080)
750+++ django/contrib/redirects/models.py  (working copy)
751@@ -4,9 +4,9 @@
752 
753 class Redirect(models.Model):
754     site = models.ForeignKey(Site, radio_admin=models.VERTICAL)
755-    old_path = models.CharField(_('redirect from'), maxlength=200, db_index=True,
756+    old_path = models.CharField(_('redirect from'), maxlength=100, db_index=False,
757         help_text=_("This should be an absolute path, excluding the domain name. Example: '/events/search/'."))
758-    new_path = models.CharField(_('redirect to'), maxlength=200, blank=True,
759+    new_path = models.CharField(_('redirect to'), maxlength=100, blank=True,
760         help_text=_("This can be either an absolute path (as above) or a full URL starting with 'http://'."))
761 
762     class Meta: