Django

Code

Ticket #1261: firebird-6668.diff

File firebird-6668.diff, 74.8 kB (added by i_i, 10 months ago)

small fixes

  • django/db/models/base.py

    old new  
    241241            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)] 
    242242            # If the PK has been manually set, respect that. 
    243243            if pk_set: 
    244                 field_names += [f.column for f in self._meta.fields if isinstance(f, AutoField)] 
     244                if connection.features.quote_autofields: 
     245                    field_names += [qn(f.column) for f in self._meta.fields if isinstance(f, AutoField)] 
     246                else: 
     247                    field_names += [f.column for f in self._meta.fields if isinstance(f, AutoField)] 
    245248                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)] 
    246249            placeholders = ['%s'] * len(field_names) 
    247250            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): 
    8787        self.name = name 
    8888        self.verbose_name = verbose_name 
    8989        self.primary_key = primary_key 
    9090        self.max_length, self.unique = max_length, unique 
     91        self.encoding = encoding 
    9192        self.blank, self.null = blank, null 
    9293        # Oracle treats the empty string ('') as null, so coerce the null 
    9394        # option whenever '' is a possible value. 
     
    148149        data_types = get_creation_module().DATA_TYPES 
    149150        internal_type = self.get_internal_type() 
    150151        if internal_type not in data_types: 
    151             return None 
     152            return None     
    152153        return data_types[internal_type] % self.__dict__ 
    153154 
    154155    def validate_full(self, field_data, all_data): 
     
    402403        "Returns the value of this field in the given model instance." 
    403404        return getattr(obj, self.attname) 
    404405 
     406# Use the backend's Field class if it defines one. Otherwise, use _Field. 
     407if connection.features.uses_custom_field: 
     408    Field = connection.ops.field_class(_Field) 
     409else: 
     410    Field = _Field 
     411 
    405412class AutoField(Field): 
    406413    empty_strings_allowed = False 
    407414    def __init__(self, *args, **kwargs): 
     
    688695        defaults.update(kwargs) 
    689696        return super(DecimalField, self).formfield(**defaults) 
    690697 
     698class DefaultCharField(CharField): 
     699    def __init__(self, *args, **kwargs): 
     700        DEFAULT_MAX_LENGTH = 100 
     701        if hasattr(settings, 'DEFAULT_MAX_LENGTH'): 
     702           DEFAULT_MAX_LENGTH = settings.DEFAULT_MAX_LENGT 
     703        kwargs['max_length'] = kwargs.get('max_length', DEFAULT_MAX_LENGTH) 
     704        CharField.__init__(self, *args, **kwargs) 
     705 
    691706class EmailField(CharField): 
    692707    def __init__(self, *args, **kwargs): 
    693708        kwargs['max_length'] = kwargs.get('max_length', 75) 
     
    890905        defaults.update(kwargs) 
    891906        return super(IPAddressField, self).formfield(**defaults) 
    892907 
     908class LargeTextField(Field): 
     909    def get_manipulator_field_objs(self): 
     910        return [oldforms.LargeTextField] 
     911 
     912    def formfield(self, **kwargs): 
     913        defaults = {'widget': forms.Textarea} 
     914        defaults.update(kwargs) 
     915        return super(LargeTextField, self).formfield(**defaults) 
     916 
    893917class NullBooleanField(Field): 
    894918    empty_strings_allowed = False 
    895919    def __init__(self, *args, **kwargs): 
     
    9971021            # doesn't support microseconds. 
    9981022            if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'): 
    9991023                value = value.replace(microsecond=0) 
    1000             if settings.DATABASE_ENGINE == 'oracle'
    1001                 # cx_Oracle expects a datetime.datetime to persist into TIMESTAMP field. 
     1024            if settings.DATABASE_ENGINE in ('oracle', 'firebird')
     1025                # cx_Oracle and kinterbasdb expect a datetime.datetime to persist into TIMESTAMP field. 
    10021026                if isinstance(value, datetime.time): 
    10031027                    value = datetime.datetime(1900, 1, 1, value.hour, value.minute, 
    10041028                                              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/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): 
     
    6567        This SQL is executed when a table is created. 
    6668        """ 
    6769        return None 
    68  
     70     
     71    def cascade_delete_update_sql(self): 
     72        """ 
     73        Returns the SQL necessary to make a cascading deletes and updates 
     74        of foreign key references during a CREATE TABLE statement. 
     75        """ 
     76        return '' 
     77     
    6978    def date_extract_sql(self, lookup_type, field_name): 
    7079        """ 
    7180        Given a lookup_type of 'year', 'month' or 'day', returns the SQL that 
     
    127136        contain a '%s' placeholder for the value being searched against. 
    128137        """ 
    129138        raise NotImplementedError('Full-text search is not implemented for this database backend') 
    130  
     139     
    131140    def last_executed_query(self, cursor, sql, params): 
    132141        """ 
    133142        Returns a string of the query last executed by the given cursor, with 
     
    175184        is no limit. 
    176185        """ 
    177186        return None 
    178  
     187    
    179188    def pk_default_value(self): 
    180189        """ 
    181190        Returns the value to use during an INSERT statement to specify that 
  • 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    supports_constraints = True #turn this off to pass the tests with forward/post references 
     34    uses_custom_field = True 
     35    uses_custom_queryset = True 
     36 
     37class DatabaseOperations(BaseDatabaseOperations): 
     38    _max_name_length = 31 
     39    def __init__(self): 
     40        self._firebird_version = None 
     41        self._page_size = None 
     42     
     43    def get_generator_name(self, name): 
     44        return '%s_G' % util.truncate_name(name.strip('"'), self._max_name_length-2).upper() 
     45 
     46    def get_trigger_name(self, name): 
     47        return '%s_T' % util.truncate_name(name.strip('"'), self._max_name_length-2).upper()  
     48     
     49    def _get_firebird_version(self): 
     50        if self._firebird_version is None: 
     51            from django.db import connection 
     52            self._firebird_version = [int(val) for val in connection.server_version.split()[-1].split('.')] 
     53        return self._firebird_version 
     54    firebird_version = property(_get_firebird_version) 
     55   
     56    def _get_page_size(self): 
     57        if self._page_size is None: 
     58            from django.db import connection 
     59            self._page_size = connection.database_info(Database.isc_info_page_size, 'i') 
     60        return self._page_size 
     61    page_size = property(_get_page_size) 
     62     
     63    def _get_index_limit(self): 
     64        if self.firebird_version[0] < 2: 
     65            self._index_limit = 252  
     66        else: 
     67            page_size = self._get_page_size() 
     68            self._index_limit = page_size/4 
     69        return self._index_limit 
     70    index_limit = property(_get_index_limit) 
     71 
     72    def autoinc_sql(self, style, table_name, column_name): 
     73        """ 
     74        To simulate auto-incrementing primary keys in Firebird, we have to 
     75        create a generator and a trigger. 
     76     
     77        Create the generators and triggers names based only on table name 
     78        since django only support one auto field per model 
     79        """ 
     80    
     81        generator_name = self.get_generator_name(table_name) 
     82        trigger_name = self.get_trigger_name(table_name) 
     83        column_name = self.quote_name(column_name) 
     84        table_name = self.quote_name(table_name) 
     85         
     86        generator_sql = "%s %s;" % ( style.SQL_KEYWORD('CREATE GENERATOR'),  
     87                                     style.SQL_TABLE(generator_name))       
     88        trigger_sql = "\n".join([ 
     89            "%s %s %s %s" % ( \ 
     90            style.SQL_KEYWORD('CREATE TRIGGER'), style.SQL_TABLE(trigger_name), style.SQL_KEYWORD('FOR'), 
     91            style.SQL_TABLE(table_name)), 
     92            "%s 0 %s" % (style.SQL_KEYWORD('ACTIVE BEFORE INSERT POSITION'), style.SQL_KEYWORD('AS')), 
     93            style.SQL_KEYWORD('BEGIN'),  
     94            "  %s ((%s.%s %s) %s (%s.%s = 0)) %s" % ( \ 
     95                style.SQL_KEYWORD('IF'), 
     96                style.SQL_KEYWORD('NEW'), style.SQL_FIELD(column_name), style.SQL_KEYWORD('IS NULL'), 
     97                style.SQL_KEYWORD('OR'), style.SQL_KEYWORD('NEW'), style.SQL_FIELD(column_name), 
     98                style.SQL_KEYWORD('THEN') 
     99            ), 
     100            "  %s" % style.SQL_KEYWORD('BEGIN'),  
     101            "    %s.%s = %s(%s, 1);" % ( \ 
     102                style.SQL_KEYWORD('NEW'), style.SQL_FIELD(column_name), 
     103                style.SQL_KEYWORD('GEN_ID'), style.SQL_TABLE(generator_name) 
     104            ), 
     105            "  %s" % style.SQL_KEYWORD('END'), 
     106            style.SQL_KEYWORD('END') 
     107            ]) 
     108        return (generator_sql, trigger_sql) 
     109 
     110    def max_name_length(self): 
     111        return self._max_name_length 
     112          
     113    def field_class(this, DefaultField): 
     114        from django.db import connection 
     115        from django.db.models.fields import prep_for_like_query 
     116        class FirebirdField(DefaultField): 
     117            def get_db_prep_lookup(self, lookup_type, value):        
     118                "Returns field's value prepared for database lookup." 
     119                if lookup_type in ('exact', 'regex', 'iregex', 'gt', 'gte', 'lt',  
     120                    'lte', 'month', 'day', 'search', 'icontains',  
     121                    'startswith', 'istartswith'): 
     122                    return [value] 
     123                elif lookup_type in ('range', 'in'): 
     124                    return value 
     125                elif lookup_type in ('contains',): 
     126                    return ["%%%s%%" % prep_for_like_query(value)] 
     127                elif lookup_type == 'iexact': 
     128                    return [prep_for_like_query(value)] 
     129                elif lookup_type in ('endswith', 'iendswith'): 
     130                    return ["%%%s" % prep_for_like_query(value)] 
     131                elif lookup_type == 'isnull': 
     132                    return [] 
     133                elif lookup_type == 'year': 
     134                    try: 
     135                        value = int(value) 
     136                    except ValueError: 
     137                        raise ValueError("The __year lookup type requires an integer argument") 
     138                    return ['%s-01-01 00:00:00' % value, '%s-12-31 23:59:59.999999' % value] 
     139                raise TypeError("Field has invalid lookup: %s" % lookup_type) 
     140        return FirebirdField 
     141 
     142    def query_set_class(this, DefaultQuerySet): 
     143        from django.db import connection 
     144        from django.db.models.query import EmptyResultSet, GET_ITERATOR_CHUNK_SIZE 
     145        class FirebirdQuerySet(DefaultQuerySet): 
     146            def _get_sql_clause(self): 
     147                from django.db.models.query import SortedDict, handle_legacy_orderlist, orderfield2column, fill_table_cache 
     148                qn = this.quote_name 
     149                opts = self.model._meta 
     150 
     151                # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z. 
     152                select = ["%s.%s" % (qn(opts.db_table), qn(f.column)) for f in opts.fields] 
     153                tables = [qn(t) for t in self._tables] 
     154                joins = SortedDict() 
     155                where = self._where[:] 
     156                params = self._params[:] 
     157 
     158                # Convert self._filters into SQL. 
     159                joins2, where2, params2 = self._filters.get_sql(opts) 
     160                joins.update(joins2) 
     161                where.extend(where2) 
     162                params.extend(params2) 
     163 
     164                # Add additional tables and WHERE clauses based on select_related. 
     165                if self._select_related: 
     166                    fill_table_cache(opts, select, tables, where, 
     167                                     old_prefix=opts.db_table, 
     168                                     cache_tables_seen=[opts.db_table], 
     169                                     max_depth=self._max_related_depth) 
     170                 
     171                # Add any additional SELECTs. 
     172                if self._select: 
     173                    select.extend([('(%s AS %s') % (qn(s[1]), qn(s[0])) for s in self._select.items()]) 
     174 
     175                # Start composing the body of the SQL statement. 
     176                sql = [" FROM", qn(opts.db_table)] 
     177 
     178                # Compose the join dictionary into SQL describing the joins. 
     179                if joins: 
     180                    sql.append(" ".join(["%s %s %s ON %s" % (join_type, table, alias, condition) 
     181                                    for (alias, (table, join_type, condition)) in joins.items()])) 
     182 
     183                # Compose the tables clause into SQL. 
     184                if tables: 
     185                    sql.append(", " + ", ".join(tables)) 
     186 
     187                # Compose the where clause into SQL. 
     188                if where:  
     189                    sql.append(where and "WHERE " + " AND ".join(where)) 
     190 
     191                # ORDER BY clause 
     192                order_by = [] 
     193                if self._order_by is not None: 
     194                    ordering_to_use = self._order_by 
     195                else: 
     196                    ordering_to_use = opts.ordering 
     197                for f in handle_legacy_orderlist(ordering_to_use): 
     198                    if f == '?': # Special case. 
     199                        order_by.append(connection.ops.random_function_sql()) 
     200                    else: 
     201                        if f.startswith('-'): 
     202                            col_name = f[1:] 
     203                            order = "DESC" 
     204                        else: 
     205                            col_name = f 
     206                            order = "ASC" 
     207                        if "." in col_name: 
     208                            table_prefix, col_name = col_name.split('.', 1) 
     209                            table_prefix = qn(table_prefix) + '.' 
     210                        else: 
     211                            # Use the database table as a column prefix if it wasn't given, 
     212                            # and if the requested column isn't a custom SELECT. 
     213                            if "." not in col_name and col_name not in (self._select or ()): 
     214                                table_prefix = qn(opts.db_table) + '.' 
     215                            else: 
     216                                table_prefix = '' 
     217                        order_by.append('%s%s %s' % \ 
     218                            (table_prefix, qn(orderfield2column(col_name, opts)), order)) 
     219                if order_by: 
     220                    sql.append("ORDER BY " + ", ".join(order_by)) 
     221 
     222                return select, " ".join(sql), params 
     223             
     224            def iterator(self): 
     225                "Performs the SELECT database lookup of this QuerySet." 
     226                from django.db.models.query import get_cached_row 
     227                try: 
     228                    select, sql, params = self._get_sql_clause() 
     229                except EmptyResultSet: 
     230                    raise StopIteration  
     231                     
     232                # self._select is a dictionary, and dictionaries' key order is 
     233                # undefined, so we convert it to a list of tuples. 
     234                extra_select = self._select.items() 
     235                 
     236                cursor = connection.cursor()  
     237                limit_offset_before = ""  
     238                if self._limit is not None:  
     239                    limit_offset_before += "FIRST %s " % self._limit  
     240                    if self._offset:  
     241                        limit_offset_before += "SKIP %s " % self._offset 
     242                else: 
     243                    assert self._offset is None, "'offset' is not allowed without 'limit'" 
     244                cursor.execute("SELECT " + limit_offset_before + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) 
     245                fill_cache = self._select_related 
     246                fields = self.model._meta.fields 
     247                index_end = len(fields) 
     248                while 1: 
     249                    rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE) 
     250                    if not rows: 
     251                        raise StopIteration 
     252                    for row in rows: 
     253                        row = self.resolve_columns(row, fields) 
     254                        if fill_cache: 
     255                            obj, index_end = get_cached_row(klass=self.model, row=row, 
     256                                                            index_start=0, max_depth=self._max_related_depth) 
     257                        else: 
     258                            obj = self.model(*row[:index_end]) 
     259                        for i, k in enumerate(extra_select): 
     260                            setattr(obj, k[0], row[index_end+i]) 
     261                        yield obj 
     262             
     263            def resolve_columns(self, row, fields=()): 
     264                from django.db.models.fields import DateField, DateTimeField, \ 
     265                    TimeField, BooleanField, NullBooleanField, DecimalField, Field 
     266                values = [] 
     267                for value, field in map(None, row, fields): 
     268                    #if value is None and isinstance(field, Field) and field.empty_strings_allowed: 
     269                    #    value = u'' 
     270                    # Convert 1 or 0 to True or False 
     271                    if value in (1, 0) and isinstance(field, (BooleanField, NullBooleanField)): 
     272                        value = bool(value) 
     273 
     274                    values.append(value) 
     275                return values 
     276                 
     277            def extra(self, select=None, where=None, params=None, tables=None): 
     278                assert self._limit is None and self._offset is None, \ 
     279                        "Cannot change a query once a slice has been taken" 
     280                clone = self._clone() 
     281                qn = this.quote_name 
     282                if select: clone._select.update(select) 
     283                if where: 
     284                    qn_where = [] 
     285                    for where_item in where: 
     286                        try: 
     287                            table, col_exact = where_item.split(".") 
     288                            col, value = col_exact.split("=") 
     289                            where_item = "%s.%s = %s" % (qn(table.strip()),  
     290                                qn(col.strip()), value.strip()) 
     291                        except: 
     292                            try: 
     293                                table, value = where_item.split("=") 
     294                                where_item = "%s = %s" % (qn(table.strip()), qn(value.strip())) 
     295                            except: 
     296                                raise TypeError, "Can't understand extra WHERE clause: %s" % where  
     297                        qn_where.append(where_item) 
     298                    clone._where.extend(qn_where) 
     299                if params: clone._params.extend(params) 
     300                if tables: clone._tables.extend(tables) 
     301                return clone 
     302                 
     303        return FirebirdQuerySet 
     304 
     305    def quote_name(self, name): 
     306        name = '"%s"' % util.truncate_name(name.strip('"'), self._max_name_length) 
     307        return name 
     308     
     309    def quote_id_plus_number(self, name): 
     310        try: 
     311            return '"%s" + %s' % tuple(s.strip() for s in name.strip('"').split('+')) 
     312        except: 
     313            return self.quote_name(name) 
     314             
     315    def pk_default_value(self): 
     316        """ 
     317        Returns the value to use during an INSERT statement to specify that 
     318        the field should use its default value. 
     319        """ 
     320        return 'NULL' 
     321     
     322    def field_cast_sql(self, db_type): 
     323        return '%s' 
     324     
     325    def last_insert_id(self, cursor, table_name, pk_name=None): 
     326        generator_name = self.get_generator_name(table_name) 
     327        cursor.execute('SELECT GEN_ID(%s, 0) from RDB$DATABASE' % generator_name) 
     328        return cursor.fetchone()[0] 
     329 
     330    def date_extract_sql(self, lookup_type, column_name): 
     331        # lookup_type is 'year', 'month', 'day' 
     332        return "EXTRACT(%s FROM %s)" % (lookup_type, column_name) 
     333 
     334    def date_trunc_sql(self, lookup_type, column_name): 
     335        if lookup_type == 'year': 
     336             sql = "EXTRACT(year FROM %s)||'-01-01 00:00:00'" % column_name 
     337        elif lookup_type == 'month': 
     338            sql = "EXTRACT(year FROM %s)||'-'||EXTRACT(month FROM %s)||'-01 00:00:00'" % (column_name, column_name) 
     339        elif lookup_type == 'day': 
     340            sql = "EXTRACT(year FROM %s)||'-'||EXTRACT(month FROM %s)||'-'||EXTRACT(day FROM %s)||' 00:00:00'" % (column_name, column_name, column_name) 
     341        return "CAST(%s AS TIMESTAMP)" % sql 
     342     
     343    def cascade_delete_update_sql(self): 
     344        #TODO: Use ON DELETE CASCADE only on M2M tables by default  
     345        return " ON DELETE CASCADE ON UPDATE CASCADE" 
     346     
     347    def datetime_cast_sql(self): 
     348        return None 
     349 
     350    def limit_offset_sql(self, limit, offset=None): 
     351        # limits are handled in custom FirebirdQuerySet  
     352        assert False, 'Limits are handled in a different way in Firebird' 
     353        return "" 
     354 
     355    def random_function_sql(self): 
     356        return "rand()" 
     357 
     358    def start_transaction_sql(self): 
     359        return "" 
     360 
     361    def sequence_reset_sql(self, style, model_list): 
     362        from django.db import models 
     363        output = [] 
     364        sql = ['%s %s %s' % (style.SQL_KEYWORD('CREATE OR ALTER PROCEDURE'), 
     365                             style.SQL_TABLE('"GENERATOR_RESET"'), 
     366                             style.SQL_KEYWORD('AS'))] 
     367        sql.append('%s %s' % (style.SQL_KEYWORD('DECLARE VARIABLE'), style.SQL_COLTYPE('start_val integer;'))) 
     368        sql.append('%s %s' % (style.SQL_KEYWORD('DECLARE VARIABLE'), style.SQL_COLTYPE('gen_val integer;'))) 
     369        sql.append('\t%s' % style.SQL_KEYWORD('BEGIN')) 
     370        sql.append('\t\t%s %s %s %s %s %s;' % (style.SQL_KEYWORD('SELECT MAX'), style.SQL_FIELD('(%(col)s)'), 
     371                                           style.SQL_KEYWORD('FROM'), style.SQL_TABLE('%(table)s'), 
     372                                           style.SQL_KEYWORD('INTO'), style.SQL_COLTYPE(':start_val'))) 
     373        sql.append('\t\t%s (%s %s) %s' % (style.SQL_KEYWORD('IF'), style.SQL_COLTYPE('start_val'), 
     374                                    style.SQL_KEYWORD('IS NULL'), style.SQL_KEYWORD('THEN'))) 
     375        sql.append('\t\t\t%s = %s(%s, 1 - %s(%s, 0));' %\ 
     376            (style.SQL_COLTYPE('gen_val'), style.SQL_KEYWORD('GEN_ID'), style.SQL_TABLE('%(gen)s'), 
     377             style.SQL_KEYWORD('GEN_ID'), style.SQL_TABLE('%(gen)s'))) 
     378        sql.append('\t\t%s' % style.SQL_KEYWORD('ELSE')) 
     379        sql.append('\t\t\t%s = %s(%s, %s - %s(%s, 0));' %\ 
     380            (style.SQL_COLTYPE('gen_val'), style.SQL_KEYWORD('GEN_ID'), 
     381             style.SQL_TABLE('%(gen)s'), style.SQL_COLTYPE('start_val'), style.SQL_KEYWORD('GEN_ID'), 
     382             style.SQL_TABLE('%(gen)s'))) 
     383        sql.append('\t\t%s;' % style.SQL_KEYWORD('EXIT')) 
     384        sql.append('%s;' % style.SQL_KEYWORD('END')) 
     385        sql ="\n".join(sql) 
     386        for model in model_list: 
     387            for f in model._meta.fields: 
     388                if isinstance(f, models.AutoField): 
     389                    generator_name = self.get_generator_name(model._meta.db_table) 
     390                    column_name = self.quote_name(f.db_column or f.name) 
     391                    table_name = self.quote_name(model._meta.db_table) 
     392                    output.append(sql % {'col' : column_name, 'table' : table_name, 'gen' : generator_name}) 
     393                    output.append('%s %s;' % (style.SQL_KEYWORD('EXECUTE PROCEDURE'),  
     394                                              style.SQL_TABLE('"GENERATOR_RESET"'))) 
     395                    break # Only one AutoField is allowed per model, so don't bother continuing. 
     396            for f in model._meta.many_to_many: 
     397                generator_name = self.get_generator_name(f.m2m_db_table()) 
     398                table_name = self.quote_name(f.m2m_db_table()) 
     399                column_name = '"id"' 
     400                output.append(sql % {'col' : column_name, 'table' : table_name, 'gen' : generator_name}) 
     401                output.append('%s %s;' % (style.SQL_KEYWORD('EXECUTE PROCEDURE'),  
     402                                          style.SQL_TABLE('"GENERATOR_RESET"'))) 
     403        return output 
     404     
     405    def sql_flush(self, style, tables, sequences): 
     406        if tables: 
     407            #TODO: Alter all tables witk FKs without ON DELETE CASCADE to set it 
     408            # Them reset to previous state when all are deleted 
     409            # Becasue we may not want to have ON DELETE CASCADE by default on all FK fields 
     410            sql = ['%s %s %s;' % \ 
     411                    (style.SQL_KEYWORD('DELETE'), 
     412                     style.SQL_KEYWORD('FROM'), 
     413                     style.SQL_FIELD(self.quote_name(table)) 
     414                     ) for table in tables] 
     415            for generator_info in sequences: 
     416                table_name = generator_info['table'] 
     417                query = "%s %s %s 0;" % (style.SQL_KEYWORD('SET GENERATOR'),  
     418                    style.SQL_TABLE(self.get_generator_name(table_name)), style.SQL_KEYWORD('TO')) 
     419                sql.append(query) 
     420            return sql 
     421        else: 
     422            return [] 
     423 
     424    def fulltext_search_sql(self, field_name): 
     425        # We use varchar for TextFields so this is possible 
     426        # Look at http://www.volny.cz/iprenosil/interbase/ip_ib_strings.htm 
     427        return '%%s CONTAINING %s' % self.quote_name(field_name) 
     428         
     429    def drop_sequence_sql(self, table): 
     430        return "DROP GENERATOR %s;" % self.get_generator_name(table) 
     431         
     432class FirebirdCursorWrapper(object): 
     433    """ 
     434    Django uses "format" ('%s') style placeholders, but firebird uses "qmark" ('?') style. 
     435    This fixes it -- but note that if you want to use a literal "%s" in a query, 
     436    you'll need to use "%%s". 
     437     
     438    We also do all automatic type conversions here. 
     439    """ 
     440    import kinterbasdb.typeconv_datetime_stdlib as tc_dt 
     441    import kinterbasdb.typeconv_fixed_decimal as tc_fd 
     442    import kinterbasdb.typeconv_text_unicode as tc_tu 
     443    import django.utils.encoding as dj_ue 
     444     
     445    
     446    def timestamp_conv_in(self, timestamp): 
     447        if isinstance(timestamp, basestring): 
     448            #Replaces 6 digits microseconds to 4 digits allowed in Firebird 
     449            timestamp = timestamp[:24] 
     450        return self.tc_dt.timestamp_conv_in(timestamp) 
     451 
     452    def time_conv_in(self, value): 
     453        import datetime 
     454        if isinstance(value, datetime.datetime): 
     455            value = datetime.time(value.hour, value.minute, value.second, value.microsecond)        
     456        return self.tc_dt.time_conv_in(value)  
     457     
     458    def ascii_conv_in(self, text): 
     459        if text is not None:   
     460            return self.dj_ue.smart_str(text, 'ascii') 
     461     
     462    def ascii_conv_out(self, text): 
     463        if text is not None: 
     464            return self.dj_ue.smart_unicode(text) 
     465     
     466    def fixed_conv_in(self, (val, scale)): 
     467        if val is not None: 
     468            if isinstance(val, basestring): 
     469                val = decimal.Decimal(val) 
     470            return self.tc_fd.fixed_conv_in_precise((val, scale)) 
     471 
     472    def unicode_conv_in(self, text): 
     473        if text[0] is not None: 
     474            return self.tc_tu.unicode_conv_in((self.dj_ue.smart_unicode(text[0]), self.FB_CHARSET_CODE)) 
     475 
     476    def blob_conv_in(self, text):  
     477        return self.tc_tu.unicode_conv_in((self.dj_ue.smart_unicode(text), self.FB_CHARSET_CODE)) 
     478 
     479    def blob_conv_out(self, text): 
     480        return self.tc_tu.unicode_conv_out((text, self.FB_CHARSET_CODE)) 
     481         
     482    def __init__(self, cursor, connection):    
     483        self.cursor = cursor 
     484        self._connection = connection 
     485        self._statement = None #prepared statement 
     486        self.FB_CHARSET_CODE = 3 #UNICODE_FSS 
     487        if connection.charset == 'UTF8': 
     488            self.FB_CHARSET_CODE = 4 # UTF-8 with Firebird 2.0+ 
     489        self.cursor.set_type_trans_in({ 
     490            'DATE':             self.tc_dt.date_conv_in, 
     491            'TIME':             self.time_conv_in, 
     492            'TIMESTAMP':        self.timestamp_conv_in, 
     493            'FIXED':            self.fixed_conv_in, 
     494            'TEXT':             self.ascii_conv_in, 
     495            'TEXT_UNICODE':     self.unicode_conv_in, 
     496            'BLOB':             self.blob_conv_in 
     497        }) 
     498        self.cursor.set_type_trans_out({ 
     499            'DATE':             self.tc_dt.date_conv_out, 
     500            'TIME':             self.tc_dt.time_conv_out, 
     501            'TIMESTAMP':        self.tc_dt.timestamp_conv_out, 
     502            'FIXED':            self.tc_fd.fixed_conv_out_precise, 
     503            'TEXT':             self.ascii_conv_out, 
     504            'TEXT_UNICODE':     self.tc_tu.unicode_conv_out, 
     505            'BLOB':             self.blob_conv_out 
     506        }) 
     507     
     508    def execute_immediate(self, query, params=()): 
     509        query = query % tuple(params) 
     510        self._connection.execute_immediate(query) 
     511     
     512    # Prepared Statement  
     513    # http://kinterbasdb.sourceforge.net/dist_docs/usage.html#adv_prepared_statements 
     514    def prepare(self, query): 
     515        query.replace("%s", "?") 
     516        return self.cursor.prep(query) 
     517     
     518    def execute_prepared(self, statement, params): 
     519        return self.cursor.execute(statement, params) 
     520     
     521    def execute_straight(self, query, params=()): 
     522        # Use kinterbasdb style with '?' instead of '%s' 
     523        return self.cursor.execute(query, params) 
     524     
     525    def execute(self, query, params=()): 
     526        cquery = self.convert_query(query, len(params)) 
     527        if self._get_query() != cquery: 
     528            try: 
     529                self._statement = self.cursor.prep(cquery) 
     530            except Database.ProgrammingError, e: 
     531                output = ["Prepare query error."] 
     532                output.extend(str(e).split("'")[1].split('\\n')) 
     533                output.append("Query:") 
     534                output.append(cquery) 
     535                raise Database.ProgrammingError, "\n".join(output) 
     536        try: 
     537            return self.cursor.execute(self._statement, params) 
     538        except Database.ProgrammingError, e: 
     539            err_no = int(str(e).split()[0].strip(',()')) 
     540            output = ["Execute query error. FB error No. %i" % err_no] 
     541            output.extend(str(e).split("'")[1].split('\\n')) 
     542            output.append("Query:") 
     543            output.append(cquery) 
     544            output.append("Parameters:") 
     545            output.append(str(params)) 
     546            raise Database.ProgrammingError, "\n".join(output) 
     547     
     548    def executemany(self, query, param_list): 
     549        try: 
     550            cquery = self.convert_query(query, len(param_list[0])) 
     551        except IndexError: 
     552            return None 
     553        if self._get_query() != cquery: 
     554            self._statement = self.cursor.prep(cquery) 
     555        return self.cursor.executemany(self._statement, param_list) 
     556 
     557    def convert_query(self, query, num_params): 
     558        try: 
     559            return query % tuple("?" * num_params) 
     560        except TypeError, e: 
     561            print query, num_params 
     562            raise TypeError, e 
     563     
     564    def _get_query(self): 
     565        if self._statement: 
     566            return self._statement.sql 
     567     
     568    def __getattr__(self, attr): 
     569        if attr in self.__dict__: 
     570            return self.__dict__[attr] 
     571        else: 
     572            return getattr(self.cursor, attr) 
     573 
     574class DatabaseWrapper(BaseDatabaseWrapper): 
     575    features = DatabaseFeatures() 
     576    ops = DatabaseOperations() 
     577    operators = { 
     578        'exact': '= %s', 
     579        'iexact': '= UPPER(%s)', 
     580        'contains': "LIKE %s ESCAPE'\\'", 
     581        'icontains': 'CONTAINING %s', #case is ignored 
     582        'gt': '> %s', 
     583        'gte': '>= %s', 
     584        'lt': '< %s', 
     585        'lte': '<= %s', 
     586        'startswith': 'STARTING WITH %s', #looks to be faster then LIKE 
     587        'endswith': "LIKE %s ESCAPE'\\'", 
     588        'istartswith': 'STARTING WITH UPPER(%s)', 
     589        'iendswith': "LIKE UPPER(%s) ESCAPE'\\'" 
     590    } 
     591    
     592    def __init__(self, **kwargs): 
     593        from django.conf import settings 
     594        super(DatabaseWrapper, self).__init__(**kwargs) 
     595        self. _current_cursor = None 
     596        self._raw_cursor = None 
     597        self.charset = 'UNICODE_FSS' 
     598        self.FB_MAX_VARCHAR = 10921 #32765 MAX /3 
     599        self.BYTES_PER_DEFAULT_CHAR = 3 
     600        if hasattr(settings, 'FIREBIRD_CHARSET'): 
     601            if settings.FIREBIRD_CHARSET == 'UTF8': 
     602                self.charset = 'UTF8'  
     603                self.FB_MAX_VARCHAR = 8191 #32765 MAX /4 
     604                self.BYTES_PER_DEFAULT_CHAR = 4 
     605         
     606    def _connect(self, settings): 
     607        if settings.DATABASE_NAME == '': 
     608            from django.core.exceptions import ImproperlyConfigured 
     609            raise ImproperlyConfigured, "You need to specify DATABASE_NAME in your Django settings file." 
     610        kwargs = {'charset' : self.charset } 
     611        if settings.DATABASE_HOST: 
     612            kwargs['dsn'] = "%s:%s" % (settings.DATABASE_HOST, settings.DATABASE_NAME) 
     613        else: 
     614            kwargs['dsn'] = "localhost:%s" % settings.DATABASE_NAME 
     615        if settings.DATABASE_USER: 
     616            kwargs['user'] = settings.DATABASE_USER 
     617        if settings.DATABASE_PASSWORD: 
     618            kwargs['password'] = settings.DATABASE_PASSWORD 
     619        self.connection = Database.connect(**kwargs) 
     620        assert self.connection.charset == self.charset 
     621