=== django/db/models/query.py
==================================================================
--- django/db/models/query.py	(/mirror/django/trunk)	(revision 3920)

+++ django/db/models/query.py	(/local/django/db2_9)	(revision 3920)

@@ -951,6 +951,10 @@

 
 def lookup_inner(path, lookup_type, value, opts, table, column):
     qn = connection.ops.quote_name
+    if hasattr(connection.ops, 'alias'):
+        al = connection.ops.alias
+    else:
+        al = lambda x,y: "%s__%s" % (x,y)
     joins, where, params = SortedDict(), [], []
     current_opts = opts
     current_table = table
@@ -969,7 +973,7 @@

         # Does the name belong to a defined many-to-many field?
         field = find_field(name, current_opts.many_to_many, False)
         if field:
-            new_table = current_table + '__' + name
+            new_table = al(current_table, name)
             new_opts = field.rel.to._meta
             new_column = new_opts.pk.column
 
@@ -986,7 +990,7 @@

         # Does the name belong to a reverse defined many-to-many field?
         field = find_field(name, current_opts.get_all_related_many_to_many_objects(), True)
         if field:
-            new_table = current_table + '__' + name
+            new_table = al(current_table, name)
             new_opts = field.opts
             new_column = new_opts.pk.column
 
@@ -1003,7 +1007,7 @@

         # Does the name belong to a one-to-many field?
         field = find_field(name, current_opts.get_all_related_objects(), True)
         if field:
-            new_table = table + '__' + name
+            new_table = al(current_table, name)
             new_opts = field.opts
             new_column = field.field.column
             join_column = opts.pk.column
@@ -1017,7 +1021,7 @@

         field = find_field(name, current_opts.fields, False)
         if field:
             if field.rel: # One-to-One/Many-to-one field
-                new_table = current_table + '__' + name
+                new_table = al(current_table, name)
                 new_opts = field.rel.to._meta
                 new_column = new_opts.pk.column
                 join_column = field.column
=== django/db/backends/db2_9	(new directory)
==================================================================
=== django/db/backends/db2_9/base.py
==================================================================
--- django/db/backends/db2_9/base.py	(/mirror/django/trunk)	(revision 3920)

+++ django/db/backends/db2_9/base.py	(/local/django/db2_9)	(revision 3920)

@@ -0,0 +1,632 @@

+"""

+

+IBM DB2 database backend for Django

+

+Requires PyDB2: http://sourceforge.net/projects/pydb2/

+With this patch: http://sourceforge.net/tracker/index.php?func=detail&aid=1731609&group_id=67548&atid=518208

+

+Authors:

+    Javier Villavicencio

+    Koen Biermans

+

+Current issues:

+    DB2 does not support deferring foreign key constraint checking.

+        --> prereferencing does not work (e.g. in serializer_regress test)

+

+    No regex lookups.

+

+    Character sets:

+        put encoding/decoding into settings.DATABASE_CHARSET into the cursor code.

+

+    Floats as primary key does not work.

+

+Specific items:

+    Connection:

+        use a DNS for DATABASE_NAME

+        this DNS can be system DNS or defined in db2cli.ini

+            example for db2cli.ini:

+                    [DNSNAME]

+                Protocol=TCPIP

+                Hostname=DB2HOST

+                ServiceName=50001

+                Database=DATABASE

+                Port=50001

+

+    Tables may be set in models.py with their schema: 'db_table = SCHEMA.TABLENAME'

+    --> modified query.py to use get_alias function (no dots allowed in alias,

+    default implementation uses db_table)

+

+    DB2 table names may be 128 characters, but constraint names are limited to 18.

+    --> modified management.py to use get_max_constraint_length function

+

+"""

+

+from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util

+import re

+from django.utils.encoding import smart_str, force_unicode

+

+try:

+    import DB2 as Database

+except ImportError, e:

+    from django.core.exceptions import ImproperlyConfigured

+    raise ImproperlyConfigured, "Error loading DB2 python module: %s" % e

+import datetime

+from django.utils.datastructures import SortedDict

+

+Warning                 = Database.Warning

+Error                   = Database.Error

+InterfaceError          = Database.InterfaceError

+DatabaseError           = Database.DatabaseError

+DataError               = Database.DataError

+OperationalError        = Database.OperationalError

+IntegrityError          = Database.IntegrityError

+InternalError           = Database.InternalError

+ProgrammingError        = Database.ProgrammingError

+NotSupportedError       = Database.NotSupportedError

+

+class Cursor(Database.Cursor):

+    """Return a cursor.

+    Doing some translation tricks here.

+    Set the database charset in DATABASE_CHARSET setting"""

+    try:

+        charset = settings.DATABASE_CHARSET

+    except:

+        charset = 'iso-8859-1'

+

+    def _rewrite_args(self, query, params=None):

+        # formatting parameters into charset

+        if params is None:

+            params = []

+        else:

+            params = self._format_params(params)

+        # formatting query into charset

+        query = smart_str(query, self.charset)

+        a = query.find('CREATE')

+        if a >= 0 and a < 10: # assuming this is a create command

+            # DB2 doesn't want a 'lone' NULL (it's implied).

