Django

Code

Ticket #1261: firebird-6669.diff

File firebird-6669.diff, 86.3 kB (added by i_i, 10 months ago)

code clean up and support for stored procedures and computed columns

  • django/db/models/base.py

    old new  
    44from django.core.exceptions import ObjectDoesNotExist 
    55from django.db.models.fields import AutoField, ImageField, FieldDoesNotExist 
    66from django.db.models.fields.related import OneToOneRel, ManyToOneRel 
     7from django.db.models.fields.computed import ComputedField 
    78from django.db.models.query import delete_objects 
    89from django.db.models.options import Options, AdminOptions 
    910from django.db import connection, transaction 
     
    227228                self._meta.pk.get_db_prep_lookup('exact', pk_val)) 
    228229            # If it does already exist, do an UPDATE. 
    229230            if cursor.fetchone(): 
    230                 db_values = [f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, False)) for f in non_pks
     231                db_values = [f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, False)) for f in non_pks if not isinstance(f, ComputedField)
    231232                if db_values: 
    232233                    cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % \ 
    233234                        (qn(self._meta.db_table), 
    234                         ','.join(['%s=%%s' % qn(f.column) for f in non_pks]), 
     235                        ','.join(['%s=%%s' % qn(f.column) for f in non_pks if not isinstance(f, ComputedField)]), 
    235236                        qn(self._meta.pk.column)), 
    236237                        db_values + self._meta.pk.get_db_prep_lookup('exact', pk_val)) 
    237238            else: 
    238239                record_exists = False 
    239240        if not pk_set or not record_exists: 
    240             field_names = [qn(f.column) for f in self._meta.fields if not isinstance(f, AutoField)] 
    241             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)] 
     241            field_names = [qn(f.column) for f in self._meta.fields if not isinstance(f, (AutoField, ComputedField))] 
     242            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, ComputedField))] 
    242243            # If the PK has been manually set, respect that. 
    243244            if pk_set: 
    244                 field_names += [f.column for f in self._meta.fields if isinstance(f, AutoField)] 
     245                if connection.features.quote_autofields: 
     246                    field_names += [qn(f.column) for f in self._meta.fields if isinstance(f, AutoField)] 
     247                else: 
     248                    field_names += [f.column for f in self._meta.fields if isinstance(f, AutoField)] 
    245249                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)] 
    246250            placeholders = ['%s'] * len(field_names) 
    247251            if self._meta.order_with_respect_to: 
  • django/db/models/fields/__init__.py

    old new  
    66except ImportError: 
    77    from django.utils import _decimal as decimal    # for Python 2.3 
    88 
    9 from django.db import get_creation_module 
     9from django.db import connection, get_creation_module 
    1010from django.db.models import signals 
    1111from django.dispatch import dispatcher 
    1212from django.conf import settings 
     
    6666# 
    6767#     getattr(obj, opts.pk.attname) 
    6868 
    69 class Field(object): 
     69class _Field(object): 
    7070    # Provide backwards compatibility for the maxlength attribute and 
    7171    # argument for this class and all subclasses. 
    7272    __metaclass__ = LegacyMaxlength 
     
    8383        core=False, rel=None, default=NOT_PROVIDED, editable=True, serialize=True, 
    8484        prepopulate_from=None, unique_for_date=None, unique_for_month=None, 
    8585        unique_for_year=None, validator_list=None, choices=None, radio_admin=None, 
    86         help_text='', db_column=None, db_tablespace=None): 
     86        help_text='', db_column=None, db_tablespace=None, encoding=None, 
     87        on_update=None, on_delete=None): 
    8788        self.name = name 
    8889        self.verbose_name = verbose_name 
    8990        self.primary_key = primary_key 
    9091        self.max_length, self.unique = max_length, unique 
     92        self.encoding = encoding 
    9193        self.blank, self.null = blank, null 
    9294        # Oracle treats the empty string ('') as null, so coerce the null 
    9395        # option whenever '' is a possible value. 
     
    105107        self.help_text = help_text 
    106108        self.db_column = db_column 
    107109        self.db_tablespace = db_tablespace 
     110        self.on_update = on_update 
     111        self.on_delete = on_delete 
    108112 
    109113        # Set db_index to True if the field has a relationship and doesn't explicitly set db_index. 
    110114        self.db_index = db_index 
     
    148152        data_types = get_creation_module().DATA_TYPES 
    149153        internal_type = self.get_internal_type() 
    150154        if internal_type not in data_types: 
    151             return None 
     155            return None     
    152156        return data_types[internal_type] % self.__dict__ 
    153157 
    154158    def validate_full(self, field_data, all_data): 
     
    402406        "Returns the value of this field in the given model instance." 
    403407        return getattr(obj, self.attname) 
    404408 
     409# Use the backend's Field class if it defines one. Otherwise, use _Field. 
     410if connection.features.uses_custom_field: 
     411    Field = connection.ops.field_class(_Field) 
     412else: 
     413    Field = _Field 
     414 
    405415class AutoField(Field): 
    406416    empty_strings_allowed = False 
    407417    def __init__(self, *args, **kwargs): 
     
    688698        defaults.update(kwargs) 
    689699        return super(DecimalField, self).formfield(**defaults) 
    690700 
     701class DefaultCharField(CharField): 
     702    def __init__(self, *args, **kwargs): 
     703        DEFAULT_MAX_LENGTH = 100 
     704        if hasattr(settings, 'DEFAULT_MAX_LENGTH'): 
     705           DEFAULT_MAX_LENGTH = settings.DEFAULT_MAX_LENGT 
     706        kwargs['max_length'] = kwargs.get('max_length', DEFAULT_MAX_LENGTH) 
     707        CharField.__init__(self, *args, **kwargs) 
     708 
    691709class EmailField(CharField): 
    692710    def __init__(self, *args, **kwargs): 
    693711        kwargs['max_length'] = kwargs.get('max_length', 75) 
     
    890908        defaults.update(kwargs) 
    891909        return super(IPAddressField, self).formfield(**defaults) 
    892910 
     911class LargeTextField(Field): 
     912    def get_manipulator_field_objs(self): 
     913        return [oldforms.LargeTextField] 
     914 
     915    def formfield(self, **kwargs): 
     916        defaults = {'widget': forms.Textarea} 
     917        defaults.update(kwargs) 
     918        return super(LargeTextField, self).formfield(**defaults) 
     919 
    893920class NullBooleanField(Field): 
    894921    empty_strings_allowed = False 
    895922    def __init__(self, *args, **kwargs): 
     
    9971024            # doesn't support microseconds. 
    9981025            if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'): 
    9991026                value = value.replace(microsecond=0) 
    1000             if settings.DATABASE_ENGINE == 'oracle'
    1001                 # cx_Oracle expects a datetime.datetime to persist into TIMESTAMP field. 
     1027            if settings.DATABASE_ENGINE in ('oracle', 'firebird')
     1028                # cx_Oracle and kinterbasdb expect a datetime.datetime to persist into TIMESTAMP field. 
    10021029                if isinstance(value, datetime.time): 
    10031030                    value = datetime.datetime(1900, 1, 1, value.hour, value.minute, 
    10041031                                              value.second, value.microsecond) 
  • django/db/models/fields/related.py

    old new  
    331331                        new_ids.add(obj) 
    332332                # Add the newly created or already existing objects to the join table. 
    333333                # First find out which items are already added, to avoid adding them twice 
     334                qn = connection.ops.quote_name 
    334335                cursor = connection.cursor() 
    335336                cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \ 
    336                     (target_col_name, self.join_table, source_col_name
    337                     target_col_name, ",".join(['%s'] * len(new_ids))), 
     337                    (qn(target_col_name), qn(self.join_table), qn(source_col_name)
     338                    qn(target_col_name), ",".join(['%s'] * len(new_ids))), 
    338339                    [self._pk_val] + list(new_ids)) 
    339340                existing_ids = set([row[0] for row in cursor.fetchall()]) 
    340341 
    341342                # Add the ones that aren't there already 
    342343                for obj_id in (new_ids - existing_ids): 
    343                     cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \ 
    344                         (self.join_table, source_col_name, target_col_name), 
    345                         [self._pk_val, obj_id]
     344                    cursor.execute('INSERT INTO %s (%s, %s) VALUES (%%s, %%s)' % \ 
     345                        (qn(self.join_table), qn(source_col_name), qn(target_col_name)), 
     346                        (self._pk_val, obj_id)
    346347                transaction.commit_unless_managed() 
    347348 
    348349        def _remove_items(self, source_col_name, target_col_name, *objs): 
  • django/db/models/fields/computed.py

    old new  
     1from django.db.models.fields import IntegerField 
     2 
     3class ComputedField(IntegerField): 
     4    def __init__(self, **kwargs): 
     5        expression = kwargs.pop('expression') 
     6        raw_params = kwargs.pop('params') 
     7        params = [] 
     8        for rp in raw_params: 
     9            params.append('"%s"' % rp)  
     10        self.expression = expression % tuple(params) 
     11        IntegerField.__init__(self, **kwargs) 
     12     
     13    def db_type(self): 
     14        return 'ComputedField' 
     15 
     16#TODO:  Add support for other field types 
     17#       Add support for fields which values are depended on INSERT, UPDATE or DLETE 
     18#       triggers of other fields/models 
     19 
     20     
     21         
     22     
     23 
     24 
     25 
  • django/db/models/__init__.py

    old new  
    77from django.db.models.manager import Manager 
    88from django.db.models.base import Model, AdminOptions 
    99from django.db.models.fields import * 
     10from django.db.models.procedures import Procedure 
    1011from django.db.models.fields.subclassing import SubfieldBase 
    1112from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel, TABULAR, STACKED 
     13from django.db.models.fields.computed import ComputedField 
    1214from django.db.models import signals 
    1315from django.utils.functional import curry 
    1416from django.utils.text import capfirst 
  • django/db/models/procedures/__init__.py

    old new  
     1from django.db import connection, transaction 
     2 
     3class procedure_meta(type): 
     4    """ 
     5    Metaclass for native stored procedures behaving as normal Django model methods 
     6    Subclass Procedure inside your model, create procedures tuple 
     7    and use the class as ordinary method 
     8    Please treat the resulting classes as methods because they don't 
     9    behave like classes anymore 
     10    Look at tests/customtests/custom_methods/model.py for demonstration 
     11    """ 
     12    def __init__(cls, name, bases, attrs): 
     13        super(procedure_meta, cls).__init__(name, bases, attrs) 
     14        params, returns, body = None, None, None 
     15        if '__params__' in attrs: 
     16            params = attrs['__params__'] 
     17        if '__returns__' in attrs: 
     18            returns = attrs['__returns__'] 
     19        if name != 'Procedure': 
     20            assert '__body__' in attrs, "Procedure must have body"    
     21            header = ['CREATE PROCEDURE %s ' % name] 
     22            if params: 
     23                header.append('(') 
     24                header.append(', '.join([param + ' ' + f for param, f in params])) 
     25                header.append(')') 
     26            if returns: 
     27                header.append('\nRETURNS (') 
     28                header.append(', '.join([ret + ' ' + f for ret, f in returns])) 
     29                header.append(')')    
     30            cls.__sql__ = [''.join(header)] 
     31            cls.__sql__.extend(['AS', 'BEGIN', attrs['__body__'], 'END']) 
     32            cls.sql = '\n'.join(cls.__sql__) 
     33            cls.params = params 
     34            cls.returns = returns 
     35            cls.procedure_name = name 
     36     
     37    def create_procedure_sql(cls): 
     38        return cls.sql 
     39     
     40    def __call__(cls, *args): 
     41        cursor = connection.cursor() 
     42        if not cls.returns: 
     43            cursor.execute('EXECUTE PROCEDURE "%s" %s;' %\ 
     44                (cls.procedure_name, 
     45                 ' ,'.join("'%s'" % arg for arg in args))) 
     46        else: 
     47            cursor.execute_straight('SELECT %s FROM %s(%s);' %\ 
     48                (', '.join("%s" % ret[0] for ret in cls.returns), 
     49                 cls.procedure_name, 
     50                 ', '.join("?" * len(args))), args)        
     51            return cursor.fetchall() 
     52 
     53class Procedure(object): 
     54    __metaclass__ = procedure_meta 
     55 
  • django/db/models/query.py

    old new  
    612612        columns = [f.column for f in fields] 
    613613        select = ['%s.%s' % (qn(self.model._meta.db_table), qn(c)) for c in columns] 
    614614        if extra_select: 
    615             select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), qn(s[0])) for s in extra_select]) 
     615            if not settings.DATABASE_ENGINE == 'firebird': 
     616                select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), qn(s[0])) for s in extra_select]) 
     617            else: 
     618                select.extend(['(%s) AS %s' % (connection.ops.quote_id_plus_number(s[1]), qn(s[0])) for s in extra_select]) 
    616619            field_names.extend([f[0] for f in extra_select]) 
    617620 
    618621        cursor = connection.cursor() 
     
    11111114            # Last query term was a normal field. 
    11121115            column = field.column 
    11131116            db_type = field.db_type() 
    1114  
    11151117        where.append(get_where_clause(lookup_type, current_table + '.', column, value, db_type)) 
    11161118        params.extend(field.get_db_prep_lookup(lookup_type, value)) 
    11171119 
     
    11461148            if isinstance(f, generic.GenericRelation): 
    11471149                from django.contrib.contenttypes.models import ContentType 
    11481150                query_extra = 'AND %s=%%s' % f.rel.to._meta.get_field(f.content_type_field_name).column 
     1151                if settings.DATABASE_ENGINE == 'firebird': 
     1152                    query_extra = 'AND %s=%%s' % qn(f.rel.to._meta.get_field(f.content_type_field_name).column) 
    11491153                args_extra = [ContentType.objects.get_for_model(cls).id] 
    11501154            else: 
    11511155                query_extra = '' 
  • django/db/backends/__init__.py

    old new  
    4848    needs_upper_for_iops = False 
    4949    supports_constraints = True 
    5050    supports_tablespaces = False 
     51    quote_autofields = False 
    5152    uses_case_insensitive_names = False 
     53    uses_custom_field = False 
    5254    uses_custom_queryset = False 
    5355 
    5456class BaseDatabaseOperations(object): 
     
    199201        """ 
    200202        raise NotImplementedError() 
    201203 
     204    def reference_name(self, r_col, col, r_table, table): 
     205        return '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table)))) 
     206 
    202207    def random_function_sql(self): 
    203208        """ 
    204209        Returns a SQL expression that returns a random value. 
  • django/db/backends/firebird/base.py

    old new  
     1""" 
     2Firebird database backend for Django. 
     3 
     4Requires KInterbasDB 3.2: http://kinterbasdb.sourceforge.net/ 
     5The egenix mx (mx.DateTime) is NOT required 
     6 
     7Database charset should be UNICODE_FSS or UTF8 (FireBird 2.0+) 
     8To use UTF8 encoding add FIREBIRD_CHARSET = 'UTF8' to your settings.py  
     9UNICODE_FSS works with all versions and uses less memory 
     10""" 
     11 
     12from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util 
     13import sys 
     14try: 
     15    import decimal 
     16except ImportError: 
     17    from django.utils import _decimal as decimal    # for Python 2.3 
     18 
     19try: 
     20    import kinterbasdb as Database 
     21except ImportError, e: 
     22    from django.core.exceptions import ImproperlyConfigured 
     23    raise ImproperlyConfigured, "Error loading KInterbasDB module: %s" % e 
     24 
     25DatabaseError = Database.DatabaseError 
     26IntegrityError = Database.IntegrityError 
     27 
     28class DatabaseFeatures(BaseDatabaseFeatures): 
     29    inline_fk_references = False  
     30    needs_datetime_string_cast = False 
     31    needs_upper_for_iops = True 
     32    quote_autofields = True 
     33    uses_custom_field = True 
     34    uses_custom_queryset = True 
     35 
     36################################################################################ 
     37# Database operations (db.connection.ops)   
     38class DatabaseOperations(BaseDatabaseOperations): 
     39    """ 
     40    This class encapsulates all backend-specific differences, such as the way 
     41    a backend performs ordering or calculates the ID of a recently-inserted 
     42    row. 
     43    """ 
     44    # Utility ops: names, version, page size etc.:  
     45    _max_name_length = 31 
     46    def __init__(self): 
     47        self._firebird_version = None 
     48        self._page_size = None 
     49     
     50    def get_generator_name(self, name): 
     51        return '%s$G' % util.truncate_name(name.strip('"'), self._max_name_length-2).upper() 
     52         
     53    def get_trigger_name(self, name): 
     54        return '%s$T' % util.truncate_name(name.strip('"'), self._max_name_length-2).upper()  
     55     
     56    def _get_firebird_version(self): 
     57        if self._firebird_version is None: 
     58            from django.db import connection 
     59            self._firebird_version = [int(val) for val in connection.server_version.split()[-1].split('.')] 
     60        return self._firebird_version 
     61    firebird_version = property(_get_firebird_version) 
     62   
     63    def reference_name(self, r_col, col, r_table, table): 
     64        base_name = util.truncate_name('%s$%s' % (r_col, col), self._max_name_length-5) 
     65        return ('%s$%x' % (base_name, abs(hash((r_table, table))))).upper() 
     66     
     67    def _get_page_size(self): 
     68        if self._page_size is None: 
     69            from django.db import connection 
     70            self._page_size = connection.database_info(Database.isc_info_page_size, 'i') 
     71        return self._page_size 
     72    page_size = property(_get_page_size) 
     73     
     74    def _get_index_limit(self): 
     75        if self.firebird_version[0] < 2: 
     76            self._index_limit = 252  
     77        else: 
     78            page_size = self._get_page_size() 
     79            self._index_limit = page_size/4 
     80        return self._index_limit 
     81    index_limit = property(_get_index_limit) 
     82     
     83    def max_name_length(self): 
     84        return self._max_name_length 
     85     
     86    def quote_name(self, name): 
     87        name = '"%s"' % util.truncate_name(name.strip('"'), self._max_name_length) 
     88        return name 
     89     
     90    def quote_id_plus_number(self, name): 
     91        try: 
     92            return '"%s" + %s' % tuple(s.strip() for s in name.strip('"').split('+')) 
     93        except: 
     94            return self.quote_name(name) 
     95     
     96    def field_cast_sql(self, db_type): 
     97        return '%s' 
     98 
     99    ############################################################################ 
     100    # Basic SQL ops:     
     101    def last_insert_id(self, cursor, table_name, pk_name=None): 
     102        generator_name = self.get_generator_name(table_name) 
     103        cursor.execute('SELECT GEN_ID(%s, 0) from RDB$DATABASE' % generator_name) 
     104        return cursor.fetchone()[0] 
     105 
     106    def date_extract_sql(self, lookup_type, column_name): 
     107        # lookup_type is 'year', 'month', 'day' 
     108        return "EXTRACT(%s FROM %s)" % (lookup_type, column_name) 
     109 
     110    def date_trunc_sql(self, lookup_type, column_name): 
     111        if lookup_type == 'year': 
     112             sql = "EXTRACT(year FROM %s)||'-01-01 00:00:00'" % column_name 
     113        elif lookup_type == 'month': 
     114            sql = "EXTRACT(year FROM %s)||'-'||EXTRACT(month FROM %s)||'-01 00:00:00'" % (column_name, column_name) 
     115        elif lookup_type == 'day': 
     116            sql = "EXTRACT(year FROM %s)||'-'||EXTRACT(month FROM %s)||'-'||EXTRACT(day FROM %s)||' 00:00:00'" % (column_name, column_name, column_name) 
     117        return "CAST(%s AS TIMESTAMP)" % sql 
     118 
     119    def datetime_cast_sql(self): 
     120        return None 
     121 
     122    def drop_sequence_sql(self, table): 
     123        return "DROP GENERATOR %s;" % self.get_generator_name(table) 
     124     
     125    def drop_foreignkey_sql(self): 
     126        return "DROP CONSTRAINT" 
     127         
     128    def limit_offset_sql(self, limit, offset=None): 
     129        # limits are handled in custom FirebirdQuerySet  
     130        assert False, 'Limits are handled in a different way in Firebird' 
     131        return "" 
     132 
     133    def random_function_sql(self): 
     134        return "rand()" 
     135 
     136    def pk_default_value(self): 
     137        """ 
     138        Returns the value to use during an INSERT statement to specify that 
     139        the field should use its default value. 
     140        """ 
     141        return 'NULL' 
     142     
     143    def start_transaction_sql(self): 
     144        return "" 
     145 
     146    def fulltext_search_sql(self, field_name): 
     147        # We use varchar for TextFields so this is possible 
     148        # Look at http://www.volny.cz/iprenosil/interbase/ip_ib_strings.htm 
     149        return '%%s CONTAINING %s' % self.quote_name(field_name) 
     150 
     151    ############################################################################ 
     152    # Advanced SQL ops: 
     153    def autoinc_sql(self, style, table_name, column_name): 
     154        """ 
     155        To simulate auto-incrementing primary keys in Firebird, we have to 
     156        create a generator and a trigger. 
     157     
     158        Create the generators and triggers names based only on table name 
     159        since django only support one auto field per model 
     160        """ 
     161    
     162        generator_name = self.get_generator_name(table_name) 
     163        trigger_name = self.get_trigger_name(table_name) 
     164        column_name = self.quote_name(column_name) 
     165        table_name = self.quote_name(table_name) 
     166         
     167        generator_sql = "%s %s;" % ( style.SQL_KEYWORD('CREATE GENERATOR'),  
     168                                     generator_name)       
     169        trigger_sql = "\n".join([ 
     170            "%s %s %s %s" % ( \ 
     171            style.SQL_KEYWORD('CREATE TRIGGER'), trigger_name, style.SQL_KEYWORD('FOR'), 
     172            style.SQL_TABLE(table_name)), 
     173            "%s 0 %s" % (style.SQL_KEYWORD('ACTIVE BEFORE INSERT POSITION'), style.SQL_KEYWORD('AS')), 
     174            style.SQL_KEYWORD('BEGIN'),  
     175            "  %s ((%s.%s %s) %s (%s.%s = 0)) %s" % ( \ 
     176                style.SQL_KEYWORD('IF'), 
     177                style.SQL_KEYWORD('NEW'), style.SQL_FIELD(column_name), style.SQL_KEYWORD('IS NULL'), 
     178                style.SQL_KEYWORD('OR'), style.SQL_KEYWORD('NEW'), style.SQL_FIELD(column_name), 
     179                style.SQL_KEYWORD('THEN') 
     180            ), 
     181            "  %s" % style.SQL_KEYWORD('BEGIN'),  
     182            "    %s.%s = %s(%s, 1);" % ( \ 
     183                style.SQL_KEYWORD('NEW'), style.SQL_FIELD(column_name), 
     184                style.SQL_KEYWORD('GEN_ID'), generator_name 
     185            ), 
     186            "  %s" % style.SQL_KEYWORD('END'), 
     187            "%s;" % style.SQL_KEYWORD('END')]) 
     188        return (generator_sql, trigger_sql) 
     189     
     190    def sequence_reset_sql(self, style, model_list): 
     191        from django.db import models 
     192        output = [] 
     193        sql = ['%s %s %s' % (style.SQL_KEYWORD('CREATE OR ALTER PROCEDURE'), 
     194                             style.SQL_TABLE('"GENERATOR_RESET"'), 
     195                             style.SQL_KEYWORD('AS'))] 
     196        sql.append('%s %s' % (style.SQL_KEYWORD('DECLARE VARIABLE'), style.SQL_COLTYPE('start_val integer;'))) 
     197        sql.append('%s %s' % (style.SQL_KEYWORD('DECLARE VARIABLE'), style.SQL_COLTYPE('gen_val integer;'))) 
     198        sql.append('\t%s' % style.SQL_KEYWORD('BEGIN')) 
     199        sql.append('\t\t%s %s %s %s %s %s;' % (style.SQL_KEYWORD('SELECT MAX'), style.SQL_FIELD('(%(col)s)'), 
     200                                           style.SQL_KEYWORD('FROM'), style.SQL_TABLE('%(table)s'), 
     201                                           style.SQL_KEYWORD('INTO'), style.SQL_COLTYPE(':start_val'))) 
     202        sql.append('\t\t%s (%s %s) %s' % (style.SQL_KEYWORD('IF'), style.SQL_COLTYPE('start_val'), 
     203                                    style.SQL_KEYWORD('IS NULL'), style.SQL_KEYWORD('THEN'))) 
     204        sql.append('\t\t\t%s = %s(%s, 1 - %s(%s, 0));' %\ 
     205            (style.SQL_COLTYPE('gen_val'), style.SQL_KEYWORD('GEN_ID'), style.SQL_TABLE('%(gen)s'), 
     206             style.SQL_KEYWORD('GEN_ID'), style.SQL_TABLE('%(gen)s'))) 
     207        sql.append('\t\t%s' % style.SQL_KEYWORD('ELSE')) 
     208        sql.append('\t\t\t%s = %s(%s, %s - %s(%s, 0));' %\ 
     209            (style.SQL_COLTYPE('gen_val'), style.SQL_KEYWORD('GEN_ID'), 
     210             style.SQL_TABLE('%(gen)s'), style.SQL_COLTYPE('start_val'), style.SQL_KEYWORD('GEN_ID'), 
     211             style.SQL_TABLE('%(gen)s'))) 
     212        sql.append('\t\t%s;' % style.SQL_KEYWORD('EXIT')) 
     213        sql.append('%s;' % style.SQL_KEYWORD('END')) 
     214        sql ="\n".join(sql) 
     215        for model in model_list: 
     216            for f in model._meta.fields: 
     217                if isinstance(f, models.AutoField): 
     218                    generator_name = self.get_generator_name(model._meta.db_table) 
     219                    column_name = self.quote_name(f.db_column or f.name) 
     220                    table_name = self.quote_name(model._meta.db_table) 
     221                    output.append(sql % {'col' : column_name, 'table' : table_name, 'gen' : generator_name}) 
     222                    output.append('%s %s;' % (style.SQL_KEYWORD('EXECUTE PROCEDURE'),  
     223                                              style.SQL_TABLE('"GENERATOR_RESET"'))) 
     224                    break # Only one AutoField is allowed per model, so don't bother continuing. 
     225            for f in model._meta.many_to_many: 
     226                generator_name = self.get_generator_name(f.m2m_db_table()) 
     227                table_name = self.quote_name(f.m2m_db_table()) 
     228                column_name = '"id"' 
     229                output.append(sql % {'col' : column_name, 'table' : table_name, 'gen' : generator_name}) 
     230                output.append('%s %s;' % (style.SQL_KEYWORD('EXECUTE PROCEDURE'),  
     231                                          style.SQL_TABLE('"GENERATOR_RESET"'))) 
     232        return output 
     233     
     234    def sql_flush(self, style, tables, sequences): 
     235        if tables: 
     236            sql = ['%s %s %s;' % \ 
     237                    (style.SQL_KEYWORD('DELETE'), 
     238                     style.SQL_KEYWORD('FROM'), 
     239                     style.SQL_TABLE(self.quote_name(table)) 
     240                     ) for table in tables] 
     241            for generator_info in sequences: 
     242                table_name = generator_info['table'] 
     243                query = "%s %s %s 0;" % (style.SQL_KEYWORD('SET GENERATOR'),  
     244                    self.get_generator_name(table_name), style.SQL_KEYWORD('TO')) 
     245                sql.append(query) 
     246            return sql 
     247        else: 
     248            return [] 
     249             
     250    ############################################################################ 
     251    # Custom classes 
     252    def field_class(this, DefaultField): 
     253        from django.db import connection 
     254        from django.db.models.fields import prep_for_like_query 
     255        class FirebirdField(DefaultField): 
     256            def get_db_prep_lookup(self, lookup_type, value):        
     257                "Returns field's value prepared for database lookup." 
     258                if lookup_type in ('exact', 'regex', 'iregex', 'gt', 'gte', 'lt',  
     259                    'lte', 'month', 'day', 'search', 'icontains',  
     260                    'startswith', 'istartswith'): 
     261                    return [value] 
     262                elif lookup_type in ('range', 'in'): 
     263                    return value 
     264                elif lookup_type in ('contains',): 
     265                    return ["%%%s%%" % prep_for_like_query(value)] 
     266                elif lookup_type == 'iexact': 
     267                    return [prep_for_like_query(value)] 
     268                elif lookup_type in ('endswith', 'iendswith'): 
     269                    return ["%%%s" % prep_for_like_query(value)] 
     270                elif lookup_type == 'isnull': 
     271                    return [] 
     272                elif lookup_type == 'year': 
     273                    try: 
     274                        value = int(value) 
     275                    except ValueError: 
     276                        raise ValueError("The __year lookup type requires an integer argument") 
     277                    return ['%s-01-01 00:00:00' % value, '%s-12-31 23:59:59.999999' % value] 
     278                raise TypeError("Field has invalid lookup: %s" % lookup_type) 
     279        return FirebirdField 
     280 
     281    def query_set_class(this, DefaultQuerySet): 
     282        from django.db import connection 
     283        from django.db.models.query import EmptyResultSet, GET_ITERATOR_CHUNK_SIZE 
     284        class FirebirdQuerySet(DefaultQuerySet): 
     285            def _get_sql_clause(self): 
     286                from django.db.models.query import SortedDict, handle_legacy_orderlist, orderfield2column, fill_table_cache 
     287                qn = this.quote_name 
     288                opts = self.model._meta 
     289 
     290                # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z. 
     291                select = ["%s.%s" % (qn(opts.db_table), qn(f.column)) for f in opts.fields] 
     292                tables = [qn(t) for t in self._tables] 
     293                joins = SortedDict() 
     294                where = self._where[:] 
     295                params = self._params[:] 
     296 
     297                # Convert self._filters into SQL. 
     298                joins2, where2, params2 = self._filters.get_sql(opts) 
     299                joins.update(joins2) 
     300                where.extend(where2) 
     301                params.extend(params2) 
     302 
     303                # Add additional tables and WHERE clauses based on select_related. 
     304                if self._select_related: 
     305                    fill_table_cache(opts, select, tables, where, 
     306                                     old_prefix=opts.db_table, 
     307                                     cache_tables_seen=[opts.db_table], 
     308                                     max_depth=self._max_related_depth) 
     309                 
     310                # Add any additional SELECTs. 
     311                if self._select: 
     312                    select.extend([('(%s AS %s') % (qn(s[1]), qn(s[0])) for s in self._select.items()]) 
     313 
     314                # Start composing the body of the SQL statement. 
     315                sql = [" FROM", qn(opts.db_table)] 
     316 
     317                # Compose the join dictionary into SQL describing the joins. 
     318                if joins: 
     319                    sql.append(" ".join(["%s %s %s ON %s" % (join_type, table, alias, condition) 
     320                                    for (alias, (table, join_type, condition)) in joins.items()])) 
     321 
     322                # Compose the tables clause into SQL. 
     323                if tables: 
     324                    sql.append(", " + ", ".join(tables)) 
     325 
     326                # Compose the where clause into SQL. 
     327                if where:  
     328                    sql.append(where and "WHERE " + " AND ".join(where)) 
     329 
     330                # ORDER BY clause 
     331                order_by = [] 
     332                if self._order_by is not None: 
     333                    ordering_to_use = self._order_by 
     334                else: 
     335                    ordering_to_use = opts.ordering 
     336                for f in handle_legacy_orderlist(ordering_to_use): 
     337                    if f == '?': # Special case. 
     338                        order_by.append(connection.ops.random_function_sql()) 
     339                    else: 
     340                        if f.startswith('-'): 
     341                            col_name = f[1:] 
     342                            order = "DESC" 
     343                        else: 
     344                            col_name = f 
     345                            order = "ASC" 
     346                        if "." in col_name: 
     347                            table_prefix, col_name = col_name.split('.', 1) 
     348                            table_prefix = qn(table_prefix) + '.' 
     349                        else: 
     350                            # Use the database table as a column prefix if it wasn't given, 
     351                            # and if the requested column isn't a custom SELECT. 
     352                            if "." not in col_name and col_name not in (self._select or ()): 
     353                                table_prefix = qn(opts.db_table) + '.' 
     354                            else: 
     355                                table_prefix = '' 
     356                        order_by.append('%s%s %s' % \ 
     357                            (table_prefix, qn(orderfield2column(col_name, opts)), order)) 
     358                if order_by: 
     359                    sql.append("ORDER BY " + ", ".join(order_by)) 
     360 
     361                return select, " ".join(sql), params 
     362             
     363            def iterator(self): 
     364                "Performs the SELECT database lookup of this QuerySet." 
     365                from django.db.models.query import get_cached_row 
     366                try: 
     367                    select, sql, params = self._get_sql_clause() 
     368                except EmptyResultSet: 
     369                    raise StopIteration  
     370                     
     371                # self._select is a dictionary, and dictionaries' key order is 
     372                # undefined, so we convert it to a list of tuples. 
     373                extra_select = self._select.items() 
     374                 
     375                cursor = connection.cursor()  
     376                limit_offset_before = ""  
     377                if self._limit is not None:  
     378                    limit_offset_before += "FIRST %s " % self._limit  
     379                    if self._offset:  
     380                        limit_offset_before += "SKIP %s " % self._offset 
     381                else: 
     382                    assert self._offset is None, "'offset' is not allowed without 'limit'" 
     383                cursor.execute("SELECT " + limit_offset_before + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) 
     384                fill_cache = self._select_related 
     385                fields = self.model._meta.fields 
     386                index_end = len(fields) 
     387                while 1: 
     388                    rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE) 
     389                    if not rows: 
     390                        raise StopIteration 
     391                    for row in rows: 
     392                        row = self.resolve_columns(row, fields) 
     393                        if fill_cache: 
     394                            obj, index_end = get_cached_row(klass=self.model, row=row, 
     395                                                            index_start=0, max_depth=self._max_related_depth) 
     396                        else: 
     397                            obj = self.model(*row[:index_end]) 
     398                        for i, k in enumerate(extra_select): 
     399                            setattr(obj, k[0], row[index_end+i]) 
     400                        yield obj 
     401             
     402            def resolve_columns(self, row, fields=()): 
     403                from django.db.models.fields import DateField, DateTimeField, \ 
     404                    TimeField, BooleanField, NullBooleanField, DecimalField, Field 
     405                values = [] 
     406                for value, field in map(None, row, fields): 
     407                    # Convert 1 or 0 to True or False 
     408                    if value in (1, 0) and isinstance(field, (BooleanField, NullBooleanField)): 
     409                        value = bool(value) 
     410 
     411                    values.append(value) 
     412                return values 
     413                 
     414            def extra(self, select=None, where=None, params=None, tables=None): 
     415                assert self._limit is None and self._offset is None, \ 
     416                        "Cannot change a query once a slice has been taken" 
     417                clone = self._clone() 
     418                qn = this.quote_name 
     419                if select: clone._select.update(select) 
     420                if where: 
     421                    qn_where = [] 
     422                    for where_item in where: 
     423                        try: 
     424                            table, col_exact = where_item.split(".") 
     425                            col, value = col_exact.split("=") 
     426                            where_item = "%s.%s = %s" % (qn(table.strip()),  
     427                                qn(col.strip()), value.strip()) 
     428                        except: 
     429                            try: 
     430                                table, value = where_item.split("=") 
     431                                where_item = "%s = %s" % (qn(table.strip()), qn(value.strip())) 
     432                            except: 
     433                                raise TypeError, "Can't understand extra WHERE clause: %s" % where  
     434                        qn_where.append(where_item) 
     435                    clone._where.extend(qn_where) 
     436                if params: clone._params.extend(params) 
     437                if tables: clone._tables.extend(tables) 
     438                return clone 
     439                 
     440        return FirebirdQuerySet 
     441 
     442################################################################################ 
     443# Cursor wrapper         
     444class FirebirdCursorWrapper(object): 
     445    """ 
     446    Django uses "format" ('%s') style placeholders, but firebird uses "qmark" ('?') style. 
     447    This fixes it -- but note that if you want to use a literal "%s" in a query, 
     448    you'll need to use "%%s". 
     449     
     450    We also do all automatic type conversions here. 
     451    """ 
     452    import kinterbasdb.typeconv_datetime_stdlib as tc_dt 
     453    import kinterbasdb.typeconv_fixed_decimal as tc_fd 
     454    import kinterbasdb.typeconv_text_unicode as tc_tu 
     455    import django.utils.encoding as dj_ue 
     456 
     457    def ascii_conv_in(self, text): 
     458        if text is not None:   
     459            return self.dj_ue.smart_str(text, 'ascii') 
     460     
     461    def ascii_conv_out(self, text): 
     462        if text is not None: 
     463            return self.dj_ue.smart_unicode(text)     
     464 
     465    def blob_conv_in(self, text):  
     466        return self.tc_tu.unicode_conv_in((self.dj_ue.smart_unicode(text), self.FB_CHARSET_CODE)) 
     467 
     468    def blob_conv_out(self, text): 
     469        return self.tc_tu.unicode_conv_out((text, self.FB_CHARSET_CODE))    
     470 
     471    def fixed_conv_in(self, (val, scale)): 
     472        if val is not None: 
     473            if isinstance(val, basestring): 
     474                val = decimal.Decimal(val) 
     475            return self.tc_fd.fixed_conv_in_precise((val, scale)) 
     476 
     477    def timestamp_conv_in(self, timestamp): 
     478        if isinstance(timestamp, basestring): 
     479            #Replaces 6 digits microseconds to 4 digits allowed in Firebird 
     480            timestamp = timestamp[:24] 
     481        return self.tc_dt.timestamp_conv_in(timestamp) 
     482 
     483    def time_conv_in(self, value): 
     484        import datetime 
     485        if isinstance(value, datetime.datetime): 
     486            value = datetime.time(value.hour, value.minute, value.second, value.microsecond)        
     487 
     488    def unicode_conv_in(self, text): 
     489        if text[0] is not None: 
     490            return self.tc_tu.unicode_conv_in((self.dj_ue.smart_unicode(text[0]), self.FB_CHARSET_CODE)) 
     491 
     492    def __init__(self, cursor, connection):    
     493        self.cursor = cursor 
     494        self._connection = connection 
     495        self._statement = None #prepared statement 
     496        self.FB_CHARSET_CODE = 3 #UNICODE_FSS 
     497        if connection.charset == 'UTF8': 
     498            self.FB_CHARSET_CODE = 4 # UTF-8 with Firebird 2.0+ 
     499        self.cursor.set_type_trans_in({ 
     500            'DATE':             self.tc_dt.date_conv_in, 
     501            'TIME':             self.time_conv_in, 
     502            'TIMESTAMP':        self.timestamp_conv_in, 
     503            'FIXED':            self.fixed_conv_in, 
     504            'TEXT':             self.ascii_conv_in, 
     505            'TEXT_UNICODE':     self.unicode_conv_in, 
     506            'BLOB':             self.blob_conv_in 
     507        }) 
     508        self.cursor.set_ty