Code

Ticket #1261: firebird-6668.diff

File firebird-6668.diff, 74.8 KB (added by i_i, 7 years ago)

small fixes

Line 
1Index: django/db/models/base.py
2===================================================================
3--- django/db/models/base.py    (revision 6668)
4+++ django/db/models/base.py    (working copy)
5@@ -241,7 +241,10 @@
6             db_values = [f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True)) for f in self._meta.fields if not isinstance(f, AutoField)]
7             # If the PK has been manually set, respect that.
8             if pk_set:
9-                field_names += [f.column for f in self._meta.fields if isinstance(f, AutoField)]
10+                if connection.features.quote_autofields:
11+                    field_names += [qn(f.column) for f in self._meta.fields if isinstance(f, AutoField)]
12+                else:
13+                    field_names += [f.column for f in self._meta.fields if isinstance(f, AutoField)]
14                 db_values += [f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True)) for f in self._meta.fields if isinstance(f, AutoField)]
15             placeholders = ['%s'] * len(field_names)
16             if self._meta.order_with_respect_to:
17Index: django/db/models/fields/__init__.py
18===================================================================
19--- django/db/models/fields/__init__.py (revision 6668)
20+++ django/db/models/fields/__init__.py (working copy)
21@@ -6,7 +6,7 @@
22 except ImportError:
23     from django.utils import _decimal as decimal    # for Python 2.3
24 
25-from django.db import get_creation_module
26+from django.db import connection, get_creation_module
27 from django.db.models import signals
28 from django.dispatch import dispatcher
29 from django.conf import settings
30@@ -66,7 +66,7 @@
31 #
32 #     getattr(obj, opts.pk.attname)
33 
34-class Field(object):
35+class _Field(object):
36     # Provide backwards compatibility for the maxlength attribute and
37     # argument for this class and all subclasses.
38     __metaclass__ = LegacyMaxlength
39@@ -83,11 +83,12 @@
40         core=False, rel=None, default=NOT_PROVIDED, editable=True, serialize=True,
41         prepopulate_from=None, unique_for_date=None, unique_for_month=None,
42         unique_for_year=None, validator_list=None, choices=None, radio_admin=None,
43-        help_text='', db_column=None, db_tablespace=None):
44+        help_text='', db_column=None, db_tablespace=None, encoding=None):
45         self.name = name
46         self.verbose_name = verbose_name
47         self.primary_key = primary_key
48         self.max_length, self.unique = max_length, unique
49+        self.encoding = encoding
50         self.blank, self.null = blank, null
51         # Oracle treats the empty string ('') as null, so coerce the null
52         # option whenever '' is a possible value.
53@@ -148,7 +149,7 @@
54         data_types = get_creation_module().DATA_TYPES
55         internal_type = self.get_internal_type()
56         if internal_type not in data_types:
57-            return None
58+            return None   
59         return data_types[internal_type] % self.__dict__
60 
61     def validate_full(self, field_data, all_data):
62@@ -402,6 +403,12 @@
63         "Returns the value of this field in the given model instance."
64         return getattr(obj, self.attname)
65 
66+# Use the backend's Field class if it defines one. Otherwise, use _Field.
67+if connection.features.uses_custom_field:
68+    Field = connection.ops.field_class(_Field)
69+else:
70+    Field = _Field
71+
72 class AutoField(Field):
73     empty_strings_allowed = False
74     def __init__(self, *args, **kwargs):
75@@ -688,6 +695,14 @@
76         defaults.update(kwargs)
77         return super(DecimalField, self).formfield(**defaults)
78 
79+class DefaultCharField(CharField):
80+    def __init__(self, *args, **kwargs):
81+        DEFAULT_MAX_LENGTH = 100
82+        if hasattr(settings, 'DEFAULT_MAX_LENGTH'):
83+           DEFAULT_MAX_LENGTH = settings.DEFAULT_MAX_LENGT
84+        kwargs['max_length'] = kwargs.get('max_length', DEFAULT_MAX_LENGTH)
85+        CharField.__init__(self, *args, **kwargs)
86+
87 class EmailField(CharField):
88     def __init__(self, *args, **kwargs):
89         kwargs['max_length'] = kwargs.get('max_length', 75)
90@@ -890,6 +905,15 @@
91         defaults.update(kwargs)
92         return super(IPAddressField, self).formfield(**defaults)
93 
94+class LargeTextField(Field):
95+    def get_manipulator_field_objs(self):
96+        return [oldforms.LargeTextField]
97+
98+    def formfield(self, **kwargs):
99+        defaults = {'widget': forms.Textarea}
100+        defaults.update(kwargs)
101+        return super(LargeTextField, self).formfield(**defaults)
102+
103 class NullBooleanField(Field):
104     empty_strings_allowed = False
105     def __init__(self, *args, **kwargs):
106@@ -997,8 +1021,8 @@
107             # doesn't support microseconds.
108             if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
109                 value = value.replace(microsecond=0)
110-            if settings.DATABASE_ENGINE == 'oracle':
111-                # cx_Oracle expects a datetime.datetime to persist into TIMESTAMP field.
112+            if settings.DATABASE_ENGINE in ('oracle', 'firebird'):
113+                # cx_Oracle and kinterbasdb expect a datetime.datetime to persist into TIMESTAMP field.
114                 if isinstance(value, datetime.time):
115                     value = datetime.datetime(1900, 1, 1, value.hour, value.minute,
116                                               value.second, value.microsecond)
117Index: django/db/models/fields/related.py
118===================================================================
119--- django/db/models/fields/related.py  (revision 6668)
120+++ django/db/models/fields/related.py  (working copy)
121@@ -331,18 +331,19 @@
122                         new_ids.add(obj)
123                 # Add the newly created or already existing objects to the join table.
124                 # First find out which items are already added, to avoid adding them twice
125+                qn = connection.ops.quote_name
126                 cursor = connection.cursor()
127                 cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \
128-                    (target_col_name, self.join_table, source_col_name,
129-                    target_col_name, ",".join(['%s'] * len(new_ids))),
130+                    (qn(target_col_name), qn(self.join_table), qn(source_col_name),
131+                    qn(target_col_name), ",".join(['%s'] * len(new_ids))),
132                     [self._pk_val] + list(new_ids))
133                 existing_ids = set([row[0] for row in cursor.fetchall()])
134 
135                 # Add the ones that aren't there already
136                 for obj_id in (new_ids - existing_ids):
137-                    cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
138-                        (self.join_table, source_col_name, target_col_name),
139-                        [self._pk_val, obj_id])
140+                    cursor.execute('INSERT INTO %s (%s, %s) VALUES (%%s, %%s)' % \
141+                        (qn(self.join_table), qn(source_col_name), qn(target_col_name)),
142+                        (self._pk_val, obj_id))
143                 transaction.commit_unless_managed()
144 
145         def _remove_items(self, source_col_name, target_col_name, *objs):
146Index: django/db/models/query.py
147===================================================================
148--- django/db/models/query.py   (revision 6668)
149+++ django/db/models/query.py   (working copy)
150@@ -612,7 +612,10 @@
151         columns = [f.column for f in fields]
152         select = ['%s.%s' % (qn(self.model._meta.db_table), qn(c)) for c in columns]
153         if extra_select:
154-            select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), qn(s[0])) for s in extra_select])
155+            if not settings.DATABASE_ENGINE == 'firebird':
156+                select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), qn(s[0])) for s in extra_select])
157+            else:
158+                select.extend(['(%s) AS %s' % (connection.ops.quote_id_plus_number(s[1]), qn(s[0])) for s in extra_select])
159             field_names.extend([f[0] for f in extra_select])
160 
161         cursor = connection.cursor()
162@@ -1111,7 +1114,6 @@
163             # Last query term was a normal field.
164             column = field.column
165             db_type = field.db_type()
166-
167         where.append(get_where_clause(lookup_type, current_table + '.', column, value, db_type))
168         params.extend(field.get_db_prep_lookup(lookup_type, value))
169 
170@@ -1146,6 +1148,8 @@
171             if isinstance(f, generic.GenericRelation):
172                 from django.contrib.contenttypes.models import ContentType
173                 query_extra = 'AND %s=%%s' % f.rel.to._meta.get_field(f.content_type_field_name).column
174+                if settings.DATABASE_ENGINE == 'firebird':
175+                    query_extra = 'AND %s=%%s' % qn(f.rel.to._meta.get_field(f.content_type_field_name).column)
176                 args_extra = [ContentType.objects.get_for_model(cls).id]
177             else:
178                 query_extra = ''
179Index: django/db/backends/__init__.py
180===================================================================
181--- django/db/backends/__init__.py      (revision 6668)
182+++ django/db/backends/__init__.py      (working copy)
183@@ -48,7 +48,9 @@
184     needs_upper_for_iops = False
185     supports_constraints = True
186     supports_tablespaces = False
187+    quote_autofields = False
188     uses_case_insensitive_names = False
189+    uses_custom_field = False
190     uses_custom_queryset = False
191 
192 class BaseDatabaseOperations(object):
193@@ -65,7 +67,14 @@
194         This SQL is executed when a table is created.
195         """
196         return None
197-
198+   
199+    def cascade_delete_update_sql(self):
200+        """
201+        Returns the SQL necessary to make a cascading deletes and updates
202+        of foreign key references during a CREATE TABLE statement.
203+        """
204+        return ''
205+   
206     def date_extract_sql(self, lookup_type, field_name):
207         """
208         Given a lookup_type of 'year', 'month' or 'day', returns the SQL that
209@@ -127,7 +136,7 @@
210         contain a '%s' placeholder for the value being searched against.
211         """
212         raise NotImplementedError('Full-text search is not implemented for this database backend')
213-
214+   
215     def last_executed_query(self, cursor, sql, params):
216         """
217         Returns a string of the query last executed by the given cursor, with
218@@ -175,7 +184,7 @@
219         is no limit.
220         """
221         return None
222-
223+   
224     def pk_default_value(self):
225         """
226         Returns the value to use during an INSERT statement to specify that
227Index: django/db/backends/firebird/base.py
228===================================================================
229--- django/db/backends/firebird/base.py (revision 0)
230+++ django/db/backends/firebird/base.py (revision 0)
231@@ -0,0 +1,644 @@
232+"""
233+Firebird database backend for Django.
234+
235+Requires KInterbasDB 3.2: http://kinterbasdb.sourceforge.net/
236+The egenix mx (mx.DateTime) is NOT required
237+
238+Database charset should be UNICODE_FSS or UTF8 (FireBird 2.0+)
239+To use UTF8 encoding add FIREBIRD_CHARSET = 'UTF8' to your settings.py
240+UNICODE_FSS works with all versions and uses less memory
241+"""
242+
243+from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
244+import sys
245+try:
246+    import decimal
247+except ImportError:
248+    from django.utils import _decimal as decimal    # for Python 2.3
249+
250+try:
251+    import kinterbasdb as Database
252+except ImportError, e:
253+    from django.core.exceptions import ImproperlyConfigured
254+    raise ImproperlyConfigured, "Error loading KInterbasDB module: %s" % e
255+
256+DatabaseError = Database.DatabaseError
257+IntegrityError = Database.IntegrityError
258+
259+class DatabaseFeatures(BaseDatabaseFeatures):
260+    inline_fk_references = False
261+    needs_datetime_string_cast = False
262+    needs_upper_for_iops = True
263+    quote_autofields = True
264+    supports_constraints = True #turn this off to pass the tests with forward/post references
265+    uses_custom_field = True
266+    uses_custom_queryset = True
267+
268+class DatabaseOperations(BaseDatabaseOperations):
269+    _max_name_length = 31
270+    def __init__(self):
271+        self._firebird_version = None
272+        self._page_size = None
273+   
274+    def get_generator_name(self, name):
275+        return '%s_G' % util.truncate_name(name.strip('"'), self._max_name_length-2).upper()
276+
277+    def get_trigger_name(self, name):
278+        return '%s_T' % util.truncate_name(name.strip('"'), self._max_name_length-2).upper()
279+   
280+    def _get_firebird_version(self):
281+        if self._firebird_version is None:
282+            from django.db import connection
283+            self._firebird_version = [int(val) for val in connection.server_version.split()[-1].split('.')]
284+        return self._firebird_version
285+    firebird_version = property(_get_firebird_version)
286
287+    def _get_page_size(self):
288+        if self._page_size is None:
289+            from django.db import connection
290+            self._page_size = connection.database_info(Database.isc_info_page_size, 'i')
291+        return self._page_size
292+    page_size = property(_get_page_size)
293+   
294+    def _get_index_limit(self):
295+        if self.firebird_version[0] < 2:
296+            self._index_limit = 252
297+        else:
298+            page_size = self._get_page_size()
299+            self._index_limit = page_size/4
300+        return self._index_limit
301+    index_limit = property(_get_index_limit)
302+
303+    def autoinc_sql(self, style, table_name, column_name):
304+        """
305+        To simulate auto-incrementing primary keys in Firebird, we have to
306+        create a generator and a trigger.
307+   
308+        Create the generators and triggers names based only on table name
309+        since django only support one auto field per model
310+        """
311+   
312+        generator_name = self.get_generator_name(table_name)
313+        trigger_name = self.get_trigger_name(table_name)
314+        column_name = self.quote_name(column_name)
315+        table_name = self.quote_name(table_name)
316+       
317+        generator_sql = "%s %s;" % ( style.SQL_KEYWORD('CREATE GENERATOR'),
318+                                     style.SQL_TABLE(generator_name))     
319+        trigger_sql = "\n".join([
320+            "%s %s %s %s" % ( \
321+            style.SQL_KEYWORD('CREATE TRIGGER'), style.SQL_TABLE(trigger_name), style.SQL_KEYWORD('FOR'),
322+            style.SQL_TABLE(table_name)),
323+            "%s 0 %s" % (style.SQL_KEYWORD('ACTIVE BEFORE INSERT POSITION'), style.SQL_KEYWORD('AS')),
324+            style.SQL_KEYWORD('BEGIN'),
325+            "  %s ((%s.%s %s) %s (%s.%s = 0)) %s" % ( \
326+                style.SQL_KEYWORD('IF'),
327+                style.SQL_KEYWORD('NEW'), style.SQL_FIELD(column_name), style.SQL_KEYWORD('IS NULL'),
328+                style.SQL_KEYWORD('OR'), style.SQL_KEYWORD('NEW'), style.SQL_FIELD(column_name),
329+                style.SQL_KEYWORD('THEN')
330+            ),
331+            "  %s" % style.SQL_KEYWORD('BEGIN'),
332+            "    %s.%s = %s(%s, 1);" % ( \
333+                style.SQL_KEYWORD('NEW'), style.SQL_FIELD(column_name),
334+                style.SQL_KEYWORD('GEN_ID'), style.SQL_TABLE(generator_name)
335+            ),
336+            "  %s" % style.SQL_KEYWORD('END'),
337+            style.SQL_KEYWORD('END')
338+            ])
339+        return (generator_sql, trigger_sql)
340+
341+    def max_name_length(self):
342+        return self._max_name_length
343+         
344+    def field_class(this, DefaultField):
345+        from django.db import connection
346+        from django.db.models.fields import prep_for_like_query
347+        class FirebirdField(DefaultField):
348+            def get_db_prep_lookup(self, lookup_type, value):       
349+                "Returns field's value prepared for database lookup."
350+                if lookup_type in ('exact', 'regex', 'iregex', 'gt', 'gte', 'lt',
351+                    'lte', 'month', 'day', 'search', 'icontains',
352+                    'startswith', 'istartswith'):
353+                    return [value]
354+                elif lookup_type in ('range', 'in'):
355+                    return value
356+                elif lookup_type in ('contains',):
357+                    return ["%%%s%%" % prep_for_like_query(value)]
358+                elif lookup_type == 'iexact':
359+                    return [prep_for_like_query(value)]
360+                elif lookup_type in ('endswith', 'iendswith'):
361+                    return ["%%%s" % prep_for_like_query(value)]
362+                elif lookup_type == 'isnull':
363+                    return []
364+                elif lookup_type == 'year':
365+                    try:
366+                        value = int(value)
367+                    except ValueError:
368+                        raise ValueError("The __year lookup type requires an integer argument")
369+                    return ['%s-01-01 00:00:00' % value, '%s-12-31 23:59:59.999999' % value]
370+                raise TypeError("Field has invalid lookup: %s" % lookup_type)
371+        return FirebirdField
372+
373+    def query_set_class(this, DefaultQuerySet):
374+        from django.db import connection
375+        from django.db.models.query import EmptyResultSet, GET_ITERATOR_CHUNK_SIZE
376+        class FirebirdQuerySet(DefaultQuerySet):
377+            def _get_sql_clause(self):
378+                from django.db.models.query import SortedDict, handle_legacy_orderlist, orderfield2column, fill_table_cache
379+                qn = this.quote_name
380+                opts = self.model._meta
381+
382+                # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z.
383+                select = ["%s.%s" % (qn(opts.db_table), qn(f.column)) for f in opts.fields]
384+                tables = [qn(t) for t in self._tables]
385+                joins = SortedDict()
386+                where = self._where[:]
387+                params = self._params[:]
388+
389+                # Convert self._filters into SQL.
390+                joins2, where2, params2 = self._filters.get_sql(opts)
391+                joins.update(joins2)
392+                where.extend(where2)
393+                params.extend(params2)
394+
395+                # Add additional tables and WHERE clauses based on select_related.
396+                if self._select_related:
397+                    fill_table_cache(opts, select, tables, where,
398+                                     old_prefix=opts.db_table,
399+                                     cache_tables_seen=[opts.db_table],
400+                                     max_depth=self._max_related_depth)
401+               
402+                # Add any additional SELECTs.
403+                if self._select:
404+                    select.extend([('(%s AS %s') % (qn(s[1]), qn(s[0])) for s in self._select.items()])
405+
406+                # Start composing the body of the SQL statement.
407+                sql = [" FROM", qn(opts.db_table)]
408+
409+                # Compose the join dictionary into SQL describing the joins.
410+                if joins:
411+                    sql.append(" ".join(["%s %s %s ON %s" % (join_type, table, alias, condition)
412+                                    for (alias, (table, join_type, condition)) in joins.items()]))
413+
414+                # Compose the tables clause into SQL.
415+                if tables:
416+                    sql.append(", " + ", ".join(tables))
417+
418+                # Compose the where clause into SQL.
419+                if where:
420+                    sql.append(where and "WHERE " + " AND ".join(where))
421+
422+                # ORDER BY clause
423+                order_by = []
424+                if self._order_by is not None:
425+                    ordering_to_use = self._order_by
426+                else:
427+                    ordering_to_use = opts.ordering
428+                for f in handle_legacy_orderlist(ordering_to_use):
429+                    if f == '?': # Special case.
430+                        order_by.append(connection.ops.random_function_sql())
431+                    else:
432+                        if f.startswith('-'):
433+                            col_name = f[1:]
434+                            order = "DESC"
435+                        else:
436+                            col_name = f
437+                            order = "ASC"
438+                        if "." in col_name:
439+                            table_prefix, col_name = col_name.split('.', 1)
440+                            table_prefix = qn(table_prefix) + '.'
441+                        else:
442+                            # Use the database table as a column prefix if it wasn't given,
443+                            # and if the requested column isn't a custom SELECT.
444+                            if "." not in col_name and col_name not in (self._select or ()):
445+                                table_prefix = qn(opts.db_table) + '.'
446+                            else:
447+                                table_prefix = ''
448+                        order_by.append('%s%s %s' % \
449+                            (table_prefix, qn(orderfield2column(col_name, opts)), order))
450+                if order_by:
451+                    sql.append("ORDER BY " + ", ".join(order_by))
452+
453+                return select, " ".join(sql), params
454+           
455+            def iterator(self):
456+                "Performs the SELECT database lookup of this QuerySet."
457+                from django.db.models.query import get_cached_row
458+                try:
459+                    select, sql, params = self._get_sql_clause()
460+                except EmptyResultSet:
461+                    raise StopIteration
462+                   
463+                # self._select is a dictionary, and dictionaries' key order is
464+                # undefined, so we convert it to a list of tuples.
465+                extra_select = self._select.items()
466+               
467+                cursor = connection.cursor()
468+                limit_offset_before = ""
469+                if self._limit is not None:
470+                    limit_offset_before += "FIRST %s " % self._limit
471+                    if self._offset:
472+                        limit_offset_before += "SKIP %s " % self._offset
473+                else:
474+                    assert self._offset is None, "'offset' is not allowed without 'limit'"
475+                cursor.execute("SELECT " + limit_offset_before + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
476+                fill_cache = self._select_related
477+                fields = self.model._meta.fields
478+                index_end = len(fields)
479+                while 1:
480+                    rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
481+                    if not rows:
482+                        raise StopIteration
483+                    for row in rows:
484+                        row = self.resolve_columns(row, fields)
485+                        if fill_cache:
486+                            obj, index_end = get_cached_row(klass=self.model, row=row,
487+                                                            index_start=0, max_depth=self._max_related_depth)
488+                        else:
489+                            obj = self.model(*row[:index_end])
490+                        for i, k in enumerate(extra_select):
491+                            setattr(obj, k[0], row[index_end+i])
492+                        yield obj
493+           
494+            def resolve_columns(self, row, fields=()):
495+                from django.db.models.fields import DateField, DateTimeField, \
496+                    TimeField, BooleanField, NullBooleanField, DecimalField, Field
497+                values = []
498+                for value, field in map(None, row, fields):
499+                    #if value is None and isinstance(field, Field) and field.empty_strings_allowed:
500+                    #    value = u''
501+                    # Convert 1 or 0 to True or False
502+                    if value in (1, 0) and isinstance(field, (BooleanField, NullBooleanField)):
503+                        value = bool(value)
504+
505+                    values.append(value)
506+                return values
507+               
508+            def extra(self, select=None, where=None, params=None, tables=None):
509+                assert self._limit is None and self._offset is None, \
510+                        "Cannot change a query once a slice has been taken"
511+                clone = self._clone()
512+                qn = this.quote_name
513+                if select: clone._select.update(select)
514+                if where:
515+                    qn_where = []
516+                    for where_item in where:
517+                        try:
518+                            table, col_exact = where_item.split(".")
519+                            col, value = col_exact.split("=")
520+                            where_item = "%s.%s = %s" % (qn(table.strip()),
521+                                qn(col.strip()), value.strip())
522+                        except:
523+                            try:
524+                                table, value = where_item.split("=")
525+                                where_item = "%s = %s" % (qn(table.strip()), qn(value.strip()))
526+                            except:
527+                                raise TypeError, "Can't understand extra WHERE clause: %s" % where
528+                        qn_where.append(where_item)
529+                    clone._where.extend(qn_where)
530+                if params: clone._params.extend(params)
531+                if tables: clone._tables.extend(tables)
532+                return clone
533+               
534+        return FirebirdQuerySet
535+
536+    def quote_name(self, name):
537+        name = '"%s"' % util.truncate_name(name.strip('"'), self._max_name_length)
538+        return name
539+   
540+    def quote_id_plus_number(self, name):
541+        try:
542+            return '"%s" + %s' % tuple(s.strip() for s in name.strip('"').split('+'))
543+        except:
544+            return self.quote_name(name)
545+           
546+    def pk_default_value(self):
547+        """
548+        Returns the value to use during an INSERT statement to specify that
549+        the field should use its default value.
550+        """
551+        return 'NULL'
552+   
553+    def field_cast_sql(self, db_type):
554+        return '%s'
555+   
556+    def last_insert_id(self, cursor, table_name, pk_name=None):
557+        generator_name = self.get_generator_name(table_name)
558+        cursor.execute('SELECT GEN_ID(%s, 0) from RDB$DATABASE' % generator_name)
559+        return cursor.fetchone()[0]
560+
561+    def date_extract_sql(self, lookup_type, column_name):
562+        # lookup_type is 'year', 'month', 'day'
563+        return "EXTRACT(%s FROM %s)" % (lookup_type, column_name)
564+
565+    def date_trunc_sql(self, lookup_type, column_name):
566+        if lookup_type == 'year':
567+             sql = "EXTRACT(year FROM %s)||'-01-01 00:00:00'" % column_name
568+        elif lookup_type == 'month':
569+            sql = "EXTRACT(year FROM %s)||'-'||EXTRACT(month FROM %s)||'-01 00:00:00'" % (column_name, column_name)
570+        elif lookup_type == 'day':
571+            sql = "EXTRACT(year FROM %s)||'-'||EXTRACT(month FROM %s)||'-'||EXTRACT(day FROM %s)||' 00:00:00'" % (column_name, column_name, column_name)
572+        return "CAST(%s AS TIMESTAMP)" % sql
573+   
574+    def cascade_delete_update_sql(self):
575+        #TODO: Use ON DELETE CASCADE only on M2M tables by default
576+        return " ON DELETE CASCADE ON UPDATE CASCADE"
577+   
578+    def datetime_cast_sql(self):
579+        return None
580+
581+    def limit_offset_sql(self, limit, offset=None):
582+        # limits are handled in custom FirebirdQuerySet
583+        assert False, 'Limits are handled in a different way in Firebird'
584+        return ""
585+
586+    def random_function_sql(self):
587+        return "rand()"
588+
589+    def start_transaction_sql(self):
590+        return ""
591+
592+    def sequence_reset_sql(self, style, model_list):
593+        from django.db import models
594+        output = []
595+        sql = ['%s %s %s' % (style.SQL_KEYWORD('CREATE OR ALTER PROCEDURE'),
596+                             style.SQL_TABLE('"GENERATOR_RESET"'),
597+                             style.SQL_KEYWORD('AS'))]
598+        sql.append('%s %s' % (style.SQL_KEYWORD('DECLARE VARIABLE'), style.SQL_COLTYPE('start_val integer;')))
599+        sql.append('%s %s' % (style.SQL_KEYWORD('DECLARE VARIABLE'), style.SQL_COLTYPE('gen_val integer;')))
600+        sql.append('\t%s' % style.SQL_KEYWORD('BEGIN'))
601+        sql.append('\t\t%s %s %s %s %s %s;' % (style.SQL_KEYWORD('SELECT MAX'), style.SQL_FIELD('(%(col)s)'),
602+                                           style.SQL_KEYWORD('FROM'), style.SQL_TABLE('%(table)s'),
603+                                           style.SQL_KEYWORD('INTO'), style.SQL_COLTYPE(':start_val')))
604+        sql.append('\t\t%s (%s %s) %s' % (style.SQL_KEYWORD('IF'), style.SQL_COLTYPE('start_val'),
605+                                    style.SQL_KEYWORD('IS NULL'), style.SQL_KEYWORD('THEN')))
606+        sql.append('\t\t\t%s = %s(%s, 1 - %s(%s, 0));' %\
607+            (style.SQL_COLTYPE('gen_val'), style.SQL_KEYWORD('GEN_ID'), style.SQL_TABLE('%(gen)s'),
608+             style.SQL_KEYWORD('GEN_ID'), style.SQL_TABLE('%(gen)s')))
609+        sql.append('\t\t%s' % style.SQL_KEYWORD('ELSE'))
610+        sql.append('\t\t\t%s = %s(%s, %s - %s(%s, 0));' %\
611+            (style.SQL_COLTYPE('gen_val'), style.SQL_KEYWORD('GEN_ID'),
612+             style.SQL_TABLE('%(gen)s'), style.SQL_COLTYPE('start_val'), style.SQL_KEYWORD('GEN_ID'),
613+             style.SQL_TABLE('%(gen)s')))
614+        sql.append('\t\t%s;' % style.SQL_KEYWORD('EXIT'))
615+        sql.append('%s;' % style.SQL_KEYWORD('END'))
616+        sql ="\n".join(sql)
617+        for model in model_list:
618+            for f in model._meta.fields:
619+                if isinstance(f, models.AutoField):
620+                    generator_name = self.get_generator_name(model._meta.db_table)
621+                    column_name = self.quote_name(f.db_column or f.name)
622+                    table_name = self.quote_name(model._meta.db_table)
623+                    output.append(sql % {'col' : column_name, 'table' : table_name, 'gen' : generator_name})
624+                    output.append('%s %s;' % (style.SQL_KEYWORD('EXECUTE PROCEDURE'),
625+                                              style.SQL_TABLE('"GENERATOR_RESET"')))
626+                    break # Only one AutoField is allowed per model, so don't bother continuing.
627+            for f in model._meta.many_to_many:
628+                generator_name = self.get_generator_name(f.m2m_db_table())
629+                table_name = self.quote_name(f.m2m_db_table())
630+                column_name = '"id"'
631+                output.append(sql % {'col' : column_name, 'table' : table_name, 'gen' : generator_name})
632+                output.append('%s %s;' % (style.SQL_KEYWORD('EXECUTE PROCEDURE'),
633+                                          style.SQL_TABLE('"GENERATOR_RESET"')))
634+        return output
635+   
636+    def sql_flush(self, style, tables, sequences):
637+        if tables:
638+            #TODO: Alter all tables witk FKs without ON DELETE CASCADE to set it
639+            # Them reset to previous state when all are deleted
640+            # Becasue we may not want to have ON DELETE CASCADE by default on all FK fields
641+            sql = ['%s %s %s;' % \
642+                    (style.SQL_KEYWORD('DELETE'),
643+                     style.SQL_KEYWORD('FROM'),
644+                     style.SQL_FIELD(self.quote_name(table))
645+                     ) for table in tables]
646+            for generator_info in sequences:
647+                table_name = generator_info['table']
648+                query = "%s %s %s 0;" % (style.SQL_KEYWORD('SET GENERATOR'),
649+                    style.SQL_TABLE(self.get_generator_name(table_name)), style.SQL_KEYWORD('TO'))
650+                sql.append(query)
651+            return sql
652+        else:
653+            return []
654+
655+    def fulltext_search_sql(self, field_name):
656+        # We use varchar for TextFields so this is possible
657+        # Look at http://www.volny.cz/iprenosil/interbase/ip_ib_strings.htm
658+        return '%%s CONTAINING %s' % self.quote_name(field_name)
659+       
660+    def drop_sequence_sql(self, table):
661+        return "DROP GENERATOR %s;" % self.get_generator_name(table)
662+       
663+class FirebirdCursorWrapper(object):
664+    """
665+    Django uses "format" ('%s') style placeholders, but firebird uses "qmark" ('?') style.
666+    This fixes it -- but note that if you want to use a literal "%s" in a query,
667+    you'll need to use "%%s".
668+   
669+    We also do all automatic type conversions here.
670+    """
671+    import kinterbasdb.typeconv_datetime_stdlib as tc_dt
672+    import kinterbasdb.typeconv_fixed_decimal as tc_fd
673+    import kinterbasdb.typeconv_text_unicode as tc_tu
674+    import django.utils.encoding as dj_ue
675+   
676+   
677+    def timestamp_conv_in(self, timestamp):
678+        if isinstance(timestamp, basestring):
679+            #Replaces 6 digits microseconds to 4 digits allowed in Firebird
680+            timestamp = timestamp[:24]
681+        return self.tc_dt.timestamp_conv_in(timestamp)
682+
683+    def time_conv_in(self, value):
684+        import datetime
685+        if isinstance(value, datetime.datetime):
686+            value = datetime.time(value.hour, value.minute, value.second, value.microsecond)       
687+        return self.tc_dt.time_conv_in(value)
688+   
689+    def ascii_conv_in(self, text):
690+        if text is not None: 
691+            return self.dj_ue.smart_str(text, 'ascii')
692+   
693+    def ascii_conv_out(self, text):
694+        if text is not None:
695+            return self.dj_ue.smart_unicode(text)
696+   
697+    def fixed_conv_in(self, (val, scale)):
698+        if val is not None:
699+            if isinstance(val, basestring):
700+                val = decimal.Decimal(val)
701+            return self.tc_fd.fixed_conv_in_precise((val, scale))
702+
703+    def unicode_conv_in(self, text):
704+        if text[0] is not None:
705+            return self.tc_tu.unicode_conv_in((self.dj_ue.smart_unicode(text[0]), self.FB_CHARSET_CODE))
706+
707+    def blob_conv_in(self, text):
708+        return self.tc_tu.unicode_conv_in((self.dj_ue.smart_unicode(text), self.FB_CHARSET_CODE))
709+
710+    def blob_conv_out(self, text):
711+        return self.tc_tu.unicode_conv_out((text, self.FB_CHARSET_CODE))
712+       
713+    def __init__(self, cursor, connection):   
714+        self.cursor = cursor
715+        self._connection = connection
716+        self._statement = None #prepared statement
717+        self.FB_CHARSET_CODE = 3 #UNICODE_FSS
718+        if connection.charset == 'UTF8':
719+            self.FB_CHARSET_CODE = 4 # UTF-8 with Firebird 2.0+
720+        self.cursor.set_type_trans_in({
721+            'DATE':             self.tc_dt.date_conv_in,
722+            'TIME':             self.time_conv_in,
723+            'TIMESTAMP':        self.timestamp_conv_in,
724+            'FIXED':            self.fixed_conv_in,
725+            'TEXT':             self.ascii_conv_in,
726+            'TEXT_UNICODE':     self.unicode_conv_in,
727+            'BLOB':             self.blob_conv_in
728+        })
729+        self.cursor.set_type_trans_out({
730+            'DATE':             self.tc_dt.date_conv_out,
731+            'TIME':             self.tc_dt.time_conv_out,
732+            'TIMESTAMP':        self.tc_dt.timestamp_conv_out,
733+            'FIXED':            self.tc_fd.fixed_conv_out_precise,
734+            'TEXT':             self.ascii_conv_out,
735+            'TEXT_UNICODE':     self.tc_tu.unicode_conv_out,
736+            'BLOB':             self.blob_conv_out
737+        })
738+   
739+    def execute_immediate(self, query, params=()):
740+        query = query % tuple(params)
741+        self._connection.execute_immediate(query)
742+   
743+    # Prepared Statement
744+    # http://kinterbasdb.sourceforge.net/dist_docs/usage.html#adv_prepared_statements
745+    def prepare(self, query):
746+        query.replace("%s", "?")
747+        return self.cursor.prep(query)
748+   
749+    def execute_prepared(self, statement, params):
750+        return self.cursor.execute(statement, params)
751+   
752+    def execute_straight(self, query, params=()):
753+        # Use kinterbasdb style with '?' instead of '%s'
754+        return self.cursor.execute(query, params)
755+   
756+    def execute(self, query, params=()):
757+        cquery = self.convert_query(query, len(params))
758+        if self._get_query() != cquery:
759+            try:
760+                self._statement = self.cursor.prep(cquery)
761+            except Database.ProgrammingError, e:
762+                output = ["Prepare query error."]
763+                output.extend(str(e).split("'")[1].split('\\n'))
764+                output.append("Query:")
765+                output.append(cquery)
766+                raise Database.ProgrammingError, "\n".join(output)
767+        try:
768+            return self.cursor.execute(self._statement, params)
769+        except Database.ProgrammingError, e:
770+            err_no = int(str(e).split()[0].strip(',()'))
771+            output = ["Execute query error. FB error No. %i" % err_no]
772+            output.extend(str(e).split("'")[1].split('\\n'))
773+            output.append("Query:")
774+            output.append(cquery)
775+            output.append("Parameters:")
776+            output.append(str(params))
777+            raise Database.ProgrammingError, "\n".join(output)
778+   
779+    def executemany(self, query, param_list):
780+        try:
781+            cquery = self.convert_query(query, len(param_list[0]))
782+        except IndexError:
783+            return None
784+        if self._get_query() != cquery:
785+            self._statement = self.cursor.prep(cquery)
786+        return self.cursor.executemany(self._statement, param_list)
787+
788+    def convert_query(self, query, num_params):
789+        try:
790+            return query % tuple("?" * num_params)
791+        except TypeError, e:
792+            print query, num_params
793+            raise TypeError, e
794+   
795+    def _get_query(self):
796+        if self._statement:
797+            return self._statement.sql
798+   
799+    def __getattr__(self, attr):
800+        if attr in self.__dict__:
801+            return self.__dict__[attr]
802+        else:
803+            return getattr(self.cursor, attr)
804+
805+class DatabaseWrapper(BaseDatabaseWrapper):
806+    features = DatabaseFeatures()
807+    ops = DatabaseOperations()
808+    operators = {
809+        'exact': '= %s',
810+        'iexact': '= UPPER(%s)',
811+        'contains': "LIKE %s ESCAPE'\\'",
812+        'icontains': 'CONTAINING %s', #case is ignored
813+        'gt': '> %s',
814+        'gte': '>= %s',
815+        'lt': '< %s',
816+        'lte': '<= %s',
817+        'startswith': 'STARTING WITH %s', #looks to be faster then LIKE
818+        'endswith': "LIKE %s ESCAPE'\\'",
819+        'istartswith': 'STARTING WITH UPPER(%s)',
820+        'iendswith': "LIKE UPPER(%s) ESCAPE'\\'"
821+    }
822+   
823+    def __init__(self, **kwargs):
824+        from django.conf import settings
825+        super(DatabaseWrapper, self).__init__(**kwargs)
826+        self. _current_cursor = None
827+        self._raw_cursor = None
828+        self.charset = 'UNICODE_FSS'
829+        self.FB_MAX_VARCHAR = 10921 #32765 MAX /3
830+        self.BYTES_PER_DEFAULT_CHAR = 3
831+        if hasattr(settings, 'FIREBIRD_CHARSET'):
832+            if settings.FIREBIRD_CHARSET == 'UTF8':
833+                self.charset = 'UTF8'
834+                self.FB_MAX_VARCHAR = 8191 #32765 MAX /4
835+                self.BYTES_PER_DEFAULT_CHAR = 4
836+       
837+    def _connect(self, settings):
838+        if settings.DATABASE_NAME == '':
839+            from django.core.exceptions import ImproperlyConfigured
840+            raise ImproperlyConfigured, "You need to specify DATABASE_NAME in your Django settings file."
841+        kwargs = {'charset' : self.charset }
842+        if settings.DATABASE_HOST:
843+            kwargs['dsn'] = "%s:%s" % (settings.DATABASE_HOST, settings.DATABASE_NAME)
844+        else:
845+            kwargs['dsn'] = "localhost:%s" % settings.DATABASE_NAME
846+        if settings.DATABASE_USER:
847+            kwargs['user'] = settings.DATABASE_USER
848+        if settings.DATABASE_PASSWORD:
849+            kwargs['password'] = settings.DATABASE_PASSWORD
850+        self.connection = Database.connect(**kwargs)
851+        assert self.connection.charset == self.charset
852+       
853+    def cursor(self):
854+        from django.conf import settings
855+        cursor = self._cursor(settings)
856+        if settings.DEBUG:
857+            self._debug_cursor = self.make_debug_cursor(cursor)
858+            return self._debug_cursor
859+        return cursor
860+   
861+    def _cursor(self, settings):
862+        if self.connection is None:
863+            self._connect(settings)
864+        cursor = self.connection.cursor()
865+        self._raw_cursor = cursor
866+        cursor = FirebirdCursorWrapper(cursor, self)
867+        self._current_cursor = cursor
868+        return cursor
869+
870+    def __getattr__(self, attr):
871+        if attr in self.__dict__:
872+            return self.__dict__[attr]
873+        else:
874+            return getattr(self.connection, attr)
875+
876Index: django/db/backends/firebird/client.py
877===================================================================
878--- django/db/backends/firebird/client.py       (revision 0)
879+++ django/db/backends/firebird/client.py       (revision 0)
880@@ -0,0 +1,11 @@
881+from django.conf import settings
882+import os
883+
884+def runshell():
885+    args = [settings.DATABASE_NAME]
886+    args += ["-u %s" % settings.DATABASE_USER]
887+    if settings.DATABASE_PASSWORD:
888+        args += ["-p %s" % settings.DATABASE_PASSWORD]
889+    if 'FIREBIRD' not in os.environ:
890+        path = '/opt/firebird/bin/'
891+    os.system(path + 'isql ' + ' '.join(args))
892Index: django/db/backends/firebird/__init__.py
893===================================================================
894Index: django/db/backends/firebird/introspection.py
895===================================================================
896--- django/db/backends/firebird/introspection.py        (revision 0)
897+++ django/db/backends/firebird/introspection.py        (revision 0)
898@@ -0,0 +1,93 @@
899+from django.db import transaction
900+from django.db.backends.firebird.base import DatabaseOperations
901+
902+quote_name = DatabaseOperations().quote_name
903+
904+def get_table_list(cursor):
905+    "Returns a list of table names in the current database."
906+    cursor.execute("""
907+        SELECT rdb$relation_name FROM rdb$relations
908+        WHERE rdb$system_flag = 0 AND rdb$view_blr IS NULL ORDER BY rdb$relation_name""")
909+    return [str(row[0].strip().lower()) for row in cursor.fetchall()]
910+
911+def get_table_description(cursor, table_name):
912+    "Returns a description of the table, with the DB-API cursor.description interface."
913+    #cursor.execute("SELECT FIRST 1 * FROM %s" % quote_name(table_name))
914+    #return cursor.description
915+    # (name, type_code, display_size, internal_size, precision, scale, null_ok)
916+    cursor.execute("""
917+        SELECT DISTINCT R.RDB$FIELD_NAME AS FNAME,
918+                  F.RDB$FIELD_TYPE AS FTYPE,
919+                  F.RDB$FIELD_LENGTH AS FLENGTH,
920+                  F.RDB$FIELD_PRECISION AS FPRECISION,
921+                  F.RDB$FIELD_SCALE AS FSCALE,
922+                  R.RDB$NULL_FLAG AS NULL_FLAG,
923+                  R.RDB$FIELD_POSITION
924+        FROM RDB$RELATION_FIELDS R
925+             JOIN RDB$FIELDS F ON R.RDB$FIELD_SOURCE=F.RDB$FIELD_NAME
926+        WHERE F.RDB$SYSTEM_FLAG=0 and R.RDB$RELATION_NAME= %s
927+        ORDER BY R.RDB$FIELD_POSITION
928+    """, (table_name,))
929+    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()]
930+
931+
932+def get_relations(cursor, table_name):
933+    """
934+    Returns a dictionary of {field_index: (field_index_other_table, other_table)}
935+    representing all relationships to the given table. Indexes are 0-based.
936+    """
937+    cursor.execute("""
938+        SELECT seg.rdb$field_name, seg_ref.rdb$field_name, idx_ref.rdb$relation_name
939+        FROM rdb$indices idx
940+        INNER JOIN rdb$index_segments seg
941+            ON seg.rdb$index_name = idx.rdb$index_name
942+        INNER JOIN rdb$indices idx_ref
943+            ON idx_ref.rdb$index_name = idx.rdb$foreign_key
944+        INNER JOIN rdb$index_segments seg_ref
945+            ON seg_ref.rdb$index_name = idx_ref.rdb$index_name
946+        WHERE idx.rdb$relation_name = %s
947+            AND idx.rdb$foreign_key IS NOT NULL""", [table_name])
948+
949+    relations = {}
950+    for row in cursor.fetchall():
951+        relations[row[0].rstrip()] = (row[1].strip(), row[2].strip())
952+    return relations
953+
954+def get_indexes(cursor, table_name):
955+    """
956+    Returns a dictionary of fieldname -> infodict for the given table,
957+    where each infodict is in the format:
958+        {'primary_key': boolean representing whether it's the primary key,
959+         'unique': boolean representing whether it's a unique index}
960+    """
961+
962+    # This query retrieves each field name and index type on the given table.
963+    cursor.execute("""
964+        SELECT seg.rdb$field_name, const.rdb$constraint_type
965+        FROM rdb$relation_constraints const
966+        LEFT JOIN rdb$index_segments seg
967+            ON seg.rdb$index_name = const.rdb$index_name
968+        WHERE const.rdb$relation_name = %s
969+            AND (const.rdb$constraint_type = 'PRIMARY KEY'
970+                OR const.rdb$constraint_type = 'UNIQUE')""", [table_name])
971+    indexes = {}
972+    for row in cursor.fetchall():
973+        indexes[row[0].strip()] = {
974+            'primary_key': ('PRIMARY KEY' == row[1].strip()),
975+            'unique': ('UNIQUE' == row[1].strip())}
976+    return indexes
977+
978+# Maps type codes to Django Field types.
979+# !todo
980+DATA_TYPES_REVERSE = {
981+    7: 'BooleanField',
982+    7: 'SmallIntegerField',
983+    8: 'IntegerField',
984+    261: 'TextField',
985+    37: 'IPAddressField',
986+    37: 'CharField',
987+    12: 'DateField',
988+    13: 'TimeField',
989+    35: 'DateTimeField',
990+    10: 'FloatField',
991+}
992Index: django/db/backends/firebird/creation.py
993===================================================================
994--- django/db/backends/firebird/creation.py     (revision 0)
995+++ django/db/backends/firebird/creation.py     (revision 0)
996@@ -0,0 +1,436 @@
997+# This dictionary maps Field objects to their associated Firebird column
998+# types, as strings. Column-type strings can contain format strings; they'll
999+# be interpolated against the values of Field.__dict__ before being output.
1000+# If a column type is set to None, it won't be included in the output.
1001+
1002+
1003+from kinterbasdb import connect, create_database
1004+from django.core.management import call_command
1005+from django.conf import settings
1006+from django.db import connection
1007+import sys, os.path, codecs
1008+try:
1009+    set
1010+except NameError:
1011+    from sets import Set as set   # Python 2.3 fallback
1012+
1013+
1014+DATA_TYPES = {
1015+    'AutoField':                     '"AutoField"',
1016+    'BooleanField':                  '"BooleanField"',
1017+    'CharField':                     'varchar(%(max_length)s)',
1018+    'CommaSeparatedIntegerField':    'varchar(%(max_length)s) CHARACTER SET ASCII',
1019+    'DateField':                     '"DateField"',
1020+    'DateTimeField':                 '"DateTimeField"',
1021+    'DecimalField':                  'numeric(%(max_digits)s, %(decimal_places)s)',
1022+    'DefaultCharField':              '"CharField"',
1023+    'FileField':                     'varchar(%(max_length)s)',
1024+    'FilePathField':                 'varchar(%(max_length)s)',
1025+    'FloatField':                    '"FloatField"',
1026+    'ImageField':                    '"varchar(%(max_length)s)"',
1027+    'IntegerField':                  '"IntegerField"',
1028+    'IPAddressField':                'varchar(15) CHARACTER SET ASCII',
1029+    'NullBooleanField':              '"NullBooleanField"',
1030+    'OneToOneField':                 '"OneToOneField"',
1031+    'PhoneNumberField':              '"PhoneNumberField"',
1032+    'PositiveIntegerField':          '"PositiveIntegerField"',
1033+    'PositiveSmallIntegerField':     '"PositiveSmallIntegerField"',
1034+    'SlugField':                     'varchar(%(max_length)s)',
1035+    'SmallIntegerField':             '"SmallIntegerField"',
1036+    'LargeTextField':                '"LargeTextField"',
1037+    'TextField':                     '"TextField"',
1038+    'TimeField':                     '"TimeField"',
1039+    'URLField':                      'varchar(%(max_length)s) CHARACTER SET ASCII',
1040+    'USStateField':                  '"USStateField"'
1041+}
1042+     
1043+PYTHON_TO_FB_ENCODING_MAP = {
1044+    'ascii':        'ASCII',
1045+    'utf_8':        connection.charset,
1046+    'shift_jis':    'SJIS_0208',
1047+    'euc_jp':       'EUCJ_0208',
1048+    'cp737':        'DOS737',
1049+    'cp437':        'DOS437',
1050+    'cp850':        'DOS850',
1051+    'cp865':        'DOS865',
1052+    'cp860':        'DOS860',
1053+    'cp863':        'DOS863',
1054+    'cp775':        'DOS775',
1055+    'cp862':        'DOS862',
1056+    'cp864':        'DOS864',
1057+    'iso8859_1':    'ISO8859_1',
1058+    'iso8859_2':    'ISO8859_2',
1059+    'iso8859_3':    'ISO8859_3',
1060+    'iso8859_4':    'ISO8859_4',
1061+    'iso8859_5':    'ISO8859_5',
1062+    'iso8859_6':    'ISO8859_6',
1063+    'iso8859_7':    'ISO8859_7',
1064+    'iso8859_8':    'ISO8859_8',
1065+    'iso8859_9':    'ISO8859_9',
1066+    'iso8859_13':   'ISO8859_13',
1067+    'euc_kr':       'KSC_5601',
1068+    'cp852':        'DOS852',
1069+    'cp857':        'DOS857',
1070+    'cp861':        'DOS861',
1071+    'cp866':        'DOS866',
1072+    'cp869':        'DOS869',
1073+    'cp1250':       'WIN1250',
1074+    'cp1251':       'WIN1251',
1075+    'cp1252':       'WIN1252',
1076+    'cp1253':       'WIN1253',
1077+    'cp1254':       'WIN1254',
1078+    'big5':         'BIG_5',
1079+    'gb2312':       'GB_2312',
1080+    'cp1255':       'WIN1255',
1081+    'cp1256':       'WIN1256',
1082+    'cp1257':       'WIN1257',
1083+    'koi8_r':       'KOI8-R',
1084+    'koi8_u':       'KOI8-U',
1085+    'cp1258':       'WIN1258'
1086+  }
1087+
1088+def get_data_size(data_type, max_length = 100):
1089+    char_bytes = connection.BYTES_PER_DEFAULT_CHAR
1090+    size_map = {
1091+        'AutoField':                     8,
1092+        'BooleanField':                  4,
1093+        'CharField':                     char_bytes*max_length,
1094+        'CommaSeparatedIntegerField':    max_length,
1095+        'DateField':                     16,
1096+        'DateTimeField':                 16,
1097+        'DecimalField':                  16,
1098+        'FileField':                     char_bytes*max_length,
1099+        'FilePathField':                 'varchar(%(max_length)s)',
1100+        'FloatField':                    16,
1101+        'ImageField':                    char_bytes*max_length,
1102+        'IntegerField':                  8,
1103+        'IPAddressField':                15,
1104+        'NullBooleanField':              4,
1105+        'OneToOneField':                 8,
1106+        'PhoneNumberField':              20,
1107+        'PositiveIntegerField':          8,
1108+        'PositiveSmallIntegerField':     4,
1109+        'SlugField':                     char_bytes*max_length,
1110+        'SmallIntegerField':             4,
1111+        'TextBlob':                      8,
1112+        'TextField':                     32767,
1113+        'TimeField':                     16,
1114+        'URLField':                      max_length,
1115+        'USStateField':                  char_bytes*2
1116+    }
1117+    return size_map[data_type]
1118+
1119+DEFAULT_MAX_LENGTH = 100
1120+def sql_model_create(model, style, known_models=set()):
1121+    """
1122+    Returns the SQL required to create a single model, as a tuple of:
1123+        (list_of_sql, pending_references_dict)
1124+    """
1125+    from django.db import connection, models
1126+
1127+    opts = model._meta
1128+    final_output = []
1129+    table_output = []
1130+    pending_references = {}
1131+    qn = connection.ops.quote_name
1132+   
1133+    # Create domains
1134+    domains = [ ('AutoField', 'integer'),
1135+                ('BooleanField', 'smallint CHECK (VALUE IN (0,1))'),
1136+                ('DateField', 'date'),
1137+                ('CharField', 'varchar(%i)' % DEFAULT_MAX_LENGTH),
1138+                ('DateTimeField', 'timestamp'),
1139+                ('FloatField', 'double precision'),
1140+                ('IntegerField', 'integer'),
1141+                ('IPAddressField', 'varchar(15) CHARACTER SET ASCII'),
1142+                ('NullBooleanField', 'smallint CHECK ((VALUE IN (0,1)) OR (VALUE IS NULL))'),
1143+                ('OneToOneField', 'integer'),
1144+                ('PhoneNumberField', 'varchar(20) CHARACTER SET ASCII'),
1145+                ('PositiveIntegerField', 'integer CHECK ((VALUE >= 0) OR (VALUE IS NULL))'),
1146+                ('PositiveSmallIntegerField', 'smallint CHECK ((VALUE >= 0) OR (VALUE IS NULL))'),
1147+                ('SmallIntegerField', 'smallint'),
1148+                ('TextField', 'varchar(%s)' % connection.FB_MAX_VARCHAR),
1149+                ('LargeTextField', 'blob sub_type text'),
1150+                ('TimeField', 'time'),
1151+                ('USStateField', 'varchar(2) CHARACTER SET ASCII') ]
1152+
1153+    cursor = connection.cursor()
1154+    cursor.execute("SELECT RDB$FIELD_NAME FROM RDB$FIELDS")
1155+    existing_domains = set([row[0].strip() for row in cursor.fetchall() if not row[0].startswith('RDB$')])
1156+    domains = map(lambda domain: '%s "%s" AS %s;' % ('CREATE DOMAIN', domain[0], domain[1]),
1157+            filter(lambda x: x[0] not in existing_domains, domains))
1158+    final_output.extend(domains)
1159+   
1160+    # Check that row size is less than 64k and adjust TextFields if needed
1161+    row_size = 0
1162+    columns = [(f.db_type().strip('"'), f.get_internal_type(), f) for f in opts.fields]
1163+    columns_simple = [col[0] for col in columns]
1164+    text_field_type = '"TextField"'
1165+    max_alowed_bytes = 32765
1166+    if 'TextField' in columns_simple:
1167+        max_length = 100
1168+        num_text_fields = 0
1169+        for column in columns:
1170+            num_text_fields += (column[0] == 'TextField')
1171+            if column[0].startswith('varchar'):
1172+                max_length = int(column[0].split('(')[1].split(')')[0])
1173+            if column[1] in DATA_TYPES:
1174+                row_size += get_data_size(column[1], max_length)
1175+        if row_size > 65536:
1176+            max_alowed_bytes = int( (max_alowed_bytes/num_text_fields) - (row_size-65536) )
1177+            n = max_alowed_bytes / connection.BYTES_PER_DEFAULT_CHAR
1178+            if n > 512:
1179+                text_field_type = 'varchar(%s)' % n
1180+                FB_TEXTFIELD_ALTERED = True   
1181+                print
1182+                print "WARNING: Maximum number of characters in TextFields has changed to %s." % n
1183+                print "         TextField columns with custom charsets will have %s chars available" % max_alowed_bytes
1184+                print "         The change affects %s table only." % opts.db_table
1185+                print "         TextFields in other tables will have %s characters maximum" % connection.FB_MAX_VARCHAR
1186+                print "         or 32765 characters with custom (non-UTF) encoding."
1187+                print "         If you need more space in those fields try LargeTextFields instead."
1188+                print
1189+            else:
1190+                # Swich to blobs if size is too small (<1024)   
1191+                text_field_type = '"LargeTextField"'   
1192+   
1193+    # Create tables
1194+    for f in opts.fields:
1195+        col_type = f.db_type()
1196+        if col_type.strip('"') == 'TextField':
1197+            col_type = text_field_type
1198+        fb_version = "%s.%s" % (connection.ops.firebird_version[0], connection.ops.firebird_version[1])
1199+        page_size = connection.ops.page_size
1200+        #look at: http://www.volny.cz/iprenosil/interbase/ip_ib_indexcalculator.htm
1201+        if connection.ops.index_limit < 1000:
1202+            strip2ascii = False
1203+            custom_charset = False
1204+            if col_type.startswith('varchar'):
1205+                if (f.unique or f.primary_key or f.db_index):
1206+                    length = f.max_length
1207+                    if f.encoding:
1208+                        if not f.encoding.upper().startswith('UTF'):
1209+                            custom_charset = True
1210+                    if not custom_charset:
1211+                        try:
1212+                            length = f.max_length * connection.BYTES_PER_DEFAULT_CHAR
1213+                        except TypeError:
1214+                            length = 100*connection.BYTES_PER_DEFAULT_CHAR #Default for CharField
1215+                    if length >= connection.ops.index_limit:   
1216+                        strip2ascii = True
1217+                if len(opts.unique_together) > 0:
1218+                    if f.column in opts.unique_together[0]:
1219+                        num_unique_char_fields = len([ fld for fld in opts.unique_together[0] if opts.get_field(fld).db_type().startswith('varchar') ])
1220+                        num_unique_fileds = len(opts.unique_together[0])
1221+                        num_unique_nonchar_fileds = num_unique_fileds - num_unique_char_fields
1222+                        limit = connection.ops.index_limit
1223+                        limit -= ((num_unique_fileds - 1)*64)
1224+                        limit -= 8*num_unique_nonchar_fileds
1225+                        max_length = limit/num_unique_char_fields
1226+                        ascii_length = int(f.max_length)
1227+                        old_length = ascii_length*connection.BYTES_PER_DEFAULT_CHAR
1228+                         
1229+                        if (old_length >= max_length) and (ascii_length < max_length):
1230+                            strip2ascii = True
1231+                        elif old_length > max_length:
1232+                            strip2ascii = False #We change it here
1233+                            col_type = "varchar(%i) CHARACTER SET ASCII" % max_length
1234+                            msg =  "WARNING: Character set of the '%s' field\n"
1235+                            msg += "         (table %s)\n"
1236+                            msg += "         has changed to ASCII"
1237+                            msg += " to fit %s-byte limit in FB %s"
1238+                            if not page_size:
1239+                                print  msg % (f.column, opts.db_table, connection.ops.index_limit, fb_version)
1240+                            else:
1241+                                msg += " with page size %s"
1242+                                print  msg % (f.column, opts.db_table, connection.ops.index_limit, fb_version, page_size)
1243+                            print "         The maximum length of '%s' is now %s instead of %s"\
1244+                             % (f.column, max_length, old_length)     
1245+            if strip2ascii:
1246+                col_type = "%s %s %s" % (col_type, "CHARACTER SET", "ASCII")
1247+                msg =  "WARNING: Character set of the '%s' field\n"
1248+                msg += "         (table %s)\n"
1249+                msg += "         has changed to ASCII"
1250+                msg += " to fit %s-byte limit in FB %s"
1251+                if not page_size:
1252+                    print  msg % (f.column, opts.db_table, connection.ops.index_limit, fb_version)
1253+                else:
1254+                    msg += " with page size %s"
1255+                    print  msg % (f.column, opts.db_table, connection.ops.index_limit, fb_version, page_size)
1256+                   
1257+        if (col_type.startswith('varchar') or col_type.strip('"') == 'TextField') and f.encoding:
1258+            charset = PYTHON_TO_FB_ENCODING_MAP[codecs.lookup(f.encoding).name]
1259+            if col_type.strip('"') == 'TextField':
1260+                col_type = 'varchar(%i)' % max_alowed_bytes
1261+            col_type = "%s %s %s" % (col_type, "CHARACTER SET", charset)
1262+
1263+           
1264+        if col_type is None:
1265+            # Skip ManyToManyFields, because they're not represented as
1266+            # database columns in this table.
1267+            continue
1268+        # Make the definition (e.g. 'foo VARCHAR(30)') for this field.
1269+        field_output = [style.SQL_FIELD(qn(f.column)),
1270+            style.SQL_COLTYPE(col_type)]
1271+        field_output.append(style.SQL_KEYWORD('%s' % (not f.null and 'NOT NULL' or 'DEFAULT NULL')))
1272+        if f.unique:
1273+            field_output.append(style.SQL_KEYWORD('UNIQUE'))
1274+        if f.primary_key:
1275+            field_output.append(style.SQL_KEYWORD('PRIMARY KEY'))
1276+        if f.rel:
1277+            # We haven't yet created the table to which this field
1278+            # is related, so save it for later.
1279+            pr = pending_references.setdefault(f.rel.to, []).append((model, f))
1280+        table_output.append(' '.join(field_output))
1281+    if opts.order_with_respect_to:
1282+        table_output.append(style.SQL_FIELD(qn('_order')) + ' ' + \
1283+            style.SQL_COLTYPE(models.IntegerField().db_type()) + ' ' + \
1284+            style.SQL_KEYWORD('NULL'))
1285+    for field_constraints in opts.unique_together:
1286+        table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \
1287+            ", ".join([qn(style.SQL_FIELD(opts.get_field(f).column)) for f in field_constraints]))
1288+
1289+    full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' (']
1290+    for i, line in enumerate(table_output): # Combine and add commas.
1291+        full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
1292+    full_statement.append(');')
1293+    final_output.append('\n'.join(full_statement))
1294+
1295+    if opts.has_auto_field:
1296+        # Add any extra SQL needed to support auto-incrementing primary keys.
1297+        auto_column = opts.auto_field.db_column or opts.auto_field.name
1298+        autoinc_sql = connection.ops.autoinc_sql(style, opts.db_table, auto_column)
1299+        if autoinc_sql:
1300+            for stmt in autoinc_sql:
1301+                final_output.append(stmt)
1302+
1303+    # Declare exteral functions
1304+    cursor.execute("SELECT RDB$FUNCTION_NAME FROM RDB$FUNCTIONS")
1305+    existing_functions = set([row[0].strip().upper() for row in cursor.fetchall()])
1306+    if 'RAND' not in existing_functions:
1307+        final_output.append('%s %s\n\t%s %s\n\t%s %s\n\t%s;' % (style.SQL_KEYWORD('DECLARE EXTERNAL FUNCTION'),
1308+            style.SQL_TABLE('RAND'), style.SQL_KEYWORD('RETURNS'), style.SQL_COLTYPE('DOUBLE PRECISION'),
1309+            style.SQL_KEYWORD('BY VALUE ENTRY_POINT'), style.SQL_FIELD("'IB_UDF_rand'"),
1310+            style.SQL_TABLE("MODULE_NAME 'ib_udf'")))
1311+   
1312+    return final_output, pending_references
1313+
1314+def many_to_many_sql_for_model(model, style):
1315+    from django.db import connection, models
1316+    from django.contrib.contenttypes import generic
1317+    from django.db.backends.util import truncate_name
1318+
1319+    opts = model._meta
1320+    final_output = []
1321+    qn = connection.ops.quote_name
1322+    for f in opts.many_to_many:
1323+        if not isinstance(f.rel, generic.GenericRel):
1324+            table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
1325+                style.SQL_TABLE(qn(f.m2m_db_table())) + ' (']
1326+            table_output.append('    %s %s %s,' %
1327+                (style.SQL_FIELD(qn('id')),
1328+                style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type()),
1329+                style.SQL_KEYWORD('NOT NULL PRIMARY KEY')))
1330+
1331+            table_output.append('    %s %s %s,' %
1332+                (style.SQL_FIELD(qn(f.m2m_column_name())),
1333+                style.SQL_COLTYPE(models.ForeignKey(model).db_type()),
1334+                style.SQL_KEYWORD('NOT NULL')))
1335+            table_output.append('    %s %s %s,' %
1336+                (style.SQL_FIELD(qn(f.m2m_reverse_name())),
1337+                style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type()),
1338+                style.SQL_KEYWORD('NOT NULL')))
1339+            deferred = [
1340+                (f.m2m_db_table(), f.m2m_column_name(), opts.db_table,
1341+                    opts.pk.column),
1342+                ( f.m2m_db_table(), f.m2m_reverse_name(),
1343+                    f.rel.to._meta.db_table, f.rel.to._meta.pk.column)
1344+                ]
1345+
1346+            table_output.append('    %s (%s, %s)' %
1347+                (style.SQL_KEYWORD('UNIQUE'),
1348+                style.SQL_FIELD(qn(f.m2m_column_name())),
1349+                style.SQL_FIELD(qn(f.m2m_reverse_name()))))
1350+            table_output.append(');')
1351+            final_output.append('\n'.join(table_output))
1352+           
1353+            autoinc_sql = connection.ops.autoinc_sql(style, f.m2m_db_table(), 'id')
1354+            if autoinc_sql:
1355+                for stmt in autoinc_sql:
1356+                    final_output.append(stmt)
1357+           
1358+            if connection.features.supports_constraints:
1359+                for r_table, r_col, table, col in deferred:
1360+                    r_name = '%s_%s_%x' % (r_col, col,
1361+                            abs(hash((r_table, table))))
1362+                    final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' %
1363+                    (qn(r_table),
1364+                    truncate_name(r_name, connection.ops.max_name_length()),
1365+                    qn(r_col), qn(table), qn(col),
1366+                    'ON DELETE CASCADE ON UPDATE CASCADE'))
1367+
1368+    return final_output
1369+
1370+TEST_DATABASE_PREFIX = 'test_'
1371+def create_test_db(settings, connection, verbosity, autoclobber):
1372+    # KInterbasDB supports dynamic database creation and deletion
1373+    # via the module-level function create_database and the method Connection.drop_database.
1374+       
1375+    if settings.TEST_DATABASE_NAME:
1376+        TEST_DATABASE_NAME = settings.TEST_DATABASE_NAME
1377+    else:
1378+        dbnametuple = os.path.split(settings.DATABASE_NAME)
1379+        TEST_DATABASE_NAME = os.path.join(dbnametuple[0], TEST_DATABASE_PREFIX + dbnametuple[1])
1380+   
1381+    dsn = "localhost:%s" % TEST_DATABASE_NAME
1382+    if settings.DATABASE_HOST:
1383+        dsn = "%s:%s" % (settings.DATABASE_HOST, TEST_DATABASE_NAME)
1384+   
1385+    if os.path.isfile(TEST_DATABASE_NAME):
1386+        sys.stderr.write("Database %s already exists\n" % TEST_DATABASE_NAME)
1387+        if not autoclobber:
1388+            confirm = raw_input("Type 'yes' if you would like to try deleting the test database '%s', or 'no' to cancel: " % TEST_DATABASE_NAME)
1389+        if autoclobber or confirm == 'yes':
1390+            if verbosity >= 1:
1391+                print "Destroying old test database..."
1392+            old_connection = connect(dsn=dsn, user=settings.DATABASE_USER, password=settings.DATABASE_PASSWORD)
1393+            old_connection.drop_database()
1394+        else:
1395+                print "Tests cancelled."
1396+                sys.exit(1)
1397+           
1398+    if verbosity >= 1:
1399+        print "Creating test database..."
1400+    try:
1401+        charset = 'UNICODE_FSS'
1402+        if hasattr(settings, 'FIREBIRD_CHARSET'):
1403+            if settings.FIREBIRD_CHARSET == 'UTF8':
1404+                charset='UTF8'               
1405+        create_database("create database '%s' user '%s' password '%s' default character set %s" % \
1406+            (dsn, settings.DATABASE_USER, settings.DATABASE_PASSWORD, charset))
1407+    except Exception, e:
1408+        sys.stderr.write("Got an error creating the test database: %s\n" % e)
1409+        sys.exit(2)
1410+
1411+    connection.close()
1412+    settings.DATABASE_NAME = TEST_DATABASE_NAME
1413+
1414+    call_command('syncdb', verbosity=verbosity, interactive=False)
1415+
1416+    if settings.CACHE_BACKEND.startswith('db://'):
1417+        cache_name = settings.CACHE_BACKEND[len('db://'):]
1418+        call_command('createcachetable', cache_name)
1419+
1420+    # Get a cursor (even though we don't need one yet). This has
1421+    # the side effect of initializing the test database.
1422+    cursor = connection.cursor()
1423+
1424+    return TEST_DATABASE_NAME
1425+
1426+def destroy_test_db(settings, connection, old_database_name, verbosity):
1427+    # KInterbasDB supports dynamic database deletion via the method Connection.drop_database.
1428+    if verbosity >= 1:
1429+        print "Destroying test database..."
1430+    connection.drop_database()
1431+   
1432+
1433Index: django/core/cache/backends/db.py
1434===================================================================
1435--- django/core/cache/backends/db.py    (revision 6668)
1436+++ django/core/cache/backends/db.py    (working copy)
1437@@ -47,19 +47,20 @@
1438         if timeout is None:
1439             timeout = self.default_timeout
1440         cursor = connection.cursor()
1441-        cursor.execute("SELECT COUNT(*) FROM %s" % self._table)
1442+        qn = connection.ops.quote_name
1443+        cursor.execute("SELECT COUNT(*) FROM %s" % qn(self._table))
1444         num = cursor.fetchone()[0]
1445         now = datetime.now().replace(microsecond=0)
1446         exp = datetime.fromtimestamp(time.time() + timeout).replace(microsecond=0)
1447         if num > self._max_entries:
1448             self._cull(cursor, now)
1449         encoded = base64.encodestring(pickle.dumps(value, 2)).strip()
1450-        cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s" % self._table, [key])
1451+        cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s" % qn(self._table), [key])
1452         try:
1453             if mode == 'set' and cursor.fetchone():
1454-                cursor.execute("UPDATE %s SET value = %%s, expires = %%s WHERE cache_key = %%s" % self._table, [encoded, str(exp), key])
1455+                cursor.execute("UPDATE %s SET value = %%s, expires = %%s WHERE cache_key = %%s" % qn(self._table), [encoded, str(exp), key])
1456             else:
1457-                cursor.execute("INSERT INTO %s (cache_key, value, expires) VALUES (%%s, %%s, %%s)" % self._table, [key, encoded, str(exp)])
1458+                cursor.execute("INSERT INTO %s (cache_key, value, expires) VALUES (%%s, %%s, %%s)" % qn(self._table), [key, encoded, str(exp)])
1459         except DatabaseError:
1460             # To be threadsafe, updates/inserts are allowed to fail silently
1461             pass
1462Index: django/core/management/sql.py
1463===================================================================
1464--- django/core/management/sql.py       (revision 6668)
1465+++ django/core/management/sql.py       (working copy)
1466@@ -1,4 +1,5 @@
1467 from django.core.management.base import CommandError
1468+from django.conf import settings
1469 import os
1470 import re
1471 
1472@@ -66,7 +67,7 @@
1473 
1474 def sql_create(app, style):
1475     "Returns a list of the CREATE TABLE SQL statements for the given app."
1476-    from django.db import models
1477+    from django.db import models, get_creation_module
1478     from django.conf import settings
1479 
1480     if settings.DATABASE_ENGINE == 'dummy':
1481@@ -97,7 +98,7 @@
1482     # Create the many-to-many join tables.
1483     for model in app_models:
1484         final_output.extend(many_to_many_sql_for_model(model, style))
1485-
1486+   
1487     # Handle references to tables that are from other apps
1488     # but don't exist physically.
1489     not_installed_models = set(pending_references.keys())
1490@@ -245,8 +246,11 @@
1491     Returns the SQL required to create a single model, as a tuple of:
1492         (list_of_sql, pending_references_dict)
1493     """
1494-    from django.db import connection, models
1495-
1496+    from django.db import connection, models, get_creation_module
1497+    creation_module = get_creation_module()
1498+    # If the database backend wants to create model itself, let it
1499+    if hasattr(creation_module, "sql_model_create"):
1500+        return creation_module.sql_model_create(model, style, known_models)
1501     opts = model._meta
1502     final_output = []
1503     table_output = []
1504@@ -332,18 +336,24 @@
1505                 # For MySQL, r_name must be unique in the first 64 characters.
1506                 # So we are careful with character usage here.
1507                 r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table))))
1508-                final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
1509+                final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s%s;' % \
1510                     (qn(r_table), truncate_name(r_name, connection.ops.max_name_length()),
1511                     qn(r_col), qn(table), qn(col),
1512+                    connection.ops.cascade_delete_update_sql(),
1513                     connection.ops.deferrable_sql()))
1514             del pending_references[model]
1515     return final_output
1516 
1517 def many_to_many_sql_for_model(model, style):
1518-    from django.db import connection, models
1519+    from django.db import connection, models, get_creation_module
1520     from django.contrib.contenttypes import generic
1521     from django.db.backends.util import truncate_name
1522-
1523+   
1524+    creation_module = get_creation_module()
1525+    # If the database backend wants to create many_to_many sql itself, let it
1526+    if hasattr(creation_module, "many_to_many_sql_for_model"):
1527+        return creation_module.many_to_many_sql_for_model(model, style)
1528+       
1529     opts = model._meta
1530     final_output = []
1531     qn = connection.ops.quote_name
1532Index: tests/modeltests/custom_methods/models.py
1533===================================================================
1534--- tests/modeltests/custom_methods/models.py   (revision 6668)
1535+++ tests/modeltests/custom_methods/models.py   (working copy)
1536@@ -27,15 +27,16 @@
1537         """
1538         from django.db import connection
1539         cursor = connection.cursor()
1540+        # Some backends really really need quotes!
1541         cursor.execute("""
1542-            SELECT id, headline, pub_date
1543-            FROM custom_methods_article
1544-            WHERE pub_date = %s
1545-                AND id != %s""", [str(self.pub_date), self.id])
1546+            SELECT "id", "headline", "pub_date"
1547+            FROM "custom_methods_article"
1548+            WHERE "pub_date" = %s
1549+                AND "id" != %s""", [str(self.pub_date), self.id])
1550         # The asterisk in "(*row)" tells Python to expand the list into
1551         # positional arguments to Article().
1552         return [self.__class__(*row) for row in cursor.fetchall()]
1553-
1554+   
1555 __test__ = {'API_TESTS':"""
1556 # Create a couple of Articles.
1557 >>> from datetime import date
1558Index: tests/modeltests/lookup/models.py
1559===================================================================
1560--- tests/modeltests/lookup/models.py   (revision 6668)
1561+++ tests/modeltests/lookup/models.py   (working copy)
1562@@ -274,7 +274,34 @@
1563 >>> a4.save()
1564 >>> a5 = Article(pub_date=now, headline='hey-Foo')
1565 >>> a5.save()
1566+"""}
1567 
1568+# Firebird support 'magic values'
1569+if settings.DATABASE_ENGINE in ('firebird',):
1570+    __test__['API_TESTS'] += r"""
1571+# and yet more:
1572+>>> a10 = Article(pub_date='today', headline='foobar')
1573+>>> a10.save()
1574+>>> a11 = Article(pub_date='tomorrow', headline='foobaz')
1575+>>> a11.save()
1576+>>> a12 = Article(pub_date='yesterday', headline='ooF')
1577+>>> a12.save()
1578+>>> a13 = Article(pub_date='now', headline='foobarbaz')
1579+>>> a13.save()
1580+>>> a14 = Article(pub_date='today', headline='zoocarfaz')
1581+>>> a14.save()
1582+>>> a15 = Article(pub_date='today', headline='barfoobaz')
1583+>>> a15.save()
1584+>>> a16 = Article(pub_date='today', headline='bazbaRFOO')
1585+>>> a16.save()
1586+>>> Article.objects.filter(pub_date__exact='yesterday')
1587+[<Article: ooF>]
1588+"""
1589+
1590+
1591+# Firebird doesn't support regular expression lookups
1592+if settings.DATABASE_ENGINE not in ('firebird',):
1593+    __test__['API_TESTS'] += r"""
1594 # zero-or-more
1595 >>> Article.objects.filter(headline__regex=r'fo*')
1596 [<Article: f>, <Article: fo>, <Article: foo>, <Article: fooo>]
1597@@ -348,10 +375,10 @@
1598 [<Article: barfoobaz>, <Article: baz>, <Article: bazbaRFOO>, <Article: foobarbaz>, <Article: foobaz>]
1599 >>> Article.objects.filter(headline__iregex=r'b.*ar')
1600 [<Article: bar>, <Article: barfoobaz>, <Article: bazbaRFOO>, <Article: foobar>, <Article: foobarbaz>]
1601-"""}
1602+"""
1603 
1604 
1605-if settings.DATABASE_ENGINE not in ('mysql', 'mysql_old'):
1606+if settings.DATABASE_ENGINE not in ('mysql', 'mysql_old', 'firebird'):
1607     __test__['API_TESTS'] += r"""
1608 # grouping and backreferences
1609 >>> Article.objects.filter(headline__regex=r'b(.).*b\1')
1610Index: tests/regressiontests/serializers_regress/tests.py
1611===================================================================
1612--- tests/regressiontests/serializers_regress/tests.py  (revision 6668)
1613+++ tests/regressiontests/serializers_regress/tests.py  (working copy)
1614@@ -22,6 +22,10 @@
1615     import decimal
1616 except ImportError:
1617     from django.utils import _decimal as decimal
1618+try:
1619+    set
1620+except NameError:
1621+    from sets import Set as set   # Python 2.3 fallback
1622 
1623 # A set of functions that can be used to recreate
1624 # test data objects of various kinds.
1625@@ -83,8 +87,9 @@
1626     testcase.assertEqual(data, instance.data_id)
1627 
1628 def m2m_compare(testcase, pk, klass, data):
1629+    # Use sets to ignore order of data
1630     instance = klass.objects.get(id=pk)
1631-    testcase.assertEqual(data, [obj.id for obj in instance.data.all()])
1632+    testcase.assertEqual(set(data), set([obj.id for obj in instance.data.all()]))
1633 
1634 def o2o_compare(testcase, pk, klass, data):
1635     instance = klass.objects.get(data=data)
1636Index: tests/regressiontests/backends/models.py
1637===================================================================
1638--- tests/regressiontests/backends/models.py    (revision 6668)
1639+++ tests/regressiontests/backends/models.py    (working copy)
1640@@ -22,7 +22,7 @@
1641 >>> opts = Square._meta
1642 >>> f1, f2 = opts.get_field('root'), opts.get_field('square')
1643 >>> query = ('INSERT INTO %s (%s, %s) VALUES (%%s, %%s)'
1644-...         % (t_convert(opts.db_table), qn(f1.column), qn(f2.column)))
1645+...         % ((qn(t_convert(opts.db_table)), qn(f1.column), qn(f2.column))))
1646 >>> cursor.executemany(query, [(i, i**2) for i in range(-5, 6)]) and None or None
1647 >>> Square.objects.order_by('root')
1648 [<Square: -5 ** 2 == 25>, <Square: -4 ** 2 == 16>, <Square: -3 ** 2 == 9>, <Square: -2 ** 2 == 4>, <Square: -1 ** 2 == 1>, <Square: 0 ** 2 == 0>, <Square: 1 ** 2 == 1>, <Square: 2 ** 2 == 4>, <Square: 3 ** 2 == 9>, <Square: 4 ** 2 == 16>, <Square: 5 ** 2 == 25>]