| 1 | """ $Id: base.py 37 2007-06-05 19:49:06Z the_paya $ |
| 2 | |
| 3 | IBM DB2 database backend for Django |
| 4 | |
| 5 | Requires PyDB2: http://sourceforge.net/projects/pydb2/ |
| 6 | With this patch: http://sourceforge.net/tracker/index.php?func=detail&aid=1731609&group_id=67548&atid=518208 |
| 7 | """ |
| 8 | from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util |
| 9 | import re |
| 10 | from django.utils.encoding import smart_str, force_unicode |
| 11 | |
| 12 | try: |
| 13 | import DB2 as Database |
| 14 | except ImportError, e: |
| 15 | from django.core.exceptions import ImproperlyConfigured |
| 16 | raise ImproperlyConfigured, "Error loading DB2 python module: %s" % e |
| 17 | import datetime |
| 18 | from django.utils.datastructures import SortedDict |
| 19 | |
| 20 | Warning = Database.Warning |
| 21 | Error = Database.Error |
| 22 | InterfaceError = Database.InterfaceError |
| 23 | DatabaseError = Database.DatabaseError |
| 24 | DataError = Database.DataError |
| 25 | OperationalError = Database.OperationalError |
| 26 | IntegrityError = Database.IntegrityError |
| 27 | InternalError = Database.InternalError |
| 28 | ProgrammingError = Database.ProgrammingError |
| 29 | NotSupportedError = Database.NotSupportedError |
| 30 | |
| 31 | class 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 | |
| 121 | integrity_off_tables = [] |
| 122 | |
| 123 | def 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 | |
| 142 | class 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 | |
| 158 | Database.connect = Connection |
| 159 | |
| 160 | class 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 | |
| 177 | class 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 | |
| 536 | class 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 | |