Code

Ticket #1261: firebird-svn6652-full.diff

File firebird-svn6652-full.diff, 42.5 KB (added by i_i, 6 years ago)

Firebird database backend working with svn django full patch

Line 
1Index: django/db/models/fields/__init__.py
2===================================================================
3--- django/db/models/fields/__init__.py (revision 6652)
4+++ django/db/models/fields/__init__.py (working copy)
5@@ -208,16 +208,21 @@
6         return value
7 
8     def get_db_prep_lookup(self, lookup_type, value):
9+        from django.db import connection
10         "Returns field's value prepared for database lookup."
11         if lookup_type in ('exact', 'regex', 'iregex', 'gt', 'gte', 'lt', 'lte', 'month', 'day', 'search'):
12             return [value]
13         elif lookup_type in ('range', 'in'):
14             return value
15         elif lookup_type in ('contains', 'icontains'):
16+            if connection.features.uses_custom_icontains and lookup_type == 'icontains':
17+                return [value]   
18             return ["%%%s%%" % prep_for_like_query(value)]
19         elif lookup_type == 'iexact':
20             return [prep_for_like_query(value)]
21         elif lookup_type in ('startswith', 'istartswith'):
22+            if connection.features.uses_custom_startswith:
23+                return [value]
24             return ["%s%%" % prep_for_like_query(value)]
25         elif lookup_type in ('endswith', 'iendswith'):
26             return ["%%%s" % prep_for_like_query(value)]
27@@ -480,6 +485,9 @@
28         defaults.update(kwargs)
29         return super(CharField, self).formfield(**defaults)
30 
31+class ASCIICharField(CharField):
32+    pass
33+
34 # TODO: Maybe move this into contrib, because it's specialized.
35 class CommaSeparatedIntegerField(CharField):
36     def get_manipulator_field_objs(self):
37@@ -589,7 +597,9 @@
38             # doesn't support microseconds.
39             if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
40                 value = value.replace(microsecond=0)
41-            value = smart_unicode(value)
42+            #Firebird supports native datetime
43+            if settings.DATABASE_ENGINE != 'firebird':
44+                value = smart_unicode(value)
45         return Field.get_db_prep_save(self, value)
46 
47     def get_db_prep_lookup(self, lookup_type, value):
48@@ -997,8 +1007,8 @@
49             # doesn't support microseconds.
50             if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
51                 value = value.replace(microsecond=0)
52-            if settings.DATABASE_ENGINE == 'oracle':
53-                # cx_Oracle expects a datetime.datetime to persist into TIMESTAMP field.
54+            if settings.DATABASE_ENGINE in ('oracle', 'firebird'):
55+                # cx_Oracle and kinterbasdb expect a datetime.datetime to persist into TIMESTAMP field.
56                 if isinstance(value, datetime.time):
57                     value = datetime.datetime(1900, 1, 1, value.hour, value.minute,
58                                               value.second, value.microsecond)
59Index: django/db/backends/__init__.py
60===================================================================
61--- django/db/backends/__init__.py      (revision 6652)
62+++ django/db/backends/__init__.py      (working copy)
63@@ -45,10 +45,13 @@
64     autoindexes_primary_keys = True
65     inline_fk_references = True
66     needs_datetime_string_cast = True
67+    needs_default_null = False
68     needs_upper_for_iops = False
69     supports_constraints = True
70     supports_tablespaces = False
71     uses_case_insensitive_names = False
72+    uses_custom_icontains = False
73+    uses_custom_startswith = False
74     uses_custom_queryset = False
75 
76 class BaseDatabaseOperations(object):
77@@ -65,7 +68,14 @@
78         This SQL is executed when a table is created.
79         """
80         return None
81-
82+   
83+    def cascade_delete_update_sql(self):
84+        """
85+        Returns the SQL necessary to make a cascading deletes and updates
86+        of foreign key references during a CREATE TABLE statement.
87+        """
88+        return ''
89+   
90     def date_extract_sql(self, lookup_type, field_name):
91         """
92         Given a lookup_type of 'year', 'month' or 'day', returns the SQL that
93Index: django/db/backends/firebird/base.py
94===================================================================
95--- django/db/backends/firebird/base.py (revision 0)
96+++ django/db/backends/firebird/base.py (revision 0)
97@@ -0,0 +1,530 @@
98+"""
99+Firebird database backend for Django.
100+
101+Requires KInterbasDB 3.2: http://kinterbasdb.sourceforge.net/
102+The egenix mx (mx.DateTime) is NOT required
103+
104+Database charset should be UNICODE_FSS or UTF8 (FireBird 2.0+)
105+To use UTF8 encoding add FIREBIRD_CHARSET = 'UTF8' to your settings.py
106+UNICODE_FSS works with all versions and uses less memory
107+"""
108+
109+from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
110+
111+try:
112+    import kinterbasdb as Database
113+except ImportError, e:
114+    from django.core.exceptions import ImproperlyConfigured
115+    raise ImproperlyConfigured, "Error loading KInterbasDB module: %s" % e
116+
117+DatabaseError = Database.DatabaseError
118+IntegrityError = Database.IntegrityError
119+
120+class DatabaseFeatures(BaseDatabaseFeatures):
121+    needs_datetime_string_cast = False
122+    needs_default_null = True
123+    needs_upper_for_iops = True
124+    supports_constraints = False #some tests went strange without it
125+    uses_custom_icontains = True #CONTAINING <value> op instead of LIKE %<value>%
126+    uses_custom_startswith = True #STARTING WITH op. Faster than LIKE
127+    uses_custom_queryset = True
128+
129+class DatabaseOperations(BaseDatabaseOperations):
130+    _max_name_length = 31
131+    def __init__(self):
132+        self._firebird_version = None
133+   
134+    def get_generator_name(self, name):
135+        return '%s_G' % util.truncate_name(name, self._max_name_length-2).upper()
136+       
137+    def get_trigger_name(self, name):
138+        return '%s_T' % util.truncate_name(name, self._max_name_length-2).upper()
139+   
140+    def _get_firebird_version(self):
141+        if self._firebird_version is None:
142+            from django.db import connection
143+            self._firebird_version = [int(val) for val in connection.server_version.split()[-1].split('.')]
144+        return self._firebird_version
145+    firebird_version = property(_get_firebird_version)
146+   
147+    def _autoinc_sql_with_style(self, style, table_name, column_name):
148+        """
149+        To simulate auto-incrementing primary keys in Firebird, we have to
150+        create a generator and a trigger.
151+   
152+        Create the generators and triggers names based only on table name
153+        since django only support one auto field per model
154+        """
155+       
156+        KWD = style.SQL_KEYWORD
157+        TBL = style.SQL_TABLE
158+        FLD = style.SQL_FIELD
159+   
160+        generator_name = self.get_generator_name(table_name)
161+        trigger_name = self.get_trigger_name(table_name)
162+        column_name = self.quote_name(column_name)
163+        table_name = self.quote_name(table_name)
164+       
165+        generator_sql = "%s %s;" % ( KWD('CREATE GENERATOR'),
166+                                     TBL(generator_name))     
167+        trigger_sql = "\n".join([
168+            "%s %s %s %s" % ( \
169+            KWD('CREATE TRIGGER'), TBL(trigger_name), KWD('FOR'),
170+            TBL(table_name)),
171+            "%s 0 %s" % (KWD('ACTIVE BEFORE INSERT POSITION'), KWD('AS')),
172+            KWD('BEGIN'),
173+            "  %s ((%s.%s %s) %s (%s.%s = 0)) %s" % ( \
174+                KWD('IF'),
175+                KWD('NEW'), FLD(column_name), KWD('IS NULL'),
176+                KWD('OR'), KWD('NEW'), FLD(column_name),
177+                KWD('THEN')
178+            ),
179+            "  %s" % KWD('BEGIN'),
180+            "    %s.%s = %s(%s, 1);" % ( \
181+                KWD('NEW'), FLD(column_name),
182+                KWD('GEN_ID'), TBL(generator_name)
183+            ),
184+            "  %s" % KWD('END'),
185+            KWD('END')
186+            ])
187+        return (generator_sql, trigger_sql)
188+   
189+    def autoinc_sql(self, table_name, column_name):
190+        # style argument disappeared, so we'll just import django's dummy
191+        from django.core.management.color import no_style, color_style
192+        return self._autoinc_sql_with_style(no_style(), table_name, column_name)
193+
194+    def max_name_length(self):
195+        return self._max_name_length
196+
197+    def query_set_class(self, DefaultQuerySet):
198+        from django.db import connection
199+        from django.db.models.query import EmptyResultSet, GET_ITERATOR_CHUNK_SIZE, quote_only_if_word
200+
201+        class FirebirdQuerySet(DefaultQuerySet):
202+        #TODO: Optimize for Firebird and take full advanatage of its power
203+        # Now it's just a copy of django.db.models.query._QuerySet
204+        # with LIMIT/OFFSET removed and FIRST/SKIP added
205+            def _get_sql_clause(self):
206+                from django.db.models.query import SortedDict, handle_legacy_orderlist, orderfield2column, fill_table_cache
207+                qn = connection.ops.quote_name
208+                opts = self.model._meta
209+
210+                # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z.
211+                select = ["%s.%s" % (qn(opts.db_table), qn(f.column)) for f in opts.fields]
212+                tables = [quote_only_if_word(t) for t in self._tables]
213+                joins = SortedDict()
214+                where = self._where[:]
215+                params = self._params[:]
216+
217+                # Convert self._filters into SQL.
218+                joins2, where2, params2 = self._filters.get_sql(opts)
219+                joins.update(joins2)
220+                where.extend(where2)
221+                params.extend(params2)
222+
223+                # Add additional tables and WHERE clauses based on select_related.
224+                if self._select_related:
225+                    fill_table_cache(opts, select, tables, where,
226+                                     old_prefix=opts.db_table,
227+                                     cache_tables_seen=[opts.db_table],
228+                                     max_depth=self._max_related_depth)
229+
230+                # Add any additional SELECTs.
231+                if self._select:
232+                    select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), qn(s[0])) for s in self._select.items()])
233+
234+                # Start composing the body of the SQL statement.
235+                sql = [" FROM", qn(opts.db_table)]
236+
237+                # Compose the join dictionary into SQL describing the joins.
238+                if joins:
239+                    sql.append(" ".join(["%s %s AS %s ON %s" % (join_type, table, alias, condition)
240+                                    for (alias, (table, join_type, condition)) in joins.items()]))
241+
242+                # Compose the tables clause into SQL.
243+                if tables:
244+                    sql.append(", " + ", ".join(tables))
245+
246+                # Compose the where clause into SQL.
247+                if where:
248+                    sql.append(where and "WHERE " + " AND ".join(where))
249+
250+                # ORDER BY clause
251+                order_by = []
252+                if self._order_by is not None:
253+                    ordering_to_use = self._order_by
254+                else:
255+                    ordering_to_use = opts.ordering
256+                for f in handle_legacy_orderlist(ordering_to_use):
257+                    if f == '?': # Special case.
258+                        order_by.append(connection.ops.random_function_sql())
259+                    else:
260+                        if f.startswith('-'):
261+                            col_name = f[1:]
262+                            order = "DESC"
263+                        else:
264+                            col_name = f
265+                            order = "ASC"
266+                        if "." in col_name:
267+                            table_prefix, col_name = col_name.split('.', 1)
268+                            table_prefix = qn(table_prefix) + '.'
269+                        else:
270+                            # Use the database table as a column prefix if it wasn't given,
271+                            # and if the requested column isn't a custom SELECT.
272+                            if "." not in col_name and col_name not in (self._select or ()):
273+                                table_prefix = qn(opts.db_table) + '.'
274+                            else:
275+                                table_prefix = ''
276+                        order_by.append('%s%s %s' % (table_prefix, qn(orderfield2column(col_name, opts)), order))
277+                if order_by:
278+                    sql.append("ORDER BY " + ", ".join(order_by))
279+
280+                return select, " ".join(sql), params
281+           
282+            def iterator(self):
283+                "Performs the SELECT database lookup of this QuerySet."
284+                from django.db.models.query import get_cached_row
285+               
286+                try:
287+                    select, sql, params = self._get_sql_clause()
288+                except EmptyResultSet:
289+                    raise StopIteration
290+                   
291+                # self._select is a dictionary, and dictionaries' key order is
292+                # undefined, so we convert it to a list of tuples.
293+                extra_select = self._select.items()
294+               
295+                cursor = connection.cursor()
296+                limit_offset_before = ""
297+                if self._limit is not None:
298+                    limit_offset_before += "FIRST %s " % self._limit
299+                    if self._offset:
300+                        limit_offset_before += "SKIP %s " % self._offset
301+                else:
302+                    assert self._offset is None, "'offset' is not allowed without 'limit'"
303+                cursor.execute("SELECT " + limit_offset_before + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
304+                fill_cache = self._select_related
305+                fields = self.model._meta.fields
306+                index_end = len(fields)
307+                has_resolve_columns = hasattr(self, 'resolve_columns')
308+                while 1:
309+                    rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
310+                    if not rows:
311+                        raise StopIteration
312+                    for row in rows:
313+                        if has_resolve_columns:
314+                            row = self.resolve_columns(row, fields)
315+                        if fill_cache:
316+                            obj, index_end = get_cached_row(klass=self.model, row=row,
317+                                                            index_start=0, max_depth=self._max_related_depth)
318+                        else:
319+                            obj = self.model(*row[:index_end])
320+                        for i, k in enumerate(extra_select):
321+                            setattr(obj, k[0], row[index_end+i])
322+                        yield obj
323+        return FirebirdQuerySet
324+   
325+
326+    def quote_name(self, name):
327+        #Trancate and quote once.
328+        #Generally works without upper() but some tests fail? without it
329+        #So let it be like in oracle backend
330+        if not name.startswith('"') and not name.endswith('"'):
331+            name = '"%s"' % util.truncate_name(name, self._max_name_length)
332+        return name.upper()
333+
334+    def last_insert_id(self, cursor, table_name, pk_name):
335+        stmt = 'SELECT GEN_ID(%s, 0) from RDB$DATABASE'
336+        cursor.execute(stmt % self.get_generator_name(table_name))
337+        return cursor.fetchone()[0]
338+
339+    def date_extract_sql(self, lookup_type, column_name):
340+        # lookup_type is 'year', 'month', 'day'
341+        return "EXTRACT(%s FROM %s)" % (lookup_type, column_name)
342+
343+    def date_trunc_sql(self, lookup_type, column_name):
344+        if lookup_type == 'year':
345+             sql = "EXTRACT(year FROM %s)||'-01-01 00:00:00'" % column_name
346+        elif lookup_type == 'month':
347+            sql = "EXTRACT(year FROM %s)||'-'||EXTRACT(month FROM %s)||'-01 00:00:00'" % (column_name, column_name)
348+        elif lookup_type == 'day':
349+            sql = "EXTRACT(year FROM %s)||'-'||EXTRACT(month FROM %s)||'-'||EXTRACT(day FROM %s)||' 00:00:00'" % (column_name, column_name, column_name)
350+        return "CAST(%s AS TIMESTAMP)" % sql
351+   
352+    def cascade_delete_update_sql(self):
353+        # Solves FK problems with sql_flush
354+        return " ON DELETE CASCADE ON UPDATE CASCADE"
355+   
356+    def datetime_cast_sql(self):
357+        return None
358+
359+    def limit_offset_sql(self, limit, offset=None):
360+        # limits are handled in custom FirebirdQuerySet
361+        assert False, 'Limits are handled in a different way in Firebird'
362+        return ""
363+
364+    def random_function_sql(self):
365+        return "rand()"
366+
367+    def pk_default_value(self):
368+        return "NULL"
369+   
370+    def start_transaction_sql(self):
371+        return ""
372+
373+    def sequence_reset_sql(self, style, model_list):
374+        from django.db import models
375+        output = []
376+        for model in model_list:
377+            for f in model._meta.fields:
378+                if isinstance(f, models.AutoField):
379+                    generator_name = self.get_generator_name(model._meta.db_table)
380+                    output.append("SET GENERATOR %s TO 0;" % generator_name)
381+                    break # Only one AutoField is allowed per model, so don't bother continuing.
382+            for f in model._meta.many_to_many:
383+                generator_name = self.get_generator_name(f.m2m_db_table())
384+                output.append("SET GENERATOR %s TO 0;" % generator_name)
385+        return output
386+   
387+    def sql_flush(self, style, tables, sequences):
388+        if tables:
389+            # FK constraints gave us a lot of trouble with default values
390+            # that was a reason behind very ugly and dangerous code here
391+            # Solved with "ON DELETE CASCADE" with all FK references       
392+            sql = ['%s %s %s;' % \
393+                    (style.SQL_KEYWORD('DELETE'),
394+                     style.SQL_KEYWORD('FROM'),
395+                     style.SQL_FIELD(self.quote_name(table))
396+                     ) for table in tables]
397+            for generator_info in sequences:
398+                table_name = generator_info['table']
399+                query = "SET GENERATOR %s TO 0;" % self.get_generator_name(table_name)
400+                sql.append(query)
401+            return sql
402+        else:
403+            return []
404+
405+#    def fulltext_search_sql(self, field_name):
406+#        return field_name + ' CONTAINING %s'
407+       
408+    def drop_sequence_sql(self, table):
409+        return "DROP GENERATOR %s;" % self.get_generator_name(table)
410+       
411+    def last_executed_query(self, cursor, sql, params):
412+        """
413+        Returns a string of the query last executed by the given cursor, with
414+        placeholders replaced with actual values.
415+
416+        `sql` is the raw query containing placeholders, and `params` is the
417+        sequence of parameters. These are used by default, but this method
418+        exists for database backends to provide a better implementation
419+        according to their own quoting schemes.
420+        """
421+        from django.utils.encoding import smart_unicode, force_unicode
422+
423+        # Convert params to contain Unicode values.
424+        to_unicode = lambda s: force_unicode(s, strings_only=True)
425+        if isinstance(params, (list, tuple)):
426+            u_params = tuple([to_unicode(val) for val in params])
427+        else:
428+            u_params = dict([(to_unicode(k), to_unicode(v)) for k, v in params.items()])
429+        try:
430+            #Extracts sql right from KInterbasDB's prepared statement
431+            return smart_unicode(cursor.query) % u_params
432+        except TypeError:
433+            return smart_unicode(sql) % u_params
434+
435+class FirebirdCursorWrapper(object):
436+    """
437+    Django uses "format" ('%s') style placeholders, but firebird uses "qmark" ('?') style.
438+    This fixes it -- but note that if you want to use a literal "%s" in a query,
439+    you'll need to use "%%s".
440+   
441+    We also do all automatic type conversions here.
442+    """
443+    import kinterbasdb.typeconv_datetime_stdlib as tc_dt
444+    import kinterbasdb.typeconv_fixed_decimal as tc_fd
445+    import kinterbasdb.typeconv_text_unicode as tc_tu
446+    import django.utils.encoding as dj_ue
447+   
448+    def timestamp_conv_in(self, timestamp):
449+        if isinstance(timestamp, basestring):
450+            #Replaces 6 digits microseconds to 4 digits allowed in Firebird
451+            timestamp = timestamp[:24]
452+        return self.tc_dt.timestamp_conv_in(timestamp)
453+   
454+    def time_conv_in(self, value):
455+        import datetime
456+        if isinstance(value, datetime.datetime):
457+            value = datetime.time(value.hour, value.minute, value.second, value.micosecond)       
458+        return self.tc_dt.time_conv_in(value)
459+   
460+    def ascii_conv_in(self, text): 
461+        return self.dj_eu.smart_str(text, 'ascii')
462+
463+    def unicode_conv_in(self, text):
464+        return self.tc_tu.unicode_conv_in((self.dj_ue.force_unicode(text[0]), self.FB_CHARSET_CODE))
465+
466+    def blob_conv_in(self, text):
467+        return self.tc_tu.unicode_conv_in((self.dj_ue.force_unicode(text), self.FB_CHARSET_CODE))
468+
469+    def blob_conv_out(self, text):
470+        return self.tc_tu.unicode_conv_out((text, self.FB_CHARSET_CODE))
471+       
472+    def __init__(self, cursor):
473+        from django.conf import settings
474+        self.FB_CHARSET_CODE = 3 #UNICODE_FSS
475+        if hasattr(settings, 'FIREBIRD_CHARSET'):
476+            if settings.FIREBIRD_CHARSET == 'UTF8':
477+                self.FB_CHARSET_CODE = 4 # UTF-8 with Firebird 2.0+   
478+        self.cursor = cursor
479+       
480+        # Prepared Statement
481+        # http://kinterbasdb.sourceforge.net/dist_docs/usage.html#adv_prepared_statements
482+        # Need to decide wether they are useful or not
483+        # Maybe add prepare, execute_prep and executemany_pep methods here
484+        # and rewrite QuerySet to take advantage of them?
485+        # Could speed the things up
486+        self._statement = None
487+        self.cursor.set_type_trans_in({
488+            'DATE':             self.tc_dt.date_conv_in,
489+            'TIME':             self.time_conv_in,
490+            'TIMESTAMP':        self.timestamp_conv_in,
491+            'FIXED':            self.tc_fd.fixed_conv_in_imprecise,
492+            'TEXT':             self.ascii_conv_in,
493+            'TEXT_UNICODE':     self.unicode_conv_in,
494+            'BLOB':             self.blob_conv_in
495+        })
496+        self.cursor.set_type_trans_out({
497+            'DATE':             self.tc_dt.date_conv_out,
498+            'TIME':             self.tc_dt.time_conv_out,
499+            'TIMESTAMP':        self.tc_dt.timestamp_conv_out,
500+            'FIXED':            self.tc_fd.fixed_conv_out_imprecise,
501+            'TEXT':             self.dj_ue.force_unicode,
502+            'TEXT_UNICODE':     self.tc_tu.unicode_conv_out,
503+            'BLOB':             self.blob_conv_out
504+        })
505+   
506+    def _get_query(self):
507+        if self._statement:
508+            return self._statement.sql
509+    def _get_statement(self):
510+        if self._statement:
511+            return self._statement
512+    query = property(_get_query)
513+    statement = property(_get_statement)
514+       
515+    def execute(self, query, params=()):
516+        query = self.convert_query(query, len(params))
517+        if self._get_query() != query:
518+            try:
519+                self._statement = self.cursor.prep(query)
520+            except Database.ProgrammingError, e:
521+                print query % params
522+                raise DatabaseError, e
523+        return self.cursor.execute(self._statement, params)
524+
525+    def executemany(self, query, param_list):
526+        try:
527+            query = self.convert_query(query, len(param_list[0]))
528+        except IndexError:
529+            param_list = []
530+        if self._get_query() != query:
531+            self._statement = self.cursor.prep(query)
532+        return self.cursor.executemany(self._statement, param_list)
533+
534+    def convert_query(self, query, num_params):
535+        return query % tuple("?" * num_params)
536+   
537+    def __getattr__(self, attr):
538+        if attr in self.__dict__:
539+            return self.__dict__[attr]
540+        else:
541+            return getattr(self.cursor, attr)
542+
543+class DatabaseWrapper(BaseDatabaseWrapper):
544+    features = DatabaseFeatures()
545+    ops = DatabaseOperations()
546+    operators = {
547+        'exact': '= %s',
548+        'iexact': '= UPPER(%s)',
549+        'contains': "LIKE %s ESCAPE'\\'",
550+        'icontains': 'CONTAINING %s', #case is ignored
551+        'gt': '> %s',
552+        'gte': '>= %s',
553+        'lt': '< %s',
554+        'lte': '<= %s',
555+        'startswith': 'STARTING WITH %s', #looks to be faster then LIKE
556+        'endswith': "LIKE %s ESCAPE'\\'",
557+        'istartswith': 'STARTING WITH UPPER(%s)',
558+        'iendswith': "LIKE UPPER(%s) ESCAPE'\\'",
559+    }
560+    _current_cursor = None
561+    def _connect(self, settings):
562+        if settings.DATABASE_NAME == '':
563+            from django.core.exceptions import ImproperlyConfigured
564+            raise ImproperlyConfigured, "You need to specify DATABASE_NAME in your Django settings file."
565+        charset = 'UNICODE_FSS'
566+        if hasattr(settings, 'FIREBIRD_CHARSET'):
567+            if settings.FIREBIRD_CHARSET == 'UTF8':
568+                charset = 'UTF8'   
569+        kwargs = {'charset' : charset }
570+        if settings.DATABASE_HOST:
571+            kwargs['dsn'] = "%s:%s" % (settings.DATABASE_HOST, settings.DATABASE_NAME)
572+        else:
573+            kwargs['dsn'] = "localhost:%s" % settings.DATABASE_NAME
574+        if settings.DATABASE_USER:
575+            kwargs['user'] = settings.DATABASE_USER
576+        if settings.DATABASE_PASSWORD:
577+            kwargs['password'] = settings.DATABASE_PASSWORD
578+        self.connection = Database.connect(**kwargs)
579+        assert self.charset == charset
580+        try:
581+            self.connection.execute_immediate("""
582+                DECLARE EXTERNAL FUNCTION rand
583+                RETURNS DOUBLE PRECISION
584+                BY VALUE ENTRY_POINT 'IB_UDF_rand' MODULE_NAME 'ib_udf';
585+            """)
586+        except Database.ProgrammingError:
587+            pass #Already defined
588+       
589+    def cursor(self, name=None):
590+        #Cursors can be named
591+        #http://kinterbasdb.sourceforge.net/dist_docs/usage.html#adv_named_cursors
592+        #and maybe useful for scrolling updates and deletes
593+        from django.conf import settings
594+        cursor = self._cursor(settings, name)
595+        if settings.DEBUG:
596+            return self.make_debug_cursor(cursor)
597+        return cursor
598+   
599+    def _cursor(self, settings, name):
600+        if self.connection is None:
601+            self._connect(settings)
602+        cursor = self.connection.cursor()
603+        if name:
604+            cursor.name = name
605+        cursor = FirebirdCursorWrapper(cursor)
606+        self._current_cursor = cursor
607+        return cursor
608+   
609+    #Returns query from prepared statement
610+    def _get_query(self):
611+        if self._current_cursor:
612+            return self._current_cursor.query
613+    query = property(_get_query)
614+    #Returns prepared statement itself
615+    def _get_statement(self):
616+        if self._current_cursor:
617+            return self._current_cursor.statement
618+    statement = property(_get_statement)
619+       
620+   
621+    def __getattr__(self, attr):
622+        if attr in self.__dict__:
623+            return self.__dict__[attr]
624+        else:
625+            return getattr(self.connection, attr)
626+   
627+
628Index: django/db/backends/firebird/client.py
629===================================================================
630--- django/db/backends/firebird/client.py       (revision 0)
631+++ django/db/backends/firebird/client.py       (revision 0)
632@@ -0,0 +1,11 @@
633+from django.conf import settings
634+import os
635+
636+def runshell():
637+    args = [settings.DATABASE_NAME]
638+    args += ["-u %s" % settings.DATABASE_USER]
639+    if settings.DATABASE_PASSWORD:
640+        args += ["-p %s" % settings.DATABASE_PASSWORD]
641+    if 'FIREBIRD' not in os.environ:
642+        path = '/opt/firebird/bin/'
643+    os.system(path + 'isql ' + ' '.join(args))
644Index: django/db/backends/firebird/__init__.py
645===================================================================
646Index: django/db/backends/firebird/introspection.py
647===================================================================
648--- django/db/backends/firebird/introspection.py        (revision 0)
649+++ django/db/backends/firebird/introspection.py        (revision 0)
650@@ -0,0 +1,93 @@
651+from django.db import transaction
652+from django.db.backends.firebird.base import DatabaseOperations
653+
654+quote_name = DatabaseOperations().quote_name
655+
656+def get_table_list(cursor):
657+    "Returns a list of table names in the current database."
658+    cursor.execute("""
659+        SELECT rdb$relation_name FROM rdb$relations
660+        WHERE rdb$system_flag = 0 AND rdb$view_blr IS NULL ORDER BY rdb$relation_name""")
661+    return [str(row[0].strip().lower()) for row in cursor.fetchall()]
662+
663+def get_table_description(cursor, table_name):
664+    "Returns a description of the table, with the DB-API cursor.description interface."
665+    #cursor.execute("SELECT FIRST 1 * FROM %s" % quote_name(table_name))
666+    #return cursor.description
667+    # (name, type_code, display_size, internal_size, precision, scale, null_ok)
668+    cursor.execute("""
669+        SELECT DISTINCT R.RDB$FIELD_NAME AS FNAME,
670+                  F.RDB$FIELD_TYPE AS FTYPE,
671+                  F.RDB$FIELD_LENGTH AS FLENGTH,
672+                  F.RDB$FIELD_PRECISION AS FPRECISION,
673+                  F.RDB$FIELD_SCALE AS FSCALE,
674+                  R.RDB$NULL_FLAG AS NULL_FLAG,
675+                  R.RDB$FIELD_POSITION
676+        FROM RDB$RELATION_FIELDS R
677+             JOIN RDB$FIELDS F ON R.RDB$FIELD_SOURCE=F.RDB$FIELD_NAME
678+        WHERE F.RDB$SYSTEM_FLAG=0 and R.RDB$RELATION_NAME= %s
679+        ORDER BY R.RDB$FIELD_POSITION
680+    """, (table_name,))
681+    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()]
682+
683+
684+def get_relations(cursor, table_name):
685+    """
686+    Returns a dictionary of {field_index: (field_index_other_table, other_table)}
687+    representing all relationships to the given table. Indexes are 0-based.
688+    """
689+    cursor.execute("""
690+        SELECT seg.rdb$field_name, seg_ref.rdb$field_name, idx_ref.rdb$relation_name
691+        FROM rdb$indices idx
692+        INNER JOIN rdb$index_segments seg
693+            ON seg.rdb$index_name = idx.rdb$index_name
694+        INNER JOIN rdb$indices idx_ref
695+            ON idx_ref.rdb$index_name = idx.rdb$foreign_key
696+        INNER JOIN rdb$index_segments seg_ref
697+            ON seg_ref.rdb$index_name = idx_ref.rdb$index_name
698+        WHERE idx.rdb$relation_name = %s
699+            AND idx.rdb$foreign_key IS NOT NULL""", [table_name])
700+
701+    relations = {}
702+    for row in cursor.fetchall():
703+        relations[row[0].rstrip()] = (row[1].strip(), row[2].strip())
704+    return relations
705+
706+def get_indexes(cursor, table_name):
707+    """
708+    Returns a dictionary of fieldname -> infodict for the given table,
709+    where each infodict is in the format:
710+        {'primary_key': boolean representing whether it's the primary key,
711+         'unique': boolean representing whether it's a unique index}
712+    """
713+
714+    # This query retrieves each field name and index type on the given table.
715+    cursor.execute("""
716+        SELECT seg.rdb$field_name, const.rdb$constraint_type
717+        FROM rdb$relation_constraints const
718+        LEFT JOIN rdb$index_segments seg
719+            ON seg.rdb$index_name = const.rdb$index_name
720+        WHERE const.rdb$relation_name = %s
721+            AND (const.rdb$constraint_type = 'PRIMARY KEY'
722+                OR const.rdb$constraint_type = 'UNIQUE')""", [table_name])
723+    indexes = {}
724+    for row in cursor.fetchall():
725+        indexes[row[0].strip()] = {
726+            'primary_key': ('PRIMARY KEY' == row[1].strip()),
727+            'unique': ('UNIQUE' == row[1].strip())}
728+    return indexes
729+
730+# Maps type codes to Django Field types.
731+# !todo
732+DATA_TYPES_REVERSE = {
733+    7: 'BooleanField',
734+    7: 'SmallIntegerField',
735+    8: 'IntegerField',
736+    261: 'TextField',
737+    37: 'IPAddressField',
738+    37: 'CharField',
739+    12: 'DateField',
740+    13: 'TimeField',
741+    35: 'DateTimeField',
742+    10: 'FloatField',
743+}
744Index: django/db/backends/firebird/creation.py
745===================================================================
746--- django/db/backends/firebird/creation.py     (revision 0)
747+++ django/db/backends/firebird/creation.py     (revision 0)
748@@ -0,0 +1,103 @@
749+# This dictionary maps Field objects to their associated Firebird column
750+# types, as strings. Column-type strings can contain format strings; they'll
751+# be interpolated against the values of Field.__dict__ before being output.
752+# If a column type is set to None, it won't be included in the output.
753+
754+import sys, os.path
755+from kinterbasdb import connect, create_database
756+from django.core.management import call_command
757+
758+#TODO: Implement CHECKs
759+DATA_TYPES = {
760+    'ASCIICharField':                'varchar(%(max_length)s) CHARACTER SET ASCII',
761+    'AutoField':                     'integer',
762+    'BooleanField':                  'smallint', # CHECK ("%(column)s" IN (0,1))',
763+    'CharField':                     'varchar(%(max_length)s)',
764+    'CommaSeparatedIntegerField':    'varchar(%(max_length)s) CHARACTER SET ASCII',
765+    'DateField':                     'date',
766+    'DateTimeField':                 'timestamp',
767+    'DecimalField':                  'numeric(%(max_digits)s, %(decimal_places)s)',
768+    'FileField':                     'varchar(%(max_length)s)',
769+    'FilePathField':                 'varchar(%(max_length)s)',
770+    'FloatField':                    'double precision',
771+    'ImageField':                    'varchar(%(max_length)s) CHARACTER SET ASCII',
772+    'IntegerField':                  'integer',
773+    'IPAddressField':                'varchar(15) CHARACTER SET ASCII',
774+    'NullBooleanField':              'smallint', #CHECK (("%(column)"s IN (0,1)) OR ("%(column)s" IS NULL))',
775+    'OneToOneField':                 'integer',
776+    'PhoneNumberField':              'varchar(20)', # CHARACTER SET ASCII',
777+    'PositiveIntegerField':          'integer', # CHECK ("%(column)s" >= 0)',
778+    'PositiveSmallIntegerField':     'smallint',
779+    'SlugField':                     'varchar(%(max_length)s)',
780+    'SmallIntegerField':             'smallint',
781+    'TextField':                     'blob sub_type text',
782+    'TimeField':                     'time',
783+    'URLField':                      'varchar(%(max_length)s) CHARACTER SET ASCII',
784+    'USStateField':                  'varchar(2) CHARACTER SET ASCII',
785+}
786+
787+TEST_DATABASE_PREFIX = 'test_'
788+
789+def create_test_db(settings, connection, verbosity, autoclobber):
790+    # KInterbasDB supports dynamic database creation and deletion
791+    # via the module-level function create_database and the method Connection.drop_database.
792+       
793+    if settings.TEST_DATABASE_NAME:
794+        TEST_DATABASE_NAME = settings.TEST_DATABASE_NAME
795+    else:
796+        dbnametuple = os.path.split(settings.DATABASE_NAME)
797+        TEST_DATABASE_NAME = os.path.join(dbnametuple[0], TEST_DATABASE_PREFIX + dbnametuple[1])
798+   
799+    dsn = "localhost:%s" % TEST_DATABASE_NAME
800+    if settings.DATABASE_HOST:
801+        dsn = "%s:%s" % (settings.DATABASE_HOST, TEST_DATABASE_NAME)
802+   
803+    if os.path.isfile(TEST_DATABASE_NAME):
804+        sys.stderr.write("Database %s already exists\n" % TEST_DATABASE_NAME)
805+        if not autoclobber:
806+            confirm = raw_input("Type 'yes' if you would like to try deleting the test database '%s', or 'no' to cancel: " % TEST_DATABASE_NAME)
807+        if autoclobber or confirm == 'yes':
808+            if verbosity >= 1:
809+                print "Destroying old test database..."
810+            old_connection = connect(dsn=dsn, user=settings.DATABASE_USER, password=settings.DATABASE_PASSWORD)
811+            old_connection.drop_database()
812+        else:
813+                print "Tests cancelled."
814+                sys.exit(1)
815+           
816+    if verbosity >= 1:
817+        print "Creating test database..."
818+    try:
819+        charset = 'UNICODE_FSS'
820+        if hasattr(settings, 'FIREBIRD_CHARSET'):
821+            if settings.FIREBIRD_CHARSET == 'UTF8':
822+                charset='UTF8'               
823+        create_database("create database '%s' user '%s' password '%s' default character set %s" % \
824+            (dsn, settings.DATABASE_USER, settings.DATABASE_PASSWORD, charset))
825+    except Exception, e:
826+        sys.stderr.write("Got an error creating the test database: %s\n" % e)
827+        sys.exit(2)
828+           
829+
830+    connection.close()
831+    settings.DATABASE_NAME = TEST_DATABASE_NAME
832+
833+    call_command('syncdb', verbosity=verbosity, interactive=False)
834+
835+    if settings.CACHE_BACKEND.startswith('db://'):
836+        cache_name = settings.CACHE_BACKEND[len('db://'):]
837+        call_command('createcachetable', cache_name)
838+
839+    # Get a cursor (even though we don't need one yet). This has
840+    # the side effect of initializing the test database.
841+    cursor = connection.cursor()
842+
843+    return TEST_DATABASE_NAME
844+
845+def destroy_test_db(settings, connection, old_database_name, verbosity):
846+    # KInterbasDB supports dynamic database deletion via the method Connection.drop_database.
847+    if verbosity >= 1:
848+        print "Destroying test database..."
849+    connection.drop_database()
850+   
851+
852Index: django/core/management/sql.py
853===================================================================
854--- django/core/management/sql.py       (revision 6652)
855+++ django/core/management/sql.py       (working copy)
856@@ -263,7 +263,10 @@
857         # Make the definition (e.g. 'foo VARCHAR(30)') for this field.
858         field_output = [style.SQL_FIELD(qn(f.column)),
859             style.SQL_COLTYPE(col_type)]
860-        field_output.append(style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or '')))
861+        nullstring = ""
862+        if connection.features.needs_default_null:
863+            nullstring = "DEFAULT "
864+        field_output.append(style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or nullstring)))
865         if f.unique and (not f.primary_key or connection.features.allows_unique_and_pk):
866             field_output.append(style.SQL_KEYWORD('UNIQUE'))
867         if f.primary_key:
868@@ -276,8 +279,8 @@
869             if inline_references and f.rel.to in known_models:
870                 field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \
871                     style.SQL_TABLE(qn(f.rel.to._meta.db_table)) + ' (' + \
872-                    style.SQL_FIELD(qn(f.rel.to._meta.get_field(f.rel.field_name).column)) + ')' +
873-                    connection.ops.deferrable_sql()
874+                    style.SQL_FIELD(qn(f.rel.to._meta.get_field(f.rel.field_name).column)) + ')' + \
875+                    connection.ops.cascade_delete_update_sql() + connection.ops.deferrable_sql()
876                 )
877             else:
878                 # We haven't yet created the table to which this field
879@@ -335,6 +338,7 @@
880                 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
881                     (qn(r_table), truncate_name(r_name, connection.ops.max_name_length()),
882                     qn(r_col), qn(table), qn(col),
883+                    connection.ops.cascade_delete_update_sql(),
884                     connection.ops.deferrable_sql()))
885             del pending_references[model]
886     return final_output
887@@ -364,19 +368,21 @@
888                 tablespace_sql))
889             if inline_references:
890                 deferred = []
891-                table_output.append('    %s %s %s %s (%s)%s,' %
892+                table_output.append('    %s %s %s %s (%s)%s%s,' %
893                     (style.SQL_FIELD(qn(f.m2m_column_name())),
894                     style.SQL_COLTYPE(models.ForeignKey(model).db_type()),
895                     style.SQL_KEYWORD('NOT NULL REFERENCES'),
896                     style.SQL_TABLE(qn(opts.db_table)),
897                     style.SQL_FIELD(qn(opts.pk.column)),
898+                    connection.ops.cascade_delete_update_sql(),
899                     connection.ops.deferrable_sql()))
900-                table_output.append('    %s %s %s %s (%s)%s,' %
901+                table_output.append('    %s %s %s %s (%s)%s%s,' %
902                     (style.SQL_FIELD(qn(f.m2m_reverse_name())),
903                     style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type()),
904                     style.SQL_KEYWORD('NOT NULL REFERENCES'),
905                     style.SQL_TABLE(qn(f.rel.to._meta.db_table)),
906                     style.SQL_FIELD(qn(f.rel.to._meta.pk.column)),
907+                    connection.ops.cascade_delete_update_sql(),
908                     connection.ops.deferrable_sql()))
909             else:
910                 table_output.append('    %s %s %s,' %
911@@ -412,6 +418,7 @@
912                 (qn(r_table),
913                 truncate_name(r_name, connection.ops.max_name_length()),
914                 qn(r_col), qn(table), qn(col),
915+                connection.ops.cascade_delete_update_sql(),
916                 connection.ops.deferrable_sql()))
917 
918             # Add any extra SQL needed to support auto-incrementing PKs
919Index: django/contrib/contenttypes/models.py
920===================================================================
921--- django/contrib/contenttypes/models.py       (revision 6652)
922+++ django/contrib/contenttypes/models.py       (working copy)
923@@ -1,6 +1,7 @@
924 from django.db import models
925 from django.utils.translation import ugettext_lazy as _
926 from django.utils.encoding import smart_unicode
927+from django.conf import settings
928 
929 CONTENT_TYPE_CACHE = {}
930 class ContentTypeManager(models.Manager):
931@@ -31,10 +32,15 @@
932         global CONTENT_TYPE_CACHE
933         CONTENT_TYPE_CACHE = {}
934 
935-class ContentType(models.Model):
936+class ContentType(models.Model):   
937     name = models.CharField(max_length=100)
938-    app_label = models.CharField(max_length=100)
939-    model = models.CharField(_('python model class name'), max_length=100)
940+    # Need this because of Firebird restrictions on index key size < 252 bytes
941+    if settings.DATABASE_ENGINE == 'firebird':
942+        app_label = models.ASCIICharField(max_length=96)
943+        model = models.ASCIICharField(_('python model class name'), max_length=96)
944+    else:
945+        app_label = models.CharField(max_length=100)
946+        model = models.CharField(_('python model class name'), max_length=100)
947     objects = ContentTypeManager()
948     class Meta:
949         verbose_name = _('content type')
950Index: django/contrib/auth/models.py
951===================================================================
952--- django/contrib/auth/models.py       (revision 6652)
953+++ django/contrib/auth/models.py       (working copy)
954@@ -6,6 +6,7 @@
955 from django.contrib.contenttypes.models import ContentType
956 from django.utils.encoding import smart_str
957 from django.utils.translation import ugettext_lazy as _
958+from django.conf import settings
959 import datetime
960 import urllib
961 
962@@ -72,7 +73,11 @@
963     """
964     name = models.CharField(_('name'), max_length=50)
965     content_type = models.ForeignKey(ContentType)
966-    codename = models.CharField(_('codename'), max_length=100)
967+    # Need this because of Firebird restrictions on index key size < 252 bytes
968+    if settings.DATABASE_ENGINE == 'firebird':
969+        codename = models.ASCIICharField(_('codename'), max_length=100)
970+    else:
971+        codename = models.CharField(_('codename'), max_length=100)
972 
973     class Meta:
974         verbose_name = _('permission')