+            query = re.sub('(?<!NOT) NULL', '', query)

+            # DB2 does not like primary key definition without NOT NULL

+            query = re.sub('(?<!NOT NULL) PRIMARY KEY', ' NOT NULL PRIMARY KEY',query)

+        # PyDB2 uses '?' as the parameter style.

+        query = query.replace("%s", "?")

+        return query, params

+

+    def _format_params(self, params=None):

+        return self._smart_str(params)

+

+    def execute(self, query, params=None):

+        query, params = self._rewrite_args(query, params)

+        return Database.Cursor.execute(self, query, params)

+

+    def executemany(self, query, params=None):

+        query, params = self._rewrite_args(query, params)

+        return Database.Cursor.executemany(self, query, params)

+

+    def fetchone(self):

+        # force results into unicode

+        return self._force_unicode(Database.Cursor.fetchone(self))

+

+    def fetchmany(self, size=None):

+        if size is None:

+            size = self.arraysize

+        # force results into unicode

+        return self._force_unicode(Database.Cursor.fetchmany(self, size))

+

+    def fetchall(self):

+        # is this ever used ?

+        # force results into unicode

+        return self._force_unicode(Database.Cursor.fetchall(self))

+

+    def _smart_str(self, s=None):

+        if s is None:

+            return s

+        if isinstance(s, dict):

+            result = {}

+            for key, value in s.items():

+                result[_smart_str(key)] = self._smart_str(value)

+            return result

+        elif isinstance(s, (tuple,list)):

+            return tuple([self._smart_str(p) for p in s])

+        else:

+            if isinstance(s, basestring):

+                try:

+                    return smart_str(s, self.charset, True)

+                except UnicodeEncodeError:

+                    return ''

+            return s

+

+    def _force_unicode(self,s=None):

+        if s is None:

+            return s

+        if isinstance(s, dict):

+            result = {}

+            for key, value in s.items():

+                result[force_unicode(key, charset)] = self._force_unicode(value, charset)

+            return result

+        elif isinstance(s, (tuple,list)):

+            return tuple([self._force_unicode(p) for p in s])

+        else:

+            if isinstance(s, basestring):

+                try:

+                    return force_unicode(s, encoding=self.charset)

+                except UnicodeEncodeError:

+                    return u''

+            return s

+

+class Connection(Database.Connection):

+    def cursor(self):

+        return Cursor(self._db.cursor())

+

+Database.connect = Connection

+

+class DatabaseFeatures(BaseDatabaseFeatures):

+    allows_group_by_ordinal = False

+    allows_unique_and_pk = True

+    needs_datetime_string_cast = True

+    needs_upper_for_iops = True

+    needs_cast_for_iops = True

+    autoindexes_primary_keys = True

+    supports_constraints = True

+    supports_tablespaces = False

+    supports_compound_statements = True

+    uses_case_insensitive_names = True

+    uses_custom_queryset = True

+

+class DatabaseOperations(BaseDatabaseOperations):

+    def alias(self, table, column):

+        if table.count('.')==0:

+            return "%s__%s" % (table, column)

+        return "%s__%s" % (table.split('.')[-1], column)

+

+    def autoinc_sql(self, table):

+        return None

+

+    def date_extract_sql(self, lookup_type, field_name):

+        # lookup_type is 'year', 'month', 'day'

+        if lookup_type == 'year':

+            return "YEAR(%s)" % field_name

+        elif lookup_type == 'month':

+            return "MONTH(%s)" % field_name

+        elif lookup_type == 'day':

+            return "DAY(%s)" % field_name

+

+    def date_trunc_sql(self, lookup_type, field_name):

+        # lookup_type is 'year', 'month', 'day'

+        # Hard one. I have seen TRUNC_TIMESTAMP somewhere but is not present

+        # in any of my databases.

+        # Doesn't work 'directly' since the GROUP BY needs this as well,

+        # DB2 can't take "GROUP BY 1"

+        if lookup_type == 'year':

+            return "TIMESTAMP(DATE(%s -DAYOFYEAR(%s) DAYS +1 DAY),'00:00:00')" % (field_name, field_name)

+        elif lookup_type == 'month':

+            return "TIMESTAMP(DATE(%s -DAY(%s) DAYS +1 DAY),'00:00:00')" % (field_name, field_name)

+        elif lookup_type == 'day':

+            return "TIMESTAMP(DATE(%s),'00:00:00')" % field_name

+

+    def datetime_cast_sql(self):

+        return ""

+

+    def deferrable_sql(self):

+        # DB2 does not support deferring constraints to end of transaction

+        # using cascade avoid constraint problems

+        return " ON DELETE CASCADE"

+

+    def drop_foreignkey_sql(self):

+        return "DROP CONSTRAINT"

+

+    def drop_sequence(self, table):

+        return "DROP SEQUENCE %s;" % self.quote_name(get_sequence_name(table))

+

+    def fulltext_search_sql(field_name):

+        # DB2 has some nice extra packages that enables a CONTAINS() function,

+        # but they're not available in my dbs.

+        raise NotImplementedError

+

