Ticket #5052: db2_9.2.patch

File db2_9.2.patch, 41.3 KB (added by Koen Biermans <koen.biermans@…>, 17 years ago)

DB2 backend

Line 
1=== django/db/models/query.py
2==================================================================
3--- django/db/models/query.py (/mirror/django/trunk) (revision 3644)
4
5+++ django/db/models/query.py (/local/django/db2_9) (revision 3644)
6
7@@ -963,7 +963,10 @@
8
9 # Does the name belong to a defined many-to-many field?
10 field = find_field(name, current_opts.many_to_many, False)
11 if field:
12- new_table = current_table + '__' + name
13+ if hasattr(backend, 'get_alias'):
14+ new_table = backend.get_alias(current_table, name)
15+ else:
16+ new_table = current_table + '__' + name
17 new_opts = field.rel.to._meta
18 new_column = new_opts.pk.column
19
20@@ -980,7 +983,10 @@
21
22 # Does the name belong to a reverse defined many-to-many field?
23 field = find_field(name, current_opts.get_all_related_many_to_many_objects(), True)
24 if field:
25- new_table = current_table + '__' + name
26+ if hasattr(backend, 'get_alias'):
27+ new_table = backend.get_alias(current_table, name)
28+ else:
29+ new_table = current_table + '__' + name
30 new_opts = field.opts
31 new_column = new_opts.pk.column
32
33@@ -997,7 +1003,10 @@
34
35 # Does the name belong to a one-to-many field?
36 field = find_field(name, current_opts.get_all_related_objects(), True)
37 if field:
38- new_table = table + '__' + name
39+ if hasattr(backend, 'get_alias'):
40+ new_table = backend.get_alias(current_table, name)
41+ else:
42+ new_table = current_table + '__' + name
43 new_opts = field.opts
44 new_column = field.field.column
45 join_column = opts.pk.column
46@@ -1011,7 +1020,10 @@
47
48 field = find_field(name, current_opts.fields, False)
49 if field:
50 if field.rel: # One-to-One/Many-to-one field
51- new_table = current_table + '__' + name
52+ if hasattr(backend, 'get_alias'):
53+ new_table = backend.get_alias(current_table, name)
54+ else:
55+ new_table = current_table + '__' + name
56 new_opts = field.rel.to._meta
57 new_column = new_opts.pk.column
58 join_column = field.column
59=== django/db/backends/db2_9 (new directory)
60==================================================================
61=== django/db/backends/db2_9/base.py
62==================================================================
63--- django/db/backends/db2_9/base.py (/mirror/django/trunk) (revision 3644)
64
65+++ django/db/backends/db2_9/base.py (/local/django/db2_9) (revision 3644)
66
67@@ -0,0 +1,624 @@
68
69+"""
70
71+
72
73+IBM DB2 database backend for Django
74
75+
76
77+Requires PyDB2: http://sourceforge.net/projects/pydb2/
78
79+With this patch: http://sourceforge.net/tracker/index.php?func=detail&aid=1731609&group_id=67548&atid=518208
80
81+
82
83+Authors:
84
85+ Javier Villavicencio
86
87+ Koen Biermans
88
89+
90
91+Current issues:
92
93+ DB2 does not support deferring foreign key constraint checking.
94
95+ --> prereferencing does not work (e.g. in serializer_regress test)
96
97+
98
99+ No regex lookups.
100
101+
102
103+ Character sets:
104
105+ put encoding/decoding into settings.DATABASE_CHARSET into the cursor code.
106
107+
108
109+ Floats as primary key does not work.
110
111+
112
113+Specific items:
114
115+ Connection:
116
117+ use a DNS for DATABASE_NAME
118
119+ this DNS can be system DNS or defined in db2cli.ini
120
121+ example for db2cli.ini:
122
123+ [DNSNAME]
124
125+ Protocol=TCPIP
126
127+ Hostname=DB2HOST
128
129+ ServiceName=50001
130
131+ Database=DATABASE
132
133+ Port=50001
134
135+
136
137+ Tables may be set in models.py with their schema: 'db_table = SCHEMA.TABLENAME'
138
139+ --> modified query.py to use get_alias function (no dots allowed in alias,
140
141+ default implementation uses db_table)
142
143+
144
145+ DB2 table names may be 128 characters, but constraint names are limited to 18.
146
147+ --> modified management.py to use get_max_constraint_length function
148
149+
150
151+"""
152
153+
154
155+from django.db.backends import util
156
157+import re
158
159+from django.utils.encoding import smart_str, force_unicode
160
161+
162
163+try:
164
165+ import DB2 as Database
166
167+except ImportError, e:
168
169+ from django.core.exceptions import ImproperlyConfigured
170
171+ raise ImproperlyConfigured, "Error loading DB2 python module: %s" % e
172
173+import datetime
174
175+from django.utils.datastructures import SortedDict
176
177+
178
179+Warning = Database.Warning
180
181+Error = Database.Error
182
183+InterfaceError = Database.InterfaceError
184
185+DatabaseError = Database.DatabaseError
186
187+DataError = Database.DataError
188
189+OperationalError = Database.OperationalError
190
191+IntegrityError = Database.IntegrityError
192
193+InternalError = Database.InternalError
194
195+ProgrammingError = Database.ProgrammingError
196
197+NotSupportedError = Database.NotSupportedError
198
199+
200
201+class Cursor(Database.Cursor):
202
203+ """Return a cursor.
204
205+ Doing some translation tricks here.
206
207+ Set the database charset in DATABASE_CHARSET setting"""
208
209+ try:
210
211+ charset = settings.DATABASE_CHARSET
212
213+ except:
214
215+ charset = 'iso-8859-1'
216
217+
218
219+ def _rewrite_args(self, query, params=None):
220
221+ # formatting parameters into charset
222
223+ if params is None:
224
225+ params = []
226
227+ else:
228
229+ params = self._format_params(params)
230
231+ # formatting query into charset
232
233+ query = smart_str(query, self.charset)
234
235+ a = query.find('CREATE')
236
237+ if a >= 0 and a < 10: # assuming this is a create command
238
239+ # DB2 doesn't want a 'lone' NULL (it's implied).
240
241+ query = re.sub('(?<!NOT) NULL', '', query)
242
243+ # DB2 does not like primary key definition without NOT NULL
244
245+ query = re.sub('(?<!NOT NULL) PRIMARY KEY', ' NOT NULL PRIMARY KEY',query)
246
247+ # PyDB2 uses '?' as the parameter style.
248
249+ query = query.replace("%s", "?")
250
251+ return query, params
252
253+
254
255+ def _format_params(self, params=None):
256
257+ return self._smart_str(params)
258
259+
260
261+ def execute(self, query, params=None):
262
263+ query, params = self._rewrite_args(query, params)
264
265+ try:
266
267+ return Database.Cursor.execute(self, query, params)
268
269+ except:
270
271+ print 'Error executing query %s with params %s' % (query, params)
272
273+ raise
274
275+
276
277+ def executemany(self, query, params=None):
278
279+ query, params = self._rewrite_args(query, params)
280
281+ return Database.Cursor.executemany(self, query, params)
282
283+
284
285+ def fetchone(self):
286
287+ # force results into unicode
288
289+ return self._force_unicode(Database.Cursor.fetchone(self))
290
291+
292
293+ def fetchmany(self, size=None):
294
295+ if size is None:
296
297+ size = self.arraysize
298
299+ # force results into unicode
300
301+ return self._force_unicode(Database.Cursor.fetchmany(self, size))
302
303+
304
305+ def fetchall(self):
306
307+ # is this ever used ?
308
309+ # force results into unicode
310
311+ return self._force_unicode(Database.Cursor.fetchall(self))
312
313+
314
315+ def _smart_str(self, s=None):
316
317+ if s is None:
318
319+ return s
320
321+ if isinstance(s, dict):
322
323+ result = {}
324
325+ for key, value in s.items():
326
327+ result[_smart_str(key)] = self._smart_str(value)
328
329+ return result
330
331+ elif isinstance(s, (tuple,list)):
332
333+ return tuple([self._smart_str(p) for p in s])
334
335+ else:
336
337+ if isinstance(s, basestring):
338
339+ try:
340
341+ return smart_str(s, self.charset, True)
342
343+ except UnicodeEncodeError:
344
345+ return ''
346
347+ return s
348
349+
350
351+ def _force_unicode(self,s=None):
352
353+ if s is None:
354
355+ return s
356
357+ if isinstance(s, dict):
358
359+ result = {}
360
361+ for key, value in s.items():
362
363+ result[force_unicode(key, charset)] = self._force_unicode(value, charset)
364
365+ return result
366
367+ elif isinstance(s, (tuple,list)):
368
369+ return tuple([self._force_unicode(p) for p in s])
370
371+ else:
372
373+ if isinstance(s, basestring):
374
375+ try:
376
377+ return force_unicode(s, encoding=self.charset)
378
379+ except UnicodeEncodeError:
380
381+ return u''
382
383+ return s
384
385+
386
387+class Connection(Database.Connection):
388
389+ def cursor(self):
390
391+ return Cursor(self._db.cursor())
392
393+
394
395+Database.connect = Connection
396
397+
398
399+class DatabaseWrapper(object):
400
401+ def __init__(self, **kwargs):
402
403+ self.connection = None
404
405+ self.queries = []
406
407+ self.server_version = None
408
409+ self.options = kwargs
410
411+
412
413+ def _valid_connection(self):
414
415+ return self.connection is not None
416
417+
418
419+ def cursor(self):
420
421+ from django.conf import settings
422
423+ from warnings import filterwarnings
424
425+ if self.connection is None:
426
427+ conn_dict = {}
428
429+ # A DB2 client is configured with nodes, and then with databases connected
430
431+ # to these nodes, I don't know if there is a way of specifying a complete
432
433+ # DSN with a hostname and port, the PyDB2 module shows no sign of this either.
434
435+ # So this DSN is actually the database name configured in the host's client instance.
436
437+ if settings.DATABASE_NAME == '':
438
439+ from django.core.exceptions import ImproperlyConfigured
440
441+ raise ImproperlyConfigured, "You need to specify DATABASE_NAME in your Django settings file."
442
443+ conn_dict['dsn'] = settings.DATABASE_NAME
444
445+ if settings.DATABASE_USER == '':
446
447+ from django.core.exceptions import ImproperlyConfigured
448
449+ raise ImproperlyConfigured, "You need to specify DATABASE_USER in your Django settings file."
450
451+ conn_dict['uid'] = settings.DATABASE_USER
452
453+ if settings.DATABASE_PASSWORD == '':
454
455+ from django.core.exceptions import ImproperlyConfigured
456
457+ raise ImproperlyConfigured, "You need to specify DATABASE_PASSWORD in your Django settings file."
458
459+ conn_dict['pwd'] = settings.DATABASE_PASSWORD
460
461+ # Just imitating others here, I haven't seen any "options" for DB2.
462
463+ conn_dict.update(self.options)
464
465+ self.connection = Database.connect(**conn_dict)
466
467+ cursor = self.connection.cursor()
468
469+ else:
470
471+ cursor = self.connection.cursor()
472
473+ if settings.DEBUG:
474
475+ return util.CursorDebugWrapper(cursor, self)
476
477+ return cursor
478
479+
480
481+ def _commit(self):
482
483+ if self.connection is not None:
484
485+ return self.connection.commit()
486
487+
488
489+ def _rollback(self):
490
491+ if self.connection is not None:
492
493+ return self.connection.rollback()
494
495+
496
497+ def close(self):
498
499+ if self.connection is not None:
500
501+ self.connection.close()
502
503+ self.connection = None
504
505+
506
507+allows_group_by_ordinal = False
508
509+allows_unique_and_pk = True
510
511+needs_datetime_string_cast = True
512
513+needs_upper_for_iops = True
514
515+autoindexes_primary_keys = True
516
517+supports_constraints = True
518
519+supports_tablespaces = False
520
521+supports_compound_statements = True
522
523+uses_case_insensitive_names = True
524
525+
526
527+def quote_name(name):
528
529+ """Name quoting.
530
531+ Names of type schema.tablename become "schema"."tablename"."""
532
533+ from django.conf import settings
534
535+ if not name.startswith('"') and not name.endswith('"'):
536
537+ return '.'.join(['"%s"' % util.truncate_name(f.upper(), get_max_name_length()) for f in name.split('.')])
538
539+ return name.upper()
540
541+
542
543+dictfetchone = util.dictfetchone
544
545+dictfetchmany = util.dictfetchmany
546
547+dictfetchall = util.dictfetchall
548
549+
550
551+def get_last_insert_id(cursor, table_name, pk_name):
552
553+ # There is probably a 'better' way to do this.
554
555+ cursor.execute("SELECT MAX(%s) FROM %s" % (quote_name(pk_name), quote_name(table_name)))
556
557+ a = cursor.fetchone()
558
559+ if a is None:
560
561+ return 0
562
563+ return a[0]
564
565+
566
567+def get_date_extract_sql(lookup_type, field_name):
568
569+ # lookup_type is 'year', 'month', 'day'
570
571+ if lookup_type == 'year':
572
573+ return "YEAR(%s)" % field_name
574
575+ elif lookup_type == 'month':
576
577+ return "MONTH(%s)" % field_name
578
579+ elif lookup_type == 'day':
580
581+ return "DAY(%s)" % field_name
582
583+
584
585+def get_date_trunc_sql(lookup_type, field_name):
586
587+ # lookup_type is 'year', 'month', 'day'
588
589+ # Hard one. I have seen TRUNC_TIMESTAMP somewhere but is not present
590
591+ # in any of my databases.
592
593+ # Doesn't work 'directly' since the GROUP BY needs this as well,
594
595+ # DB2 can't take "GROUP BY 1"
596
597+ if lookup_type == 'year':
598
599+ return "TIMESTAMP(DATE(%s -DAYOFYEAR(%s) DAYS +1 DAY),'00:00:00')" % (field_name, field_name)
600
601+ elif lookup_type == 'month':
602
603+ return "TIMESTAMP(DATE(%s -DAY(%s) DAYS +1 DAY),'00:00:00')" % (field_name, field_name)
604
605+ elif lookup_type == 'day':
606
607+ return "TIMESTAMP(DATE(%s),'00:00:00')" % field_name
608
609+
610
611+def get_datetime_cast_sql():
612
613+ return ""
614
615+
616
617+def get_limit_offset_sql(limit, offset=None):
618
619+ # Limits and offset are too complicated to be handled here.
620
621+ # Instead, they are handled in DB2QuerySet.
622
623+ return ""
624
625+
626
627+def get_random_function_sql():
628
629+ return "RAND()"
630
631+
632
633+def get_deferrable_sql():
634
635+ # DB2 does not support deferring constraints to end of transaction
636
637+ # using cascade avoid constraint problems
638
639+ return " ON DELETE CASCADE"
640
641+
642
643+def get_fulltext_search_sql(field_name):
644
645+ # DB2 has some nice extra packages that enables a CONTAINS() function,
646
647+ # but they're not available in my dbs.
648
649+ raise NotImplementedError
650
651+
652
653+def get_drop_foreignkey_sql():
654
655+ return "DROP CONSTRAINT"
656
657+
658
659+def get_pk_default_value():
660
661+ return "DEFAULT"
662
663+
664
665+def get_max_name_length():
666
667+ return 128;
668
669+
670
671+def get_max_constraint_length():
672
673+ # This needs a patch of management.py to detect and use this function
674
675+ # 18 for primarykeys and constraint names
676
677+ return 18;
678
679+
680
681+def get_alias(table, column):
682
683+ if table.count('.')==0:
684
685+ return "%s__%s" % (table, column)
686
687+ return "%s__%s" % (table.split('.')[-1], column)
688
689+
690
691+def get_start_transaction_sql():
692
693+ return "BEGIN;"
694
695+
696
697+def get_autoinc_sql(table):
698
699+ return None
700
701+
702
703+def get_drop_sequence(table):
704
705+ return "DROP SEQUENCE %s;" % quote_name(get_sequence_name(table))
706
707+
708
709+def _get_sequence_reset_sql():
710
711+ return 'ALTER TABLE %s ALTER COLUMN %s RESTART WITH %s'
712
713+
714
715+def _get_sql_flush(style, table, emptytables):
716
717+ """ Recursive function to retrieve the list of delete statements in the
718
719+ right order.
720
721+ """
722
723+ from django.db.backends.db2_9.introspection import get_foreignkey_relations
724
725+ from django.db import connection
726
727+ from django.utils.encoding import smart_str
728
729+ cursor = connection.cursor()
730
731+ sql = []
732
733+ te = []
734
735+ if emptytables:
736
737+ temptytables = emptytables[:]
738
739+ else:
740
741+ temptytables = []
742
743+ relations = get_foreignkey_relations(cursor, table)
744
745+ for relation in relations:
746
747+ relation = smart_str(relation)
748
749+ if relation not in temptytables:
750
751+ if temptytables:
752
753+ tt2 = temptytables[:].extend(table) # avoid circular reference endless loop
754
755+ else:
756
757+ tt2 = [table]
758
759+ tsql, tem = _get_sql_flush(style, relation, tt2)
760
761+ sql.extend(tsql)
762
763+ te.extend(tem)
764
765+ temptytables.extend(tem)
766
767+ if table not in temptytables:
768
769+ sql.append('%s %s %s;' % (style.SQL_KEYWORD('DELETE'), style.SQL_KEYWORD('FROM'),
770
771+ style.SQL_FIELD(quote_name(table))))
772
773+ te.append(table)
774
775+ return sql, te
776
777+
778
779+def get_sql_flush(style, tables, sequences):
780
781+ """Return a list of SQL statements required to remove all data from
782
783+ all tables in the database (without actually removing the tables
784
785+ themselves) and put the database in an empty 'initial' state"""
786
787+ if tables:
788
789+ emptytables = []
790
791+ sql = []
792
793+ for table in tables:
794
795+ if table.count('.') == 0:
796
797+ if table not in emptytables:
798
799+ tsql, te = _get_sql_flush(style, table, emptytables)
800
801+ sql.extend(tsql)
802
803+ emptytables.extend(te)
804
805+ for sequence_info in sequences:
806
807+ if sequence_info['table'].upper() == table.upper():
808
809+ column = sequence_info['column']
810
811+ if column is not None:
812
813+ query = _get_sequence_reset_sql() % (quote_name(table),quote_name(column),1)
814
815+ sql.append(query)
816
817+ return sql
818
819+ else:
820
821+ return []
822
823+
824
825+def get_sql_sequence_reset(style, model_list):
826
827+ "Returns a list of the SQL statements to reset sequences for the given models."
828
829+ from django.db import models
830
831+ from django.db import connection
832
833+ output = []
834
835+ query = _get_sequence_reset_sql()
836
837+ for model in model_list:
838
839+ for f in model._meta.fields:
840
841+ if isinstance(f, models.AutoField):
842
843+ cursor = connection.cursor()
844
845+ max_id = get_last_insert_id(cursor, model._meta.db_table, f.column) + 1
846
847+ output.append(query % (quote_name(model._meta.db_table), quote_name(f.column), max_id))
848
849+ cursor.close()
850
851+ cursor = None
852
853+ break # Only one AutoField is allowed per model, so don't bother continuing.
854
855+ #~ for f in model._meta.many_to_many:
856
857+ #~ cursor = connection.cursor()
858
859+ #~ max_id = get_last_insert_id(cursor, model._meta.db_table, f.column) + 1
860
861+ #~ output.append(query % (quote_name(f.m2m_db_table()), quote_name(f.m2m_column_name()), max_id))
862
863+ return output
864
865+
866
867+def get_query_set_class(DefaultQuerySet):
868
869+ "Create a custom QuerySet class for DB2."
870
871+
872
873+ from django.db import backend, connection
874
875+ from django.db.models.query import EmptyResultSet, GET_ITERATOR_CHUNK_SIZE, quote_only_if_word
876
877+
878
879+ class DB2QuerySet(DefaultQuerySet):
880
881+
882
883+ def iterator(self):
884
885+ "Performs the SELECT database lookup of this QuerySet."
886
887+
888
889+ from django.db.models.query import get_cached_row
890
891+
892
893+ # self._select is a dictionary, and dictionaries' key order is
894
895+ # undefined, so we convert it to a list of tuples.
896
897+ extra_select = self._select.items()
898
899+
900
901+ full_query = None
902
903+
904
905+ try:
906
907+ try:
908
909+ select, sql, params, full_query = self._get_sql_clause(get_full_query=True)
910
911+ except TypeError:
912
913+ select, sql, params = self._get_sql_clause()
914
915+ except EmptyResultSet:
916
917+ raise StopIteration
918
919+ if not full_query:
920
921+ full_query = "SELECT %s%s\n%s" % \
922
923+ ((self._distinct and "DISTINCT " or ""),
924
925+ ', '.join(select), sql)
926
927+
928
929+ cursor = connection.cursor()
930
931+ cursor.execute(full_query, params)
932
933+
934
935+ fill_cache = self._select_related
936
937+ fields = self.model._meta.fields
938
939+ index_end = len(fields)
940
941+
942
943+ # so here's the logic;
944
945+ # 1. retrieve each row in turn
946
947+ # 2. convert NCLOBs
948
949+
950
951+ while 1:
952
953+ rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
954
955+ if not rows:
956
957+ raise StopIteration
958
959+ for row in rows:
960
961+ row = self.resolve_columns(row, fields)
962
963+ if fill_cache:
964
965+ obj, index_end = get_cached_row(klass=self.model, row=row,
966
967+ index_start=0, max_depth=self._max_related_depth)
968
969+ else:
970
971+ obj = self.model(*row[:index_end])
972
973+ for i, k in enumerate(extra_select):
974
975+ setattr(obj, k[0], row[index_end+i])
976
977+ yield obj
978
979+
980
981+ def _get_sql_clause(self, get_full_query=False):
982
983+ from django.db.models.query import fill_table_cache, \
984
985+ handle_legacy_orderlist, orderfield2column
986
987+
988
989+ opts = self.model._meta
990
991+
992
993+ select = ["%s.%s" % (backend.quote_name(opts.db_table), backend.quote_name(f.column)) for f in opts.fields]
994
995+ tables = [quote_only_if_word(t) for t in self._tables]
996
997+ joins = SortedDict()
998
999+ where = self._where[:]
1000
1001+ params = self._params[:]
1002
1003+
1004
1005+ # Convert self._filters into SQL.
1006
1007+ joins2, where2, params2 = self._filters.get_sql(opts)
1008
1009+ joins.update(joins2)
1010
1011+ where.extend(where2)
1012
1013+ params.extend(params2)
1014
1015+
1016
1017+ # Add additional tables and WHERE clauses based on select_related.
1018
1019+ if self._select_related:
1020
1021+ fill_table_cache(opts, select, tables, where, opts.db_table, [opts.db_table])
1022
1023+
1024
1025+ # Add any additional SELECTs.
1026
1027+ if self._select:
1028
1029+ select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select.items()])
1030
1031+
1032
1033+ # Start composing the body of the SQL statement.
1034
1035+ sql = [" FROM", backend.quote_name(opts.db_table)]
1036
1037+
1038
1039+ # Compose the join dictionary into SQL describing the joins.
1040
1041+ if joins:
1042
1043+ sql.append(" ".join(["%s %s %s ON %s" % (join_type, table, alias, condition)
1044
1045+ for (alias, (table, join_type, condition)) in joins.items()]))
1046
1047+
1048
1049+ # Compose the tables clause into SQL.
1050
1051+ if tables:
1052
1053+ sql.append(", " + ", ".join(tables))
1054
1055+
1056
1057+ # Compose the where clause into SQL.
1058
1059+ if where:
1060
1061+ sql.append(where and "WHERE " + " AND ".join(where))
1062
1063+
1064
1065+ # ORDER BY clause
1066
1067+ order_by = []
1068
1069+ if self._order_by is not None:
1070
1071+ ordering_to_use = self._order_by
1072
1073+ else:
1074
1075+ ordering_to_use = opts.ordering
1076
1077+ for f in handle_legacy_orderlist(ordering_to_use):
1078
1079+ if f == '?': # Special case.
1080
1081+ order_by.append(backend.get_random_function_sql())
1082
1083+ else:
1084
1085+ if f.startswith('-'):
1086
1087+ col_name = f[1:]
1088
1089+ order = "DESC"
1090
1091+ else:
1092
1093+ col_name = f
1094
1095+ order = "ASC"
1096
1097+ if "." in col_name:
1098
1099+ table_prefix, col_name = col_name.split('.', 1)
1100
1101+ table_prefix = backend.quote_name(table_prefix) + '.'
1102
1103+ else:
1104
1105+ # Use the database table as a column prefix if it wasn't given,
1106
1107+ # and if the requested column isn't a custom SELECT.
1108
1109+ if "." not in col_name and col_name not in (self._select or ()):
1110
1111+ table_prefix = backend.quote_name(opts.db_table) + '.'
1112
1113+ else:
1114
1115+ table_prefix = ''
1116
1117+ order_by.append('%s%s %s' % (table_prefix, backend.quote_name(orderfield2column(col_name, opts)), order))
1118
1119+ if order_by:
1120
1121+ sql.append("ORDER BY " + ", ".join(order_by))
1122
1123+
1124
1125+ # Look for column name collisions in the select elements
1126
1127+ # and fix them with an AS alias. This allows us to do a
1128
1129+ # SELECT * later in the paging query.
1130
1131+ cols = [clause.split('.')[-1] for clause in select]
1132
1133+ for index, col in enumerate(cols):
1134
1135+ if cols.count(col) > 1:
1136
1137+ col = '%s%d' % (col.replace('"', ''), index)
1138
1139+ cols[index] = col
1140
1141+ select[index] = '%s AS %s' % (select[index], col)
1142
1143+
1144
1145+ # LIMIT and OFFSET clauses
1146
1147+ # To support limits and offsets, Oracle requires some funky rewriting of an otherwise normal looking query.
1148
1149+ select_clause = ",".join(select)
1150
1151+ distinct = (self._distinct and "DISTINCT " or "")
1152
1153+
1154
1155+ if order_by:
1156
1157+ order_by_clause = " OVER (ORDER BY %s )" % (", ".join(order_by))
1158
1159+ else:
1160
1161+ #DB2's row_number() function always requires an order-by clause.
1162
1163+ #So we need to define a default order-by, since none was provided.
1164
1165+ order_by_clause = " OVER (ORDER BY %s.%s)" % \
1166
1167+ (backend.quote_name(opts.db_table),
1168
1169+ backend.quote_name(opts.fields[0].db_column or opts.fields[0].column))
1170
1171+ # limit_and_offset_clause
1172
1173+ if self._limit is None:
1174
1175+ assert self._offset is None, "'offset' is not allowed without 'limit'"
1176
1177+
1178
1179+ if self._offset is not None:
1180
1181+ offset = int(self._offset)
1182
1183+ else:
1184
1185+ offset = 0
1186
1187+ if self._limit is not None:
1188
1189+ limit = int(self._limit)
1190
1191+ else:
1192
1193+ limit = None
1194
1195+# if limit == 0:
1196
1197+# limit = None
1198
1199+ limit_and_offset_clause = ''
1200
1201+ if limit is not None:
1202
1203+ limit_and_offset_clause = "WHERE rn > %s AND rn <= %s" % (offset, limit+offset)
1204
1205+ elif offset:
1206
1207+ limit_and_offset_clause = "WHERE rn > %s" % (offset)
1208
1209+
1210
1211+ if len(limit_and_offset_clause) > 0:
1212
1213+ if limit is not None:
1214
1215+ fmt = "SELECT * FROM (SELECT %s%s, ROW_NUMBER()%s AS rn %s FETCH FIRST %s ROWS ONLY) AS foo %s"
1216
1217+ full_query = fmt % (distinct, select_clause,
1218
1219+ order_by_clause, ' '.join(sql).strip(), limit+offset,
1220
1221+ limit_and_offset_clause)
1222
1223+ else:
1224
1225+ fmt = "SELECT * FROM (SELECT %s%s, ROW_NUMBER()%s AS rn %s ) AS foo %s"
1226
1227+ full_query = fmt % (distinct, select_clause,
1228
1229+ order_by_clause, ' '.join(sql).strip(),
1230
1231+ limit_and_offset_clause)
1232
1233+
1234
1235+ else:
1236
1237+ full_query = None
1238
1239+
1240
1241+ if get_full_query:
1242
1243+ return select, " ".join(sql), params, full_query
1244
1245+ else:
1246
1247+ return select, " ".join(sql), params
1248
1249+
1250
1251+ def resolve_columns(self, row, fields=()):
1252
1253+ from django.db.models.fields import Field, CharField, BooleanField, TextField
1254
1255+ values = []
1256
1257+ for value, field in map(None, row, fields):
1258
1259+ # strip trailing spaces in char and text fields
1260
1261+ #if isinstance(field, (CharField, TextField,)):
1262
1263+ if isinstance(value, basestring):
1264
1265+ if value:
1266
1267+ value = value.strip()
1268
1269+ # create real booleans
1270
1271+ if isinstance(field, BooleanField):
1272
1273+ value = {0: False, 1: True}.get(value, False)
1274
1275+ values.append(value)
1276
1277+ return values
1278
1279+
1280
1281+ return DB2QuerySet
1282
1283+
1284
1285+# UPPER needs typecasting or DB2 does not know which upper function to use
1286
1287+# it does not matter if the typecast is correct
1288
1289+OPERATOR_MAPPING = {
1290
1291+ 'exact': "= %s",
1292
1293+ 'iexact': "= UPPER(CAST(%s AS VARCHAR(50)))",
1294
1295+ 'contains': "LIKE %s ESCAPE '\\'",
1296
1297+ 'icontains': "LIKE UPPER(CAST(%s AS VARCHAR(50))) ESCAPE '\\'",
1298
1299+ 'gt': "> %s",
1300
1301+ 'gte': ">= %s",
1302
1303+ 'lt': "< %s",
1304
1305+ 'lte': "<= %s",
1306
1307+ 'startswith': "LIKE %s ESCAPE '\\'",
1308
1309+ 'endswith': "LIKE %s ESCAPE '\\'",
1310
1311+ 'istartswith': "LIKE UPPER(CAST(%s AS VARCHAR(50))) ESCAPE '\\'",
1312
1313+ 'iendswith': "LIKE UPPER(CAST(%s AS VARCHAR(50))) ESCAPE '\\'",
1314
1315+}
1316
1317=== django/db/backends/db2_9/client.py
1318==================================================================
1319--- django/db/backends/db2_9/client.py (/mirror/django/trunk) (revision 3644)
1320
1321+++ django/db/backends/db2_9/client.py (/local/django/db2_9) (revision 3644)
1322
1323@@ -0,0 +1,7 @@
1324
1325+from django.conf import settings
1326+import os
1327+
1328+def runshell():
1329+ # Nothing fancy here, as the environment should have the
1330+ # instnace properly configured for anything to work at all.
1331+ os.execvp('db2')
1332=== django/db/backends/db2_9/__init__.py
1333==================================================================
1334=== django/db/backends/db2_9/introspection.py
1335==================================================================
1336--- django/db/backends/db2_9/introspection.py (/mirror/django/trunk) (revision 3644)
1337
1338+++ django/db/backends/db2_9/introspection.py (/local/django/db2_9) (revision 3644)
1339
1340@@ -0,0 +1,177 @@
1341
1342+"""
1343
1344+Optional: use a DATABASE_SCHEMA setting to let inspectdb find only tables from
1345
1346+a specific schema.
1347
1348+"""
1349
1350+from django.db.backends.db2_9.base import quote_name
1351
1352+from django.conf import settings
1353
1354+
1355
1356+def get_table_list(cursor):
1357
1358+ """ Here's the tricky part. The tables are accessed by their schema
1359
1360+ for example, all the systems tables are prefixed by SYSIBM as their schema.
1361
1362+ Since we can get really *lots* of tables without filtering by creator this can get
1363
1364+ really messy, So I've used DATABASE_SCHEMA to filter it.
1365
1366+ RTRIMs are needed because the fields return in full column size filled with spaces.
1367
1368+ Using the SYSIBM.TABLES VIEW.
1369
1370+ """
1371
1372+ from django.conf import settings
1373
1374+ from django.utils.encoding import smart_str, force_unicode
1375
1376+ try:
1377
1378+ schema = settings.DATABASE_SCHEMA.upper()
1379
1380+ except:
1381
1382+ schema = ''
1383
1384+ if schema != '':
1385
1386+ cursor.execute("""SELECT RTRIM(table_name) as TABLES, RTRIM(table_schema) as SCHEMA FROM SYSIBM.TABLES
1387
1388+ WHERE TABLE_SCHEMA = %s AND TABLE_TYPE = 'BASE TABLE' ORDER BY table_name""", [schema])
1389
1390+ else: # Fallback to everything but SYS*
1391
1392+ cursor.execute("""SELECT RTRIM(table_name) as TABLES, RTRIM(table_schema) as SCHEMA FROM SYSIBM.tables
1393
1394+ WHERE table_schema NOT LIKE 'SYS%%' AND TABLE_TYPE = 'BASE TABLE' ORDER BY table_name""", [])
1395
1396+ rows = cursor.fetchall()
1397
1398+ res = []
1399
1400+ # for tables from the user: do not append schema, others: use SCHEMA.TABLE as name
1401
1402+ for row in rows:
1403
1404+ if row[1].upper() == settings.DATABASE_USER.upper():
1405
1406+ res.append(smart_str(row[0].upper()))
1407
1408+ else:
1409
1410+ res.append(smart_str("%s.%s" % (row[1].upper(), row[0].upper())))
1411
1412+ return res
1413
1414+
1415
1416+def get_table_description(cursor, table_name):
1417
1418+ "Returns a description of the table with the DB-API cursor.description interface."
1419
1420+ cursor.execute("SELECT * FROM %s FETCH FIRST 1 ROWS ONLY" % (table_name,))
1421
1422+ return cursor.description
1423
1424+
1425
1426+def _name_to_index(cursor, table_name):
1427
1428+ """
1429
1430+ Returns a dictionary of {field_name: field_index} for the given table.
1431
1432+ Indexes are 0-based.
1433
1434+ """
1435
1436+ return dict([(d[0], i) for i, d in enumerate(get_table_description(cursor, table_name))])
1437
1438+
1439
1440+def get_relations(cursor, table_name):
1441
1442+ """
1443
1444+ Returns a dictionary of {field_index: (field_index_other_table, other_table)}
1445
1446+ representing all relationships to the given table. Indexes are 0-based.
1447
1448+ Beware, PyDB2 returns the full length of the field, filled with spaces,
1449
1450+ I have to .strip() every single string field >.<
1451
1452+ """
1453
1454+ from django.conf import settings
1455
1456+
1457
1458+ if table_name.count('.') == 1:
1459
1460+ schema, tn = table_name.split('.')
1461
1462+ else:
1463
1464+ tn = table_name
1465
1466+ schema = settings.DATABASE_USER
1467
1468+ cursor.execute("""SELECT fk.ORDINAL_POSITION, pk.ORDINAL_POSITION, pk.TABLE_NAME
1469
1470+ FROM SYSIBM.SQLFOREIGNKEYS r, SYSIBM.SQLCOLUMNS fk, SYSIBM.SQLCOLUMNS pk
1471
1472+ WHERE r.FKTABLE_SCHEM = %s AND r.FKTABLE_NAME = %s
1473
1474+ AND r.FKTABLE_SCHEM = fk.TABLE_SCHEM AND r.FKTABLE_NAME = fk.TABLE_NAME
1475
1476+ AND r.FKCOLUMN_NAME = fk.COLUMN_NAME
1477
1478+ AND r.PKTABLE_SCHEM = pk.TABLE_SCHEM AND r.PKTABLE_NAME = pk.TABLE_NAME
1479
1480+ AND r.PKCOLUMN_NAME = pk.COLUMN_NAME
1481
1482+ """, (schema.upper(), tn))
1483
1484+
1485
1486+ relations = {}
1487
1488+ for row in cursor.fetchall():
1489
1490+ relations[int(row[0]) - 1] = (int(row[1]) -1, row[2].strip())
1491
1492+ return relations
1493
1494+
1495
1496+def get_foreignkey_relations(cursor, table_name):
1497
1498+ """
1499
1500+ Returns a dictionary of {field_index: other_table}
1501
1502+ representing all relationships to other tables. Indexes are 0-based.
1503
1504+ Beware, PyDB2 returns the full length of the field, filled with spaces,
1505
1506+ I have to .strip() every single string field >.<
1507
1508+ """
1509
1510+ from django.conf import settings
1511
1512+
1513
1514+ if table_name.count('.') == 1:
1515
1516+ schema, tn = table_name.split('.')
1517
1518+ else:
1519
1520+ tn = table_name
1521
1522+ schema = settings.DATABASE_USER
1523
1524+ cursor.execute("""SELECT r.FKCOLUMN_NAME, r.PKTABLE_NAME
1525
1526+ FROM SYSIBM.SQLFOREIGNKEYS r
1527
1528+ WHERE r.FKTABLE_SCHEM = %s AND r.FKTABLE_NAME = %s
1529
1530+ """, (schema.upper(), tn))
1531
1532+
1533
1534+ relations = []
1535
1536+ for row in cursor.fetchall():
1537
1538+ relations.append(row[1].strip())
1539
1540+ return relations
1541
1542+
1543
1544+def get_indexes(cursor, table_name):
1545
1546+ """
1547
1548+ Returns a dictionary of fieldname -> infodict for the given table,
1549
1550+ where each infodict is in the format:
1551
1552+ {'primary_key': boolean representing whether it's the primary key,
1553
1554+ 'unique': boolean representing whether it's a unique index}
1555
1556+ DB2 is weird here, like this seems to be ok but I don't get 100% of the indexes
1557
1558+ syscolumns.keyseq means the column part of a "parent key".
1559
1560+ sysindexes.uniquerule == P means it's primary, U == unique,
1561
1562+ and D == duplicates allowed.
1563
1564+ """
1565
1566+ from django.conf import settings
1567
1568+ if table_name.count('.') == 1:
1569
1570+ schema, tn = table_name.split('.')
1571
1572+ else:
1573
1574+ tn = table_name
1575
1576+ schema = settings.DATABASE_USER
1577
1578+ cursor.execute("""SELECT c.COLUMN_NAME, i.UNIQUERULE
1579
1580+ FROM SYSIBM.SQLCOLUMNS c, SYSCAT.INDEXES i, SYSCAT.INDEXCOLUSE k
1581
1582+ WHERE c.TABLE_SCHEM = %s AND c.TABLE_NAME = %s
1583
1584+ AND C.TABLE_SCHEM = i.TABSCHEMA AND c.TABLE_NAME = i.TABNAME
1585
1586+ AND i.INDSCHEMA = k.INDSCHEMA AND i.INDNAME = k.INDNAME
1587
1588+ AND c.COLUMN_NAME = k.COLNAME
1589
1590+ """, (schema.upper(), tn))
1591
1592+
1593
1594+ indexes = {}
1595
1596+ for row in cursor.fetchall():
1597
1598+ urule = row[1].strip()
1599
1600+ name = row[0].strip()
1601
1602+ if urule == "P":
1603
1604+ # Override everything, as this is a primary key.
1605
1606+ indexes[name] = {'primary_key':True, 'unique':False}
1607
1608+ elif urule == "U":
1609
1610+ try:
1611
1612+ if indexes[name]['primary_key'] == True:
1613
1614+ # Can appear twice, but primary is primary anyway.
1615
1616+ continue
1617
1618+ else:
1619
1620+ indexes[name] = {'primary_key':False, 'unique':True}
1621
1622+ except: # TODO: Only a keyerror can happen here, right?
1623
1624+ indexes[name] = {'primary_key':False, 'unique':True}
1625
1626+ else: # urule = "D" not sure if there are others.
1627
1628+ try:
1629
1630+ # Should never happen, but...
1631
1632+ if indexes[name]['primary_key'] == True or indexes[name]['unique'] == True:
1633
1634+ continue
1635
1636+ else:
1637
1638+ indexes[name] = {'primary_key':False, 'unique':False}
1639
1640+ except: # TODO: same as above ^_^
1641
1642+ indexes[name] = {'primary_key':False, 'unique':False}
1643
1644+ return indexes
1645
1646+
1647
1648+DATA_TYPES_REVERSE = {
1649
1650+ -99:'TextField', # CLOB
1651
1652+ -98:'TextField', # BLOB
1653
1654+ -97:'TextField', # Long VarGraphic
1655
1656+ -96:'TextField', # VarGraphic
1657
1658+ -95:'TextField', # Graphic
1659
1660+ -5: 'IntegerField', # Big Int
1661
1662+ -4: 'TextField', # Binary Long VarChar
1663
1664+ -3: 'TextField', # Binary VarChar
1665
1666+ -2: 'TextField', # Binary
1667
1668+ -1: 'TextField', # Long VarChar
1669
1670+ 1: 'CharField', # Char
1671
1672+ 2: 'FloatField', # Numeric
1673
1674+ 3: 'FloatField', # Decimal
1675
1676+ 4: 'IntegerField', # Integer
1677
1678+ 5: 'BooleanField', # SmallInt
1679
1680+ 6: 'FloatField', # Float
1681
1682+ 7: 'FloatField', # Real
1683
1684+ 8: 'FloatField', # Double
1685
1686+ 12: 'CharField', # VarChar
1687
1688+ 91: 'DateField', # Date
1689
1690+ 92: 'TimeField', # Time
1691
1692+ 93: 'DateTimeField', # TimeStamp
1693
1694+}
1695
1696=== django/db/backends/db2_9/creation.py
1697==================================================================
1698--- django/db/backends/db2_9/creation.py (/mirror/django/trunk) (revision 3644)
1699
1700+++ django/db/backends/db2_9/creation.py (/local/django/db2_9) (revision 3644)
1701
1702@@ -0,0 +1,80 @@
1703
1704+"""
1705
1706+TEST_DATABASE_USER must be an existing user (DB2 v9 uses OS authentication)
1707
1708+"""
1709
1710+import sys, time
1711
1712+from django.core import management
1713
1714+from django.db.models.signals import pre_save
1715
1716+from django.db.backends.db2_9 import base
1717
1718+
1719
1720+# Not quite sure about the Boolean and Float fields.
1721
1722+# Not sure what to use for Boolean, since there seems to be a 'BOOLEAN' type,
1723
1724+# but it isn't 'documented' anywhere. For Float, we have DOUBLE, DECIMAL and NUMERIC
1725
1726+# to choose from :+/, too many.
1727
1728+DATA_TYPES = {
1729
1730+ 'AutoField': 'INTEGER GENERATED BY DEFAULT AS IDENTITY', # ALWAYS breaks basic test (specifying key explicitly)
1731
1732+ 'BooleanField': 'SMALLINT',
1733
1734+ 'CharField': 'VARCHAR(%(maxlength)s)',
1735
1736+ 'CommaSeparatedIntegerField': 'VARCHAR(%(maxlength)s)',
1737
1738+ 'DateField': 'DATE',
1739
1740+ 'DateTimeField': 'TIMESTAMP',
1741
1742+ 'DecimalField': 'DECIMAL(%(max_digits)s, %(decimal_places)s)',
1743
1744+ 'FileField': 'VARCHAR(100)',
1745
1746+ 'FilePathField': 'VARCHAR(100)',
1747
1748+ 'FloatField': 'FLOAT',
1749
1750+ 'ImageField': 'VARCHAR(100)',
1751
1752+ 'IntegerField': 'INTEGER',
1753
1754+ 'IPAddressField': 'CHAR(15)',
1755
1756+ 'ManyToManyField': None,
1757
1758+ 'NullBooleanField': 'SMALLINT',
1759
1760+ 'OneToOneField': 'INTEGER',
1761
1762+ 'PhoneNumberField': 'CHAR(20)',
1763
1764+ 'PositiveIntegerField': 'INTEGER',
1765
1766+ 'PositiveSmallIntegerField': 'SMALLINT',
1767
1768+ 'SlugField': 'VARCHAR(%(maxlength)s)',
1769
1770+ 'SmallIntegerField': 'SMALLINT',
1771
1772+ 'TextField': 'LONG VARCHAR',
1773
1774+ 'TimeField': 'TIME',
1775
1776+ 'USStateField': 'CHAR(2)',
1777
1778+}
1779
1780+
1781
1782+REMEMBER = {}
1783
1784+
1785
1786+def create_test_db(settings, connection, backend, verbosity=1, autoclobber=False):
1787
1788+ """Assuming here that a TEST_DATABASE_USER is set in settings that is known to the OS and DB2
1789
1790+ with DB2ADM rights.
1791
1792+ Best is that this user has a separate default tablespace defined in DB2 (but this is not required)."""
1793
1794+ REMEMBER['user'] = settings.DATABASE_USER
1795
1796+ REMEMBER['passwd'] = settings.DATABASE_PASSWORD
1797
1798+ settings.DATABASE_USER = settings.TEST_DATABASE_USER
1799
1800+ settings.DATABASE_PASSWORD = settings.TEST_DATABASE_PASSWORD
1801
1802+ management.syncdb(verbosity, interactive=False)
1803
1804+
1805
1806+def destroy_test_db(settings, connection, backend, old_database_name, verbosity=1):
1807
1808+ """Delete all tables from the test user."""
1809
1810+ connection.close()
1811
1812+
1813
1814+ settings.DATABASE_USER = REMEMBER['user']
1815
1816+ settings.DATABASE_PASSWORD = REMEMBER['passwd']
1817
1818+
1819
1820+ cursor = connection.cursor()
1821
1822+
1823
1824+ time.sleep(1) # To avoid "database is being accessed by other users" errors.
1825
1826+
1827
1828+ if verbosity >= 2:
1829
1830+ print "_destroy_test_db(): removing tables from schema %s" % settings.TEST_DATABASE_USER
1831
1832+
1833
1834+ cursor.execute("""SELECT RTRIM(table_name) as TABLES FROM SYSIBM.TABLES
1835
1836+ WHERE TABLE_SCHEMA = %s AND TABLE_TYPE = 'BASE TABLE' ORDER BY table_name""", [settings.TEST_DATABASE_USER.upper()])
1837
1838+
1839
1840+ rows = cursor.fetchall()
1841
1842+ for row in rows:
1843
1844+ stmt = 'DROP TABLE %s.%s' % (settings.TEST_DATABASE_USER.upper(), row[0])
1845
1846+ if verbosity >= 2:
1847
1848+ print stmt
1849
1850+ try:
1851
1852+ cursor.execute(stmt)
1853
1854+ except Exception, err:
1855
1856+ sys.stderr.write("Failed (%s)\n" % (err))
1857
1858+ raise
1859
1860+ connection._commit()
1861
1862+ connection.close()
1863
1864\ No newline at end of file
1865
1866
1867Property changes on: django/db/backends/db2_9
1868___________________________________________________________________
1869Name: svn:ignore
1870 +*.pyc
1871 +
1872
1873=== django/core/management.py
1874==================================================================
1875--- django/core/management.py (/mirror/django/trunk) (revision 3644)
1876
1877+++ django/core/management.py (/local/django/db2_9) (revision 3644)
1878
1879@@ -235,8 +235,12 @@
1880
1881 # For MySQL, r_name must be unique in the first 64 characters.
1882 # So we are careful with character usage here.
1883 r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table))))
1884+ if hasattr(backend, 'get_max_constraint_length'):
1885+ get_max = backend.get_max_constraint_length
1886+ else:
1887+ get_max = backend.get_max_name_length
1888 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
1889- (backend.quote_name(r_table), truncate_name(r_name, backend.get_max_name_length()),
1890+ (backend.quote_name(r_table), truncate_name(r_name, get_max()),
1891 backend.quote_name(r_col), backend.quote_name(table), backend.quote_name(col),
1892 backend.get_deferrable_sql()))
1893 del pending_references[model]
1894@@ -919,7 +923,7 @@
1895
1896 extra_params['decimal_places'] = row[5]
1897
1898 # Add primary_key and unique, if necessary.
1899- column_name = extra_params.get('db_column', att_name)
1900+ column_name = extra_params.get('db_column', row[0])
1901 if column_name in indexes:
1902 if indexes[column_name]['primary_key']:
1903 extra_params['primary_key'] = True
1904
Back to Top