Django

Code

Ticket #1261: firebird-svn6654.diff

File firebird-svn6654.diff, 54.3 kB (added by i_i, 1 year ago)

a lot of fixes and improvements for firebird backend

  • 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  
    148148        data_types = get_creation_module().DATA_TYPES 
    149149        internal_type = self.get_internal_type() 
    150150        if internal_type not in data_types: 
    151             return None 
     151            return None     
    152152        return data_types[internal_type] % self.__dict__ 
    153  
     153     
     154    def db_type_check(self): 
     155        creation_module = get_creation_module()  
     156        if hasattr(creation_module, 'DATA_TYPE_CHECKS'): 
     157            internal_type = self.get_internal_type() 
     158            data_checks = creation_module.DATA_TYPE_CHECKS 
     159            if internal_type in data_checks: 
     160                return data_checks[internal_type] % self.__dict__ 
     161        return None 
     162     
    154163    def validate_full(self, field_data, all_data): 
    155164        """ 
    156165        Returns a list of errors for this field. This is the main interface, 
     
    208217        return value 
    209218 
    210219    def get_db_prep_lookup(self, lookup_type, value): 
     220        from django.db import connection 
    211221        "Returns field's value prepared for database lookup." 
    212222        if lookup_type in ('exact', 'regex', 'iregex', 'gt', 'gte', 'lt', 'lte', 'month', 'day', 'search'): 
    213223            return [value] 
    214224        elif lookup_type in ('range', 'in'): 
    215225            return value 
    216226        elif lookup_type in ('contains', 'icontains'): 
     227            if connection.features.uses_custom_icontains and lookup_type == 'icontains': 
     228                return [value]     
    217229            return ["%%%s%%" % prep_for_like_query(value)] 
    218230        elif lookup_type == 'iexact': 
    219231            return [prep_for_like_query(value)] 
    220232        elif lookup_type in ('startswith', 'istartswith'): 
     233            if connection.features.uses_custom_startswith: 
     234                return [value] 
    221235            return ["%s%%" % prep_for_like_query(value)] 
    222236        elif lookup_type in ('endswith', 'iendswith'): 
    223237            return ["%%%s" % prep_for_like_query(value)] 
     
    589603            # doesn't support microseconds. 
    590604            if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'): 
    591605                value = value.replace(microsecond=0) 
    592             value = smart_unicode(value) 
     606            #Firebird supports native datetime 
     607            if settings.DATABASE_ENGINE != 'firebird': 
     608                value = smart_unicode(value) 
    593609        return Field.get_db_prep_save(self, value) 
    594610 
    595611    def get_db_prep_lookup(self, lookup_type, value): 
     
    9971013            # doesn't support microseconds. 
    9981014            if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'): 
    9991015                value = value.replace(microsecond=0) 
    1000             if settings.DATABASE_ENGINE == 'oracle'
    1001                 # cx_Oracle expects a datetime.datetime to persist into TIMESTAMP field. 
     1016            if settings.DATABASE_ENGINE in ('oracle', 'firebird')
     1017                # cx_Oracle and kinterbasdb expect a datetime.datetime to persist into TIMESTAMP field. 
    10021018                if isinstance(value, datetime.time): 
    10031019                    value = datetime.datetime(1900, 1, 1, value.hour, value.minute, 
    10041020                                              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): 
    343344                    cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \ 
    344                         (self.join_table, source_col_name, target_col_name), 
     345                        (qn(self.join_table), qn(source_col_name), qn(target_col_name)), 
    345346                        [self._pk_val, obj_id]) 
    346347                transaction.commit_unless_managed() 
    347348 
  • django/db/models/query.py

    old new  
    11111111            # Last query term was a normal field. 
    11121112            column = field.column 
    11131113            db_type = field.db_type() 
    1114  
     1114        if settings.DATABASE_ENGINE == 'firebird': 
     1115            current_table = qn(current_table) 
     1116            column = qn(column)    
    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  
    4545    autoindexes_primary_keys = True 
    4646    inline_fk_references = True 
    4747    needs_datetime_string_cast = True 
     48    needs_default_null = False 
    4849    needs_upper_for_iops = False 
    4950    supports_constraints = True 
    5051    supports_tablespaces = False 
     52    quote_autofields = False 
    5153    uses_case_insensitive_names = False 
     54    uses_custom_icontains = False 
     55    uses_custom_startswith = False 
    5256    uses_custom_queryset = False 
    5357 
    5458class BaseDatabaseOperations(object): 
     
    6569        This SQL is executed when a table is created. 
    6670        """ 
    6771        return None 
    68  
     72     
     73    def cascade_delete_update_sql(self): 
     74        """ 
     75        Returns the SQL necessary to make a cascading deletes and updates 
     76        of foreign key references during a CREATE TABLE statement. 
     77        """ 
     78        return '' 
     79     
    6980    def date_extract_sql(self, lookup_type, field_name): 
    7081        """ 
    7182        Given a lookup_type of 'year', 'month' or 'day', returns the SQL 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 
     13 
     14try: 
     15    import kinterbasdb as Database 
     16except ImportError, e: 
     17    from django.core.exceptions import ImproperlyConfigured 
     18    raise ImproperlyConfigured, "Error loading KInterbasDB module: %s" % e 
     19 
     20DatabaseError = Database.DatabaseError 
     21IntegrityError = Database.IntegrityError 
     22 
     23class DatabaseFeatures(BaseDatabaseFeatures): 
     24    needs_datetime_string_cast = False 
     25    needs_default_null = True 
     26    needs_upper_for_iops = True 
     27    quote_autofields = True 
     28    supports_constraints = False #some tests went strange without it 
     29    uses_custom_icontains = True #CONTAINING <value> op instead of LIKE %<value>% 
     30    uses_custom_startswith = True #STARTING WITH op. Faster than LIKE 
     31    uses_custom_queryset = True 
     32 
     33class DatabaseOperations(BaseDatabaseOperations): 
     34    _max_name_length = 27 
     35    def __init__(self): 
     36        self._firebird_version = None 
     37        self._page_size = None 
     38     
     39    def get_generator_name(self, name): 
     40        return '%s_G' % util.truncate_name(name, self._max_name_length-2).upper() 
     41         
     42    def get_trigger_name(self, name): 
     43        return '%s_T' % util.truncate_name(name, self._max_name_length-2).upper()  
     44     
     45    def _get_firebird_version(self): 
     46        if self._firebird_version is None: 
     47            from django.db import connection 
     48            self._firebird_version = [int(val) for val in connection.server_version.split()[-1].split('.')] 
     49        return self._firebird_version 
     50    firebird_version = property(_get_firebird_version) 
     51   
     52    def _get_page_size(self): 
     53        if self._page_size is None: 
     54            from django.db import connection 
     55            self._page_size = connection.database_info(Database.isc_info_page_size, 'i') 
     56            return self._page_size 
     57    page_size = property(_get_page_size) 
     58     
     59    def _get_index_limit(self): 
     60        if self.firebird_version[0] < 2: 
     61            self._index_limit = 252  
     62        else: 
     63            page_size = self.page_size 
     64            self._index_limit = page_size/4 
     65        return self._index_limit 
     66    index_limit = property(_get_index_limit) 
     67     
     68    def _autoinc_sql_with_style(self, style, table_name, column_name): 
     69        """ 
     70        To simulate auto-incrementing primary keys in Firebird, we have to 
     71        create a generator and a trigger. 
     72     
     73        Create the generators and triggers names based only on table name 
     74        since django only support one auto field per model 
     75        """ 
     76         
     77        KWD = style.SQL_KEYWORD 
     78        TBL = style.SQL_TABLE 
     79        FLD = style.SQL_FIELD 
     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;" % ( KWD('CREATE GENERATOR'),  
     87                                     TBL(generator_name))       
     88        trigger_sql = "\n".join([ 
     89            "%s %s %s %s" % ( \ 
     90            KWD('CREATE TRIGGER'), TBL(trigger_name), KWD('FOR'), 
     91            TBL(table_name)), 
     92            "%s 0 %s" % (KWD('ACTIVE BEFORE INSERT POSITION'), KWD('AS')), 
     93            KWD('BEGIN'),  
     94            "  %s ((%s.%s %s) %s (%s.%s = 0)) %s" % ( \ 
     95                KWD('IF'), 
     96                KWD('NEW'), FLD(column_name), KWD('IS NULL'), 
     97                KWD('OR'), KWD('NEW'), FLD(column_name), 
     98                KWD('THEN') 
     99            ), 
     100            "  %s" % KWD('BEGIN'),  
     101            "    %s.%s = %s(%s, 1);" % ( \ 
     102                KWD('NEW'), FLD(column_name), 
     103                KWD('GEN_ID'), TBL(generator_name) 
     104            ), 
     105            "  %s" % KWD('END'), 
     106            KWD('END') 
     107            ]) 
     108        return (generator_sql, trigger_sql) 
     109     
     110    def autoinc_sql(self, table_name, column_name): 
     111        # style argument disappeared, so we'll just import django's dummy 
     112        from django.core.management.color import no_style, color_style 
     113        return self._autoinc_sql_with_style(no_style(), table_name, column_name) 
     114 
     115    def max_name_length(self): 
     116        return self._max_name_length 
     117 
     118    def query_set_class(this, DefaultQuerySet): 
     119        from django.db import connection 
     120        from django.db.models.query import EmptyResultSet, GET_ITERATOR_CHUNK_SIZE, quote_only_if_word 
     121 
     122        class FirebirdQuerySet(DefaultQuerySet): 
     123            def _get_sql_clause(self): 
     124                from django.db.models.query import SortedDict, handle_legacy_orderlist, orderfield2column, fill_table_cache 
     125                qn = connection.ops.quote_name 
     126                opts = self.model._meta 
     127 
     128                # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z. 
     129                select = ["%s.%s" % (qn(opts.db_table), qn(f.column)) for f in opts.fields] 
     130                tables = [qn(t) for t in self._tables] 
     131                joins = SortedDict() 
     132                where = self._where[:] 
     133                params = self._params[:] 
     134 
     135                # Convert self._filters into SQL. 
     136                joins2, where2, params2 = self._filters.get_sql(opts) 
     137                joins.update(joins2) 
     138                where.extend(where2) 
     139                params.extend(params2) 
     140 
     141                # Add additional tables and WHERE clauses based on select_related. 
     142                if self._select_related: 
     143                    fill_table_cache(opts, select, tables, where, 
     144                                     old_prefix=opts.db_table, 
     145                                     cache_tables_seen=[opts.db_table], 
     146                                     max_depth=self._max_related_depth) 
     147                 
     148                # Add any additional SELECTs. 
     149                if self._select: 
     150                    select.extend([('(%s AS %s') % (qn(s[1]), qn(s[0])) for s in self._select.items()]) 
     151 
     152                # Start composing the body of the SQL statement. 
     153                sql = [" FROM", qn(opts.db_table)] 
     154 
     155                # Compose the join dictionary into SQL describing the joins. 
     156                if joins: 
     157                    sql.append(" ".join(["%s %s %s ON %s" % (join_type, table, alias, condition) 
     158                                    for (alias, (table, join_type, condition)) in joins.items()])) 
     159 
     160                # Compose the tables clause into SQL. 
     161                if tables: 
     162                    sql.append(", " + ", ".join(tables)) 
     163 
     164                # Compose the where clause into SQL. 
     165                if where: 
     166                    sql.append(where and "WHERE " + " AND ".join(where)) 
     167 
     168                # ORDER BY clause 
     169                order_by = [] 
     170                if self._order_by is not None: 
     171                    ordering_to_use = self._order_by 
     172                else: 
     173                    ordering_to_use = opts.ordering 
     174                for f in handle_legacy_orderlist(ordering_to_use): 
     175                    if f == '?': # Special case. 
     176                        order_by.append(connection.ops.random_function_sql()) 
     177                    else: 
     178                        if f.startswith('-'): 
     179                            col_name = f[1:] 
     180                            order = "DESC" 
     181                        else: 
     182                            col_name = f 
     183                            order = "ASC" 
     184                        if "." in col_name: 
     185                            table_prefix, col_name = col_name.split('.', 1) 
     186                            table_prefix = qn(table_prefix) + '.' 
     187                        else: 
     188                            # Use the database table as a column prefix if it wasn't given, 
     189                            # and if the requested column isn't a custom SELECT. 
     190                            if "." not in col_name and col_name not in (self._select or ()): 
     191                                table_prefix = qn(opts.db_table) + '.' 
     192                            else: 
     193                                table_prefix = '' 
     194                        order_by.append('%s%s %s' % (table_prefix, qn(orderfield2column(col_name, opts)), order)) 
     195                if order_by: 
     196                    sql.append("ORDER BY " + ", ".join(order_by)) 
     197 
     198                return select, " ".join(sql), params 
     199             
     200            def iterator(self): 
     201                "Performs the SELECT database lookup of this QuerySet." 
     202                from django.db.models.query import get_cached_row 
     203                 
     204                try: 
     205                    select, sql, params = self._get_sql_clause() 
     206                except EmptyResultSet: 
     207                    raise StopIteration  
     208                     
     209                # self._select is a dictionary, and dictionaries' key order is 
     210                # undefined, so we convert it to a list of tuples. 
     211                extra_select = self._select.items() 
     212                 
     213                cursor = connection.cursor()  
     214                limit_offset_before = ""  
     215                if self._limit is not None:  
     216                    limit_offset_before += "FIRST %s " % self._limit  
     217                    if self._offset:  
     218                        limit_offset_before += "SKIP %s " % self._offset 
     219                else: 
     220                    assert self._offset is None, "'offset' is not allowed without 'limit'" 
     221                cursor.execute("SELECT " + limit_offset_before + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) 
     222                fill_cache = self._select_related 
     223                fields = self.model._meta.fields 
     224                index_end = len(fields) 
     225                while 1: 
     226                    rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE) 
     227                    if not rows: 
     228                        raise StopIteration 
     229                    for row in rows: 
     230                        row = self.resolve_columns(row, fields) 
     231                        if fill_cache: 
     232                            obj, index_end = get_cached_row(klass=self.model, row=row, 
     233                                                            index_start=0, max_depth=self._max_related_depth) 
     234                        else: 
     235                            obj = self.model(*row[:index_end]) 
     236                        for i, k in enumerate(extra_select): 
     237                            setattr(obj, k[0], row[index_end+i]) 
     238                        yield obj 
     239             
     240            def resolve_columns(self, row, fields=()): 
     241                from django.db.models.fields import DateField, DateTimeField, \ 
     242                    TimeField, BooleanField, NullBooleanField, DecimalField, Field 
     243                values = [] 
     244                for value, field in map(None, row, fields): 
     245                    #if value is None and isinstance(field, Field) and field.empty_strings_allowed: 
     246                    #    value = u'' 
     247                    # Convert 1 or 0 to True or False 
     248                    if value in (1, 0) and isinstance(field, (BooleanField, NullBooleanField)): 
     249                        value = bool(value) 
     250 
     251                    values.append(value) 
     252                return values 
     253         
     254        return FirebirdQuerySet 
     255 
     256    def quote_name(self, name): 
     257        # Trancate and quote once. No need for uppercase since 
     258        # we quote autofields too 
     259        if not name.startswith('"') and not name.endswith('"'): 
     260            name = '"%s"' % util.truncate_name(name, self._max_name_length) 
     261        return name 
     262     
     263    def field_cast_sql(self, db_type): 
     264        return '%s' 
     265     
     266    def last_insert_id(self, cursor, table_name, pk_name): 
     267        generator_name = self.get_generator_name(table_name) 
     268        cursor.execute('SELECT GEN_ID(%s, 0) from RDB$DATABASE' % generator_name) 
     269        return cursor.fetchone()[0] 
     270 
     271    def date_extract_sql(self, lookup_type, column_name): 
     272        # lookup_type is 'year', 'month', 'day' 
     273        return "EXTRACT(%s FROM %s)" % (lookup_type, column_name) 
     274 
     275    def date_trunc_sql(self, lookup_type, column_name): 
     276        if lookup_type == 'year': 
     277             sql = "EXTRACT(year FROM %s)||'-01-01 00:00:00'" % column_name 
     278        elif lookup_type == 'month': 
     279            sql = "EXTRACT(year FROM %s)||'-'||EXTRACT(month FROM %s)||'-01 00:00:00'" % (column_name, column_name) 
     280        elif lookup_type == 'day': 
     281            sql = "EXTRACT(year FROM %s)||'-'||EXTRACT(month FROM %s)||'-'||EXTRACT(day FROM %s)||' 00:00:00'" % (column_name, column_name, column_name) 
     282        return "CAST(%s AS TIMESTAMP)" % sql 
     283     
     284    def cascade_delete_update_sql(self): 
     285        # Solves FK problems with sql_flush 
     286        return " ON DELETE CASCADE ON UPDATE CASCADE" 
     287     
     288    def datetime_cast_sql(self): 
     289        return None 
     290 
     291    def limit_offset_sql(self, limit, offset=None): 
     292        # limits are handled in custom FirebirdQuerySet  
     293        assert False, 'Limits are handled in a different way in Firebird' 
     294        return "" 
     295 
     296    def random_function_sql(self): 
     297        return "rand()" 
     298 
     299    def pk_default_value(self): 
     300        return "NULL" 
     301     
     302    def start_transaction_sql(self): 
     303        return "START TRANSACTION;" 
     304 
     305    def sequence_reset_sql(self, style, model_list): 
     306        from django.db import models 
     307        output = [] 
     308        sql = """\ 
     309CREATE OR ALTER PROCEDURE "GENERATOR_RESET" AS 
     310        DECLARE VARIABLE start_val integer; 
     311        DECLARE VARIABLE gen_val integer; 
     312        BEGIN 
     313            SELECT MAX(%(col)s) FROM %(table)s INTO :start_val; 
     314            IF (start_val IS NULL) THEN 
     315                gen_val = GEN_ID(%(gen)s, 1 - GEN_ID(%(gen)s, 0)); 
     316            ELSE 
     317                gen_val = GEN_ID(%(gen)s, start_val - GEN_ID(%(gen)s, 0)); 
     318            EXIT; 
     319        END;""" 
     320        for model in model_list: 
     321            for f in model._meta.fields: 
     322                if isinstance(f, models.AutoField): 
     323                    generator_name = self.get_generator_name(model._meta.db_table) 
     324                    column_name = self.quote_name(f.db_column or f.name) 
     325                    table_name = self.quote_name(model._meta.db_table) 
     326                    output.append(sql % {'col' : column_name, 'table' : table_name, 'gen' : generator_name}) 
     327                    output.append('EXECUTE PROCEDURE "GENERATOR_RESET";') 
     328                    break # Only one AutoField is allowed per model, so don't bother continuing. 
     329            for f in model._meta.many_to_many: 
     330                generator_name = self.get_generator_name(f.m2m_db_table()) 
     331                table_name = self.quote_name(f.m2m_db_table()) 
     332                column_name = '"id"' 
     333                output.append(sql % {'col' : column_name, 'table' : table_name, 'gen' : generator_name}) 
     334                output.append('EXECUTE PROCEDURE "GENERATOR_RESET";') 
     335        return output 
     336     
     337    def sql_flush(self, style, tables, sequences): 
     338        if tables: 
     339            # FK constraints gave us a lot of trouble with default values 
     340            # that was a reason behind very ugly and dangerous code here 
     341            # Solved with "ON DELETE CASCADE" with all FK references         
     342            sql = ['%s %s %s;' % \ 
     343                    (style.SQL_KEYWORD('DELETE'), 
     344                     style.SQL_KEYWORD('FROM'), 
     345                     style.SQL_FIELD(self.quote_name(table)) 
     346                     ) for table in tables] 
     347            for generator_info in sequences: 
     348                table_name = generator_info['table'] 
     349                query = "SET GENERATOR %s TO 0;" % self.get_generator_name(table_name) 
     350                sql.append(query) 
     351            return sql 
     352        else: 
     353            return [] 
     354 
     355#    def fulltext_search_sql(self, field_name): 
     356#        return field_name + ' CONTAINING %s' 
     357         
     358    def drop_sequence_sql(self, table): 
     359        return "DROP GENERATOR %s;" % self.get_generator_name(table) 
     360         
     361    def last_executed_query(self, cursor, sql, params): 
     362        """ 
     363        Returns a string of the query last executed by the given cursor, with 
     364        placeholders replaced with actual values. 
     365 
     366        `sql` is the raw query containing placeholders, and `params` is the 
     367        sequence of parameters. These are used by default, but this method 
     368        exists for database backends to provide a better implementation 
     369        according to their own quoting schemes. 
     370        """ 
     371        from django.utils.encoding import smart_unicode, force_unicode 
     372 
     373        # Convert params to contain Unicode values. 
     374        to_unicode = lambda s: force_unicode(s, strings_only=True) 
     375        if isinstance(params, (list, tuple)): 
     376            u_params = tuple([to_unicode(val) for val in params]) 
     377        else: 
     378            u_params = dict([(to_unicode(k), to_unicode(v)) for k, v in params.items()]) 
     379        try: 
     380            #Extracts sql right from KInterbasDB's prepared statement 
     381            return smart_unicode(cursor.query) % u_params 
     382        except TypeError: 
     383            return smart_unicode(sql) % u_params 
     384 
     385class FirebirdCursorWrapper(object): 
     386    """ 
     387    Django uses "format" ('%s') style placeholders, but firebird uses "qmark" ('?') style. 
     388    This fixes it -- but note that if you want to use a literal "%s" in a query, 
     389    you'll need to use "%%s". 
     390     
     391    We also do all automatic type conversions here. 
     392    """ 
     393    import kinterbasdb.typeconv_datetime_stdlib as tc_dt 
     394    import kinterbasdb.typeconv_fixed_decimal as tc_fd 
     395    import kinterbasdb.typeconv_text_unicode as tc_tu 
     396    import django.utils.encoding as dj_ue 
     397    
     398    def timestamp_conv_in(self, timestamp): 
     399        if isinstance(timestamp, basestring): 
     400            #Replaces 6 digits microseconds to 4 digits allowed in Firebird 
     401            timestamp = timestamp[:24] 
     402        return self.tc_dt.timestamp_conv_in(timestamp) 
     403     
     404    def time_conv_in(self, value): 
     405        import datetime 
     406        if isinstance(value, datetime.datetime): 
     407            value = datetime.time(value.hour, value.minute, value.second, value.microsecond)        
     408        return self.tc_dt.time_conv_in(value)  
     409     
     410    def ascii_conv_in(self, text):   
     411        return self.dj_ue.smart_str(text, 'ascii') 
     412 
     413    def unicode_conv_in(self, text): 
     414        return self.tc_tu.unicode_conv_in((self.dj_ue.force_unicode(text[0]), self.FB_CHARSET_CODE)) 
     415 
     416    def blob_conv_in(self, text):  
     417        return self.tc_tu.unicode_conv_in((self.dj_ue.force_unicode(text), self.FB_CHARSET_CODE)) 
     418 
     419    def blob_conv_out(self, text): 
     420        return self.tc_tu.unicode_conv_out((text, self.FB_CHARSET_CODE)) 
     421         
     422    def __init__(self, cursor): 
     423        from django.conf import settings 
     424        self.FB_CHARSET_CODE = 3 #UNICODE_FSS 
     425        if hasattr(settings, 'FIREBIRD_CHARSET'): 
     426            if settings.FIREBIRD_CHARSET == 'UTF8': 
     427                self.FB_CHARSET_CODE = 4 # UTF-8 with Firebird 2.0+     
     428        self.cursor = cursor 
     429         
     430        # Prepared Statement  
     431        # http://kinterbasdb.sourceforge.net/dist_docs/usage.html#adv_prepared_statements 
     432        # Need to decide wether they are useful or not 
     433        # Maybe add prepare, execute_prep and executemany_pep methods here 
     434        # and rewrite QuerySet to take advantage of them? 
     435        # Could speed the things up 
     436        self._statement = None 
     437        self.cursor.set_type_trans_in({ 
     438            'DATE':             self.tc_dt.date_conv_in, 
     439            'TIME':             self.time_conv_in, 
     440            'TIMESTAMP':        self.timestamp_conv_in, 
     441            'FIXED':            self.tc_fd.fixed_conv_in_imprecise, 
     442            'TEXT':             self.ascii_conv_in, 
     443            'TEXT_UNICODE':     self.unicode_conv_in, 
     444            'BLOB':             self.blob_conv_in 
     445        }) 
     446        self.cursor.set_type_trans_out({ 
     447            'DATE':             self.tc_dt.date_conv_out, 
     448            'TIME':             self.tc_dt.time_conv_out, 
     449            'TIMESTAMP':        self.tc_dt.timestamp_conv_out, 
     450            'FIXED':            self.tc_fd.fixed_conv_out_imprecise, 
     451            'TEXT':             self.dj_ue.force_unicode, 
     452            'TEXT_UNICODE':     self.tc_tu.unicode_conv_out, 
     453            'BLOB':             self.blob_conv_out 
     454        }) 
     455     
     456    def _get_query(self): 
     457        if self._statement: 
     458            return self._statement.sql 
     459    def _get_statement(self): 
     460        if self._statement: 
     461            return self._statement 
     462    query = property(_get_query) 
     463    statement = property(_get_statement) 
     464         
     465    def execute(self, query, params=()): 
     466        cquery = self.convert_query(query, len(params)) 
     467        if self._get_query() != cquery: 
     468            try: 
     469                self._statement = self.cursor.prep(cquery) 
     470            except Database.ProgrammingError, e: 
     471                output = ["Error preparing query."] 
     472                output.extend(str(e).split(', ')[1].strip("'").split('\\n')) 
     473                output.append("\nThe problem query was:") 
     474                output.append(query % params) 
     475                raise DatabaseError, "\n".join(output) 
     476        try: 
     477            return self.cursor.execute(self._statement, params) 
     478        except Database.ProgrammingError, e: 
     479            err_no = int(str(e).split()[0].strip(',()')) 
     480            output = ["Error executing query. FB error No. %i" % err_no] 
     481            output.extend(str(e).split(', ')[1].strip("'").split('\\n')) 
     482            #e.append(err_no) 
     483            output.append("\nThe problem query was:") 
     484            output.append(query % params) 
     485            raise DatabaseError, "\n".join(output) 
     486     
     487    def executemany(self, query, param_list): 
     488        try: 
     489            cquery = self.convert_query(query, len(param_list[0])) 
     490        except IndexError: 
     491            return None 
     492        if self._get_query() != cquery: 
     493            self._statement = self.cursor.prep(cquery) 
     494        return self.cursor.executemany(self._statement, param_list) 
     495 
     496    def convert_query(self, query, num_params): 
     497        try: 
     498            return query % tuple("?" * num_params) 
     499        except TypeError, e: 
     500            print query, num_params 
     501            raise TypeError, e 
     502     
     503    def __getattr__(self, attr): 
     504        if attr in self.__dict__: 
     505            return self.__dict__[attr] 
     506        else: 
     507            return getattr(self.cursor, attr) 
     508 
     509class DatabaseWrapper(BaseDatabaseWrapper): 
     510    features = DatabaseFeatures() 
     511    ops = DatabaseOperations() 
     512    operators = { 
     513        'exact': '= %s', 
     514        'iexact': '= UPPER(%s)', 
     515        'contains': "LIKE %s ESCAPE'\\'", 
     516        'icontains': 'CONTAINING %s', #case is ignored 
     517        'gt': '> %s', 
     518        'gte': '>= %s', 
     519        'lt': '< %s', 
     520        'lte': '<= %s', 
     521        'startswith': 'STARTING WITH %s', #looks to be faster then LIKE 
     522        'endswith': "LIKE %s ESCAPE'\\'", 
     523        'istartswith': 'STARTING WITH UPPER(%s)', 
     524        'iendswith': "LIKE UPPER(%s) ESCAPE'\\'", 
     525    } 
     526    _current_cursor = None 
     527    def _connect(self, settings): 
     528        if settings.DATABASE_NAME == '': 
     529            from django.core.exceptions import ImproperlyConfigured 
     530            raise ImproperlyConfigured, "You need to specify DATABASE_NAME in your Django settings file." 
     531        charset = 'UNICODE_FSS' 
     532        if hasattr(settings, 'FIREBIRD_CHARSET'): 
     533            if settings.FIREBIRD_CHARSET == 'UTF8': 
     534                charset = 'UTF8'     
     535        kwargs = {'charset' : charset } 
     536        if settings.DATABASE_HOST: 
     537            kwargs['dsn'] = "%s:%s" % (settings.DATABASE_HOST, settings.DATABASE_NAME) 
     538        else: 
     539            kwargs['dsn'] = "localhost:%s" % settings.DATABASE_NAME 
     540        if settings.DATABASE_USER: 
     541            kwargs['user'] = settings.DATABASE_USER 
     542        if settings.DATABASE_PASSWORD: 
     543            kwargs['password'] = settings.DATABASE_PASSWORD 
     544        self.connection = Database.connect(**kwargs) 
     545        assert self.charset == charset 
     546        try: 
     547            self.connection.execute_immediate(""" 
     548                DECLARE EXTERNAL FUNCTION rand 
     549                RETURNS DOUBLE PRECISION 
     550                BY VALUE ENTRY_POINT 'IB_UDF_rand' MODULE_NAME 'ib_udf'; 
     551            """) 
     552        except Database.ProgrammingError: 
     553            pass #Already defined 
     554         
     555         
     556         
     557    def cursor(self, name=None): 
     558        #Cursors can be named 
     559        #http://kinterbasdb.sourceforge.net/dist_docs/usage.html#adv_named_cursors 
     560        #and maybe useful for scrolling updates and deletes 
     561        from django.conf import settings 
     562        cursor = self._cursor(settings, name) 
     563        if settings.DEBUG: 
     564            return self.make_debug_cursor(cursor) 
     565        return cursor 
     566     
     567    def _cursor(self, settings, name): 
     568        if self.connection is None: 
     569            self._connect(settings) 
     570        cursor = self.connection.cursor() 
     571        if name: 
     572            cursor.name = name 
     573        cursor = FirebirdCursorWrapper(cursor) 
     574        self._current_cursor = cursor 
     575        return cursor 
     576     
     577    #Returns query from prepared statement 
     578    def _get_query(self): 
     579        if self._current_cursor: 
     580            return self._current_cursor.query 
     581    query = property(_get_query) 
     582    #Returns prepared statement itself 
     583    def _get_statement(self): 
     584        if self._current_cursor: 
     585            return self._current_cursor.statement 
     586    statement = property(_get_statement) 
     587         
     588     
     589    def __getattr__(self, attr): 
     590        if attr in self.__dict__: 
     591            return self.__dict__[attr] 
     592        else: 
     593            return getattr(self.connection, attr) 
     594     
     595 
  • django/db/backends/firebird/client.py

    old new  
     1from django.conf import settings 
     2import os 
     3 
     4def runshell(): 
     5    args = [settings.DATABASE_NAME] 
     6    args += ["-u %s" % settings.DATABASE_USER] 
     7    if settings.DATABASE_PASSWORD: 
     8        args += ["-p %s" % settings.DATABASE_PASSWORD] 
     9    if 'FIREBIRD' not in os.environ: 
     10        path = '/opt/firebird/bin/' 
     11    os.system(path + 'isql ' + ' '.join(args)) 
  • django/db/backends/firebird/introspection.py

    old new  
     1from django.db import transaction 
     2from django.db.backends.firebird.base import DatabaseOperations 
     3 
     4quote_name = DatabaseOperations().quote_name 
     5 
     6def get_table_list(cursor): 
     7    "Returns a list of table names in the current database." 
     8    cursor.execute(""" 
     9        SELECT rdb$relation_name FROM rdb$relations 
     10        WHERE rdb$system_flag = 0 AND rdb$view_blr IS NULL ORDER BY rdb$relation_name""") 
     11    return [str(row[0].strip().lower()) for row in cursor.fetchall()] 
     12 
     13def get_table_description(cursor, table_name): 
     14    "Returns a description of the table, with the DB-API cursor.description interface." 
     15    #cursor.execute("SELECT FIRST 1 * FROM %s" % quote_name(table_name)) 
     16    #return cursor.description 
     17    # (name, type_code, display_size, internal_size, precision, scale, null_ok) 
     18    cursor.execute(""" 
     19        SELECT DISTINCT R.RDB$FIELD_NAME AS FNAME, 
     20                  F.RDB$FIELD_TYPE AS FTYPE, 
     21                  F.RDB$FIELD_LENGTH AS FLENGTH, 
     22                  F.RDB$FIELD_PRECISION AS FPRECISION, 
     23                  F.RDB$FIELD_SCALE AS FSCALE, 
     24                  R.RDB$NULL_FLAG AS NULL_FLAG, 
     25                  R.RDB$FIELD_POSITION 
     26        FROM RDB$RELATION_FIELDS R 
     27             JOIN RDB$FIELDS F ON R.RDB$FIELD_SOURCE=F.RDB$FIELD_NAME 
     28        WHERE F.RDB$SYSTEM_FLAG=0 and R.RDB$RELATION_NAME= %s 
     29        ORDER BY R.RDB$FIELD_POSITION 
     30    """, (table_name,)) 
     31    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()] 
     32 
     33 
     34def get_relations(cursor, table_name): 
     35    """ 
     36    Returns a dictionary of {field_index: (field_index_other_table, other_table)} 
     37    representing all relationships to the given table. Indexes are 0-based. 
     38    """ 
     39    cursor.execute(""" 
     40        SELECT seg.rdb$field_name, seg_ref.rdb$field_name, idx_ref.rdb$relation_name 
     41        FROM rdb$indices idx 
     42        INNER JOIN rdb$index_segments seg 
     43            ON seg.rdb$index_name = idx.rdb$index_name 
     44        INNER JOIN rdb$indices idx_ref 
     45            ON idx_ref.rdb$index_name = idx.rdb$foreign_key 
     46        INNER JOIN rdb$index_segments seg_ref 
     47            ON seg_ref.rdb$index_name = idx_ref.rdb$index_name 
     48        WHERE idx.rdb$relation_name = %s 
     49            AND idx.rdb$foreign_key IS NOT NULL""", [table_name]) 
     50 
     51    relations = {} 
     52    for row in cursor.fetchall(): 
     53        relations[row[0].rstrip()] = (row[1].strip(), row[2].strip()) 
     54    return relations 
     55 
     56def get_indexes(cursor, table_name): 
     57    """ 
     58    Returns a dictionary of fieldname -> infodict for the given table, 
     59    where each infodict is in the format: 
     60        {'primary_key': boolean representing whether it's the primary key, 
     61         'unique': boolean representing whether it's a unique index} 
     62    """ 
     63 
     64    # This query retrieves each field name and index type on the given table. 
     65    cursor.execute(""" 
     66        SELECT seg.rdb$field_name, const.rdb$constraint_type 
     67        FROM rdb$relation_constraints const 
     68        LEFT JOIN rdb$index_segments seg 
     69            ON seg.rdb$index_name = const.rdb$index_name 
     70        WHERE const.rdb$relation_name = %s 
     71            AND (const.rdb$constraint_type = 'PRIMARY KEY' 
     72                OR const.rdb$constraint_type = 'UNIQUE')""", [table_name]) 
     73    indexes = {} 
     74    for row in cursor.fetchall(): 
     75        indexes[row[0].strip()] = { 
     76            'primary_key': ('PRIMARY KEY' == row[1].strip()), 
     77            'unique': ('UNIQUE' == row[1].strip())} 
     78    return indexes 
     79 
     80# Maps type codes to Django Field types. 
     81# !todo 
     82DATA_TYPES_REVERSE = { 
     83    7: 'BooleanField', 
     84    7: 'SmallIntegerField', 
     85    8: 'IntegerField', 
     86    261: 'TextField', 
     87    37: 'IPAddressField', 
     88    37: 'CharField', 
     89    12: 'DateField', 
     90    13: 'TimeField', 
     91    35: 'DateTimeField', 
     92    10: 'FloatField', 
     93} 
  • django/db/backends/firebird/creation.py

    <