+    def last_insert_id(self, cursor, table_name, pk_name):

+        # There is probably a 'better' way to do this.

+        cursor.execute("SELECT IDENTITY_VAL_LOCAL() FROM %s" % self.quote_name(table_name))

+##        cursor.execute("SELECT MAX(%s) FROM %s" % (self.quote_name(pk_name),

+##            self.quote_name(table_name)))

+        a = cursor.fetchone()

+        if a is None:

+            return 0

+        return int(a[0])

+##        return a[0]

+

+    def limit_offset_sql(self, limit, offset=None):

+        # Limits and offset are too complicated to be handled here.

+        # Instead, they are handled in DB2QuerySet.

+        return ""

+

+    def max_constraint_length(self):

+        # This needs a patch of management.py to detect and use this function

+        # 18 for primarykeys and constraint names

+        return 18;

+

+    def max_name_length(self):

+        return 128;

+

+    def pk_default_value(self):

+        return "DEFAULT"

+

+    def quote_name(self,name):

+        """Name quoting.

+        Names of type schema.tablename become "schema"."tablename"."""

+        from django.conf import settings

+        if not name.startswith('"') and not name.endswith('"'):

+            return '.'.join(['"%s"' % util.truncate_name(f.upper(),

+                self.max_name_length()) for f in name.split('.')])

+        return name.upper()

+

+    def random_function_sql(self):

+        return "RAND()"

+

+    def _sequence_reset_sql(self):

+        return 'ALTER TABLE %s ALTER COLUMN %s RESTART WITH %s'

+

+    def _sql_flush(self, style, table, emptytables):

+        """ Recursive function to retrieve the list of delete statements in the

+        right order.

+        """

+        from django.db.backends.db2_9.introspection import get_foreignkey_relations

+        from django.db import connection

+        from django.utils.encoding import smart_str

+        cursor = connection.cursor()

+        sql = []

+        te = []

+        if emptytables:

+            temptytables = emptytables[:]

+        else:

+            temptytables = []

+        relations = get_foreignkey_relations(cursor, table)

+        for relation in relations:

+            relation = smart_str(relation)

+            if relation not in temptytables:

+                if temptytables:

+                    tt2 = temptytables[:].extend(table) # avoid circular reference endless loop

+                else:

+                    tt2 = [table]

+                tsql, tem = self._sql_flush(style, relation, tt2)

+                sql.extend(tsql)

+                te.extend(tem)

+                temptytables.extend(tem)

+        if table not in temptytables:

+            sql.append('%s %s %s;' % (style.SQL_KEYWORD('DELETE'), style.SQL_KEYWORD('FROM'),

+            style.SQL_FIELD(self.quote_name(table))))

+            te.append(table)

+        return sql, te

+

+    def sql_flush(self, style, tables, sequences):

+        """Return a list of SQL statements required to remove all data from

+        all tables in the database (without actually removing the tables

+        themselves) and put the database in an empty 'initial' state"""

+        if tables:

+            emptytables = []

+            sql = []

+            for table in tables:

+                if table.count('.') == 0:

+                    if table not in emptytables:

+                        tsql, te = self._sql_flush(style, table, emptytables)

+                        sql.extend(tsql)

+                        emptytables.extend(te)

+            for sequence_info in sequences:

+                if sequence_info['table'].upper() == table.upper():

+                    column = sequence_info['column']

+                    if column is not None:

+                        query = self._sequence_reset_sql() % (self.quote_name(table),

+                            self.quote_name(column),1)

+                        sql.append(query)

+            return sql

+        else:

+            return []

+

+    def sql_sequence_reset(self, style, model_list):

+        """Returns a list of the SQL statements to reset sequences for the

+        given models."""

+        from django.db import models

+        from django.db import connection

+        output = []

+        query = self._sequence_reset_sql()

+        for model in model_list:

+            for f in model._meta.fields:

+                if isinstance(f, models.AutoField):

+                    cursor = connection.cursor()

+                    max_id = self.last_insert_id(cursor, model._meta.db_table,

+                        f.column) + 1

+                    output.append(query % (self.quote_name(model._meta.db_table),

+                        self.quote_name(f.column), max_id))

+                    cursor.close()

+                    cursor = None

+                    break # Only one AutoField is allowed per model, so don't bother continuing.

+            #~ for f in model._meta.many_to_many:

+                #~ cursor = connection.cursor()

+                #~ max_id = get_last_insert_id(cursor, model._meta.db_table, f.column) + 1

+                #~ output.append(query % (self.quote_name(f.m2m_db_table()), self.quote_name(f.m2m_column_name()), max_id))

+        return output

+

+    def start_transaction_sql(self):

+        return "BEGIN;"

+

+    def query_set_class(self, DefaultQuerySet):

+        "Create a custom QuerySet class for DB2."

+

+        from django.db import connection

+        from django.db.models.query import EmptyResultSet, GET_ITERATOR_CHUNK_SIZE, quote_only_if_word

+

+        class DB2QuerySet(DefaultQuerySet):

+

+            def iterator(self):

+                "Performs the SELECT database lookup of this QuerySet."

+

+                from django.db.models.query import get_cached_row

