Ticket #5052: db2_9_6110.diff

File db2_9_6110.diff, 43.0 KB (added by Koen Biermans <koen.biermans@…>, 17 years ago)

patch against trunk r6110, still a lot issues left though

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