Ticket #1261: firebird-6668.diff

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

small fixes

  • django/db/models/base.py

     
    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

     
    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

     
    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

     
    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

     
    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

     
     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       
     622    def cursor(self):
     623        from django.conf import settings
     624        cursor = self._cursor(settings)
     625        if settings.DEBUG:
     626            self._debug_cursor = self.make_debug_cursor(cursor)
     627            return self._debug_cursor
     628        return cursor
     629   
     630    def _cursor(self, settings):
     631        if self.connection is None:
     632            self._connect(settings)
     633        cursor = self.connection.cursor()
     634        self._raw_cursor = cursor
     635        cursor = FirebirdCursorWrapper(cursor, self)
     636        self._current_cursor = cursor
     637        return cursor
     638
     639    def __getattr__(self, attr):
     640        if attr in self.__dict__:
     641            return self.__dict__[attr]
     642        else:
     643            return getattr(self.connection, attr)
     644
  • django/db/backends/firebird/client.py

     
     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

     
     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

     
     1# This dictionary maps Field objects to their associated Firebird column
     2# types, as strings. Column-type strings can contain format strings; they'll
     3# be interpolated against the values of Field.__dict__ before being output.
     4# If a column type is set to None, it won't be included in the output.
     5
     6
     7from kinterbasdb import connect, create_database
     8from django.core.management import call_command
     9from django.conf import settings
     10from django.db import connection
     11import sys, os.path, codecs
     12try:
     13    set
     14except NameError:
     15    from sets import Set as set   # Python 2.3 fallback
     16
     17
     18DATA_TYPES = {
     19    'AutoField':                     '"AutoField"',
     20    'BooleanField':                  '"BooleanField"',
     21    'CharField':                     'varchar(%(max_length)s)',
     22    'CommaSeparatedIntegerField':    'varchar(%(max_length)s) CHARACTER SET ASCII',
     23    'DateField':                     '"DateField"',
     24    'DateTimeField':                 '"DateTimeField"',
     25    'DecimalField':                  'numeric(%(max_digits)s, %(decimal_places)s)',
     26    'DefaultCharField':              '"CharField"',
     27    'FileField':                     'varchar(%(max_length)s)',
     28    'FilePathField':                 'varchar(%(max_length)s)',
     29    'FloatField':                    '"FloatField"',
     30    'ImageField':                    '"varchar(%(max_length)s)"',
     31    'IntegerField':                  '"IntegerField"',
     32    'IPAddressField':                'varchar(15) CHARACTER SET ASCII',
     33    'NullBooleanField':              '"NullBooleanField"',
     34    'OneToOneField':                 '"OneToOneField"',
     35    'PhoneNumberField':              '"PhoneNumberField"',
     36    'PositiveIntegerField':          '"PositiveIntegerField"',
     37    'PositiveSmallIntegerField':     '"PositiveSmallIntegerField"',
     38    'SlugField':                     'varchar(%(max_length)s)',
     39    'SmallIntegerField':             '"SmallIntegerField"',
     40    'LargeTextField':                '"LargeTextField"',
     41    'TextField':                     '"TextField"',
     42    'TimeField':                     '"TimeField"',
     43    'URLField':                      'varchar(%(max_length)s) CHARACTER SET ASCII',
     44    'USStateField':                  '"USStateField"'
     45}
     46     
     47PYTHON_TO_FB_ENCODING_MAP = {
     48    'ascii':        'ASCII',
     49    'utf_8':        connection.charset,
     50    'shift_jis':    'SJIS_0208',
     51    'euc_jp':       'EUCJ_0208',
     52    'cp737':        'DOS737',
     53    'cp437':        'DOS437',
     54    'cp850':        'DOS850',
     55    'cp865':        'DOS865',
     56    'cp860':        'DOS860',
     57    'cp863':        'DOS863',
     58    'cp775':        'DOS775',
     59    'cp862':        'DOS862',
     60    'cp864':        'DOS864',
     61    'iso8859_1':    'ISO8859_1',
     62    'iso8859_2':    'ISO8859_2',
     63    'iso8859_3':    'ISO8859_3',
     64    'iso8859_4':    'ISO8859_4',
     65    'iso8859_5':    'ISO8859_5',
     66    'iso8859_6':    'ISO8859_6',
     67    'iso8859_7':    'ISO8859_7',
     68    'iso8859_8':    'ISO8859_8',
     69    'iso8859_9':    'ISO8859_9',
     70    'iso8859_13':   'ISO8859_13',
     71    'euc_kr':       'KSC_5601',
     72    'cp852':        'DOS852',
     73    'cp857':        'DOS857',
     74    'cp861':        'DOS861',
     75    'cp866':        'DOS866',
     76    'cp869':        'DOS869',
     77    'cp1250':       'WIN1250',
     78    'cp1251':       'WIN1251',
     79    'cp1252':       'WIN1252',
     80    'cp1253':       'WIN1253',
     81    'cp1254':       'WIN1254',
     82    'big5':         'BIG_5',
     83    'gb2312':       'GB_2312',
     84    'cp1255':       'WIN1255',
     85    'cp1256':       'WIN1256',
     86    'cp1257':       'WIN1257',
     87    'koi8_r':       'KOI8-R',
     88    'koi8_u':       'KOI8-U',
     89    'cp1258':       'WIN1258'
     90  }
     91
     92def get_data_size(data_type, max_length = 100):
     93    char_bytes = connection.BYTES_PER_DEFAULT_CHAR
     94    size_map = {
     95        'AutoField':                     8,
     96        'BooleanField':                  4,
     97        'CharField':                     char_bytes*max_length,
     98        'CommaSeparatedIntegerField':    max_length,
     99        'DateField':                     16,
     100        'DateTimeField':                 16,
     101        'DecimalField':                  16,
     102        'FileField':                     char_bytes*max_length,
     103        'FilePathField':                 'varchar(%(max_length)s)',
     104        'FloatField':                    16,
     105        'ImageField':                    char_bytes*max_length,
     106        'IntegerField':                  8,
     107        'IPAddressField':                15,
     108        'NullBooleanField':              4,
     109        'OneToOneField':                 8,
     110        'PhoneNumberField':              20,
     111        'PositiveIntegerField':          8,
     112        'PositiveSmallIntegerField':     4,
     113        'SlugField':                     char_bytes*max_length,
     114        'SmallIntegerField':             4,
     115        'TextBlob':                      8,
     116        'TextField':                     32767,
     117        'TimeField':                     16,
     118        'URLField':                      max_length,
     119        'USStateField':                  char_bytes*2
     120    }
     121    return size_map[data_type]
     122
     123DEFAULT_MAX_LENGTH = 100
     124def sql_model_create(model, style, known_models=set()):
     125    """
     126    Returns the SQL required to create a single model, as a tuple of:
     127        (list_of_sql, pending_references_dict)
     128    """
     129    from django.db import connection, models
     130
     131    opts = model._meta
     132    final_output = []
     133    table_output = []
     134    pending_references = {}
     135    qn = connection.ops.quote_name
     136   
     137    # Create domains
     138    domains = [ ('AutoField', 'integer'),
     139                ('BooleanField', 'smallint CHECK (VALUE IN (0,1))'),
     140                ('DateField', 'date'),
     141                ('CharField', 'varchar(%i)' % DEFAULT_MAX_LENGTH),
     142                ('DateTimeField', 'timestamp'),
     143                ('FloatField', 'double precision'),
     144                ('IntegerField', 'integer'),
     145                ('IPAddressField', 'varchar(15) CHARACTER SET ASCII'),
     146                ('NullBooleanField', 'smallint CHECK ((VALUE IN (0,1)) OR (VALUE IS NULL))'),
     147                ('OneToOneField', 'integer'),
     148                ('PhoneNumberField', 'varchar(20) CHARACTER SET ASCII'),
     149                ('PositiveIntegerField', 'integer CHECK ((VALUE >= 0) OR (VALUE IS NULL))'),
     150                ('PositiveSmallIntegerField', 'smallint CHECK ((VALUE >= 0) OR (VALUE IS NULL))'),
     151                ('SmallIntegerField', 'smallint'),
     152                ('TextField', 'varchar(%s)' % connection.FB_MAX_VARCHAR),
     153                ('LargeTextField', 'blob sub_type text'),
     154                ('TimeField', 'time'),
     155                ('USStateField', 'varchar(2) CHARACTER SET ASCII') ]
     156
     157    cursor = connection.cursor()
     158    cursor.execute("SELECT RDB$FIELD_NAME FROM RDB$FIELDS")
     159    existing_domains = set([row[0].strip() for row in cursor.fetchall() if not row[0].startswith('RDB$')])
     160    domains = map(lambda domain: '%s "%s" AS %s;' % ('CREATE DOMAIN', domain[0], domain[1]),
     161            filter(lambda x: x[0] not in existing_domains, domains))
     162    final_output.extend(domains)
     163   
     164    # Check that row size is less than 64k and adjust TextFields if needed
     165    row_size = 0
     166    columns = [(f.db_type().strip('"'), f.get_internal_type(), f) for f in opts.fields]
     167    columns_simple = [col[0] for col in columns]
     168    text_field_type = '"TextField"'
     169    max_alowed_bytes = 32765
     170    if 'TextField' in columns_simple:
     171        max_length = 100
     172        num_text_fields = 0
     173        for column in columns:
     174            num_text_fields += (column[0] == 'TextField')
     175            if column[0].startswith('varchar'):
     176                max_length = int(column[0].split('(')[1].split(')')[0])
     177            if column[1] in DATA_TYPES:
     178                row_size += get_data_size(column[1], max_length)
     179        if row_size > 65536:
     180            max_alowed_bytes = int( (max_alowed_bytes/num_text_fields) - (row_size-65536) )
     181            n = max_alowed_bytes / connection.BYTES_PER_DEFAULT_CHAR
     182            if n > 512:
     183                text_field_type = 'varchar(%s)' % n
     184                FB_TEXTFIELD_ALTERED = True   
     185                print
     186                print "WARNING: Maximum number of characters in TextFields has changed to %s." % n
     187                print "         TextField columns with custom charsets will have %s chars available" % max_alowed_bytes
     188                print "         The change affects %s table only." % opts.db_table
     189                print "         TextFields in other tables will have %s characters maximum" % connection.FB_MAX_VARCHAR
     190                print "         or 32765 characters with custom (non-UTF) encoding."
     191                print "         If you need more space in those fields try LargeTextFields instead."
     192                print
     193            else:
     194                # Swich to blobs if size is too small (<1024)   
     195                text_field_type = '"LargeTextField"'   
     196   
     197    # Create tables
     198    for f in opts.fields:
     199        col_type = f.db_type()
     200        if col_type.strip('"') == 'TextField':
     201            col_type = text_field_type
     202        fb_version = "%s.%s" % (connection.ops.firebird_version[0], connection.ops.firebird_version[1])
     203        page_size = connection.ops.page_size
     204        #look at: http://www.volny.cz/iprenosil/interbase/ip_ib_indexcalculator.htm
     205        if connection.ops.index_limit < 1000:
     206            strip2ascii = False
     207            custom_charset = False
     208            if col_type.startswith('varchar'):
     209                if (f.unique or f.primary_key or f.db_index):
     210                    length = f.max_length
     211                    if f.encoding:
     212                        if not f.encoding.upper().startswith('UTF'):
     213                            custom_charset = True
     214                    if not custom_charset:
     215                        try:
     216                            length = f.max_length * connection.BYTES_PER_DEFAULT_CHAR
     217                        except TypeError:
     218                            length = 100*connection.BYTES_PER_DEFAULT_CHAR #Default for CharField
     219                    if length >= connection.ops.index_limit:   
     220                        strip2ascii = True
     221                if len(opts.unique_together) > 0:
     222                    if f.column in opts.unique_together[0]:
     223                        num_unique_char_fields = len([ fld for fld in opts.unique_together[0] if opts.get_field(fld).db_type().startswith('varchar') ])
     224                        num_unique_fileds = len(opts.unique_together[0])
     225                        num_unique_nonchar_fileds = num_unique_fileds - num_unique_char_fields
     226                        limit = connection.ops.index_limit
     227                        limit -= ((num_unique_fileds - 1)*64)
     228                        limit -= 8*num_unique_nonchar_fileds
     229                        max_length = limit/num_unique_char_fields
     230                        ascii_length = int(f.max_length)
     231                        old_length = ascii_length*connection.BYTES_PER_DEFAULT_CHAR
     232                         
     233                        if (old_length >= max_length) and (ascii_length < max_length):
     234                            strip2ascii = True
     235                        elif old_length > max_length:
     236                            strip2ascii = False #We change it here
     237                            col_type = "varchar(%i) CHARACTER SET ASCII" % max_length
     238                            msg =  "WARNING: Character set of the '%s' field\n"
     239                            msg += "         (table %s)\n"
     240                            msg += "         has changed to ASCII"
     241                            msg += " to fit %s-byte limit in FB %s"
     242                            if not page_size:
     243                                print  msg % (f.column, opts.db_table, connection.ops.index_limit, fb_version)
     244                            else:
     245                                msg += " with page size %s"
     246                                print  msg % (f.column, opts.db_table, connection.ops.index_limit, fb_version, page_size)
     247                            print "         The maximum length of '%s' is now %s instead of %s"\
     248                             % (f.column, max_length, old_length)     
     249            if strip2ascii:
     250                col_type = "%s %s %s" % (col_type, "CHARACTER SET", "ASCII")
     251                msg =  "WARNING: Character set of the '%s' field\n"
     252                msg += "         (table %s)\n"
     253                msg += "         has changed to ASCII"
     254                msg += " to fit %s-byte limit in FB %s"
     255                if not page_size:
     256                    print  msg % (f.column, opts.db_table, connection.ops.index_limit, fb_version)
     257                else:
     258                    msg += " with page size %s"
     259                    print  msg % (f.column, opts.db_table, connection.ops.index_limit, fb_version, page_size)
     260                   
     261        if (col_type.startswith('varchar') or col_type.strip('"') == 'TextField') and f.encoding:
     262            charset = PYTHON_TO_FB_ENCODING_MAP[codecs.lookup(f.encoding).name]
     263            if col_type.strip('"') == 'TextField':
     264                col_type = 'varchar(%i)' % max_alowed_bytes
     265            col_type = "%s %s %s" % (col_type, "CHARACTER SET", charset)
     266 
     267           
     268        if col_type is None:
     269            # Skip ManyToManyFields, because they're not represented as
     270            # database columns in this table.
     271            continue
     272        # Make the definition (e.g. 'foo VARCHAR(30)') for this field.
     273        field_output = [style.SQL_FIELD(qn(f.column)),
     274            style.SQL_COLTYPE(col_type)]
     275        field_output.append(style.SQL_KEYWORD('%s' % (not f.null and 'NOT NULL' or 'DEFAULT NULL')))
     276        if f.unique:
     277            field_output.append(style.SQL_KEYWORD('UNIQUE'))
     278        if f.primary_key:
     279            field_output.append(style.SQL_KEYWORD('PRIMARY KEY'))
     280        if f.rel:
     281            # We haven't yet created the table to which this field
     282            # is related, so save it for later.
     283            pr = pending_references.setdefault(f.rel.to, []).append((model, f))
     284        table_output.append(' '.join(field_output))
     285    if opts.order_with_respect_to:
     286        table_output.append(style.SQL_FIELD(qn('_order')) + ' ' + \
     287            style.SQL_COLTYPE(models.IntegerField().db_type()) + ' ' + \
     288            style.SQL_KEYWORD('NULL'))
     289    for field_constraints in opts.unique_together:
     290        table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \
     291            ", ".join([qn(style.SQL_FIELD(opts.get_field(f).column)) for f in field_constraints]))
     292
     293    full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' (']
     294    for i, line in enumerate(table_output): # Combine and add commas.
     295        full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
     296    full_statement.append(');')
     297    final_output.append('\n'.join(full_statement))
     298
     299    if opts.has_auto_field:
     300        # Add any extra SQL needed to support auto-incrementing primary keys.
     301        auto_column = opts.auto_field.db_column or opts.auto_field.name
     302        autoinc_sql = connection.ops.autoinc_sql(style, opts.db_table, auto_column)
     303        if autoinc_sql:
     304            for stmt in autoinc_sql:
     305                final_output.append(stmt)
     306
     307    # Declare exteral functions
     308    cursor.execute("SELECT RDB$FUNCTION_NAME FROM RDB$FUNCTIONS")
     309    existing_functions = set([row[0].strip().upper() for row in cursor.fetchall()])
     310    if 'RAND' not in existing_functions:
     311        final_output.append('%s %s\n\t%s %s\n\t%s %s\n\t%s;' % (style.SQL_KEYWORD('DECLARE EXTERNAL FUNCTION'),
     312            style.SQL_TABLE('RAND'), style.SQL_KEYWORD('RETURNS'), style.SQL_COLTYPE('DOUBLE PRECISION'),
     313            style.SQL_KEYWORD('BY VALUE ENTRY_POINT'), style.SQL_FIELD("'IB_UDF_rand'"),
     314            style.SQL_TABLE("MODULE_NAME 'ib_udf'")))
     315   
     316    return final_output, pending_references
     317
     318def many_to_many_sql_for_model(model, style):
     319    from django.db import connection, models
     320    from django.contrib.contenttypes import generic
     321    from django.db.backends.util import truncate_name
     322
     323    opts = model._meta
     324    final_output = []
     325    qn = connection.ops.quote_name
     326    for f in opts.many_to_many:
     327        if not isinstance(f.rel, generic.GenericRel):
     328            table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
     329                style.SQL_TABLE(qn(f.m2m_db_table())) + ' (']
     330            table_output.append('    %s %s %s,' %
     331                (style.SQL_FIELD(qn('id')),
     332                style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type()),
     333                style.SQL_KEYWORD('NOT NULL PRIMARY KEY')))
     334
     335            table_output.append('    %s %s %s,' %
     336                (style.SQL_FIELD(qn(f.m2m_column_name())),
     337                style.SQL_COLTYPE(models.ForeignKey(model).db_type()),
     338                style.SQL_KEYWORD('NOT NULL')))
     339            table_output.append('    %s %s %s,' %
     340                (style.SQL_FIELD(qn(f.m2m_reverse_name())),
     341                style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type()),
     342                style.SQL_KEYWORD('NOT NULL')))
     343            deferred = [
     344                (f.m2m_db_table(), f.m2m_column_name(), opts.db_table,
     345                    opts.pk.column),
     346                ( f.m2m_db_table(), f.m2m_reverse_name(),
     347                    f.rel.to._meta.db_table, f.rel.to._meta.pk.column)
     348                ]
     349
     350            table_output.append('    %s (%s, %s)' %
     351                (style.SQL_KEYWORD('UNIQUE'),
     352                style.SQL_FIELD(qn(f.m2m_column_name())),
     353                style.SQL_FIELD(qn(f.m2m_reverse_name()))))
     354            table_output.append(');')
     355            final_output.append('\n'.join(table_output))
     356           
     357            autoinc_sql = connection.ops.autoinc_sql(style, f.m2m_db_table(), 'id')
     358            if autoinc_sql:
     359                for stmt in autoinc_sql:
     360                    final_output.append(stmt)
     361           
     362            if connection.features.supports_constraints:
     363                for r_table, r_col, table, col in deferred:
     364                    r_name = '%s_%s_%x' % (r_col, col,
     365                            abs(hash((r_table, table))))
     366                    final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' %
     367                    (qn(r_table),
     368                    truncate_name(r_name, connection.ops.max_name_length()),
     369                    qn(r_col), qn(table), qn(col),
     370                    'ON DELETE CASCADE ON UPDATE CASCADE'))
     371
     372    return final_output
     373
     374TEST_DATABASE_PREFIX = 'test_'
     375def create_test_db(settings, connection, verbosity, autoclobber):
     376    # KInterbasDB supports dynamic database creation and deletion
     377    # via the module-level function create_database and the method Connection.drop_database.
     378       
     379    if settings.TEST_DATABASE_NAME:
     380        TEST_DATABASE_NAME = settings.TEST_DATABASE_NAME
     381    else:
     382        dbnametuple = os.path.split(settings.DATABASE_NAME)
     383        TEST_DATABASE_NAME = os.path.join(dbnametuple[0], TEST_DATABASE_PREFIX + dbnametuple[1])
     384   
     385    dsn = "localhost:%s" % TEST_DATABASE_NAME
     386    if settings.DATABASE_HOST:
     387        dsn = "%s:%s" % (settings.DATABASE_HOST, TEST_DATABASE_NAME)
     388   
     389    if os.path.isfile(TEST_DATABASE_NAME):
     390        sys.stderr.write("Database %s already exists\n" % TEST_DATABASE_NAME)
     391        if not autoclobber:
     392            confirm = raw_input("Type 'yes' if you would like to try deleting the test database '%s', or 'no' to cancel: " % TEST_DATABASE_NAME)
     393        if autoclobber or confirm == 'yes':
     394            if verbosity >= 1:
     395                print "Destroying old test database..."
     396            old_connection = connect(dsn=dsn, user=settings.DATABASE_USER, password=settings.DATABASE_PASSWORD)
     397            old_connection.drop_database()
     398        else:
     399                print "Tests cancelled."
     400                sys.exit(1)
     401           
     402    if verbosity >= 1:
     403        print "Creating test database..."
     404    try:
     405        charset = 'UNICODE_FSS'
     406        if hasattr(settings, 'FIREBIRD_CHARSET'):
     407            if settings.FIREBIRD_CHARSET == 'UTF8':
     408                charset='UTF8'               
     409        create_database("create database '%s' user '%s' password '%s' default character set %s" % \
     410            (dsn, settings.DATABASE_USER, settings.DATABASE_PASSWORD, charset))
     411    except Exception, e:
     412        sys.stderr.write("Got an error creating the test database: %s\n" % e)
     413        sys.exit(2)
     414
     415    connection.close()
     416    settings.DATABASE_NAME = TEST_DATABASE_NAME
     417
     418    call_command('syncdb', verbosity=verbosity, interactive=False)
     419
     420    if settings.CACHE_BACKEND.startswith('db://'):
     421        cache_name = settings.CACHE_BACKEND[len('db://'):]
     422        call_command('createcachetable', cache_name)
     423
     424    # Get a cursor (even though we don't need one yet). This has
     425    # the side effect of initializing the test database.
     426    cursor = connection.cursor()
     427
     428    return TEST_DATABASE_NAME
     429
     430def destroy_test_db(settings, connection, old_database_name, verbosity):
     431    # KInterbasDB supports dynamic database deletion via the method Connection.drop_database.
     432    if verbosity >= 1:
     433        print "Destroying test database..."
     434    connection.drop_database()
     435   
     436
  • django/core/cache/backends/db.py

     
    4747        if timeout is None:
    4848            timeout = self.default_timeout
    4949        cursor = connection.cursor()
    50         cursor.execute("SELECT COUNT(*) FROM %s" % self._table)
     50        qn = connection.ops.quote_name
     51        cursor.execute("SELECT COUNT(*) FROM %s" % qn(self._table))
    5152        num = cursor.fetchone()[0]
    5253        now = datetime.now().replace(microsecond=0)
    5354        exp = datetime.fromtimestamp(time.time() + timeout).replace(microsecond=0)
    5455        if num > self._max_entries:
    5556            self._cull(cursor, now)
    5657        encoded = base64.encodestring(pickle.dumps(value, 2)).strip()
    57         cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s" % self._table, [key])
     58        cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s" % qn(self._table), [key])
    5859        try:
    5960            if mode == 'set' and cursor.fetchone():
    60                 cursor.execute("UPDATE %s SET value = %%s, expires = %%s WHERE cache_key = %%s" % self._table, [encoded, str(exp), key])
     61                cursor.execute("UPDATE %s SET value = %%s, expires = %%s WHERE cache_key = %%s" % qn(self._table), [encoded, str(exp), key])
    6162            else:
    62                 cursor.execute("INSERT INTO %s (cache_key, value, expires) VALUES (%%s, %%s, %%s)" % self._table, [key, encoded, str(exp)])
     63                cursor.execute("INSERT INTO %s (cache_key, value, expires) VALUES (%%s, %%s, %%s)" % qn(self._table), [key, encoded, str(exp)])
    6364        except DatabaseError:
    6465            # To be threadsafe, updates/inserts are allowed to fail silently
    6566            pass
  • django/core/management/sql.py

     
    11from django.core.management.base import CommandError
     2from django.conf import settings
    23import os
    34import re
    45
     
    6667
    6768def sql_create(app, style):
    6869    "Returns a list of the CREATE TABLE SQL statements for the given app."
    69     from django.db import models
     70    from django.db import models, get_creation_module
    7071    from django.conf import settings
    7172
    7273    if settings.DATABASE_ENGINE == 'dummy':
     
    9798    # Create the many-to-many join tables.
    9899    for model in app_models:
    99100        final_output.extend(many_to_many_sql_for_model(model, style))
    100 
     101   
    101102    # Handle references to tables that are from other apps
    102103    # but don't exist physically.
    103104    not_installed_models = set(pending_references.keys())
     
    245246    Returns the SQL required to create a single model, as a tuple of:
    246247        (list_of_sql, pending_references_dict)
    247248    """
    248     from django.db import connection, models
    249 
     249    from django.db import connection, models, get_creation_module
     250    creation_module = get_creation_module()
     251    # If the database backend wants to create model itself, let it
     252    if hasattr(creation_module, "sql_model_create"):
     253        return creation_module.sql_model_create(model, style, known_models)
    250254    opts = model._meta
    251255    final_output = []
    252256    table_output = []
     
    332336                # For MySQL, r_name must be unique in the first 64 characters.
    333337                # So we are careful with character usage here.
    334338                r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table))))
    335                 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
     339                final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s%s;' % \
    336340                    (qn(r_table), truncate_name(r_name, connection.ops.max_name_length()),
    337341                    qn(r_col), qn(table), qn(col),
     342                    connection.ops.cascade_delete_update_sql(),
    338343                    connection.ops.deferrable_sql()))
    339344            del pending_references[model]
    340345    return final_output
    341346
    342347def many_to_many_sql_for_model(model, style):
    343     from django.db import connection, models
     348    from django.db import connection, models, get_creation_module
    344349    from django.contrib.contenttypes import generic
    345350    from django.db.backends.util import truncate_name
    346 
     351   
     352    creation_module = get_creation_module()
     353    # If the database backend wants to create many_to_many sql itself, let it
     354    if hasattr(creation_module, "many_to_many_sql_for_model"):
     355        return creation_module.many_to_many_sql_for_model(model, style)
     356       
    347357    opts = model._meta
    348358    final_output = []
    349359    qn = connection.ops.quote_name
  • tests/modeltests/custom_methods/models.py

     
    2727        """
    2828        from django.db import connection
    2929        cursor = connection.cursor()
     30        # Some backends really really need quotes!
    3031        cursor.execute("""
    31             SELECT id, headline, pub_date
    32             FROM custom_methods_article
    33             WHERE pub_date = %s
    34                 AND id != %s""", [str(self.pub_date), self.id])
     32            SELECT "id", "headline", "pub_date"
     33            FROM "custom_methods_article"
     34            WHERE "pub_date" = %s
     35                AND "id" != %s""", [str(self.pub_date), self.id])
    3536        # The asterisk in "(*row)" tells Python to expand the list into
    3637        # positional arguments to Article().
    3738        return [self.__class__(*row) for row in cursor.fetchall()]
    38 
     39   
    3940__test__ = {'API_TESTS':"""
    4041# Create a couple of Articles.
    4142>>> from datetime import date
  • tests/modeltests/lookup/models.py

     
    274274>>> a4.save()
    275275>>> a5 = Article(pub_date=now, headline='hey-Foo')
    276276>>> a5.save()
     277"""}
    277278
     279# Firebird support 'magic values'
     280if settings.DATABASE_ENGINE in ('firebird',):
     281    __test__['API_TESTS'] += r"""
     282# and yet more:
     283>>> a10 = Article(pub_date='today', headline='foobar')
     284>>> a10.save()
     285>>> a11 = Article(pub_date='tomorrow', headline='foobaz')
     286>>> a11.save()
     287>>> a12 = Article(pub_date='yesterday', headline='ooF')
     288>>> a12.save()
     289>>> a13 = Article(pub_date='now', headline='foobarbaz')
     290>>> a13.save()
     291>>> a14 = Article(pub_date='today', headline='zoocarfaz')
     292>>> a14.save()
     293>>> a15 = Article(pub_date='today', headline='barfoobaz')
     294>>> a15.save()
     295>>> a16 = Article(pub_date='today', headline='bazbaRFOO')
     296>>> a16.save()
     297>>> Article.objects.filter(pub_date__exact='yesterday')
     298[<Article: ooF>]
     299"""
     300
     301
     302# Firebird doesn't support regular expression lookups
     303if settings.DATABASE_ENGINE not in ('firebird',):
     304    __test__['API_TESTS'] += r"""
    278305# zero-or-more
    279306>>> Article.objects.filter(headline__regex=r'fo*')
    280307[<Article: f>, <Article: fo>, <Article: foo>, <Article: fooo>]
     
    348375[<Article: barfoobaz>, <Article: baz>, <Article: bazbaRFOO>, <Article: foobarbaz>, <Article: foobaz>]
    349376>>> Article.objects.filter(headline__iregex=r'b.*ar')
    350377[<Article: bar>, <Article: barfoobaz>, <Article: bazbaRFOO>, <Article: foobar>, <Article: foobarbaz>]
    351 """}
     378"""
    352379
    353380
    354 if settings.DATABASE_ENGINE not in ('mysql', 'mysql_old'):
     381if settings.DATABASE_ENGINE not in ('mysql', 'mysql_old', 'firebird'):
    355382    __test__['API_TESTS'] += r"""
    356383# grouping and backreferences
    357384>>> Article.objects.filter(headline__regex=r'b(.).*b\1')
  • tests/regressiontests/serializers_regress/tests.py

     
    2222    import decimal
    2323except ImportError:
    2424    from django.utils import _decimal as decimal
     25try:
     26    set
     27except NameError:
     28    from sets import Set as set   # Python 2.3 fallback
    2529
    2630# A set of functions that can be used to recreate
    2731# test data objects of various kinds.
     
    8387    testcase.assertEqual(data, instance.data_id)
    8488
    8589def m2m_compare(testcase, pk, klass, data):
     90    # Use sets to ignore order of data
    8691    instance = klass.objects.get(id=pk)
    87     testcase.assertEqual(data, [obj.id for obj in instance.data.all()])
     92    testcase.assertEqual(set(data), set([obj.id for obj in instance.data.all()]))
    8893
    8994def o2o_compare(testcase, pk, klass, data):
    9095    instance = klass.objects.get(data=data)
  • tests/regressiontests/backends/models.py

     
    2222>>> opts = Square._meta
    2323>>> f1, f2 = opts.get_field('root'), opts.get_field('square')
    2424>>> query = ('INSERT INTO %s (%s, %s) VALUES (%%s, %%s)'
    25 ...         % (t_convert(opts.db_table), qn(f1.column), qn(f2.column)))
     25...         % ((qn(t_convert(opts.db_table)), qn(f1.column), qn(f2.column))))
    2626>>> cursor.executemany(query, [(i, i**2) for i in range(-5, 6)]) and None or None
    2727>>> Square.objects.order_by('root')
    2828[<Square: -5 ** 2 == 25>, <Square: -4 ** 2 == 16>, <Square: -3 ** 2 == 9>, <Square: -2 ** 2 == 4>, <Square: -1 ** 2 == 1>, <Square: 0 ** 2 == 0>, <Square: 1 ** 2 == 1>, <Square: 2 ** 2 == 4>, <Square: 3 ** 2 == 9>, <Square: 4 ** 2 == 16>, <Square: 5 ** 2 == 25>]
Back to Top