+

+                # self._select is a dictionary, and dictionaries' key order is

+                # undefined, so we convert it to a list of tuples.

+                extra_select = self._select.items()

+

+                full_query = None

+

+                try:

+                    try:

+                        select, sql, params, full_query = self._get_sql_clause(get_full_query=True)

+                    except TypeError:

+                        select, sql, params = self._get_sql_clause()

+                except EmptyResultSet:

+                    raise StopIteration

+                if not full_query:

+                    full_query = "SELECT %s%s\n%s" % \

+                                 ((self._distinct and "DISTINCT " or ""),

+                                  ', '.join(select), sql)

+

+                cursor = connection.cursor()

+                cursor.execute(full_query, params)

+

+                fill_cache = self._select_related

+                fields = self.model._meta.fields

+                index_end = len(fields)

+

+                # so here's the logic;

+                # 1. retrieve each row in turn

+                # 2. convert NCLOBs

+

+                while 1:

+                    rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)

+                    if not rows:

+                        raise StopIteration

+                    for row in rows:

+                        row = self.resolve_columns(row, fields)

+                        if fill_cache:

+                            obj, index_end = get_cached_row(klass=self.model,

+                                row=row, index_start=0,

+                                max_depth=self._max_related_depth)

+                        else:

+                            obj = self.model(*row[:index_end])

+                        for i, k in enumerate(extra_select):

+                            setattr(obj, k[0], row[index_end+i])

+                        yield obj

+

+            def _get_sql_clause(self, get_full_query=False):

+                from django.db.models.query import fill_table_cache, \

+                    handle_legacy_orderlist, orderfield2column

+

+                opts = self.model._meta

+                qn = connection.ops.quote_name

+

+                select = ["%s.%s" % (qn(opts.db_table), qn(f.column)) for f in opts.fields]

+                tables = [quote_only_if_word(t) for t in self._tables]

+                joins = SortedDict()

+                where = self._where[:]

+                params = self._params[:]

+

+                # Convert self._filters into SQL.

+                joins2, where2, params2 = self._filters.get_sql(opts)

+                joins.update(joins2)

+                where.extend(where2)

+                params.extend(params2)

+

+                # Add additional tables and WHERE clauses based on select_related.

+                if self._select_related:

+                    fill_table_cache(opts, select, tables, where, opts.db_table, [opts.db_table])

+

+                # Add any additional SELECTs.

+                if self._select:

+                    select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), qn(s[0])) for s in self._select.items()])

+

+                # Start composing the body of the SQL statement.

+                sql = [" FROM", qn(opts.db_table)]

+

+                # Compose the join dictionary into SQL describing the joins.

+                if joins:

+                    sql.append(" ".join(["%s %s %s ON %s" % (join_type, table, alias, condition)

+                                    for (alias, (table, join_type, condition)) in joins.items()]))

+

+                # Compose the tables clause into SQL.

+                if tables:

+                    sql.append(", " + ", ".join(tables))

+

+                # Compose the where clause into SQL.

+                if where:

+                    sql.append(where and "WHERE " + " AND ".join(where))

+

+                # ORDER BY clause

+                order_by = []

+                if self._order_by is not None:

+                    ordering_to_use = self._order_by

+                else:

+                    ordering_to_use = opts.ordering

+                for f in handle_legacy_orderlist(ordering_to_use):

+                    if f == '?': # Special case.

+                        order_by.append(DatabaseOperations().random_function_sql())

+                    else:

+                        if f.startswith('-'):

+                            col_name = f[1:]

+                            order = "DESC"

+                        else:

+                            col_name = f

+                            order = "ASC"

+                        if "." in col_name:

+                            table_prefix, col_name = col_name.split('.', 1)

+                            table_prefix = qn(table_prefix) + '.'

+                        else:

+                            # Use the database table as a column prefix if it wasn't given,

+                            # and if the requested column isn't a custom SELECT.

+                            if "." not in col_name and col_name not in (self._select or ()):

+                                table_prefix = qn(opts.db_table) + '.'

+                            else:

+                                table_prefix = ''

+                        order_by.append('%s%s %s' % (table_prefix, qn(orderfield2column(col_name, opts)), order))

+                if order_by:

+                    sql.append("ORDER BY " + ", ".join(order_by))

+

+                # Look for column name collisions in the select elements

+                # and fix them with an AS alias.  This allows us to do a

+                # SELECT * later in the paging query.

+                cols = [clause.split('.')[-1] for clause in select]

+                for index, col in enumerate(cols):

+                    if cols.count(col) > 1:

+                        col = '%s%d' % (col.replace('"', ''), index)

+                        cols[index] = col

+                        select[index] = '%s AS %s' % (select[index], col)

+

+                # LIMIT and OFFSET clauses

+                # To support limits and offsets, Oracle requires some funky rewriting of an otherwise normal looking query.

+                select_clause = ",".join(select)

+                distinct = (self._distinct and "DISTINCT " or "")

+

+                if order_by:

+                    order_by_clause = " OVER (ORDER BY %s )" % (", ".join(order_by))

+                else:

+                    #DB2's row_number() function always requires an order-by clause.

