Ticket #1261: firebird-6669-update.diff

File firebird-6669-update.diff, 98.2 KB (added by Ivan Illarionov, 16 years ago)

django-on-fire patch against django svn 6669

  • django/db/models/base.py

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

     
    7777    def filter(self, *args, **kwargs):
    7878        return self.get_query_set().filter(*args, **kwargs)
    7979
     80    def firefilter(self, *args, **kwargs):
     81        return self.get_query_set().firefilter(*args, **kwargs)
     82   
    8083    def complex_filter(self, *args, **kwargs):
    8184        return self.get_query_set().complex_filter(*args, **kwargs)
    8285
  • 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,
     87        on_update=None, on_delete=None):
    8788        self.name = name
    8889        self.verbose_name = verbose_name
    8990        self.primary_key = primary_key
    9091        self.max_length, self.unique = max_length, unique
     92        self.encoding = encoding
    9193        self.blank, self.null = blank, null
    9294        # Oracle treats the empty string ('') as null, so coerce the null
    9395        # option whenever '' is a possible value.
     
    105107        self.help_text = help_text
    106108        self.db_column = db_column
    107109        self.db_tablespace = db_tablespace
     110        self.on_update = on_update
     111        self.on_delete = on_delete
    108112
    109113        # Set db_index to True if the field has a relationship and doesn't explicitly set db_index.
    110114        self.db_index = db_index
     
    148152        data_types = get_creation_module().DATA_TYPES
    149153        internal_type = self.get_internal_type()
    150154        if internal_type not in data_types:
    151             return None
     155            return None   
    152156        return data_types[internal_type] % self.__dict__
    153157
    154158    def validate_full(self, field_data, all_data):
     
    402406        "Returns the value of this field in the given model instance."
    403407        return getattr(obj, self.attname)
    404408
     409# Use the backend's Field class if it defines one. Otherwise, use _Field.
     410if connection.features.uses_custom_field:
     411    Field = connection.ops.field_class(_Field)
     412else:
     413    Field = _Field
     414
    405415class AutoField(Field):
    406416    empty_strings_allowed = False
    407417    def __init__(self, *args, **kwargs):
     
    688698        defaults.update(kwargs)
    689699        return super(DecimalField, self).formfield(**defaults)
    690700
     701class DefaultCharField(CharField):
     702    def __init__(self, *args, **kwargs):
     703        DEFAULT_MAX_LENGTH = 100
     704        if hasattr(settings, 'DEFAULT_MAX_LENGTH'):
     705           DEFAULT_MAX_LENGTH = settings.DEFAULT_MAX_LENGT
     706        kwargs['max_length'] = kwargs.get('max_length', DEFAULT_MAX_LENGTH)
     707        CharField.__init__(self, *args, **kwargs)
     708
    691709class EmailField(CharField):
    692710    def __init__(self, *args, **kwargs):
    693711        kwargs['max_length'] = kwargs.get('max_length', 75)
     
    890908        defaults.update(kwargs)
    891909        return super(IPAddressField, self).formfield(**defaults)
    892910
     911class LargeTextField(Field):
     912    def get_manipulator_field_objs(self):
     913        return [oldforms.LargeTextField]
     914
     915    def formfield(self, **kwargs):
     916        defaults = {'widget': forms.Textarea}
     917        defaults.update(kwargs)
     918        return super(LargeTextField, self).formfield(**defaults)
     919
    893920class NullBooleanField(Field):
    894921    empty_strings_allowed = False
    895922    def __init__(self, *args, **kwargs):
     
    9971024            # doesn't support microseconds.
    9981025            if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
    9991026                value = value.replace(microsecond=0)
    1000             if settings.DATABASE_ENGINE == 'oracle':
    1001                 # cx_Oracle expects a datetime.datetime to persist into TIMESTAMP field.
     1027            if settings.DATABASE_ENGINE in ('oracle', 'firebird'):
     1028                # cx_Oracle and kinterbasdb expect a datetime.datetime to persist into TIMESTAMP field.
    10021029                if isinstance(value, datetime.time):
    10031030                    value = datetime.datetime(1900, 1, 1, value.hour, value.minute,
    10041031                                              value.second, value.microsecond)
  • django/db/models/fields/related.py

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

     
     1from django.db.models.fields import CharField, DateField, DateTimeField, DecimalField
     2from django.db.models.fields import IntegerField, FloatField, SmallIntegerField, TimeField
     3
     4class ComputedField(object):
     5    def __init__(self, **kwargs):
     6        expression = kwargs.pop('expression')
     7        if 'params' in kwargs:
     8            raw_params = kwargs.pop('params')
     9            params = []
     10            for rp in raw_params:
     11                params.append('"%s"' % rp)
     12            self.expression = expression % tuple(params)
     13        else:
     14            self.expression = expression   
     15    def db_type(self):
     16        return 'ComputedField'
     17
     18class ComputedCharField(ComputedField, CharField):
     19    def __init__(self, **kwargs):
     20        ComputedField.__init__(self, **kwargs)
     21        if 'params' in kwargs:
     22            del kwargs['params']
     23        if 'expression' in kwargs:
     24            del kwargs['expression']
     25        CharField.__init__(self, **kwargs)
     26
     27class ComputedDateField(ComputedField, DateField):
     28    def __init__(self, **kwargs):
     29        ComputedField.__init__(self, **kwargs)
     30        if 'params' in kwargs:
     31            del kwargs['params']
     32        if 'expression' in kwargs:
     33            del kwargs['expression']
     34        DateField.__init__(self, **kwargs) 
     35
     36class ComputedDateTimeField(ComputedField, DateTimeField):
     37    def __init__(self, **kwargs):
     38        ComputedField.__init__(self, **kwargs)
     39        if 'params' in kwargs:
     40            del kwargs['params']
     41        if 'expression' in kwargs:
     42            del kwargs['expression']
     43        DateTimeField.__init__(self, **kwargs)
     44
     45class ComputedDecimalField(ComputedField, DecimalField):
     46    def __init__(self, **kwargs):
     47        ComputedField.__init__(self, **kwargs)
     48        if 'params' in kwargs:
     49            del kwargs['params']
     50        if 'expression' in kwargs:
     51            del kwargs['expression']
     52        DecimalField.__init__(self, **kwargs)
     53       
     54class ComputedFloatField(ComputedField, FloatField):
     55    def __init__(self, **kwargs):
     56        ComputedField.__init__(self, **kwargs)
     57        if 'params' in kwargs:
     58            del kwargs['params']
     59        if 'expression' in kwargs:
     60            del kwargs['expression']
     61        FloatField.__init__(self, **kwargs)
     62
     63class ComputedIntegerField(ComputedField, IntegerField):
     64    def __init__(self, **kwargs):
     65        ComputedField.__init__(self, **kwargs)
     66        if 'params' in kwargs:
     67            del kwargs['params']
     68        if 'expression' in kwargs:
     69            del kwargs['expression']
     70        IntegerField.__init__(self, **kwargs)
     71
     72class ComputedSmallIntegerField(ComputedField, SmallIntegerField):
     73    def __init__(self, **kwargs):
     74        ComputedField.__init__(self, **kwargs)
     75        if 'params' in kwargs:
     76            del kwargs['params']
     77        if 'expression' in kwargs:
     78            del kwargs['expression']
     79        SmallIntegerField.__init__(self, **kwargs)
     80
     81class ComputedTimeField(ComputedField, TimeField):
     82    def __init__(self, **kwargs):
     83        ComputedField.__init__(self, **kwargs)
     84        if 'params' in kwargs:
     85            del kwargs['params']
     86        if 'expression' in kwargs:
     87            del kwargs['expression']
     88        TimeField.__init__(self, **kwargs)   
     89
  • django/db/models/__init__.py

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

     
     1from django.db import connection, transaction
     2
     3
     4class procedure_meta(type):
     5    """
     6    Metaclass for native stored procedures behaving as normal Django model methods
     7    Subclass Procedure inside your model, create procedures tuple
     8    and use the class as ordinary method
     9    Please treat the resulting classes as methods because they don't
     10    behave like classes anymore
     11    Look at tests/customtests/custom_methods/model.py for demonstration
     12    """
     13    def __init__(cls, name, bases, attrs):
     14        super(procedure_meta, cls).__init__(name, bases, attrs)
     15        params, returns, vars, body = None, None, None, None
     16        if '__params__' in attrs:
     17            params = attrs['__params__']
     18        if '__returns__' in attrs:
     19            returns = attrs['__returns__']
     20        if '__vars__' in attrs:
     21            vars = attrs['__vars__']
     22        if name != 'Procedure':
     23            assert '__body__' in attrs, "Procedure must have body"   
     24            header = ['CREATE OR ALTER PROCEDURE %s ' % name]
     25            if params:
     26                header.append('(')
     27                header.append(', '.join([param + ' ' + paramtype for param, paramtype in params]))
     28                header.append(')')
     29            if returns:
     30                header.append('\nRETURNS (')
     31                header.append(', '.join([ret + ' ' + rettype for ret, rettype in returns]))
     32                header.append(')')   
     33            cls.__sql__ = [''.join(header)]
     34            cls.__sql__.append('AS')
     35            if vars:
     36                var_declare = ['DECLARE VARIABLE ']
     37                var_declare.append(', '.join([var + ' ' + vartype for var, vartype in vars]))
     38                var_declare.append(';')
     39                cls.__sql__.append(''.join(var_declare))
     40            cls.__sql__.extend(['BEGIN', attrs['__body__'], 'END'])
     41            cls.sql = '\n'.join(cls.__sql__)
     42            cls.params = params
     43            cls.returns = returns
     44            cls.procedure_name = name
     45   
     46    def create_procedure_sql(cls):
     47        return cls.sql
     48   
     49    def execute(cls, cursor, *args):
     50        if args:
     51            return cursor.execute_straight('SELECT %s FROM %s(%s);' %\
     52                    (', '.join("%s" % ret[0] for ret in cls.returns),
     53                     cls.procedure_name,
     54                     ', '.join("?" * len(args))), args)
     55        else:
     56            return cursor.execute_straight('SELECT %s FROM %s;' %\
     57                    (', '.join("%s" % ret[0] for ret in cls.returns),
     58                     cls.procedure_name))
     59
     60    def __call__(cls, *args):
     61        cursor = connection.cursor()
     62        if not cls.returns:
     63            cursor.execute('EXECUTE PROCEDURE "%s" %s;' %\
     64                (cls.procedure_name,
     65                 ' ,'.join("'%s'" % arg for arg in args)))
     66        elif len(cls.returns) == 1:
     67            # Procedure returns value
     68            cursor.callproc(cls.procedure_name, args)
     69            return cursor.fetchone()[0]
     70       
     71        else:
     72            cursor.execute_straight('SELECT %s FROM %s(%s);' %\
     73                (', '.join("%s" % ret[0] for ret in cls.returns),
     74                 cls.procedure_name,
     75                 ', '.join("?" * len(args))), args)       
     76            return cursor.fetchall()
     77
     78class trigger_meta(type):
     79    """
     80    """
     81    def __init__(cls, name, bases, attrs):
     82        super(trigger_meta, cls).__init__(name, bases, attrs)
     83        vars, body, table, mode = None, None, None, None
     84        if '__vars__' in attrs:
     85            vars = attrs['__vars__']
     86        if name != 'Trigger':
     87            assert '__body__' in attrs, "Triggers must have some SQL code"
     88            assert '__table__' in attrs, "Triggers must have the table"
     89            assert '__mode__' in attrs, "Triggers must have a mode, e.g. AFTER INSERT"   
     90            header = 'CREATE OR ALTER TRIGGER %s FOR %s\n%s AS' %\
     91             (name, connection.ops.quote_name(attrs['__table__']), attrs['__mode__'])
     92            cls.__sql__ = [header]
     93            if vars:
     94                var_declare = ['DECLARE VARIABLE ']
     95                var_declare.append(', '.join([var + ' ' + vartype for var, vartype in vars]))
     96                var_declare.append(';')
     97                cls.__sql__.append(''.join(var_declare))
     98            cls.__sql__.extend(['BEGIN', attrs['__body__'], 'END'])
     99            cls.sql = '\n'.join(cls.__sql__)
     100            cls.trigger_name = name
     101    def create_trigger_sql(cls):
     102        return cls.sql
     103
     104class Procedure(object):
     105    __metaclass__ = procedure_meta
     106
     107class Trigger(object):
     108    __metaclass__ = trigger_meta
     109
  • django/db/models/procedures/firemethod.py

     
     1# Stored procedures' Django model method wrapper
     2#
     3# Works only with Python 2.5+
     4# For Python < 2.5 use class notation
     5#
     6#    Example:
     7#
     8#    @firemethod   
     9#    def was_published_today_from_id(self_id='integer'):
     10#        """
     11#        SELECT "id"
     12#        FROM "firemethod_article"
     13#        WHERE "pub_date" = 'today' AND "id" = :self_id
     14#        INTO :result;
     15#        IF (result IS NULL) THEN
     16#            result = 0;
     17#        ELSE
     18#            result = 1;   
     19#        EXIT;
     20#        """
     21#        return ('result', 'integer')
     22#   
     23#    @firemethod   
     24#    def was_published_today_filter():
     25#        """
     26#        FOR SELECT "id", "headline", "pub_date"
     27#        FROM "firemethod_article"
     28#        WHERE "pub_date" = 'today'
     29#        INTO :other_id, :other_headline, :other_pub_date
     30#        DO BEGIN SUSPEND; END
     31#        """
     32#        # Dummy variable just to show how to declare
     33#        declare = ('dummy', 'varchar(200)')
     34#        return ('other_id', 'integer'), ('other_headline', 'varchar(100)'), ('other_pub_date', 'date')
     35#   
     36#   procedures = (was_published_today_filter, was_published_today_from_id)
     37#
     38#
     39# >>> article.was_published_today_from_id(article.pk)
     40# 1
     41# >>> bool(art.was_published_today_from_id(other_article.pk))
     42# False
     43# >>>Article.objects.firefilter(self.was_published_today_filter)
     44# [<Article: Only for today>, <Article: The life is always in a current moment>]
     45
     46from functools import *
     47from django.db.models import Procedure
     48from django.db.models.procedures import procedure_meta
     49
     50def firemethod(f):
     51    @wraps(f)
     52    def wrapper():
     53        params, returns, vars, ret = None, None, None, f()
     54        attrs = {}
     55        if f.func_code.co_varnames:
     56            all_vars = list(f.func_code.co_varnames)
     57            if 'declare' in all_vars:
     58                all_tuples = set([ d for d in f.func_code.co_consts if isinstance(d, tuple) ])
     59                if isinstance(ret[0], basestring):
     60                    ret = [ret]
     61                ret_tuples = set(ret)
     62                all_vars.remove('declare')
     63                vars = []
     64                for var, var_type in all_tuples - ret_tuples:
     65                    vars.append((var, var_type))
     66            if len(all_vars) > 0 and f.func_defaults:
     67                params = []
     68                for key, value in zip(all_vars, f.func_defaults) :
     69                     params.append((key, value))
     70        if ret:
     71            returns = []
     72            if isinstance(ret[0], basestring):
     73                    ret = [ret]
     74            for var, var_type in ret:
     75                returns.append((var, var_type))         
     76        if params:
     77            attrs['__params__'] = params
     78        if returns:
     79            attrs['__returns__'] = returns
     80        if vars:
     81            attrs['__vars__'] = vars
     82        attrs['__body__'] = f.__doc__
     83        return procedure_meta(f.__name__, (Procedure,), attrs)
     84    return wrapper()
     85       
  • 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):
     
    199201        """
    200202        raise NotImplementedError()
    201203
     204    def reference_name(self, r_col, col, r_table, table):
     205        return '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table))))
     206
    202207    def random_function_sql(self):
    203208        """
    204209        Returns a SQL expression that returns a random value.
  • django/db/backends/firebird/base.py

     
     1"""
     2Firebird database backend for Django.
     3
     4Requires KInterbasDB 3.2: http://kinterbasdb.sourceforge.net/
     5The egenix mx (mx.DateTime) is NOT required
     6
     7Database charset should be UNICODE_FSS or UTF8 (FireBird 2.0+)
     8To use UTF8 encoding add FIREBIRD_CHARSET = 'UTF8' to your settings.py
     9UNICODE_FSS works with all versions and uses less memory
     10"""
     11
     12from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
     13import sys
     14try:
     15    import decimal
     16except ImportError:
     17    from django.utils import _decimal as decimal    # for Python 2.3
     18
     19try:
     20    import kinterbasdb as Database
     21except ImportError, e:
     22    from django.core.exceptions import ImproperlyConfigured
     23    raise ImproperlyConfigured, "Error loading KInterbasDB module: %s" % e
     24
     25DatabaseError = Database.DatabaseError
     26IntegrityError = Database.IntegrityError
     27
     28class DatabaseFeatures(BaseDatabaseFeatures):
     29    inline_fk_references = False
     30    needs_datetime_string_cast = False
     31    needs_upper_for_iops = True
     32    quote_autofields = True
     33    uses_custom_field = True
     34    uses_custom_queryset = True
     35
     36################################################################################
     37# Database operations (db.connection.ops) 
     38class DatabaseOperations(BaseDatabaseOperations):
     39    """
     40    This class encapsulates all backend-specific differences, such as the way
     41    a backend performs ordering or calculates the ID of a recently-inserted
     42    row.
     43    """
     44    # Utility ops: names, version, page size etc.:
     45    _max_name_length = 31
     46    def __init__(self):
     47        self._firebird_version = None
     48        self._page_size = None
     49   
     50    def get_generator_name(self, name):
     51        return '%s$G' % util.truncate_name(name.strip('"'), self._max_name_length-2).upper()
     52       
     53    def get_trigger_name(self, name):
     54        return '%s$T' % util.truncate_name(name.strip('"'), self._max_name_length-2).upper()
     55   
     56    def _get_firebird_version(self):
     57        if self._firebird_version is None:
     58            from django.db import connection
     59            self._firebird_version = [int(val) for val in connection.server_version.split()[-1].split('.')]
     60        return self._firebird_version
     61    firebird_version = property(_get_firebird_version)
     62 
     63    def reference_name(self, r_col, col, r_table, table):
     64        base_name = util.truncate_name('%s$%s' % (r_col, col), self._max_name_length-5)
     65        return ('%s$%x' % (base_name, abs(hash((r_table, table))))).upper()
     66   
     67    def _get_page_size(self):
     68        if self._page_size is None:
     69            from django.db import connection
     70            self._page_size = connection.database_info(Database.isc_info_page_size, 'i')
     71        return self._page_size
     72    page_size = property(_get_page_size)
     73   
     74    def _get_index_limit(self):
     75        if self.firebird_version[0] < 2:
     76            self._index_limit = 252
     77        else:
     78            page_size = self._get_page_size()
     79            self._index_limit = page_size/4
     80        return self._index_limit
     81    index_limit = property(_get_index_limit)
     82   
     83    def max_name_length(self):
     84        return self._max_name_length
     85   
     86    def quote_name(self, name):
     87        name = '"%s"' % util.truncate_name(name.strip('"'), self._max_name_length)
     88        return name
     89   
     90    def quote_id_plus_number(self, name):
     91        try:
     92            return '"%s" + %s' % tuple(s.strip() for s in name.strip('"').split('+'))
     93        except:
     94            return self.quote_name(name)
     95   
     96    def field_cast_sql(self, db_type):
     97        return '%s'
     98
     99    ############################################################################
     100    # Basic SQL ops:   
     101    def last_insert_id(self, cursor, table_name, pk_name=None):
     102        generator_name = self.get_generator_name(table_name)
     103        cursor.execute('SELECT GEN_ID(%s, 0) from RDB$DATABASE' % generator_name)
     104        return cursor.fetchone()[0]
     105
     106    def date_extract_sql(self, lookup_type, column_name):
     107        # lookup_type is 'year', 'month', 'day'
     108        return "EXTRACT(%s FROM %s)" % (lookup_type, column_name)
     109
     110    def date_trunc_sql(self, lookup_type, column_name):
     111        if lookup_type == 'year':
     112             sql = "EXTRACT(year FROM %s)||'-01-01 00:00:00'" % column_name
     113        elif lookup_type == 'month':
     114            sql = "EXTRACT(year FROM %s)||'-'||EXTRACT(month FROM %s)||'-01 00:00:00'" % (column_name, column_name)
     115        elif lookup_type == 'day':
     116            sql = "EXTRACT(year FROM %s)||'-'||EXTRACT(month FROM %s)||'-'||EXTRACT(day FROM %s)||' 00:00:00'" % (column_name, column_name, column_name)
     117        return "CAST(%s AS TIMESTAMP)" % sql
     118
     119    def datetime_cast_sql(self):
     120        return None
     121
     122    def drop_sequence_sql(self, table):
     123        return "DROP GENERATOR %s;" % self.get_generator_name(table)
     124   
     125    def drop_foreignkey_sql(self):
     126        return "DROP CONSTRAINT"
     127       
     128    def limit_offset_sql(self, limit, offset=None):
     129        # limits are handled in custom FirebirdQuerySet
     130        assert False, 'Limits are handled in a different way in Firebird'
     131        return ""
     132
     133    def random_function_sql(self):
     134        return "rand()"
     135
     136    def pk_default_value(self):
     137        """
     138        Returns the value to use during an INSERT statement to specify that
     139        the field should use its default value.
     140        """
     141        return 'NULL'
     142   
     143    def start_transaction_sql(self):
     144        return ""
     145
     146    def fulltext_search_sql(self, field_name):
     147        # We use varchar for TextFields so this is possible
     148        # Look at http://www.volny.cz/iprenosil/interbase/ip_ib_strings.htm
     149        return '%%s CONTAINING %s' % self.quote_name(field_name)
     150
     151    ############################################################################
     152    # Advanced SQL ops:
     153    def autoinc_sql(self, style, table_name, column_name):
     154        """
     155        To simulate auto-incrementing primary keys in Firebird, we have to
     156        create a generator and a trigger.
     157   
     158        Create the generators and triggers names based only on table name
     159        since django only support one auto field per model
     160        """
     161   
     162        generator_name = self.get_generator_name(table_name)
     163        trigger_name = self.get_trigger_name(table_name)
     164        column_name = self.quote_name(column_name)
     165        table_name = self.quote_name(table_name)
     166       
     167        generator_sql = "%s %s;" % ( style.SQL_KEYWORD('CREATE GENERATOR'),
     168                                     generator_name)     
     169        trigger_sql = "\n".join([
     170            "%s %s %s %s" % ( \
     171            style.SQL_KEYWORD('CREATE TRIGGER'), trigger_name, style.SQL_KEYWORD('FOR'),
     172            style.SQL_TABLE(table_name)),
     173            "%s 0 %s" % (style.SQL_KEYWORD('ACTIVE BEFORE INSERT POSITION'), style.SQL_KEYWORD('AS')),
     174            style.SQL_KEYWORD('BEGIN'),
     175            "  %s ((%s.%s %s) %s (%s.%s = 0)) %s" % ( \
     176                style.SQL_KEYWORD('IF'),
     177                style.SQL_KEYWORD('NEW'), style.SQL_FIELD(column_name), style.SQL_KEYWORD('IS NULL'),
     178                style.SQL_KEYWORD('OR'), style.SQL_KEYWORD('NEW'), style.SQL_FIELD(column_name),
     179                style.SQL_KEYWORD('THEN')
     180            ),
     181            "  %s" % style.SQL_KEYWORD('BEGIN'),
     182            "    %s.%s = %s(%s, 1);" % ( \
     183                style.SQL_KEYWORD('NEW'), style.SQL_FIELD(column_name),
     184                style.SQL_KEYWORD('GEN_ID'), generator_name
     185            ),
     186            "  %s" % style.SQL_KEYWORD('END'),
     187            "%s;" % style.SQL_KEYWORD('END')])
     188        return (generator_sql, trigger_sql)
     189   
     190    def sequence_reset_sql(self, style, model_list):
     191        from django.db import models
     192        output = []
     193        sql = ['%s %s %s' % (style.SQL_KEYWORD('CREATE OR ALTER PROCEDURE'),
     194                             style.SQL_TABLE('"GENERATOR_RESET"'),
     195                             style.SQL_KEYWORD('AS'))]
     196        sql.append('%s %s' % (style.SQL_KEYWORD('DECLARE VARIABLE'), style.SQL_COLTYPE('start_val integer;')))
     197        sql.append('%s %s' % (style.SQL_KEYWORD('DECLARE VARIABLE'), style.SQL_COLTYPE('gen_val integer;')))
     198        sql.append('\t%s' % style.SQL_KEYWORD('BEGIN'))
     199        sql.append('\t\t%s %s %s %s %s %s;' % (style.SQL_KEYWORD('SELECT MAX'), style.SQL_FIELD('(%(col)s)'),
     200                                           style.SQL_KEYWORD('FROM'), style.SQL_TABLE('%(table)s'),
     201                                           style.SQL_KEYWORD('INTO'), style.SQL_COLTYPE(':start_val')))
     202        sql.append('\t\t%s (%s %s) %s' % (style.SQL_KEYWORD('IF'), style.SQL_COLTYPE('start_val'),
     203                                    style.SQL_KEYWORD('IS NULL'), style.SQL_KEYWORD('THEN')))
     204        sql.append('\t\t\t%s = %s(%s, 1 - %s(%s, 0));' %\
     205            (style.SQL_COLTYPE('gen_val'), style.SQL_KEYWORD('GEN_ID'), style.SQL_TABLE('%(gen)s'),
     206             style.SQL_KEYWORD('GEN_ID'), style.SQL_TABLE('%(gen)s')))
     207        sql.append('\t\t%s' % style.SQL_KEYWORD('ELSE'))
     208        sql.append('\t\t\t%s = %s(%s, %s - %s(%s, 0));' %\
     209            (style.SQL_COLTYPE('gen_val'), style.SQL_KEYWORD('GEN_ID'),
     210             style.SQL_TABLE('%(gen)s'), style.SQL_COLTYPE('start_val'), style.SQL_KEYWORD('GEN_ID'),
     211             style.SQL_TABLE('%(gen)s')))
     212        sql.append('\t\t%s;' % style.SQL_KEYWORD('EXIT'))
     213        sql.append('%s;' % style.SQL_KEYWORD('END'))
     214        sql ="\n".join(sql)
     215        for model in model_list:
     216            for f in model._meta.fields:
     217                if isinstance(f, models.AutoField):
     218                    generator_name = self.get_generator_name(model._meta.db_table)
     219                    column_name = self.quote_name(f.db_column or f.name)
     220                    table_name = self.quote_name(model._meta.db_table)
     221                    output.append(sql % {'col' : column_name, 'table' : table_name, 'gen' : generator_name})
     222                    output.append('%s %s;' % (style.SQL_KEYWORD('EXECUTE PROCEDURE'),
     223                                              style.SQL_TABLE('"GENERATOR_RESET"')))
     224                    break # Only one AutoField is allowed per model, so don't bother continuing.
     225            for f in model._meta.many_to_many:
     226                generator_name = self.get_generator_name(f.m2m_db_table())
     227                table_name = self.quote_name(f.m2m_db_table())
     228                column_name = '"id"'
     229                output.append(sql % {'col' : column_name, 'table' : table_name, 'gen' : generator_name})
     230                output.append('%s %s;' % (style.SQL_KEYWORD('EXECUTE PROCEDURE'),
     231                                          style.SQL_TABLE('"GENERATOR_RESET"')))
     232        return output
     233   
     234    def sql_flush(self, style, tables, sequences):
     235        if tables:
     236            sql = ['%s %s %s;' % \
     237                    (style.SQL_KEYWORD('DELETE'),
     238                     style.SQL_KEYWORD('FROM'),
     239                     style.SQL_TABLE(self.quote_name(table))
     240                     ) for table in tables]
     241            for generator_info in sequences:
     242                table_name = generator_info['table']
     243                query = "%s %s %s 0;" % (style.SQL_KEYWORD('SET GENERATOR'),
     244                    self.get_generator_name(table_name), style.SQL_KEYWORD('TO'))
     245                sql.append(query)
     246            return sql
     247        else:
     248            return []
     249           
     250    ############################################################################
     251    # Custom classes
     252    def field_class(this, DefaultField):
     253        from django.db import connection
     254        from django.db.models.fields import prep_for_like_query
     255        class FirebirdField(DefaultField):
     256            def get_db_prep_lookup(self, lookup_type, value):       
     257                "Returns field's value prepared for database lookup."
     258                if lookup_type in ('exact', 'regex', 'iregex', 'gt', 'gte', 'lt',
     259                    'lte', 'month', 'day', 'search', 'icontains',
     260                    'startswith', 'istartswith'):
     261                    return [value]
     262                elif lookup_type in ('range', 'in'):
     263                    return value
     264                elif lookup_type in ('contains',):
     265                    return ["%%%s%%" % prep_for_like_query(value)]
     266                elif lookup_type == 'iexact':
     267                    return [prep_for_like_query(value)]
     268                elif lookup_type in ('endswith', 'iendswith'):
     269                    return ["%%%s" % prep_for_like_query(value)]
     270                elif lookup_type == 'isnull':
     271                    return []
     272                elif lookup_type == 'year':
     273                    try:
     274                        value = int(value)
     275                    except ValueError:
     276                        raise ValueError("The __year lookup type requires an integer argument")
     277                    return ['%s-01-01 00:00:00' % value, '%s-12-31 23:59:59.999999' % value]
     278                raise TypeError("Field has invalid lookup: %s" % lookup_type)
     279        return FirebirdField
     280
     281    def query_set_class(this, DefaultQuerySet):
     282        from django.db import connection
     283        from django.db.models.query import EmptyResultSet, GET_ITERATOR_CHUNK_SIZE
     284       
     285        class FirebirdQuerySet(DefaultQuerySet):
     286            __firefilter__ = None
     287            ___fireargs__ = ()
     288            def _get_sql_clause(self):
     289                from django.db.models.query import SortedDict, handle_legacy_orderlist, orderfield2column, fill_table_cache
     290                qn = this.quote_name
     291                opts = self.model._meta
     292
     293                # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z.
     294                select = ["%s.%s" % (qn(opts.db_table), qn(f.column)) for f in opts.fields]
     295                tables = [qn(t) for t in self._tables]
     296                joins = SortedDict()
     297                where = self._where[:]
     298                params = self._params[:]
     299
     300                # Convert self._filters into SQL.
     301                joins2, where2, params2 = self._filters.get_sql(opts)
     302                joins.update(joins2)
     303                where.extend(where2)
     304                params.extend(params2)
     305
     306                # Add additional tables and WHERE clauses based on select_related.
     307                if self._select_related:
     308                    fill_table_cache(opts, select, tables, where,
     309                                     old_prefix=opts.db_table,
     310                                     cache_tables_seen=[opts.db_table],
     311                                     max_depth=self._max_related_depth)
     312               
     313                # Add any additional SELECTs.
     314                if self._select:
     315                    select.extend([('(%s AS %s') % (qn(s[1]), qn(s[0])) for s in self._select.items()])
     316
     317                # Start composing the body of the SQL statement.
     318                sql = [" FROM", qn(opts.db_table)]
     319
     320                # Compose the join dictionary into SQL describing the joins.
     321                if joins:
     322                    sql.append(" ".join(["%s %s %s ON %s" % (join_type, table, alias, condition)
     323                                    for (alias, (table, join_type, condition)) in joins.items()]))
     324
     325                # Compose the tables clause into SQL.
     326                if tables:
     327                    sql.append(", " + ", ".join(tables))
     328
     329                # Compose the where clause into SQL.
     330                if where:
     331                    sql.append(where and "WHERE " + " AND ".join(where))
     332
     333                # ORDER BY clause
     334                order_by = []
     335                if self._order_by is not None:
     336                    ordering_to_use = self._order_by
     337                else:
     338                    ordering_to_use = opts.ordering
     339                for f in handle_legacy_orderlist(ordering_to_use):
     340                    if f == '?': # Special case.
     341                        order_by.append(connection.ops.random_function_sql())
     342                    else:
     343                        if f.startswith('-'):
     344                            col_name = f[1:]
     345                            order = "DESC"
     346                        else:
     347                            col_name = f
     348                            order = "ASC"
     349                        if "." in col_name:
     350                            table_prefix, col_name = col_name.split('.', 1)
     351                            table_prefix = qn(table_prefix) + '.'
     352                        else:
     353                            # Use the database table as a column prefix if it wasn't given,
     354                            # and if the requested column isn't a custom SELECT.
     355                            if "." not in col_name and col_name not in (self._select or ()):
     356                                table_prefix = qn(opts.db_table) + '.'
     357                            else:
     358                                table_prefix = ''
     359                        order_by.append('%s%s %s' % \
     360                            (table_prefix, qn(orderfield2column(col_name, opts)), order))
     361                if order_by:
     362                    sql.append("ORDER BY " + ", ".join(order_by))
     363
     364                return select, " ".join(sql), params
     365           
     366            def iterator(self):
     367                "Performs the SELECT database lookup of this QuerySet."
     368                from django.db.models.query import get_cached_row
     369                if self.__firefilter__:
     370                    extra_select = self._select.items()
     371                    cursor = connection.cursor()
     372                    self.__firefilter__.execute(cursor, *self.__fireargs__)
     373                else:   
     374                    try:
     375                        select, sql, params = self._get_sql_clause()
     376                    except EmptyResultSet:
     377                        raise StopIteration
     378                       
     379                    # self._select is a dictionary, and dictionaries' key order is
     380                    # undefined, so we convert it to a list of tuples.
     381                    extra_select = self._select.items()
     382                    cursor = connection.cursor()
     383                    limit_offset_before = ""
     384                    if self._limit is not None:
     385                        limit_offset_before += "FIRST %s " % self._limit
     386                        if self._offset:
     387                            limit_offset_before += "SKIP %s " % self._offset
     388                    else:
     389                        assert self._offset is None, "'offset' is not allowed without 'limit'"
     390                    cursor.execute("SELECT " + limit_offset_before + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
     391                   
     392                fill_cache = self._select_related
     393                fields = self.model._meta.fields
     394                index_end = len(fields)
     395                while 1:
     396                    rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
     397                    if not rows:
     398                        raise StopIteration
     399                    for row in rows:
     400                        row = self.resolve_columns(row, fields)
     401                        if fill_cache:
     402                            obj, index_end = get_cached_row(klass=self.model, row=row,
     403                                                            index_start=0, max_depth=self._max_related_depth)
     404                        else:
     405                            obj = self.model(*row[:index_end])
     406                        for i, k in enumerate(extra_select):
     407                            setattr(obj, k[0], row[index_end+i])
     408                        yield obj
     409           
     410            def resolve_columns(self, row, fields=()):
     411                from django.db.models.fields import DateField, DateTimeField, \
     412                    TimeField, BooleanField, NullBooleanField, DecimalField, Field
     413                values = []
     414                for value, field in map(None, row, fields):
     415                    # Convert 1 or 0 to True or False
     416                    if value in (1, 0) and isinstance(field, (BooleanField, NullBooleanField)):
     417                        value = bool(value)
     418
     419                    values.append(value)
     420                return values
     421           
     422            def firefilter(self, proc, *args, **kwargs):
     423                assert not kwargs, "Keyword arguments not supported with stored procedures"
     424                if len(args) > 0:
     425                    assert self._limit is None and self._offset is None, \
     426                        "Cannot filter a query once a slice has been taken."
     427                clone = self._clone()
     428                clone.__firefilter__ = proc
     429                clone.__fireargs__ = args
     430                return clone
     431               
     432            def extra(self, select=None, where=None, params=None, tables=None):
     433                assert self._limit is None and self._offset is None, \
     434                        "Cannot change a query once a slice has been taken"
     435                clone = self._clone()
     436                qn = this.quote_name
     437                if select: clone._select.update(select)
     438                if where:
     439                    qn_where = []
     440                    for where_item in where:
     441                        try:
     442                            table, col_exact = where_item.split(".")
     443                            col, value = col_exact.split("=")
     444                            where_item = "%s.%s = %s" % (qn(table.strip()),
     445                                qn(col.strip()), value.strip())
     446                        except:
     447                            try:
     448                                table, value = where_item.split("=")
     449                                where_item = "%s = %s" % (qn(table.strip()), qn(value.strip()))
     450                            except:
     451                                raise TypeError, "Can't understand extra WHERE clause: %s" % where
     452                        qn_where.append(where_item)
     453                    clone._where.extend(qn_where)
     454                if params: clone._params.extend(params)
     455                if tables: clone._tables.extend(tables)
     456                return clone
     457               
     458        return FirebirdQuerySet
     459
     460################################################################################
     461# Cursor wrapper       
     462class FirebirdCursorWrapper(object):
     463    """
     464    Django uses "format" ('%s') style placeholders, but firebird uses "qmark" ('?') style.
     465    This fixes it -- but note that if you want to use a literal "%s" in a query,
     466    you'll need to use "%%s".
     467   
     468    We also do all automatic type conversions here.
     469    """
     470    import kinterbasdb.typeconv_datetime_stdlib as tc_dt
     471    import kinterbasdb.typeconv_fixed_decimal as tc_fd
     472    import kinterbasdb.typeconv_text_unicode as tc_tu
     473    import django.utils.encoding as dj_ue
     474
     475    def ascii_conv_in(self, text):
     476        if text is not None: 
     477            return self.dj_ue.smart_str(text, 'ascii')
     478   
     479    def ascii_conv_out(self, text):
     480        if text is not None:
     481            return self.dj_ue.smart_unicode(text)   
     482
     483    def blob_conv_in(self, text):
     484        return self.tc_tu.unicode_conv_in((self.dj_ue.smart_unicode(text), self.FB_CHARSET_CODE))
     485
     486    def blob_conv_out(self, text):
     487        return self.tc_tu.unicode_conv_out((text, self.FB_CHARSET_CODE))   
     488
     489    def fixed_conv_in(self, (val, scale)):
     490        if val is not None:
     491            if isinstance(val, basestring):
     492                val = decimal.Decimal(val)
     493            return self.tc_fd.fixed_conv_in_precise((val, scale))
     494
     495    def timestamp_conv_in(self, timestamp):
     496        if isinstance(timestamp, basestring):
     497            #Replaces 6 digits microseconds to 4 digits allowed in Firebird
     498            timestamp = timestamp[:24]
     499        return self.tc_dt.timestamp_conv_in(timestamp)
     500
     501    def time_conv_in(self, value):
     502        import datetime
     503        if isinstance(value, datetime.datetime):
     504            value = datetime.time(value.hour, value.minute, value.second, value.microsecond)
     505        return self.tc_dt.time_conv_in(value)       
     506
     507    def unicode_conv_in(self, text):
     508        if text[0] is not None:
     509            return self.tc_tu.unicode_conv_in((self.dj_ue.smart_unicode(text[0]), self.FB_CHARSET_CODE))
     510
     511    def __init__(self, cursor, connection):   
     512        self.cursor = cursor
     513        self._connection = connection
     514        self._statement = None #prepared statement
     515        self.FB_CHARSET_CODE = 3 #UNICODE_FSS
     516        if connection.charset == 'UTF8':
     517            self.FB_CHARSET_CODE = 4 # UTF-8 with Firebird 2.0+
     518        self.cursor.set_type_trans_in({
     519            'DATE':             self.tc_dt.date_conv_in,
     520            'TIME':             self.time_conv_in,
     521            'TIMESTAMP':        self.timestamp_conv_in,
     522            'FIXED':            self.fixed_conv_in,
     523            'TEXT':             self.ascii_conv_in,
     524            'TEXT_UNICODE':     self.unicode_conv_in,
     525            'BLOB':             self.blob_conv_in
     526        })
     527        self.cursor.set_type_trans_out({
     528            'DATE':             self.tc_dt.date_conv_out,
     529            'TIME':             self.tc_dt.time_conv_out,
     530            'TIMESTAMP':        self.tc_dt.timestamp_conv_out,
     531            'FIXED':            self.tc_fd.fixed_conv_out_precise,
     532            'TEXT':             self.ascii_conv_out,
     533            'TEXT_UNICODE':     self.tc_tu.unicode_conv_out,
     534            'BLOB':             self.blob_conv_out
     535        })
     536   
     537    def execute_immediate(self, query, params=()):
     538        query = query % tuple(params)
     539        self._connection.execute_immediate(query)
     540   
     541    def prepare(self, query):
     542        """
     543        Returns prepared statement for use with execute_prepared
     544        http://kinterbasdb.sourceforge.net/dist_docs/usage.html#adv_prepared_statements
     545        """
     546        query.replace("%s", "?")
     547        return self.cursor.prep(query)
     548   
     549    def execute_prepared(self, statement, params):
     550        return self.cursor.execute(statement, params)
     551   
     552    def execute_straight(self, query, params=()):
     553        """
     554        Kinterbasdb-style execute with '?' instead of '%s'
     555        """
     556        try:
     557            return self.cursor.execute(query, params)
     558        except Database.ProgrammingError, e:
     559            err_no = int(str(e).split()[0].strip(',()'))
     560            output = ["Execute query error. FB error No. %i" % err_no]
     561            output.extend(str(e).split("'")[1].split('\\n'))
     562            output.append("Query:")
     563            output.append(query)
     564            output.append("Parameters:")
     565            output.append(str(params))
     566            raise Database.ProgrammingError, "\n".join(output)
     567   
     568    def execute(self, query, params=()):
     569        cquery = self.convert_query(query, len(params))
     570        if self._get_query() != cquery:
     571            try:
     572                self._statement = self.cursor.prep(cquery)
     573            except Database.ProgrammingError, e:
     574                output = ["Prepare query error."]
     575                output.extend(str(e).split("'")[1].split('\\n'))
     576                output.append("Query:")
     577                output.append(cquery)
     578                raise Database.ProgrammingError, "\n".join(output)
     579        try:
     580            return self.cursor.execute(self._statement, params)
     581        except Database.ProgrammingError, e:
     582            err_no = int(str(e).split()[0].strip(',()'))
     583            output = ["Execute query error. FB error No. %i" % err_no]
     584            output.extend(str(e).split("'")[1].split('\\n'))
     585            output.append("Query:")
     586            output.append(cquery)
     587            output.append("Parameters:")
     588            output.append(str(params))
     589            raise Database.ProgrammingError, "\n".join(output)
     590   
     591    def executemany(self, query, param_list):
     592        try:
     593            cquery = self.convert_query(query, len(param_list[0]))
     594        except IndexError:
     595            return None
     596        if self._get_query() != cquery:
     597            self._statement = self.cursor.prep(cquery)
     598        return self.cursor.executemany(self._statement, param_list)
     599
     600    def convert_query(self, query, num_params):
     601        try:
     602            return query % tuple("?" * num_params)
     603        except TypeError, e:
     604            print query, num_params
     605            raise TypeError, e
     606   
     607    def _get_query(self):
     608        if self._statement:
     609            return self._statement.sql
     610   
     611    def __getattr__(self, attr):
     612        if attr in self.__dict__:
     613            return self.__dict__[attr]
     614        else:
     615            return getattr(self.cursor, attr)
     616
     617################################################################################
     618# DatabaseWrapper(db.connection) 
     619class DatabaseWrapper(BaseDatabaseWrapper):
     620    features = DatabaseFeatures()
     621    ops = DatabaseOperations()
     622    operators = {
     623        'exact': '= %s',
     624        'iexact': '= UPPER(%s)',
     625        'contains': "LIKE %s ESCAPE'\\'",
     626        'icontains': 'CONTAINING %s', #case is ignored
     627        'gt': '> %s',
     628        'gte': '>= %s',
     629        'lt': '< %s',
     630        'lte': '<= %s',
     631        'startswith': 'STARTING WITH %s', #looks to be faster then LIKE
     632        'endswith': "LIKE %s ESCAPE'\\'",
     633        'istartswith': 'STARTING WITH UPPER(%s)',
     634        'iendswith': "LIKE UPPER(%s) ESCAPE'\\'"
     635    }
     636   
     637    def __init__(self, **kwargs):
     638        from django.conf import settings
     639        super(DatabaseWrapper, self).__init__(**kwargs)
     640        self. _current_cursor = None
     641        self._raw_cursor = None
     642        self.charset = 'UNICODE_FSS'
     643        self.FB_MAX_VARCHAR = 10921 #32765 MAX /3
     644        self.BYTES_PER_DEFAULT_CHAR = 3
     645        if hasattr(settings, 'FIREBIRD_CHARSET'):
     646            if settings.FIREBIRD_CHARSET == 'UTF8':
     647                self.charset = 'UTF8'
     648                self.FB_MAX_VARCHAR = 8191 #32765 MAX /4
     649                self.BYTES_PER_DEFAULT_CHAR = 4
     650       
     651    def _connect(self, settings):
     652        if settings.DATABASE_NAME == '':
     653            from django.core.exceptions import ImproperlyConfigured
     654            raise ImproperlyConfigured, "You need to specify DATABASE_NAME in your Django settings file."
     655        kwargs = {'charset' : self.charset }
     656        if settings.DATABASE_HOST:
     657            kwargs['dsn'] = "%s:%s" % (settings.DATABASE_HOST, settings.DATABASE_NAME)
     658        else:
     659            kwargs['dsn'] = "localhost:%s" % settings.DATABASE_NAME
     660        if settings.DATABASE_USER:
     661            kwargs['user'] = settings.DATABASE_USER
     662        if settings.DATABASE_PASSWORD:
     663            kwargs['password'] = settings.DATABASE_PASSWORD
     664        self.connection = Database.connect(**kwargs)
     665        assert self.connection.charset == self.charset
     666       
     667    def cursor(self):
     668        from django.conf import settings
     669        cursor = self._cursor(settings)
     670        if settings.DEBUG:
     671            self._debug_cursor = self.make_debug_cursor(cursor)
     672            return self._debug_cursor
     673        return cursor
     674   
     675    def _cursor(self, settings):
     676        if self.connection is None:
     677            self._connect(settings)
     678        cursor = self.connection.cursor()
     679        self._raw_cursor = cursor
     680        cursor = FirebirdCursorWrapper(cursor, self)
     681        self._current_cursor = cursor
     682        return cursor
     683
     684    def __getattr__(self, attr):
     685        if attr in self.__dict__:
     686            return self.__dict__[attr]
     687        else:
     688            return getattr(self.connection, attr)
     689
  • 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
     4qn = quote_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()) 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_straight("""
     19        SELECT DISTINCT R.RDB$FIELD_NAME AS FNAME,
     20                  F.RDB$FIELD_TYPE AS FTYPE,
     21                  F.RDB$CHARACTER_LENGTH AS FCHARLENGTH,
     22                  F.RDB$FIELD_LENGTH AS FLENGTH,
     23                  F.RDB$FIELD_PRECISION AS FPRECISION,
     24                  F.RDB$FIELD_SCALE AS FSCALE,
     25                  R.RDB$NULL_FLAG AS NULL_FLAG,
     26                  R.RDB$FIELD_POSITION
     27        FROM RDB$RELATION_FIELDS R
     28             JOIN RDB$FIELDS F ON R.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME
     29        WHERE F.RDB$SYSTEM_FLAG=0 and R.RDB$RELATION_NAME STARTING WITH ?
     30        ORDER BY R.RDB$FIELD_POSITION
     31    """, (qn(table_name).strip('"'),))
     32    return [(row[0].rstrip(), row[1], row[2] or 0, row[3], row[4], row[5], row[6] and True or False) for row in cursor.fetchall()]
     33
     34
     35def get_relations(cursor, table_name):
     36    """
     37    Returns a dictionary of {field_index: (field_index_other_table, other_table)}
     38    representing all relationships to the given table. Indexes are 0-based.
     39    """
     40    cursor.execute_straight("""
     41        SELECT seg.rdb$field_name, seg_ref.rdb$field_name, idx_ref.rdb$relation_name
     42        FROM rdb$indices idx
     43        INNER JOIN rdb$index_segments seg
     44            ON seg.rdb$index_name = idx.rdb$index_name
     45        INNER JOIN rdb$indices idx_ref
     46            ON idx_ref.rdb$index_name = idx.rdb$foreign_key
     47        INNER JOIN rdb$index_segments seg_ref
     48            ON seg_ref.rdb$index_name = idx_ref.rdb$index_name
     49        WHERE idx.rdb$relation_name STARTING WITH ?
     50            AND idx.rdb$foreign_key IS NOT NULL""", [qn(table_name).strip('"')])
     51
     52    relations = {}
     53    for row in cursor.fetchall():
     54        relations[row[0].rstrip()] = (row[1].strip(), row[2].strip())
     55    return relations
     56
     57
     58def get_indexes(cursor, table_name):
     59    """
     60    Returns a dictionary of fieldname -> infodict for the given table,
     61    where each infodict is in the format:
     62        {'primary_key': boolean representing whether it's the primary key,
     63         'unique': boolean representing whether it's a unique index}
     64    """
     65
     66    # This query retrieves each field name and index type on the given table.
     67    cursor.execute("""
     68        SELECT seg.RDB$FIELD_NAME, const.RDB$CONSTRAINT_TYPE
     69        FROM RDB$RELATION_CONSTRAINTS const
     70        LEFT JOIN RDB$INDEX_SEGMENTS seg
     71            ON seg.RDB$INDEX_NAME = const.RDB$INDEX_NAME
     72        WHERE const.RDB$RELATION_NAME STARTING WITH ?
     73        ORDER BY seg.RDB$FIELD_POSITION)""",
     74        [qn(table_name).strip('"')])
     75    indexes = {}
     76    for row in cursor.fetchall():
     77        indexes[row[0]] = {'primary_key': row[1].startswith('PRIMARY'),
     78                           'unique': row[1].startswith('UNIQUE')}   
     79    return indexes
     80
     81# Maps type codes to Django Field types.
     82DATA_TYPES_REVERSE = {
     83   261: 'LargeTextField',
     84    14:  'CharField',
     85    40:  'CharField',
     86    11:  'FloatField',
     87    27:  'FloatField',
     88    10:  'FloatField',
     89    16:  'IntegerField',
     90     8:   'IntegerField',
     91     9:   'IntegerField',
     92     7:   'SmallIntegerField',
     93    12:  'DateField',
     94    13:  'TimeField',
     95    35:  'DateTimeField',
     96    37: 'CharField'
     97}
  • 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, os.path, codecs
     12try:
     13    set
     14except NameError:
     15    from sets import Set as set   # Python 2.3 fallback
     16
     17# Setting TEST_MODE to 1 enables cascading deletes (for table flush) which are dangerous
     18# Setting TEST_MODE to 2 disables strict FK constraints (for forward/post references)
     19# Setting TEST_MODE to 0 is the most secure option (it even fails some official Django tests  because of it)
     20TEST_MODE = 0
     21if 'FB_DJANGO_TEST_MODE' in os.environ:
     22    TEST_MODE = int(os.environ['FB_DJANGO_TEST_MODE'])
     23
     24DATA_TYPES = {
     25    'AutoField':                     '"AutoField"',
     26    'BooleanField':                  '"BooleanField"',
     27    'CharField':                     'varchar(%(max_length)s)',
     28    'CommaSeparatedIntegerField':    'varchar(%(max_length)s) CHARACTER SET ASCII',
     29    'DateField':                     '"DateField"',
     30    'DateTimeField':                 '"DateTimeField"',
     31    'DecimalField':                  'numeric(%(max_digits)s, %(decimal_places)s)',
     32    'DefaultCharField':              '"CharField"',
     33    'FileField':                     'varchar(%(max_length)s)',
     34    'FilePathField':                 'varchar(%(max_length)s)',
     35    'FloatField':                    '"FloatField"',
     36    'ImageField':                    '"varchar(%(max_length)s)"',
     37    'IntegerField':                  '"IntegerField"',
     38    'IPAddressField':                'varchar(15) CHARACTER SET ASCII',
     39    'NullBooleanField':              '"NullBooleanField"',
     40    'OneToOneField':                 '"OneToOneField"',
     41    'PhoneNumberField':              '"PhoneNumberField"',
     42    'PositiveIntegerField':          '"PositiveIntegerField"',
     43    'PositiveSmallIntegerField':     '"PositiveSmallIntegerField"',
     44    'SlugField':                     'varchar(%(max_length)s)',
     45    'SmallIntegerField':             '"SmallIntegerField"',
     46    'LargeTextField':                '"LargeTextField"',
     47    'TextField':                     '"TextField"',
     48    'TimeField':                     '"TimeField"',
     49    'URLField':                      'varchar(%(max_length)s) CHARACTER SET ASCII',
     50    'USStateField':                  '"USStateField"'
     51}
     52     
     53PYTHON_TO_FB_ENCODING_MAP = {
     54    'ascii':        'ASCII',
     55    'utf_8':        connection.charset,
     56    'shift_jis':    'SJIS_0208',
     57    'euc_jp':       'EUCJ_0208',
     58    'cp737':        'DOS737',
     59    'cp437':        'DOS437',
     60    'cp850':        'DOS850',
     61    'cp865':        'DOS865',
     62    'cp860':        'DOS860',
     63    'cp863':        'DOS863',
     64    'cp775':        'DOS775',
     65    'cp862':        'DOS862',
     66    'cp864':        'DOS864',
     67    'iso8859_1':    'ISO8859_1',
     68    'iso8859_2':    'ISO8859_2',
     69    'iso8859_3':    'ISO8859_3',
     70    'iso8859_4':    'ISO8859_4',
     71    'iso8859_5':    'ISO8859_5',
     72    'iso8859_6':    'ISO8859_6',
     73    'iso8859_7':    'ISO8859_7',
     74    'iso8859_8':    'ISO8859_8',
     75    'iso8859_9':    'ISO8859_9',
     76    'iso8859_13':   'ISO8859_13',
     77    'euc_kr':       'KSC_5601',
     78    'cp852':        'DOS852',
     79    'cp857':        'DOS857',
     80    'cp861':        'DOS861',
     81    'cp866':        'DOS866',
     82    'cp869':        'DOS869',
     83    'cp1250':       'WIN1250',
     84    'cp1251':       'WIN1251',
     85    'cp1252':       'WIN1252',
     86    'cp1253':       'WIN1253',
     87    'cp1254':       'WIN1254',
     88    'big5':         'BIG_5',
     89    'gb2312':       'GB_2312',
     90    'cp1255':       'WIN1255',
     91    'cp1256':       'WIN1256',
     92    'cp1257':       'WIN1257',
     93    'koi8_r':       'KOI8-R',
     94    'koi8_u':       'KOI8-U',
     95    'cp1258':       'WIN1258'
     96  }
     97
     98def get_data_size(data_type, max_length = 100):
     99    char_bytes = connection.BYTES_PER_DEFAULT_CHAR
     100    size_map = {
     101        'AutoField':                     8,
     102        'BooleanField':                  4,
     103        'CharField':                     char_bytes*max_length,
     104        'CommaSeparatedIntegerField':    max_length,
     105        'DateField':                     16,
     106        'DateTimeField':                 16,
     107        'DecimalField':                  16,
     108        'FileField':                     char_bytes*max_length,
     109        'FilePathField':                 'varchar(%(max_length)s)',
     110        'FloatField':                    16,
     111        'ImageField':                    char_bytes*max_length,
     112        'IntegerField':                  8,
     113        'IPAddressField':                15,
     114        'NullBooleanField':              4,
     115        'OneToOneField':                 8,
     116        'PhoneNumberField':              20,
     117        'PositiveIntegerField':          8,
     118        'PositiveSmallIntegerField':     4,
     119        'SlugField':                     char_bytes*max_length,
     120        'SmallIntegerField':             4,
     121        'TextBlob':                      8,
     122        'TextField':                     32767,
     123        'TimeField':                     16,
     124        'URLField':                      max_length,
     125        'USStateField':                  char_bytes*2
     126    }
     127    return size_map[data_type]
     128
     129DEFAULT_MAX_LENGTH = 100
     130def sql_model_create(model, style, known_models=set()):
     131    """
     132    Returns the SQL required to create a single model, as a tuple of:
     133        (list_of_sql, pending_references_dict)
     134    """
     135    from django.db import connection, models
     136
     137    opts = model._meta
     138    final_output = []
     139    table_output = []
     140    pending_references = {}
     141    qn = connection.ops.quote_name
     142   
     143    # Create domains
     144    domains = [ ('AutoField', 'integer'),
     145                ('BooleanField', 'smallint CHECK (VALUE IN (0,1))'),
     146                ('DateField', 'date'),
     147                ('CharField', 'varchar(%i)' % DEFAULT_MAX_LENGTH),
     148                ('DateTimeField', 'timestamp'),
     149                ('FloatField', 'double precision'),
     150                ('IntegerField', 'integer'),
     151                ('IPAddressField', 'varchar(15) CHARACTER SET ASCII'),
     152                ('NullBooleanField', 'smallint CHECK ((VALUE IN (0,1)) OR (VALUE IS NULL))'),
     153                ('OneToOneField', 'integer'),
     154                ('PhoneNumberField', 'varchar(20) CHARACTER SET ASCII'),
     155                ('PositiveIntegerField', 'integer CHECK ((VALUE >= 0) OR (VALUE IS NULL))'),
     156                ('PositiveSmallIntegerField', 'smallint CHECK ((VALUE >= 0) OR (VALUE IS NULL))'),
     157                ('SmallIntegerField', 'smallint'),
     158                ('TextField', 'varchar(%s)' % connection.FB_MAX_VARCHAR),
     159                ('LargeTextField', 'blob sub_type text'),
     160                ('TimeField', 'time'),
     161                ('USStateField', 'varchar(2) CHARACTER SET ASCII') ]
     162   
     163    connected = True
     164    try:
     165        cursor = connection.cursor()
     166    except:
     167        connected = False
     168    if connected:
     169        cursor.execute("SELECT RDB$FIELD_NAME FROM RDB$FIELDS")
     170        existing_domains = set([row[0].strip() for row in cursor.fetchall() if not row[0].startswith('RDB$')])
     171        domains = map(lambda domain: '%s "%s" AS %s;' % ('CREATE DOMAIN', domain[0], domain[1]),
     172            filter(lambda x: x[0] not in existing_domains, domains))
     173        final_output.extend(domains)
     174   
     175    # Check that row size is less than 64k and adjust TextFields if needed
     176    row_size = 0
     177    columns = [(f.db_type().strip('"'), f.get_internal_type(), f) for f in opts.fields]
     178    columns_simple = [col[0] for col in columns]
     179    text_field_type = '"TextField"'
     180    max_alowed_bytes = 32765
     181    if 'TextField' in columns_simple:
     182        max_length = 100
     183        num_text_fields = 0
     184        for column in columns:
     185            num_text_fields += (column[0] == 'TextField')
     186            if column[0].startswith('varchar'):
     187                max_length = int(column[0].split('(')[1].split(')')[0])
     188            if column[1] in DATA_TYPES:
     189                row_size += get_data_size(column[1], max_length)
     190        if row_size > 65536:
     191            max_alowed_bytes = int( (max_alowed_bytes/num_text_fields) - (row_size-65536) )
     192            n = max_alowed_bytes / connection.BYTES_PER_DEFAULT_CHAR
     193            if n > 512:
     194                text_field_type = 'varchar(%s)' % n
     195                FB_TEXTFIELD_ALTERED = True   
     196                print
     197                print "WARNING: Maximum number of characters in TextFields has changed to %s." % n
     198                print "         TextField columns with custom charsets will have %s chars available" % max_alowed_bytes
     199                print "         The change affects %s table only." % opts.db_table
     200                print "         TextFields in other tables will have %s characters maximum" % connection.FB_MAX_VARCHAR
     201                print "         or 32765 characters with custom (non-UTF) encoding."
     202                print "         If you need more space in those fields try LargeTextFields instead."
     203                print
     204            else:
     205                # Swich to blobs if size is too small (<1024)   
     206                text_field_type = '"LargeTextField"'   
     207   
     208    # Create tables
     209    for f in opts.fields:
     210        col_type = f.db_type()
     211        if col_type.strip('"') == 'TextField':
     212            col_type = text_field_type
     213        fb_version = "%s.%s" % (connection.ops.firebird_version[0], connection.ops.firebird_version[1])
     214        page_size = connection.ops.page_size
     215        #look at: http://www.volny.cz/iprenosil/interbase/ip_ib_indexcalculator.htm
     216        if connection.ops.index_limit < 1000:
     217            strip2ascii = False
     218            custom_charset = False
     219            if col_type.startswith('varchar'):
     220                if (f.unique or f.primary_key or f.db_index):
     221                    length = f.max_length
     222                    if f.encoding:
     223                        if not f.encoding.upper().startswith('UTF'):
     224                            custom_charset = True
     225                    if not custom_charset:
     226                        try:
     227                            length = f.max_length * connection.BYTES_PER_DEFAULT_CHAR
     228                        except TypeError:
     229                            length = 100*connection.BYTES_PER_DEFAULT_CHAR #Default for CharField
     230                    if length >= connection.ops.index_limit:   
     231                        strip2ascii = True
     232                if len(opts.unique_together) > 0:
     233                    if f.column in opts.unique_together[0]:
     234                        num_unique_char_fields = len([ fld for fld in opts.unique_together[0] if opts.get_field(fld).db_type().startswith('varchar') ])
     235                        num_unique_fileds = len(opts.unique_together[0])
     236                        num_unique_nonchar_fileds = num_unique_fileds - num_unique_char_fields
     237                        limit = connection.ops.index_limit
     238                        limit -= ((num_unique_fileds - 1)*64)
     239                        limit -= 8*num_unique_nonchar_fileds
     240                        max_length = limit/num_unique_char_fields
     241                        ascii_length = int(f.max_length)
     242                        old_length = ascii_length*connection.BYTES_PER_DEFAULT_CHAR
     243                         
     244                        if (old_length >= max_length) and (ascii_length < max_length):
     245                            strip2ascii = True
     246                        elif old_length > max_length:
     247                            strip2ascii = False #We change it here
     248                            col_type = "varchar(%i) CHARACTER SET ASCII" % max_length
     249                            msg =  "WARNING: Character set of the '%s' field\n"
     250                            msg += "         (table %s)\n"
     251                            msg += "         has changed to ASCII"
     252                            msg += " to fit %s-byte limit in FB %s"
     253                            if not page_size:
     254                                print  msg % (f.column, opts.db_table, connection.ops.index_limit, fb_version)
     255                            else:
     256                                msg += " with page size %s"
     257                                print  msg % (f.column, opts.db_table, connection.ops.index_limit, fb_version, page_size)
     258                            print "         The maximum length of '%s' is now %s instead of %s"\
     259                             % (f.column, max_length, old_length)     
     260            if strip2ascii:
     261                col_type = "%s %s %s" % (col_type, "CHARACTER SET", "ASCII")
     262                msg =  "WARNING: Character set of the '%s' field\n"
     263                msg += "         (table %s)\n"
     264                msg += "         has changed to ASCII"
     265                msg += " to fit %s-byte limit in FB %s"
     266                if not page_size:
     267                    print  msg % (f.column, opts.db_table, connection.ops.index_limit, fb_version)
     268                else:
     269                    msg += " with page size %s"
     270                    print  msg % (f.column, opts.db_table, connection.ops.index_limit, fb_version, page_size)
     271                   
     272        if (col_type.startswith('varchar') or col_type.strip('"') == 'TextField') and f.encoding:
     273            charset = PYTHON_TO_FB_ENCODING_MAP[codecs.lookup(f.encoding).name]
     274            if col_type.strip('"') == 'TextField':
     275                col_type = 'varchar(%i)' % max_alowed_bytes
     276            col_type = "%s %s %s" % (col_type, "CHARACTER SET", charset)
     277       
     278        if col_type is None:
     279            # Skip ManyToManyFields, because they're not represented as
     280            # database columns in this table.
     281            continue
     282        if col_type == 'ComputedField':
     283            # Make the definition (e.g. 'foo COMPUTED BY (oldfoo*2)') for this field.
     284            field_output = [ style.SQL_FIELD(qn(f.column)), style.SQL_KEYWORD('COMPUTED BY'),
     285                             '(%s)' % f.expression ]
     286               
     287        else:   
     288            # Make the definition (e.g. 'foo VARCHAR(30)') for this field.
     289            field_output = [style.SQL_FIELD(qn(f.column)),
     290                style.SQL_COLTYPE(col_type)]
     291            field_output.append(style.SQL_KEYWORD('%s' % (not f.null and 'NOT NULL' or '')))
     292        if f.unique:
     293            field_output.append(style.SQL_KEYWORD('UNIQUE'))
     294        if f.primary_key:
     295            field_output.append(style.SQL_KEYWORD('PRIMARY KEY'))
     296        if f.rel:
     297            # We haven't yet created the table to which this field
     298            # is related, so save it for later.
     299            pr = pending_references.setdefault(f.rel.to, []).append((model, f))
     300        table_output.append(' '.join(field_output))
     301    for field_constraints in opts.unique_together:
     302        table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \
     303            ", ".join([qn(style.SQL_FIELD(opts.get_field(f).column)) for f in field_constraints]))
     304
     305    full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' (']
     306    for i, line in enumerate(table_output): # Combine and add commas.
     307        full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
     308    full_statement.append(');')
     309    final_output.append('\n'.join(full_statement))
     310
     311    if opts.has_auto_field:
     312        # Add any extra SQL needed to support auto-incrementing primary keys.
     313        auto_column = opts.auto_field.db_column or opts.auto_field.name
     314        autoinc_sql = connection.ops.autoinc_sql(style, opts.db_table, auto_column)
     315        if autoinc_sql:
     316            for stmt in autoinc_sql:
     317                final_output.append(stmt)
     318
     319    # Declare exteral functions
     320    if connected:
     321        cursor.execute("SELECT RDB$FUNCTION_NAME FROM RDB$FUNCTIONS")
     322        existing_functions = set([row[0].strip().upper() for row in cursor.fetchall()])
     323        if 'RAND' not in existing_functions:
     324            final_output.append('%s %s\n\t%s %s\n\t%s %s\n\t%s;' % (style.SQL_KEYWORD('DECLARE EXTERNAL FUNCTION'),
     325                style.SQL_TABLE('RAND'), style.SQL_KEYWORD('RETURNS'), style.SQL_COLTYPE('DOUBLE PRECISION'),
     326                style.SQL_KEYWORD('BY VALUE ENTRY_POINT'), style.SQL_FIELD("'IB_UDF_rand'"),
     327                style.SQL_TABLE("MODULE_NAME 'ib_udf'")))
     328        if 'SUBSTR' not in existing_functions:
     329            final_output.append("""DECLARE EXTERNAL FUNCTION SUBSTR CSTRING(255), SMALLINT, SMALLINT
     330                                   RETURNS CSTRING(255) FREE_IT
     331                                   ENTRY_POINT 'IB_UDF_substr' MODULE_NAME 'ib_udf';""")
     332
     333    # Create stored procedures
     334    if hasattr(model, 'procedures'):
     335        for proc in model.procedures:
     336            final_output.append(proc.create_procedure_sql())
     337
     338    # Create triggers
     339    if hasattr(model, 'triggers'):
     340        for proc in model.triggers:
     341            final_output.append(proc.create_trigger_sql())   
     342   
     343    return final_output, pending_references
     344
     345def many_to_many_sql_for_model(model, style):
     346    from django.db import connection, models
     347    from django.contrib.contenttypes import generic
     348    from django.db.backends.util import truncate_name
     349
     350    opts = model._meta
     351    final_output = []
     352    qn = connection.ops.quote_name
     353    for f in opts.many_to_many:
     354        if not isinstance(f.rel, generic.GenericRel):
     355            table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
     356                style.SQL_TABLE(qn(f.m2m_db_table())) + ' (']
     357            table_output.append('    %s %s %s,' %
     358                (style.SQL_FIELD(qn('id')),
     359                style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type()),
     360                style.SQL_KEYWORD('NOT NULL PRIMARY KEY')))
     361
     362            table_output.append('    %s %s %s,' %
     363                (style.SQL_FIELD(qn(f.m2m_column_name())),
     364                style.SQL_COLTYPE(models.ForeignKey(model).db_type()),
     365                style.SQL_KEYWORD('NOT NULL')))
     366            table_output.append('    %s %s %s,' %
     367                (style.SQL_FIELD(qn(f.m2m_reverse_name())),
     368                style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type()),
     369                style.SQL_KEYWORD('NOT NULL')))
     370            deferred = [
     371                (f.m2m_db_table(), f.m2m_column_name(), opts.db_table,
     372                    opts.pk.column),
     373                ( f.m2m_db_table(), f.m2m_reverse_name(),
     374                    f.rel.to._meta.db_table, f.rel.to._meta.pk.column)
     375                ]
     376
     377            table_output.append('    %s (%s, %s)' %
     378                (style.SQL_KEYWORD('UNIQUE'),
     379                style.SQL_FIELD(qn(f.m2m_column_name())),
     380                style.SQL_FIELD(qn(f.m2m_reverse_name()))))
     381            table_output.append(');')
     382            final_output.append('\n'.join(table_output))
     383           
     384            autoinc_sql = connection.ops.autoinc_sql(style, f.m2m_db_table(), 'id')
     385            if autoinc_sql:
     386                for stmt in autoinc_sql:
     387                    final_output.append(stmt)
     388           
     389            if TEST_MODE < 2:
     390                for r_table, r_col, table, col in deferred:
     391                    r_name = '%s_refs_%s_%x' % (r_col, col,
     392                            abs(hash((r_table, table))))
     393                    final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' %
     394                    (qn(r_table),
     395                    truncate_name(r_name, connection.ops.max_name_length()),
     396                    qn(r_col), qn(table), qn(col),
     397                    'ON DELETE CASCADE ON UPDATE CASCADE'))
     398
     399    return final_output
     400
     401def sql_for_pending_references(model, style, pending_references):
     402    """
     403    Returns any ALTER TABLE statements to add constraints after the fact.
     404    """
     405    from django.db import connection
     406   
     407    qn = connection.ops.quote_name
     408    final_output = []
     409    if TEST_MODE < 2:
     410        opts = model._meta
     411        if model in pending_references:
     412            for rel_class, f in pending_references[model]:
     413                rel_opts = rel_class._meta
     414                r_table = rel_opts.db_table
     415                r_col = f.column
     416                table = opts.db_table
     417                col = opts.get_field(f.rel.field_name).column
     418                r_name = connection.ops.reference_name(r_col, col, r_table, table)
     419                if not f.on_update:
     420                    f.on_update = 'CASCADE'
     421                if not f.on_delete:
     422                    if TEST_MODE > 0:
     423                        f.on_delete = 'CASCADE'
     424                    else:
     425                        if f.null:
     426                            f.on_delete = 'SET NULL'
     427                        else:
     428                            f.on_delete = 'NO ACTION'
     429                final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s) ON UPDATE %s ON DELETE %s;' % \
     430                    (qn(r_table), r_name, qn(r_col), qn(table), qn(col),
     431                    f.on_update, f.on_delete))
     432
     433            del pending_references[model]
     434    return final_output
     435
     436
     437TEST_DATABASE_PREFIX = 'test_'
     438def create_test_db(settings, connection, verbosity, autoclobber):
     439    # KInterbasDB supports dynamic database creation and deletion
     440    # via the module-level function create_database and the method Connection.drop_database.
     441       
     442    if settings.TEST_DATABASE_NAME:
     443        TEST_DATABASE_NAME = settings.TEST_DATABASE_NAME
     444    else:
     445        dbnametuple = os.path.split(settings.DATABASE_NAME)
     446        TEST_DATABASE_NAME = os.path.join(dbnametuple[0], TEST_DATABASE_PREFIX + dbnametuple[1])
     447   
     448    dsn = "localhost:%s" % TEST_DATABASE_NAME
     449    if settings.DATABASE_HOST:
     450        dsn = "%s:%s" % (settings.DATABASE_HOST, TEST_DATABASE_NAME)
     451   
     452    if os.path.isfile(TEST_DATABASE_NAME):
     453        sys.stderr.write("Database %s already exists\n" % TEST_DATABASE_NAME)
     454        if not autoclobber:
     455            confirm = raw_input("Type 'yes' if you would like to try deleting the test database '%s', or 'no' to cancel: " % TEST_DATABASE_NAME)
     456        if autoclobber or confirm == 'yes':
     457            if verbosity >= 1:
     458                print "Destroying old test database..."
     459            old_connection = connect(dsn=dsn, user=settings.DATABASE_USER, password=settings.DATABASE_PASSWORD)
     460            old_connection.drop_database()
     461        else:
     462                print "Tests cancelled."
     463                sys.exit(1)
     464           
     465    if verbosity >= 1:
     466        print "Creating test database..."
     467    try:
     468        charset = 'UNICODE_FSS'
     469        if hasattr(settings, 'FIREBIRD_CHARSET'):
     470            if settings.FIREBIRD_CHARSET == 'UTF8':
     471                charset='UTF8'               
     472        create_database("create database '%s' user '%s' password '%s' default character set %s" % \
     473            (dsn, settings.DATABASE_USER, settings.DATABASE_PASSWORD, charset))
     474    except Exception, e:
     475        sys.stderr.write("Got an error creating the test database: %s\n" % e)
     476        sys.exit(2)
     477
     478    connection.close()
     479    settings.DATABASE_NAME = TEST_DATABASE_NAME
     480
     481    call_command('syncdb', verbosity=verbosity, interactive=False)
     482
     483    if settings.CACHE_BACKEND.startswith('db://'):
     484        cache_name = settings.CACHE_BACKEND[len('db://'):]
     485        call_command('createcachetable', cache_name)
     486
     487    # Get a cursor (even though we don't need one yet). This has
     488    # the side effect of initializing the test database.
     489    cursor = connection.cursor()
     490
     491    return TEST_DATABASE_NAME
     492
     493def destroy_test_db(settings, connection, old_database_name, verbosity):
     494    # KInterbasDB supports dynamic database deletion via the method Connection.drop_database.
     495    if verbosity >= 1:
     496        print "Destroying test database..."
     497    connection.drop_database()
     498   
     499
  • 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())
     
    163164                    col = f.column
    164165                    r_table = model._meta.db_table
    165166                    r_col = model._meta.get_field(f.rel.field_name).column
    166                     r_name = '%s_refs_%s_%x' % (col, r_col, abs(hash((table, r_table))))
     167                    r_name = connection.ops.reference_name(r_col, col, r_table, table)
    167168                    output.append('%s %s %s %s;' % \
    168169                        (style.SQL_KEYWORD('ALTER TABLE'),
    169170                        style.SQL_TABLE(qn(table)),
     
    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 = []
     
    315319    """
    316320    Returns any ALTER TABLE statements to add constraints after the fact.
    317321    """
    318     from django.db import connection
     322    from django.db import connection, get_creation_module
    319323    from django.db.backends.util import truncate_name
    320 
     324   
     325    creation_module = get_creation_module()
     326    # If the database backend wants to create many_to_many sql itself, let it
     327    if hasattr(creation_module, "sql_for_pending_references"):
     328        return creation_module.sql_for_pending_references(model, style, pending_references)
     329   
    321330    qn = connection.ops.quote_name
    322331    final_output = []
    323332    if connection.features.supports_constraints:
     
    331340                col = opts.get_field(f.rel.field_name).column
    332341                # For MySQL, r_name must be unique in the first 64 characters.
    333342                # So we are careful with character usage here.
    334                 r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table))))
     343                r_name = connection.ops.reference_name(r_col, col, r_table, table)
    335344                final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
    336345                    (qn(r_table), truncate_name(r_name, connection.ops.max_name_length()),
    337346                    qn(r_col), qn(table), qn(col),
     
    340349    return final_output
    341350
    342351def many_to_many_sql_for_model(model, style):
    343     from django.db import connection, models
     352    from django.db import connection, models, get_creation_module
    344353    from django.contrib.contenttypes import generic
    345354    from django.db.backends.util import truncate_name
    346 
     355   
     356    creation_module = get_creation_module()
     357    # If the database backend wants to create many_to_many sql itself, let it
     358    if hasattr(creation_module, "many_to_many_sql_for_model"):
     359        return creation_module.many_to_many_sql_for_model(model, style)
     360       
    347361    opts = model._meta
    348362    final_output = []
    349363    qn = connection.ops.quote_name
     
    406420            final_output.append('\n'.join(table_output))
    407421
    408422            for r_table, r_col, table, col in deferred:
    409                 r_name = '%s_refs_%s_%x' % (r_col, col,
    410                         abs(hash((r_table, table))))
     423                r_name = connection.ops.reference_name(r_col, col, r_table, table)
    411424                final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' %
    412425                (qn(r_table),
    413426                truncate_name(r_name, connection.ops.max_name_length()),
  • 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()]
     39   
     40    class articles_from_same_day_3(models.Procedure):
     41        """
     42        Stored procedure version of get_articles_from_same_day_1, which executes
     43        a custom stored procedure for the sake of demonstration.
     44        """
     45        __params__ = [ ('self_pub_date', 'date'),
     46                       ('self_id', 'integer') ]
     47        __returns__ = [ ('other_id', 'integer'),
     48                       ('other_headline', 'varchar(100)'),
     49                       ('other_pub_date', 'date') ]
     50        __body__ = \
     51        """FOR SELECT "id", "headline", "pub_date"
     52           FROM "custom_methods_article"
     53           WHERE "pub_date" = :self_pub_date
     54           AND "id" != :self_id
     55           INTO :other_id, :other_headline, :other_pub_date
     56           DO BEGIN SUSPEND; END"""
     57   
     58    def articles_from_same_day_4(self):
     59        # The asterisk in "(*row)" tells Python to expand the list into
     60        # positional arguments to Article().
     61        return [self.__class__(*row) for row in self.articles_from_same_day_3(self.pub_date, self.pk) ]
     62     
     63    # Tell the creation module to install the procedure   
     64    procedures = (articles_from_same_day_3,)
    3865
     66   
    3967__test__ = {'API_TESTS':"""
    4068# Create a couple of Articles.
    4169>>> from datetime import date
     
    5179[<Article: Beatles reunite>]
    5280>>> a.articles_from_same_day_2()
    5381[<Article: Beatles reunite>]
     82>>> [Article(*row) for row in a.articles_from_same_day_3(a.pub_date, a.pk)]
     83[<Article: Beatles reunite>]
     84>>> a.articles_from_same_day_4()
     85[<Article: Beatles reunite>]
    5486>>> b.articles_from_same_day_1()
    5587[<Article: Area man programs in Python>]
    5688>>> b.articles_from_same_day_2()
  • 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/initial_sql_regress/sql/simple.sql

     
    1 INSERT INTO initial_sql_regress_simple (name) VALUES ('John');
    2 INSERT INTO initial_sql_regress_simple (name) VALUES ('Paul');
    3 INSERT INTO initial_sql_regress_simple (name) VALUES ('Ringo');
    4 INSERT INTO initial_sql_regress_simple (name) VALUES ('George');
    5 INSERT INTO initial_sql_regress_simple (name) VALUES ('Miles O''Brien');
    6 INSERT INTO initial_sql_regress_simple (name) VALUES ('Semicolon;Man');
    7 INSERT INTO initial_sql_regress_simple (name) VALUES ('This line has a Windows line ending');
     1INSERT INTO "initial_sql_regress_simple" ("name") VALUES ('John');
     2INSERT INTO "initial_sql_regress_simple" ("name") VALUES ('Paul');
     3INSERT INTO "initial_sql_regress_simple" ("name") VALUES ('Ringo');
     4INSERT INTO "initial_sql_regress_simple" ("name") VALUES ('George');
     5INSERT INTO "initial_sql_regress_simple" ("name") VALUES ('Miles O''Brien');
     6INSERT INTO "initial_sql_regress_simple" ("name") VALUES ('Semicolon;Man');
     7INSERT INTO "initial_sql_regress_simple" ("name") VALUES ('This line has a Windows line ending');
    88
  • 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