Ticket #5052: db2_9_6110.diff

File db2_9_6110.diff, 43.0 KB (added by Koen Biermans <koen.biermans@…>, 8 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