+                    #So we need to define a default order-by, since none was provided.

+                    order_by_clause = " OVER (ORDER BY %s.%s)" % \

+                        (qn(opts.db_table),

+                        qn(opts.fields[0].db_column or opts.fields[0].column))

+                # limit_and_offset_clause

+                if self._limit is None:

+                    assert self._offset is None, "'offset' is not allowed without 'limit'"

+

+                if self._offset is not None:

+                    offset = int(self._offset)

+                else:

+                    offset = 0

+                if self._limit is not None:

+                    limit = int(self._limit)

+                else:

+                    limit = None

+

+                limit_and_offset_clause = ''

+                if limit is not None:

+                    limit_and_offset_clause = "WHERE rn > %s AND rn <= %s" % (offset, limit+offset)

+                elif offset:

+                    limit_and_offset_clause = "WHERE rn > %s" % (offset)

+

+                if len(limit_and_offset_clause) > 0:

+                    if limit is not None:

+                        fmt = "SELECT * FROM (SELECT %s%s, ROW_NUMBER()%s AS rn %s FETCH FIRST %s ROWS ONLY) AS foo %s"

+                        full_query = fmt % (distinct, select_clause,

+                                        order_by_clause, ' '.join(sql).strip(), limit+offset,

+                                        limit_and_offset_clause)

+                    else:

+                        fmt = "SELECT * FROM (SELECT %s%s, ROW_NUMBER()%s AS rn %s ) AS foo %s"

+                        full_query = fmt % (distinct, select_clause,

+                                        order_by_clause, ' '.join(sql).strip(),

+                                        limit_and_offset_clause)

+

+                else:

+                    full_query = None

+

+                if get_full_query:

+                    return select, " ".join(sql), params, full_query

+                else:

+                    return select, " ".join(sql), params

+

+            def resolve_columns(self, row, fields=()):

+                from django.db.models.fields import Field, CharField, BooleanField, TextField

+                values = []

+                for value, field in map(None, row, fields):

+                    # strip trailing spaces in char and text fields

+                    #if isinstance(field, (CharField, TextField,)):

+                    if isinstance(value, basestring):

+                        if value:

+                            value = value.strip()

+                    # create real booleans

+                    if isinstance(field, BooleanField):

+                        value = {0: False, 1: True}.get(value, False)

+                    values.append(value)

+                return values

+

+        return DB2QuerySet

+

+class DatabaseWrapper(BaseDatabaseWrapper):

+

+    features = DatabaseFeatures()

+    ops = DatabaseOperations()

+

+    # UPPER needs typecasting or DB2 does not know which upper function to use

+    # it does not matter if the typecast is correct

+    string_lookup_length = '50'

+    operators = {

+        'exact': "= %s",

+        'iexact': "= UPPER(CAST(%s as VARCHAR("+string_lookup_length+"))) ",

+        'contains': "LIKE %s ESCAPE '\\'",

+        'icontains': "LIKE UPPER(CAST(%s as VARCHAR("+string_lookup_length+"))) ESCAPE '\\'",

+        'gt': "> %s",

+        'gte': ">= %s",

+        'lt': "< %s",

+        'lte': "<= %s",

+        'startswith': "LIKE %s ESCAPE '\\'",

+        'endswith': "LIKE %s ESCAPE '\\'",

+        'istartswith': "LIKE UPPER(CAST(%s as VARCHAR("+string_lookup_length+")))  ESCAPE '\\'",

+        'iendswith': "LIKE UPPER(CAST(%s as VARCHAR("+string_lookup_length+")))  ESCAPE '\\'",

+    }

+

+    def __init__(self, **kwargs):

+        self.connection = None

+        self.queries = []

+        self.server_version = None

+        self.options = kwargs

+

+    def _valid_connection(self):

+        return self.connection is not None

+

+    def _cursor(self, settings):

+        from warnings import filterwarnings

+        if self.connection is None:

+            conn_dict = {}

+            # A DB2 client is configured with nodes, and then with databases connected

+            # to these nodes, I don't know if there is a way of specifying a complete

+            # DSN with a hostname and port, the PyDB2 module shows no sign of this either.

+            # So this DSN is actually the database name configured in the host's client instance.

+            if settings.DATABASE_NAME == '':

+                from django.core.exceptions import ImproperlyConfigured

+                raise ImproperlyConfigured, "You need to specify DATABASE_NAME in your Django settings file."

+            conn_dict['dsn'] = settings.DATABASE_NAME

+            if settings.DATABASE_USER == '':

+                from django.core.exceptions import ImproperlyConfigured

+                raise ImproperlyConfigured, "You need to specify DATABASE_USER in your Django settings file."

+            conn_dict['uid'] = settings.DATABASE_USER

+            if settings.DATABASE_PASSWORD == '':

+                from django.core.exceptions import ImproperlyConfigured

+                raise ImproperlyConfigured, "You need to specify DATABASE_PASSWORD in your Django settings file."

+            conn_dict['pwd'] = settings.DATABASE_PASSWORD

+            # Just imitating others here, I haven't seen any "options" for DB2.

+            conn_dict.update(self.options)

