Code

Ticket #5052: db2_9.2.patch

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

DB2 backend

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