Ticket #5052: db2_9_6600.diff

File db2_9_6600.diff, 41.7 KB (added by batiste@…, 8 years ago)

A new version of the db2 9 backend. Some Oracle code has been reactivated.

  • django/db/backends/db2_9/base.py

     
     1""" $Id: base.py 37 2007-06-05 19:49:06Z the_paya $
     2
     3IBM DB2 database backend for Django
     4
     5Requires PyDB2: http://sourceforge.net/projects/pydb2/
     6With this patch: http://sourceforge.net/tracker/index.php?func=detail&aid=1731609&group_id=67548&atid=518208
     7"""
     8from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
     9import re
     10from django.utils.encoding import smart_str, force_unicode
     11
     12try:
     13    import DB2 as Database
     14except ImportError, e:
     15    from django.core.exceptions import ImproperlyConfigured
     16    raise ImproperlyConfigured, "Error loading DB2 python module: %s" % e
     17import datetime
     18from django.utils.datastructures import SortedDict
     19
     20Warning                 = Database.Warning
     21Error                   = Database.Error
     22InterfaceError          = Database.InterfaceError
     23DatabaseError           = Database.DatabaseError
     24DataError               = Database.DataError
     25OperationalError        = Database.OperationalError
     26IntegrityError          = Database.IntegrityError
     27InternalError           = Database.InternalError
     28ProgrammingError        = Database.ProgrammingError
     29NotSupportedError       = Database.NotSupportedError
     30
     31class Cursor(Database.Cursor):
     32    """Return a cursor.
     33    Doing some translation tricks here.
     34    Set the database charset in DATABASE_CHARSET setting"""
     35    try:
     36        charset = settings.DATABASE_CHARSET
     37    except:
     38        charset = 'iso-8859-1'
     39
     40    def _rewrite_args(self, query, params=None):
     41        # formatting parameters into charset
     42        if params is None:
     43            params = []
     44        else:
     45            params = self._format_params(params)
     46        # formatting query into charset
     47        query = smart_str(query, self.charset)
     48        a = query.find('CREATE')
     49        if a >= 0 and a < 10: # assuming this is a create command
     50            # DB2 doesn't want a 'lone' NULL (it's implied).
     51            query = re.sub('(?<!NOT) NULL', '', query)
     52            # DB2 does not like primary key definition without NOT NULL
     53            query = re.sub('(?<!NOT NULL) PRIMARY KEY', ' NOT NULL PRIMARY KEY', query)
     54        # PyDB2 uses '?' as the parameter style.
     55        query = query.replace("%s", "?")
     56        return query, params
     57
     58    def _format_params(self, params=None):
     59        return self._smart_str(params)
     60
     61    def execute(self, query, params=None):
     62        query, params = self._rewrite_args(query, params)
     63        return Database.Cursor.execute(self, query, params)
     64
     65    def executemany(self, query, params=None):
     66        query, params = self._rewrite_args(query, params)
     67        return Database.Cursor.executemany(self, query, params)
     68
     69    def fetchone(self):
     70        # force results into unicode
     71        return self._force_unicode(Database.Cursor.fetchone(self))
     72
     73    def fetchmany(self, size=None):
     74        if size is None:
     75            size = self.arraysize
     76        # force results into unicode
     77        return self._force_unicode(Database.Cursor.fetchmany(self, size))
     78
     79    def fetchall(self):
     80        # is this ever used ?
     81        # force results into unicode
     82        return self._force_unicode(Database.Cursor.fetchall(self))
     83
     84    def _smart_str(self, s=None):
     85        if s is None:
     86            return s
     87        if isinstance(s, dict):
     88            result = {}
     89            charset = self.charset
     90            for key, value in s.items():
     91                result[smart_str(key, charset)] = self._smart_str(value, charset)
     92            return result
     93        elif isinstance(s, (tuple,list)):
     94            return tuple([self._smart_str(p) for p in s])
     95        else:
     96            if isinstance(s, basestring):
     97                try:
     98                    return smart_str(s, self.charset, True)
     99                except UnicodeEncodeError:
     100                    return ''
     101            return s
     102
     103    def _force_unicode(self,s=None):
     104        if s is None:
     105            return s
     106        if isinstance(s, dict):
     107            result = {}
     108            for key, value in s.items():
     109                result[force_unicode(key, charset)] = self._force_unicode(value, charset)
     110            return result
     111        elif isinstance(s, (tuple,list)):
     112            return tuple([self._force_unicode(p) for p in s])
     113        else:
     114            if isinstance(s, basestring):
     115                try:
     116                    return force_unicode(s, encoding=self.charset)
     117                except UnicodeEncodeError:
     118                    return u''
     119            return s
     120
     121integrity_off_tables = []
     122
     123def turn_integrity_off_if_transaction(sender):
     124    """ Turns off integrity on a table if transaction is managed manually.
     125    This function will be connected to the pre_save signal"""
     126    from django.db import transaction, connection
     127    from django.db.models import Model
     128    from django.db.backends.db2_9.introspection import get_table_list
     129    if transaction.is_managed():
     130        if issubclass(sender, Model):
     131            t = sender._meta.db_table
     132            if t not in integrity_off_tables:
     133                print "turning off integrity for %s" % t
     134                cursor = connection.cursor()
     135                # a = get_table_list(cursor)
     136                # if t in a:
     137                cursor.execute('SET INTEGRITY FOR %s OFF READ ACCESS' % (self.quote_name(t),))
     138                cursor.close()
     139                cursor = None
     140                integrity_off_tables.append(t)
     141
     142class Connection(Database.Connection):
     143    def cursor(self):
     144        return Cursor(self._db.cursor())
     145
     146    def commit(self):
     147        from django.db.backends.db2_9.introspection import get_table_list
     148        cursor = self.cursor()
     149        a = get_table_list(cursor)             
     150        for t in integrity_off_tables:
     151            if t in a:
     152                print "turning on integrity for %s" % t
     153                cursor.execute('SET INTEGRITY FOR %s IMMEDIATE CHECKED' % (self.quote_name(t),))
     154        cursor.close()
     155        cursor = None
     156        self._db.commit()
     157
     158Database.connect = Connection
     159
     160class DatabaseFeatures(BaseDatabaseFeatures):
     161    allows_group_by_ordinal = False
     162    allows_unique_and_pk = True
     163    needs_datetime_string_cast = True
     164    needs_upper_for_iops = True
     165    needs_cast_for_iops = True
     166    autoindexes_primary_keys = True
     167    supports_constraints = True
     168    supports_tablespaces = False
     169    supports_compound_statements = True
     170    uses_case_insensitive_names = True
     171    uses_custom_queryset = True
     172
     173#dictfetchone = util.dictfetchone
     174#dictfetchmany = util.dictfetchmany
     175#dictfetchall = util.dictfetchall
     176
     177class DatabaseOperations(BaseDatabaseOperations):
     178   
     179    def quote_name(self, name):
     180        """Name quoting.
     181        Names of type schema.tablename become "schema"."tablename"."""
     182        from django.conf import settings
     183        if not name.startswith('"') and not name.endswith('"'):
     184            return '.'.join(['"%s"' % util.truncate_name(f.upper(), self.max_name_length()) for f in name.split('.')])
     185        return name.upper()
     186
     187    def last_insert_id(self, cursor, table_name, pk_name):
     188        cursor.execute("SELECT IDENTITY_VAL_LOCAL() FROM %s" % self.quote_name(table_name))
     189        # There is probably a 'better' way to do this.
     190        #cursor.execute("SELECT MAX(%s) FROM %s" % (self.quote_name(pk_name), self.quote_name(table_name)))
     191        a = cursor.fetchone()
     192        if a is None:
     193            return 0
     194        return int(a[0])
     195   
     196    def date_extract_sql(self, lookup_type, field_name):
     197        # lookup_type is 'year', 'month', 'day'
     198        if lookup_type == 'year':
     199            return "YEAR(%s)" % field_name
     200        elif lookup_type == 'month':
     201            return "MONTH(%s)" % field_name
     202        elif lookup_type == 'day':
     203            return "DAY(%s)" % field_name
     204   
     205    def date_trunc_sql(self, lookup_type, field_name):
     206        # lookup_type is 'year', 'month', 'day'
     207        # Hard one. I have seen TRUNC_TIMESTAMP somewhere but is not present
     208        # in any of my databases.
     209        # Doesn't work 'directly' since the GROUP BY needs this as well,
     210        # DB2 can't take "GROUP BY 1"
     211        if lookup_type == 'year':
     212            return "TIMESTAMP(DATE(%s -DAYOFYEAR(%s) DAYS +1 DAY),'00:00:00')" % (field_name, field_name)
     213        elif lookup_type == 'month':
     214            return "TIMESTAMP(DATE(%s -DAY(%s) DAYS +1 DAY),'00:00:00')" % (field_name, field_name)
     215        elif lookup_type == 'day':
     216            return "TIMESTAMP(DATE(%s),'00:00:00')" % field_name
     217   
     218    def datetime_cast_sql(self):
     219        return ""
     220   
     221    def limit_offset_sql(self, limit, offset=None):
     222        # Limits and offset are too complicated to be handled here.
     223        # Instead, they are handled in DB2QuerySet.
     224        return ""
     225   
     226    def random_function_sql(self):
     227        return "RAND()"
     228   
     229    def deferrable_sql(self):
     230        # DB2 does not support deferring constraints to end of transaction ?
     231        # return ""
     232        return " ON DELETE CASCADE"
     233        # return " ON DELETE SET NULL"
     234        # does not work when column is NOT NULL
     235   
     236    def fulltext_search_sql(self, field_name):
     237        # DB2 has some nice extra packages that enables a CONTAINS() function,
     238        # but they're not available in my dbs.
     239        raise NotImplementedError
     240   
     241    def drop_foreignkey_sql(self):
     242        return "DROP CONSTRAINT"
     243   
     244    def pk_default_value(self):
     245        return "DEFAULT"
     246   
     247    def max_name_length(self):
     248        return 128;
     249   
     250    def max_constraint_length(self):
     251        # This needs a patch of management.py to detect and use this function
     252        # 18 for primarykeys and constraint names
     253        return 18;
     254   
     255    def alias(self, table, column):
     256        if table.count('.')==0:
     257            return "%s__%s" % (table, column)
     258        return "%s__%s" % (table.split('.')[-1], column)
     259   
     260    def start_transaction_sql(self):
     261        return "BEGIN;"
     262   
     263    def autoinc_sql(self, table, auto_column):
     264        return None
     265   
     266    def drop_sequence(self, table):
     267        return "DROP SEQUENCE %s;" % self.quote_name(self.sequence_name(table))
     268   
     269    def sequence_name(self, table):
     270        return table
     271   
     272    def _sequence_reset_sql(self):
     273        return 'ALTER TABLE %s ALTER COLUMN %s RESTART WITH %s'
     274   
     275    def sql_flush(self, style, tables, sequences):
     276        """Return a list of SQL statements required to remove all data from
     277        all tables in the database (without actually removing the tables
     278        themselves) and put the database in an empty 'initial' state"""
     279        if tables:
     280            sql = []
     281            #~ for table in tables:
     282                #~ if table.count('.') == 0:
     283                    #~ sql.append('SET INTEGRITY FOR %s OFF' % (quote_name(table),))
     284            #~ for table in tables:
     285                #~ if table.count('.') == 0:
     286                    #~ sql.append('ALTER TABLE %s ACTIVATE NOT LOGGED INITIALLY WITH EMPTY TABLE' % (quote_name(table),))
     287            #~ for table in tables:
     288                #~ if table.count('.') == 0:
     289                    #~ sql.append('%s %s %s;' % \
     290                        #~ (style.SQL_KEYWORD('DELETE'), style.SQL_KEYWORD('FROM'),
     291                            #~ style.SQL_FIELD(quote_name(table))))
     292            #~ for table in reversed(tables):
     293                #~ if table.count('.') == 0:
     294                    #~ sql.append('SET INTEGRITY FOR %s IMMEDIATE CHECKED' % (quote_name(table),))
     295            #~ for table in tables:
     296                #~ if table.count('.') == 0:
     297                    #~ for sequence_info in sequences:
     298                        #~ if sequence_info['table'].upper() == table.upper():
     299                            #~ column = sequence_info['column']
     300                            #~ if column is not None:
     301                                #~ query = _get_sequence_reset_sql() % (quote_name(table),quote_name(column),1)
     302                                #~ sql.append(query)
     303            return sql
     304        else:
     305            return []
     306   
     307    def sql_sequence_reset(self, style, model_list):
     308        "Returns a list of the SQL statements to reset sequences for the given models."
     309        from django.db import models
     310        from django.db import connection
     311        output = []
     312        query = self._sequence_reset_sql()
     313        for model in model_list:
     314            for f in model._meta.fields:
     315                if isinstance(f, models.AutoField):
     316                    cursor = connection.cursor()
     317                    max_id = self.last_insert_id(cursor, model._meta.db_table, f.column) + 1
     318                    output.append(query % (self.quote_name(model._meta.db_table), self.quote_name(f.column), max_id))
     319                    cursor.close()
     320                    cursor = None
     321                    break # Only one AutoField is allowed per model, so don't bother continuing.
     322            #~ for f in model._meta.many_to_many:
     323                #~ cursor = connection.cursor()
     324                #~ max_id = last_insert_id(cursor, model._meta.db_table, f.column) + 1
     325                #~ output.append(query % (quote_name(f.m2m_db_table()), quote_name(f.m2m_column_name()), max_id))
     326        return output
     327   
     328    def query_set_class(self, DefaultQuerySet):
     329        "Create a custom QuerySet class for DB2."
     330        from django.db import backend, connection
     331        from django.db.models.query import EmptyResultSet, GET_ITERATOR_CHUNK_SIZE, quote_only_if_word
     332       
     333   
     334        class DB2QuerySet(DefaultQuerySet):
     335   
     336            def iterator(self):
     337                "Performs the SELECT database lookup of this QuerySet."
     338                from django.db.models.query import get_cached_row
     339   
     340                # self._select is a dictionary, and dictionaries' key order is
     341                # undefined, so we convert it to a list of tuples.
     342                extra_select = self._select.items()
     343   
     344                full_query = None
     345   
     346                try:
     347                    try:
     348                        select, sql, params, full_query = self._get_sql_clause(get_full_query=True)
     349                    except TypeError:
     350                        select, sql, params = self._get_sql_clause()
     351                except EmptyResultSet:
     352                    raise StopIteration
     353                if not full_query:
     354                    full_query = "SELECT %s%s\n%s" % \
     355                                ((self._distinct and "DISTINCT " or ""),
     356                                ', '.join(select), sql)
     357   
     358                cursor = connection.cursor()
     359                cursor.execute(full_query, params)
     360   
     361                fill_cache = self._select_related
     362                fields = self.model._meta.fields
     363                index_end = len(fields)
     364   
     365                # so here's the logic;
     366                # 1. retrieve each row in turn
     367                # 2. convert NCLOBs
     368                while 1:
     369                    rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
     370                    if not rows:
     371                        raise StopIteration
     372                    for row in rows:
     373                        row = self.resolve_columns(row, fields)
     374                        if fill_cache:
     375                            obj, index_end = get_cached_row(klass=self.model, row=row,
     376                                                            index_start=0, max_depth=self._max_related_depth)
     377                        else:
     378                            obj = self.model(*row[:index_end])
     379                        for i, k in enumerate(extra_select):
     380                            setattr(obj, k[0], row[index_end+i])
     381                        yield obj
     382   
     383            # DISTINCT could not work properly
     384            def _get_sql_clause(self, get_full_query=False):
     385                from django.db.models.query import fill_table_cache, \
     386                    handle_legacy_orderlist, orderfield2column
     387   
     388                opts = self.model._meta
     389   
     390                select = ["%s.%s" % (connection.ops.quote_name(opts.db_table), connection.ops.quote_name(f.column)) for f in opts.fields]
     391                tables = [quote_only_if_word(t) for t in self._tables]
     392                joins = SortedDict()
     393                where = self._where[:]
     394                params = self._params[:]
     395   
     396                # Convert self._filters into SQL.
     397                joins2, where2, params2 = self._filters.get_sql(opts)
     398                joins.update(joins2)
     399                where.extend(where2)
     400                params.extend(params2)
     401   
     402                # Add additional tables and WHERE clauses based on select_related.
     403                if self._select_related:
     404                    fill_table_cache(opts, select, tables, where, opts.db_table, [opts.db_table])
     405   
     406                # Add any additional SELECTs.
     407                if self._select:
     408                    select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), connection.ops.quote_name(s[0])) for s in self._select.items()])
     409   
     410                # Start composing the body of the SQL statement.
     411                sql = [" FROM", connection.ops.quote_name(opts.db_table)]
     412   
     413                # Compose the join dictionary into SQL describing the joins.
     414                if joins:
     415                    sql.append(" ".join(["%s %s %s ON %s" % (join_type, table, alias, condition)
     416                                    for (alias, (table, join_type, condition)) in joins.items()]))
     417   
     418                # Compose the tables clause into SQL.
     419                if tables:
     420                    sql.append(", " + ", ".join(tables))
     421   
     422                # Compose the where clause into SQL.
     423                if where:
     424                    sql.append(where and "WHERE " + " AND ".join(where))
     425   
     426                # ORDER BY clause
     427                order_by = []
     428                if self._order_by is not None:
     429                    ordering_to_use = self._order_by
     430                else:
     431                    ordering_to_use = opts.ordering
     432                for f in handle_legacy_orderlist(ordering_to_use):
     433                    if f == '?': # Special case.
     434                        order_by.append(backend.get_random_function_sql())
     435                    else:
     436                        if f.startswith('-'):
     437                            col_name = f[1:]
     438                            order = "DESC"
     439                        else:
     440                            col_name = f
     441                            order = "ASC"
     442                        if "." in col_name:
     443                            table_prefix, col_name = col_name.split('.', 1)
     444                            table_prefix = self.quote_name(table_prefix) + '.'
     445                        else:
     446                            # Use the database table as a column prefix if it wasn't given,
     447                            # and if the requested column isn't a custom SELECT.
     448                            if "." not in col_name and col_name not in (self._select or ()):
     449                                table_prefix = connection.ops.quote_name(opts.db_table) + '.'
     450                            else:
     451                                table_prefix = ''
     452                        order_by.append('%s%s %s' % (table_prefix, connection.ops.quote_name(orderfield2column(col_name, opts)), order))
     453                if order_by:
     454                    sql.append("ORDER BY " + ", ".join(order_by))
     455   
     456                # Look for column name collisions in the select elements
     457                # and fix them with an AS alias.  This allows us to do a
     458                # SELECT * later in the paging query.
     459                cols = [clause.split('.')[-1] for clause in select]
     460                for index, col in enumerate(cols):
     461                    if cols.count(col) > 1:
     462                        col = '%s%d' % (col.replace('"', ''), index)
     463                        cols[index] = col
     464                        select[index] = '%s AS %s' % (select[index], col)
     465   
     466                # LIMIT and OFFSET clauses
     467                # To support limits and offsets, Oracle requires some funky rewriting of an otherwise normal looking query.
     468                select_clause = ",".join(select)
     469                distinct = (self._distinct and "DISTINCT " or "")
     470   
     471                if order_by:
     472                    order_by_clause = " OVER (ORDER BY %s )" % (", ".join(order_by))
     473                else:
     474                    #DB2's row_number() function always requires an order-by clause.
     475                    #So we need to define a default order-by, since none was provided.
     476                    order_by_clause = " OVER (ORDER BY %s.%s)" % \
     477                        (connection.ops.quote_name(opts.db_table),
     478                        connection.ops.quote_name(opts.fields[0].db_column or opts.fields[0].column))
     479                # limit_and_offset_clause
     480                if self._limit is None:
     481                    assert self._offset is None, "'offset' is not allowed without 'limit'"
     482   
     483                if self._offset is not None:
     484                    offset = int(self._offset)
     485                else:
     486                    offset = 0
     487                if self._limit is not None:
     488                    limit = int(self._limit)
     489                else:
     490                    limit = None
     491                if limit == 0:
     492                    limit = None
     493                limit_and_offset_clause = ''
     494                if limit is not None:
     495                    limit_and_offset_clause = "WHERE rn > %s AND rn <= %s" % (offset, limit+offset)
     496                elif offset:
     497                    limit_and_offset_clause = "WHERE rn > %s" % (offset)
     498   
     499                if len(limit_and_offset_clause) > 0:
     500                    if limit is not None:
     501                        fmt = "SELECT * FROM (SELECT %s%s, ROW_NUMBER()%s AS rn %s FETCH FIRST %s ROWS ONLY) AS foo %s"
     502                        full_query = fmt % (distinct, select_clause,
     503                                        order_by_clause, ' '.join(sql).strip(), limit+offset,
     504                                        limit_and_offset_clause)
     505                    else:
     506                        fmt = "SELECT * FROM (SELECT %s%s, ROW_NUMBER()%s AS rn %s ) AS foo %s"
     507                        full_query = fmt % (distinct, select_clause,
     508                                        order_by_clause, ' '.join(sql).strip(),
     509                                        limit_and_offset_clause)
     510   
     511                else:
     512                    full_query = None
     513   
     514                if get_full_query:
     515                    return select, " ".join(sql), params, full_query
     516                else:
     517                    return select, " ".join(sql), params
     518   
     519            def resolve_columns(self, row, fields=()):
     520                from django.db.models.fields import Field, CharField, BooleanField, TextField
     521                values = []
     522                for value, field in map(None, row, fields):
     523                    # strip trailing spaces in char and text fields
     524                    #if isinstance(field, (CharField, TextField,)):
     525                    if isinstance(value, basestring):
     526                        if value:
     527                            value = value.strip()
     528                    # create real booleans
     529                    if isinstance(field, BooleanField):
     530                        value = {0: False, 1: True}.get(value, False)
     531                    values.append(value)
     532                return values
     533   
     534        return DB2QuerySet
     535
     536class DatabaseWrapper(object):
     537   
     538    features = DatabaseFeatures()
     539    ops = DatabaseOperations()
     540   
     541    # UPPER needs typecasting or DB2 does not know which upper function to use
     542    # it does not matter if the typecast is correct
     543    string_lookup_length = '50'
     544    operators = {
     545        'exact': "= %s",
     546        'iexact': "= UPPER(CAST(%s as VARCHAR("+string_lookup_length+"))) ",
     547        'contains': "LIKE %s ESCAPE '\\'",
     548        'icontains': "LIKE UPPER(CAST(%s as VARCHAR("+string_lookup_length+"))) ESCAPE '\\'",
     549        'gt': "> %s",
     550        'gte': ">= %s",
     551        'lt': "< %s",
     552        'lte': "<= %s",
     553        'startswith': "LIKE %s ESCAPE '\\'",
     554        'endswith': "LIKE %s ESCAPE '\\'",
     555        'istartswith': "LIKE UPPER(CAST(%s as VARCHAR("+string_lookup_length+")))  ESCAPE '\\'",
     556        'iendswith': "LIKE UPPER(CAST(%s as VARCHAR("+string_lookup_length+")))  ESCAPE '\\'",
     557    }
     558   
     559    def __init__(self, **kwargs):
     560        self.connection = None
     561        self.queries = []
     562        self.server_version = None
     563        self.options = kwargs
     564   
     565    def _valid_connection(self):
     566        return self.connection is not None
     567   
     568    def cursor(self):
     569        from django.conf import settings
     570        from warnings import filterwarnings
     571        if self.connection is None:
     572            conn_dict = {}
     573            # A DB2 client is configured with nodes, and then with databases connected
     574            # to these nodes, I don't know if there is a way of specifying a complete
     575            # DSN with a hostname and port, the PyDB2 module shows no sign of this either.
     576            # So this DSN is actually the database name configured in the host's client instance.
     577            if settings.DATABASE_NAME == '':
     578                from django.core.exceptions import ImproperlyConfigured
     579                raise ImproperlyConfigured, "You need to specify DATABASE_NAME in your Django settings file."
     580            conn_dict['dsn'] = settings.DATABASE_NAME
     581            if settings.DATABASE_USER == '':
     582                from django.core.exceptions import ImproperlyConfigured
     583                raise ImproperlyConfigured, "You need to specify DATABASE_USER in your Django settings file."
     584            conn_dict['uid'] = settings.DATABASE_USER
     585            if settings.DATABASE_PASSWORD == '':
     586                from django.core.exceptions import ImproperlyConfigured
     587                raise ImproperlyConfigured, "You need to specify DATABASE_PASSWORD in your Django settings file."
     588            conn_dict['pwd'] = settings.DATABASE_PASSWORD
     589            # Just imitating others here, I haven't seen any "options" for DB2.
     590            conn_dict.update(self.options)
     591            self.connection = Database.connect(**conn_dict)
     592            cursor = self.connection.cursor()
     593        else:
     594            cursor = self.connection.cursor()
     595        if settings.DEBUG:
     596            return util.CursorDebugWrapper(cursor, self)
     597        return cursor
     598
     599    def _commit(self):
     600        if self.connection is not None:
     601            return self.connection.commit()
     602   
     603    def _rollback(self):
     604        if self.connection is not None:
     605            return self.connection.rollback()
     606   
     607    def close(self):
     608        if self.connection is not None:
     609            self.connection.close()
     610            self.connection = None
     611
  • django/db/backends/db2_9/client.py

     
     1# $Id: client.py 35 2007-06-04 08:11:23Z the_paya $
     2from django.conf import settings
     3import os
     4
     5def runshell():
     6        # Nothing fancy here, as the environment should have the
     7        # instnace properly configured for anything to work at all.
     8        os.execvp('db2')
  • django/db/backends/db2_9/introspection.py

     
     1#from django.db.backends.db2_9.base import quote_name
     2from django.conf import settings
     3
     4def get_table_list(cursor):
     5        """ Here's the tricky part. The tables are accessed by their schema
     6        for example, all the systems tables are prefixed by SYSIBM as their schema.
     7        Since we can get really *lots* of tables without filtering by creator this can get
     8        really messy, So I've used DATABASE_SCHEMA to filter it.
     9        RTRIMs are needed because the fields return in full column size filled with spaces.
     10        Using the SYSIBM.TABLES VIEW.
     11        """
     12        from django.conf import settings
     13        from django.utils.encoding import smart_str, force_unicode
     14        try:
     15                schema = settings.DATABASE_SCHEMA.upper()
     16        except:
     17                schema = ''
     18        if schema != '':
     19                cursor.execute("""SELECT RTRIM(table_name) as TABLES, RTRIM(table_schema) as SCHEMA FROM SYSIBM.TABLES
     20                WHERE TABLE_SCHEMA = %s AND TABLE_TYPE = 'BASE TABLE' ORDER BY table_name""", [schema])
     21        else: # Fallback to everything but SYS*
     22                cursor.execute("""SELECT RTRIM(table_name) as TABLES, RTRIM(table_schema) as SCHEMA FROM SYSIBM.tables
     23                WHERE table_schema NOT LIKE 'SYS%%' AND TABLE_TYPE = 'BASE TABLE' ORDER BY table_name""", [])
     24        rows = cursor.fetchall()
     25        res = []
     26        # for tables from the user: do not append schema, others: use SCHEMA.TABLE as name
     27        for row in rows:
     28                if row[1].upper() == settings.DATABASE_USER.upper():
     29                        res.append(smart_str(row[0].upper()))
     30                else:
     31                        res.append(smart_str("%s.%s" % (row[1].upper(), row[0].upper())))
     32        return res
     33
     34def get_table_description(cursor, table_name):
     35        "Returns a description of the table with the DB-API cursor.description interface."
     36        cursor.execute("SELECT * FROM %s FETCH FIRST 1 ROWS ONLY" % (table_name,))
     37        return cursor.description
     38
     39def _name_to_index(cursor, table_name):
     40    """
     41    Returns a dictionary of {field_name: field_index} for the given table.
     42    Indexes are 0-based.
     43    """
     44    return dict([(d[0], i) for i, d in enumerate(get_table_description(cursor, table_name))])
     45
     46def get_relations(cursor, table_name):
     47        """
     48        Returns a dictionary of {field_index: (field_index_other_table, other_table)}
     49        representing all relationships to the given table. Indexes are 0-based.
     50        Beware, PyDB2 returns the full length of the field, filled with spaces,
     51        I have to .strip() every single string field >.<
     52        """
     53        from django.conf import settings
     54       
     55        if table_name.count('.') == 1:
     56                schema, tn = table_name.split('.')
     57        else:
     58                tn = table_name
     59                schema = settings.DATABASE_USER
     60        cursor.execute("""SELECT fk.ORDINAL_POSITION, pk.ORDINAL_POSITION, pk.TABLE_NAME
     61                        FROM SYSIBM.SQLFOREIGNKEYS r, SYSIBM.SQLCOLUMNS fk, SYSIBM.SQLCOLUMNS pk
     62                        WHERE r.FKTABLE_SCHEM = %s AND r.FKTABLE_NAME = %s
     63                        AND r.FKTABLE_SCHEM = fk.TABLE_SCHEM AND r.FKTABLE_NAME = fk.TABLE_NAME
     64                        AND r.FKCOLUMN_NAME = fk.COLUMN_NAME
     65                        AND r.PKTABLE_SCHEM = pk.TABLE_SCHEM AND r.PKTABLE_NAME = pk.TABLE_NAME
     66                        AND r.PKCOLUMN_NAME = pk.COLUMN_NAME
     67                """, (schema.upper(), tn))
     68               
     69        relations = {}
     70        for row in cursor.fetchall():
     71                relations[int(row[0]) - 1] = (int(row[1]) -1, row[2].strip())
     72        return relations
     73       
     74def get_indexes(cursor, table_name):
     75        """
     76        Returns a dictionary of fieldname -> infodict for the given table,
     77        where each infodict is in the format:
     78                {'primary_key': boolean representing whether it's the primary key,
     79                'unique': boolean representing whether it's a unique index}
     80        DB2 is weird here, like this seems to be ok but I don't get 100% of the indexes
     81                syscolumns.keyseq means the column part of a "parent key".
     82                sysindexes.uniquerule == P means it's primary, U == unique,
     83                        and D == duplicates allowed.
     84        """     
     85        from django.conf import settings
     86        if table_name.count('.') == 1:
     87                schema, tn = table_name.split('.')
     88        else:
     89                tn = table_name
     90                schema = settings.DATABASE_USER
     91        cursor.execute("""SELECT c.COLUMN_NAME, i.UNIQUERULE
     92                        FROM SYSIBM.SQLCOLUMNS c, SYSCAT.INDEXES i, SYSCAT.INDEXCOLUSE k
     93                        WHERE c.TABLE_SCHEM = %s AND c.TABLE_NAME = %s
     94                        AND C.TABLE_SCHEM = i.TABSCHEMA AND c.TABLE_NAME = i.TABNAME
     95                        AND i.INDSCHEMA = k.INDSCHEMA AND i.INDNAME = k.INDNAME
     96                        AND c.COLUMN_NAME = k.COLNAME
     97                """, (schema.upper(), tn))
     98
     99        indexes = {}
     100        for row in cursor.fetchall():
     101                urule = row[1].strip()
     102                name = row[0].strip()
     103                if urule == "P":
     104                        # Override everything, as this is a primary key.
     105                        indexes[name] = {'primary_key':True, 'unique':False}
     106                elif urule == "U":
     107                        try:
     108                                if indexes[name]['primary_key'] == True:
     109                                        # Can appear twice, but primary is primary anyway.
     110                                        continue
     111                                else:
     112                                        indexes[name] = {'primary_key':False, 'unique':True}
     113                        except: # TODO: Only a keyerror can happen here, right?
     114                                indexes[name] = {'primary_key':False, 'unique':True}
     115                else: # urule = "D" not sure if there are others.
     116                        try:
     117                                # Should never happen, but...
     118                                if indexes[name]['primary_key'] == True or indexes[name]['unique'] == True:
     119                                        continue
     120                                else:
     121                                        indexes[name] = {'primary_key':False, 'unique':False}
     122                        except: # TODO: same as above ^_^
     123                                indexes[name] = {'primary_key':False, 'unique':False}
     124        return indexes
     125
     126DATA_TYPES_REVERSE = {
     127        -99:'TextField', # CLOB
     128        -98:'TextField', # BLOB
     129        -97:'TextField', # Long VarGraphic
     130        -96:'TextField', # VarGraphic
     131        -95:'TextField', # Graphic
     132        -5: 'IntegerField', # Big Int
     133        -4: 'TextField', # Binary Long VarChar
     134        -3: 'TextField', # Binary VarChar
     135        -2: 'TextField', # Binary
     136        -1: 'TextField', # Long VarChar
     137        1: 'CharField',  # Char
     138        2: 'FloatField', # Numeric
     139        3: 'FloatField', # Decimal
     140        4: 'IntegerField', # Integer
     141        5: 'BooleanField', # SmallInt
     142        6: 'FloatField', # Float
     143        7: 'FloatField', # Real
     144        8: 'FloatField', # Double
     145        12: 'CharField', # VarChar
     146        91: 'DateField', # Date
     147        92: 'TimeField', # Time
     148        93: 'DateTimeField', # TimeStamp
     149}
  • django/db/backends/db2_9/dbbackend.txt

     
     1Backend for DB2 9
     2=================
     3
     4Modified management.py to use a get_max_constraint_length from the backend if available.
     5Reason: DB2 has a 18 character limit on constraint names, although table_names may be 128 characters.
     6
     7Added to settings:
     8- DATABASE_SCHEMA: use this to have inspectdb find only tables from a specific schema, NOT NEEDED for normal operation.
     9- TEST_DATABASE_USER  must be an existing user (DB9 uses OS authentication)
     10- TEST_DATABASE_PASSWORD
     11- DATABASE_CHARSET: I use this to encode the data to and from the database (I needed iso-8859-1 compliance for legacy db)
     12
     13
     14
     15Issues:
     16- Deferral of foreign key constraint checking : DB2 does not know this ?
     17    ? Option: do a "SET INTEGRITY FOR X OFF READ ACCESS CASCADE DEFERRED"
     18    and a "SET INTEGRITY FOR X IMMEDIATE CHECKED" at commit ?
     19    COULD NOT GET THIS TO WORK YET
     20- flushing also fails due to this limitation --> alike solution ?
     21- no regex lookups
     22- order of objects.all() --> test needs improving (default ordering)?
     23
     24- new forms custom date/time cleaning is failing on me why ?
     25
     26- character sets
     27
     28
     29Googling for TRUNCATE:
     30    On Unix :
     31        db2 connect to <dbname>
     32        db2 import from /dev/null of del replace into <table-name>
     33    On Windows:
     34        Create a blank file C:\temp\blank.txt
     35        db2 connect to <dbname>
     36        db2 import from C:\temp\blank.txt of del replace into <tablename>
     37or
     38    ALTER TABLE T ACTIVATE NOT LOGGED INITIALLY WITH EMPTY TABLE;
     39    COMMIT;
     40 Pas de fin de ligne à la fin du fichier
  • django/db/backends/db2_9/creation.py

     
     1import sys, time
     2from django.core import management
     3from django.db.models.signals import pre_save
     4from django.db.backends.db2_9 import base
     5
     6# Not quite sure about the Boolean and Float fields.
     7# Not sure what to use for Boolean, since there seems to be a 'BOOLEAN' type,
     8# but it isn't 'documented' anywhere. For Float, we have DOUBLE, DECIMAL and NUMERIC
     9# to choose from :+/, too many.
     10DATA_TYPES = {
     11    #(START WITH 1, INCREMENT BY 1, CACHE 20)
     12        'AutoField':                    'INTEGER GENERATED BY DEFAULT AS IDENTITY', # ALWAYS breaks basic test (specifying key explicitly)
     13        'BooleanField':                 'SMALLINT',
     14        'CharField':                    'VARCHAR(%(max_length)s)',
     15        'CommaSeparatedIntegerField':   'VARCHAR(%(max_length)s)',
     16        'DateField':                    'DATE',
     17        'DateTimeField':                'TIMESTAMP',
     18        'DecimalField':                 'DECIMAL(%(max_digits)s, %(decimal_places)s)',
     19        'FileField':                    'VARCHAR(100)',
     20        'FilePathField':                'VARCHAR(100)',
     21        'FloatField':                   'DOUBLE',
     22        'ImageField':                   'VARCHAR(100)',
     23        'IntegerField':                 'INTEGER',
     24        'IPAddressField':               'CHAR(15)',
     25        'ManyToManyField':              None,
     26        'NullBooleanField':             'SMALLINT',
     27        'OneToOneField':                'INTEGER',
     28        'PhoneNumberField':             'CHAR(20)',
     29        'PositiveIntegerField':         'INTEGER',
     30        'PositiveSmallIntegerField':    'SMALLINT',
     31        'SlugField':                    'VARCHAR(%(max_length)s)',
     32        'SmallIntegerField':            'SMALLINT',
     33        'TextField':                    'VARCHAR(5000)',
     34        'TimeField':                    'TIME',
     35        'USStateField':                 'CHAR(2)',
     36}
     37
     38REMEMBER = {}
     39
     40def create_test_db(settings, connection, verbosity=2, autoclobber=False):
     41    """Assuming here that a TEST_DATABASE_USER is set in settings that is known to the OS and DB2
     42    with DB2ADM rights. Best is that this user has a separate default tablespace defined in DB2 (but this is not required)."""
     43    if not settings.TEST_DATABASE_NAME:
     44        raise Exception('DB2 backend require that you define TEST_DATABASE_NAME in your settings')
     45   
     46    #save settings
     47    REMEMBER['name'] = settings.DATABASE_NAME
     48    REMEMBER['user'] = settings.DATABASE_USER
     49    REMEMBER['passwd'] = settings.DATABASE_PASSWORD
     50   
     51    settings.DATABASE_NAME = settings.TEST_DATABASE_NAME
     52    if settings.TEST_DATABASE_USER:
     53        settings.DATABASE_USER = settings.TEST_DATABASE_USER
     54    if settings.TEST_DATABASE_PASSWORD:
     55        settings.DATABASE_PASSWORD = settings.TEST_DATABASE_PASSWORD
     56   
     57    management.call_command('syncdb', verbosity=verbosity, interactive=False)
     58   
     59    cursor = connection.cursor()
     60    cursor.execute("""SELECT RTRIM(table_name) as TABLES FROM SYSIBM.TABLES
     61        WHERE TABLE_SCHEMA = %s AND TABLE_TYPE = 'BASE TABLE' ORDER BY table_name""", [settings.TEST_DATABASE_USER.upper()])
     62       
     63    rows = cursor.fetchall()
     64    for row in rows:
     65        stmt = 'SELECT MAX(ID) FROM %s.%s' % (settings.TEST_DATABASE_USER.upper(), row[0])
     66        alter = True
     67        try:
     68            cursor.execute(stmt)
     69            max = cursor.fetchall()
     70        except Exception, err:
     71            alter = False
     72        try:
     73           max = int(max[0][0]) + 1
     74        except Exception, err:
     75            alter = False
     76        if alter:
     77            stmt = 'ALTER TABLE %s.%s ALTER COLUMN ID RESTART WITH %d' % (settings.TEST_DATABASE_USER.upper(), row[0], max)
     78            print 'Update max id counter %d for table %s.%s' % (max, settings.TEST_DATABASE_USER.upper(), row[0])
     79            try:
     80                cursor.execute(stmt)
     81            except Exception, err:
     82                sys.stderr.write("Failed (%s)\n" % (err))
     83                raise
     84    connection._commit()
     85    connection.close()
     86
     87def destroy_test_db(settings, connection, backend, old_database_name, verbosity=2):
     88    """Delete all tables from the test user."""
     89   
     90    #connection.close()
     91   
     92    cursor = connection.cursor()
     93
     94    time.sleep(1) # To avoid "database is being accessed by other users" errors.
     95
     96    if verbosity >= 2:
     97        print "destroy_test_db(): removing tables from schema %s" % settings.TEST_DATABASE_USER
     98
     99    cursor.execute("""SELECT RTRIM(table_name) as TABLES FROM SYSIBM.TABLES
     100        WHERE TABLE_SCHEMA = %s AND TABLE_TYPE = 'BASE TABLE' ORDER BY table_name""", [settings.TEST_DATABASE_USER.upper()])
     101
     102    rows = cursor.fetchall()
     103    for row in rows:
     104        stmt = 'DROP TABLE %s.%s' % (settings.TEST_DATABASE_USER.upper(), row[0])
     105        if verbosity >= 2:
     106            print 'Destroy table %s.%s' % (settings.TEST_DATABASE_USER.upper(), row[0])
     107        try:
     108            cursor.execute(stmt)
     109        except Exception, err:
     110            sys.stderr.write("Failed (%s)\n" % (err))
     111            raise
     112    connection._commit()
     113    connection.close()
     114   
     115    # restore settings
     116    settings.DATABASE_NAME = REMEMBER['name']
     117    settings.DATABASE_USER = REMEMBER['user']
     118    settings.DATABASE_PASSWORD = REMEMBER['passwd']
     119 Pas de fin de ligne à la fin du fichier
  • django/core/management/sql.py

     
    328328                r_col = f.column
    329329                table = opts.db_table
    330330                col = opts.get_field(f.rel.field_name).column
     331                if connection.ops.max_constraint_length:
     332                    max_constraint_length = connection.ops.max_constraint_length()
     333                else:
     334                    max_constraint_length = connection.ops.max_name_length()
    331335                # For MySQL, r_name must be unique in the first 64 characters.
    332336                # So we are careful with character usage here.
    333337                r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table))))
    334338                final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
    335                     (qn(r_table), truncate_name(r_name, connection.ops.max_name_length()),
     339                    (qn(r_table), truncate_name(r_name, max_constraint_length),
    336340                    qn(r_col), qn(table), qn(col),
    337341                    connection.ops.deferrable_sql()))
    338342            del pending_references[model]
Back to Top