+            self.connection = Database.connect(**conn_dict)

+            cursor = self.connection.cursor()

+        else:

+            cursor = self.connection.cursor()

+        return cursor

+

+    def _commit(self):

+        if self.connection is not None:

+            return self.connection.commit()

+

+    def _rollback(self):

+        if self.connection is not None:

+            return self.connection.rollback()

+

+    def close(self):

+        if self.connection is not None:

+            self.connection.close()

+            self.connection = None

+

=== django/db/backends/db2_9/client.py
==================================================================
--- django/db/backends/db2_9/client.py	(/mirror/django/trunk)	(revision 3920)

+++ django/db/backends/db2_9/client.py	(/local/django/db2_9)	(revision 3920)

@@ -0,0 +1,7 @@

+from django.conf import settings
+import os
+
+def runshell():
+	# Nothing fancy here, as the environment should have the
+	# instnace properly configured for anything to work at all.
+	os.execvp('db2')
=== django/db/backends/db2_9/__init__.py
==================================================================
=== django/db/backends/db2_9/introspection.py
==================================================================
--- django/db/backends/db2_9/introspection.py	(/mirror/django/trunk)	(revision 3920)

+++ django/db/backends/db2_9/introspection.py	(/local/django/db2_9)	(revision 3920)

@@ -0,0 +1,176 @@

+"""

+Optional: use a DATABASE_SCHEMA setting to let inspectdb find only tables from

+a specific schema.

+"""

+from django.conf import settings

+

+def get_table_list(cursor):

+	""" Here's the tricky part. The tables are accessed by their schema

+	for example, all the systems tables are prefixed by SYSIBM as their schema.

+	Since we can get really *lots* of tables without filtering by creator this can get

+	really messy, So I've used DATABASE_SCHEMA to filter it.

+	RTRIMs are needed because the fields return in full column size filled with spaces.

+	Using the SYSIBM.TABLES VIEW.

+	"""

+	from django.conf import settings

+	from django.utils.encoding import smart_str, force_unicode

+	try:

+		schema = settings.DATABASE_SCHEMA.upper()

+	except:

+		schema = ''

+	if schema != '':

+		cursor.execute("""SELECT RTRIM(table_name) as TABLES, RTRIM(table_schema) as SCHEMA FROM SYSIBM.TABLES 

+		WHERE TABLE_SCHEMA = %s AND TABLE_TYPE = 'BASE TABLE' ORDER BY table_name""", [schema])

+	else: # Fallback to everything but SYS*

+		cursor.execute("""SELECT RTRIM(table_name) as TABLES, RTRIM(table_schema) as SCHEMA FROM SYSIBM.tables 

+		WHERE table_schema NOT LIKE 'SYS%%' AND TABLE_TYPE = 'BASE TABLE' ORDER BY table_name""", [])

+	rows = cursor.fetchall()

+	res = []

+	# for tables from the user: do not append schema, others: use SCHEMA.TABLE as name

+	for row in rows:

+		if row[1].upper() == settings.DATABASE_USER.upper():

+			res.append(smart_str(row[0].upper()))

+		else:

+			res.append(smart_str("%s.%s" % (row[1].upper(), row[0].upper())))

+	return res

+

+def get_table_description(cursor, table_name):

+	"Returns a description of the table with the DB-API cursor.description interface."

+	cursor.execute("SELECT * FROM %s FETCH FIRST 1 ROWS ONLY" % (table_name,))

+	return cursor.description

+

+def _name_to_index(cursor, table_name):

+    """

+    Returns a dictionary of {field_name: field_index} for the given table.

+    Indexes are 0-based.

+    """

+    return dict([(d[0], i) for i, d in enumerate(get_table_description(cursor, table_name))])

+

+def get_relations(cursor, table_name):

+	"""

+	Returns a dictionary of {field_index: (field_index_other_table, other_table)}

+	representing all relationships to the given table. Indexes are 0-based.

+	Beware, PyDB2 returns the full length of the field, filled with spaces, 

+	I have to .strip() every single string field >.<

+	"""

+	from django.conf import settings

+	

+	if table_name.count('.') == 1:

+		schema, tn = table_name.split('.')

+	else:

+		tn = table_name

+		schema = settings.DATABASE_USER

+	cursor.execute("""SELECT fk.ORDINAL_POSITION, pk.ORDINAL_POSITION, pk.TABLE_NAME

+			FROM SYSIBM.SQLFOREIGNKEYS r, SYSIBM.SQLCOLUMNS fk, SYSIBM.SQLCOLUMNS pk

+			WHERE r.FKTABLE_SCHEM = %s AND r.FKTABLE_NAME = %s

+			AND r.FKTABLE_SCHEM = fk.TABLE_SCHEM AND r.FKTABLE_NAME = fk.TABLE_NAME

+			AND r.FKCOLUMN_NAME = fk.COLUMN_NAME

+			AND r.PKTABLE_SCHEM = pk.TABLE_SCHEM AND r.PKTABLE_NAME = pk.TABLE_NAME

+			AND r.PKCOLUMN_NAME = pk.COLUMN_NAME

+		""", (schema.upper(), tn))

+		

+	relations = {}

+	for row in cursor.fetchall():

+		relations[int(row[0]) - 1] = (int(row[1]) -1, row[2].strip())

+	return relations

+	

+def get_foreignkey_relations(cursor, table_name):

+	"""

+	Returns a dictionary of {field_index: other_table}

+	representing all relationships to other tables. Indexes are 0-based.

+	Beware, PyDB2 returns the full length of the field, filled with spaces,

+	I have to .strip() every single string field >.<

+	"""

+	from django.conf import settings

+

+	if table_name.count('.') == 1:

+		schema, tn = table_name.split('.')

+	else:

+		tn = table_name

+		schema = settings.DATABASE_USER

+	cursor.execute("""SELECT r.FKCOLUMN_NAME, r.PKTABLE_NAME

+			FROM SYSIBM.SQLFOREIGNKEYS r

+			WHERE r.FKTABLE_SCHEM = %s AND r.FKTABLE_NAME = %s

+		""", (schema.upper(), tn))

+

+	relations = []

+	for row in cursor.fetchall():

+		relations.append(row[1].strip())

+	return relations

+

+def get_indexes(cursor, table_name):

+	"""

+	Returns a dictionary of fieldname -> infodict for the given table,

+	where each infodict is in the format:

+		{'primary_key': boolean representing whether it's the primary key,

+		'unique': boolean representing whether it's a unique index}

+	DB2 is weird here, like this seems to be ok but I don't get 100% of the indexes

+		syscolumns.keyseq means the column part of a "parent key".

+		sysindexes.uniquerule == P means it's primary, U == unique,

+			and D == duplicates allowed.

+	"""	

+	from django.conf import settings

+	if table_name.count('.') == 1:

+		schema, tn = table_name.split('.')

+	else:

+		tn = table_name

+		schema = settings.DATABASE_USER

+	cursor.execute("""SELECT c.COLUMN_NAME, i.UNIQUERULE 

+			FROM SYSIBM.SQLCOLUMNS c, SYSCAT.INDEXES i, SYSCAT.INDEXCOLUSE k

+			WHERE c.TABLE_SCHEM = %s AND c.TABLE_NAME = %s

+			AND C.TABLE_SCHEM = i.TABSCHEMA AND c.TABLE_NAME = i.TABNAME

+			AND i.INDSCHEMA = k.INDSCHEMA AND i.INDNAME = k.INDNAME

+			AND c.COLUMN_NAME = k.COLNAME

+		""", (schema.upper(), tn))

+

+	indexes = {}

+	for row in cursor.fetchall():

+		urule = row[1].strip()

+		name = row[0].strip()

+		if urule == "P":

+			# Override everything, as this is a primary key.

+			indexes[name] = {'primary_key':True, 'unique':False}

+		elif urule == "U":

+			try:

+				if indexes[name]['primary_key'] == True:

+					# Can appear twice, but primary is primary anyway.

+					continue

+				else:

+					indexes[name] = {'primary_key':False, 'unique':True}

+			except: # TODO: Only a keyerror can happen here, right?

+				indexes[name] = {'primary_key':False, 'unique':True}

+		else: # urule = "D" not sure if there are others.

+			try:

+				# Should never happen, but...

+				if indexes[name]['primary_key'] == True or indexes[name]['unique'] == True:

+					continue

+				else:

+					indexes[name] = {'primary_key':False, 'unique':False}

+			except: # TODO: same as above ^_^

+				indexes[name] = {'primary_key':False, 'unique':False}

+	return indexes

+

+DATA_TYPES_REVERSE = {

+	-99:'TextField', # CLOB

+	-98:'TextField', # BLOB

+	-97:'TextField', # Long VarGraphic

+	-96:'TextField', # VarGraphic

+	-95:'TextField', # Graphic

+	-5: 'IntegerField', # Big Int

+	-4: 'TextField', # Binary Long VarChar

+	-3: 'TextField', # Binary VarChar

+	-2: 'TextField', # Binary

+	-1: 'TextField', # Long VarChar

+	1: 'CharField',  # Char

+	2: 'FloatField', # Numeric

+	3: 'FloatField', # Decimal

+	4: 'IntegerField', # Integer

+	5: 'BooleanField', # SmallInt

+	6: 'FloatField', # Float

+	7: 'FloatField', # Real

+	8: 'FloatField', # Double

+	12: 'CharField', # VarChar

+	91: 'DateField', # Date

+	92: 'TimeField', # Time

+	93: 'DateTimeField', # TimeStamp

+}

=== django/db/backends/db2_9/creation.py
==================================================================
--- django/db/backends/db2_9/creation.py	(/mirror/django/trunk)	(revision 3920)

+++ django/db/backends/db2_9/creation.py	(/local/django/db2_9)	(revision 3920)

@@ -0,0 +1,80 @@

+"""

+TEST_DATABASE_USER must be an existing user (DB2 v9 uses OS authentication)

+"""

+import sys, time

+from django.core import management

+from django.db.models.signals import pre_save

+from django.db.backends.db2_9 import base

+

+# Not quite sure about the Boolean and Float fields.

+# Not sure what to use for Boolean, since there seems to be a 'BOOLEAN' type, 

+# but it isn't 'documented' anywhere. For Float, we have DOUBLE, DECIMAL and NUMERIC

+# to choose from :+/, too many.

+DATA_TYPES = {

+	'AutoField':                    'INTEGER GENERATED BY DEFAULT AS IDENTITY', # ALWAYS breaks basic test (specifying key explicitly)

+	'BooleanField':                 'SMALLINT',

+	'CharField':                    'VARCHAR(%(max_length)s)',

+	'CommaSeparatedIntegerField':   'VARCHAR(%(max_length)s)',

+	'DateField':                    'DATE',

+	'DateTimeField':                'TIMESTAMP',

+	'DecimalField':                 'DECIMAL(%(max_digits)s, %(decimal_places)s)',

+	'FileField':                    'VARCHAR(100)',

+	'FilePathField':                'VARCHAR(100)',

+	'FloatField':                   'FLOAT',

+	'ImageField':                   'VARCHAR(100)',

+	'IntegerField':                 'INTEGER',

+	'IPAddressField':               'CHAR(15)',

+	'ManyToManyField':              None,

+	'NullBooleanField':             'SMALLINT',

+	'OneToOneField':                'INTEGER',

+	'PhoneNumberField':             'CHAR(20)',

+	'PositiveIntegerField':         'INTEGER',

+	'PositiveSmallIntegerField':    'SMALLINT',

+	'SlugField':                    'VARCHAR(%(max_length)s)',

+	'SmallIntegerField':            'SMALLINT',

+	'TextField':                    'LONG VARCHAR',

+	'TimeField':                    'TIME',

+	'USStateField':                 'CHAR(2)',

+}

+

+REMEMBER = {}

+

+def create_test_db(settings, connection, verbosity=1, autoclobber=False):

+    """Assuming here that a TEST_DATABASE_USER is set in settings that is known to the OS and DB2 

+    with DB2ADM rights.

+    Best is that this user has a separate default tablespace defined in DB2 (but this is not required)."""

+    REMEMBER['user'] = settings.DATABASE_USER

+    REMEMBER['passwd'] = settings.DATABASE_PASSWORD

+    settings.DATABASE_USER = settings.TEST_DATABASE_USER

+    settings.DATABASE_PASSWORD = settings.TEST_DATABASE_PASSWORD

+    management.call_command('syncdb', verbosity=verbosity, interactive=False)

+

+def destroy_test_db(settings, connection, old_database_name, verbosity=1):

+    """Delete all tables from the test user."""

+    connection.close()

+    

+    settings.DATABASE_USER = REMEMBER['user']

+    settings.DATABASE_PASSWORD = REMEMBER['passwd']

+    

+    cursor = connection.cursor()

+

+    time.sleep(1) # To avoid "database is being accessed by other users" errors.

+

+    if verbosity >= 2:

+        print "_destroy_test_db(): removing tables from schema %s" % settings.TEST_DATABASE_USER

+

+    cursor.execute("""SELECT RTRIM(table_name) as TABLES FROM SYSIBM.TABLES 

+        WHERE TABLE_SCHEMA = %s AND TABLE_TYPE = 'BASE TABLE' ORDER BY table_name""", [settings.TEST_DATABASE_USER.upper()])

+

+    rows = cursor.fetchall()

+    for row in rows:

+        stmt = 'DROP TABLE %s.%s' % (settings.TEST_DATABASE_USER.upper(), row[0])

+        if verbosity >= 2:

+            print stmt

+        try:

+            cursor.execute(stmt)

+        except Exception, err:

+            sys.stderr.write("Failed (%s)\n" % (err))

+            raise

+    connection._commit()

+    connection.close()

\ No newline at end of file


Property changes on: django/db/backends/db2_9
___________________________________________________________________
Name: svn:ignore
 +*.pyc
 +

=== django/core/management/sql.py
==================================================================
--- django/core/management/sql.py	(/mirror/django/trunk)	(revision 3920)

+++ django/core/management/sql.py	(/local/django/db2_9)	(revision 3920)

@@ -317,6 +317,10 @@

     from django.db.backends.util import truncate_name
 
     qn = connection.ops.quote_name
+    if hasattr(connection.ops, 'max_constraint_length'):
+        mnl = connection.ops.max_constraint_length
+    else:
+        mnl = connection.ops.max_name_length
     final_output = []
     if connection.features.supports_constraints:
         opts = model._meta
@@ -331,7 +335,7 @@

                 # So we are careful with character usage here.
                 r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table))))
                 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
-                    (qn(r_table), truncate_name(r_name, connection.ops.max_name_length()),
+                    (qn(r_table), truncate_name(r_name, mnl()),
                     qn(r_col), qn(table), qn(col),
                     connection.ops.deferrable_sql()))
             del pending_references[model]

Property changes on: 
___________________________________________________________________
Name: svk:merge
 -bcc190cf-cafb-0310-a4f2-bffc1f526a37:/django/trunk:1054
 +bcc190cf-cafb-0310-a4f2-bffc1f526a37:/django/trunk:6110

