Ticket #87: unified_diff_oracle_backend_rev_656.txt

File unified_diff_oracle_backend_rev_656.txt, 29.3 KB (added by Jason Huggns, 15 years ago)

Updated unified diff for Oracle backend

Line 
1--- trunk/django/django/models/auth.py  (revision 656)
2+++ trunk/django/django/models/auth.py  (working copy)
3@@ -181,7 +181,7 @@
4     action_time = meta.DateTimeField(auto_now=True)
5     user = meta.ForeignKey(User)
6     content_type = meta.ForeignKey(core.ContentType, blank=True, null=True) # TODO: content_type_id name?
7-    object_id = meta.TextField(blank=True, null=True)
8+    object_id = meta.CharField(maxlength=100, blank=True, null=True)
9     object_repr = meta.CharField(maxlength=200)
10     action_flag = meta.PositiveSmallIntegerField()
11     change_message = meta.TextField(blank=True)
12--- trunk/django/django/core/db/__init__.py (revision 656)
13+++ trunk/django/django/core/db/__init__.py (working copy)
14@@ -44,3 +44,4 @@
15 OPERATOR_MAPPING = dbmod.OPERATOR_MAPPING
16 DATA_TYPES = dbmod.DATA_TYPES
17 DATA_TYPES_REVERSE = dbmod.DATA_TYPES_REVERSE
18+EMPTY_STR_EQUIV = dbmod.EMPTY_STR_EQUIV
19--- trunk/django/django/core/db/backends/postgresql.py  (revision 656)
20+++ trunk/django/django/core/db/backends/postgresql.py  (working copy)
21@@ -188,3 +188,5 @@
22     1266: 'TimeField',
23     1700: 'FloatField',
24 }
25+
26+EMPTY_STR_EQUIV = ''
27\ No newline at end of file
28--- trunk/django/django/core/db/backends/sqlite3.py (revision 656)
29+++ trunk/django/django/core/db/backends/sqlite3.py (working copy)
30@@ -174,3 +174,5 @@
31 }
32 
33 DATA_TYPES_REVERSE = {}
34+
35+EMPTY_STR_EQUIV = ''
36\ No newline at end of file
37--- trunk/django/django/core/db/backends/mysql.py   (revision 656)
38+++ trunk/django/django/core/db/backends/mysql.py   (working copy)
39@@ -155,3 +155,5 @@
40     FIELD_TYPE.LONG_BLOB: 'TextField',
41     FIELD_TYPE.VAR_STRING: 'CharField',
42 }
43+
44+EMPTY_STR_EQUIV = ''
45\ No newline at end of file
46--- None    (revision 656)
47+++ trunk/django/django/core/db/backends/oracle.py  (working copy)
48@@ -0,0 +1,162 @@
49+"""
50+Oracle database backend for Django.
51+
52+Requires cx_Oracle: http://www.computronix.com/utilities.shtml
53+"""
54+
55+from django.core.db import base, typecasts
56+import cx_Oracle  as Database
57+
58+#needed for fetchone, fetchmany, fetchall support
59+from django.core.db.dicthelpers import *
60+
61+
62+DatabaseError = Database.DatabaseError
63+
64+class DatabaseWrapper:
65+    def __init__(self):
66+        self.connection = None
67+        self.queries = []
68+
69+    def cursor(self):
70+        from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PASSWORD, DEBUG
71+        if self.connection is None:
72+            if DATABASE_NAME == '' or DATABASE_USER == '' or DATABASE_PASSWORD == '':
73+                from django.core.exceptions import ImproperlyConfigured
74+                raise ImproperlyConfigured, "You need to specify DATABASE_NAME, DATABASE_USER, and DATABASE_PASSWORD in your Django settings file."
75+            conn_string = "%s/%s@%s" % (DATABASE_USER, DATABASE_PASSWORD, DATABASE_NAME)
76+            self.connection = Database.connect(conn_string)               
77+        return FormatStylePlaceholderCursor(self.connection)
78+
79+    def commit(self):
80+        self.connection.commit()
81+
82+    def rollback(self):
83+        if self.connection:
84+            self.connection.rollback()
85+
86+    def close(self):
87+        if self.connection is not None:
88+            self.connection.close()
89+            self.connection = None
90+
91+class FormatStylePlaceholderCursor(Database.Cursor):
92+    """
93+    Django uses "format" (e.g. '%s') style placeholders, but Oracle uses ":var" style.
94+    This fixes it -- but note that if you want to use a literal "%s" in a query,
95+    you'll need to use "%%s" (which I belive is true of other wrappers as well).
96+    """
97+   
98+    def execute(self, query, params=[]):
99+        query = self.convert_arguments(query, len(params))
100+        return Database.Cursor.execute(self, query, params)
101+       
102+    def executemany(self, query, params=[]): 
103+        query = self.convert_arguments(query, len(params[0]))
104+        return Database.Cursor.executemany(self, query, params)
105+       
106+    def convert_arguments(self, query, num_params):
107+        # replace occurances of "%s" with ":arg" - Oracle requires colons for parameter placeholders.
108+        args = [':arg' for i in range(num_params)]
109+        return query % tuple(args)
110+
111+def get_last_insert_id(cursor, table_name, pk_name):
112+    query = "SELECT %s_sq.currval from dual" % table_name
113+    cursor.execute(query)
114+    return cursor.fetchone()[0]
115+
116+def get_date_extract_sql(lookup_type, table_name):
117+    # lookup_type is 'year', 'month', 'day'
118+    # http://www.psoug.org/reference/date_func.html   
119+    return "EXTRACT(%s FROM %s)" % (lookup_type, table_name)
120+
121+def get_date_trunc_sql(lookup_type, field_name):
122+    return "EXTRACT(%s FROM TRUNC(%s))" % (lookup_type, table_name)
123+   
124+def get_limit_offset_sql(limit, offset=None):
125+    # Limits and offset are too complicated to be handled here.
126+    #Instead, they are handled in django.core.meta.__init__.py
127+    pass
128+   
129+def get_random_function_sql():
130+    return "DBMS_RANDOM.RANDOM"   
131+   
132+def get_table_list(cursor):
133+    "Returns a list of table names in the current database."
134+    cursor.execute("SELECT TABLE_NAME FROM USER_TABLES")
135+    return [row[0] for row in cursor.fetchall()]
136+
137+def get_relations(cursor, table_name):
138+    """
139+    Returns a dictionary of {field_index: (field_index_other_table, other_table)}
140+    representing all relationships to the given table. Indexes are 0-based.
141+    """
142+    raise NotImplementedError
143+
144+
145+OPERATOR_MAPPING = {
146+    'exact': '=',
147+    'iexact': 'LIKE',
148+    'contains': 'LIKE',
149+    'icontains': 'LIKE',
150+    'ne': '!=',
151+    'gt': '>',
152+    'gte': '>=',
153+    'lt': '<',
154+    'lte': '<=',
155+    'startswith': 'LIKE',
156+    'endswith': 'LIKE',
157+    'istartswith': 'LIKE',
158+    'iendswith': 'LIKE',
159+}
160+
161+# This dictionary maps Field objects to their associated MySQL column
162+# types, as strings. Column-type strings can contain format strings; they'll
163+# be interpolated against the values of Field.__dict__ before being output.
164+# If a column type is set to None, it won't be included in the output.
165+DATA_TYPES = {
166+    'AutoField':         'number(38)',
167+    'BooleanField':      'number(1)',
168+    'CharField':         'varchar2(%(maxlength)s)',
169+    'CommaSeparatedIntegerField': 'varchar2(%(maxlength)s)',
170+    'DateField':         'date',
171+    'DateTimeField':     'date',
172+    'EmailField':        'varchar2(75)',
173+    'FileField':         'varchar2(100)',
174+    'FloatField':        'number(%(max_digits)s, %(decimal_places)s)',
175+    'ImageField':        'varchar2(100)',
176+    'IntegerField':      'integer',
177+    'IPAddressField':    'char(15)',
178+    'ManyToManyField':   None,
179+    'NullBooleanField':  'integer',
180+    'OneToOneField':     'integer',
181+    'PhoneNumberField':  'varchar(20)',
182+    'PositiveIntegerField': 'integer',
183+    'PositiveSmallIntegerField': 'smallint',
184+    'SlugField':         'varchar(50)',
185+    'SmallIntegerField': 'smallint',
186+    'SmallTextField':    'varchar2(4000)',
187+    'TextField':         'long',
188+    'TimeField':         'timestamp',
189+    'URLField':          'varchar(200)',
190+    'USStateField':      'varchar(2)',
191+    'XMLField':          'long',
192+}
193+
194+# Maps type codes to Django Field types.
195+DATA_TYPES_REVERSE = {
196+    16: 'BooleanField',
197+    21: 'SmallIntegerField',
198+    23: 'IntegerField',
199+    25: 'TextField',
200+    869: 'IPAddressField',
201+    1043: 'CharField',
202+    1082: 'DateField',
203+    1083: 'TimeField',
204+    1114: 'DateTimeField',
205+    1184: 'DateTimeField',
206+    1266: 'TimeField',
207+    1700: 'FloatField',
208+}
209+
210+EMPTY_STR_EQUIV = ' '
211\ No newline at end of file
212--- trunk/django/django/core/meta/__init__.py   (revision 656)
213+++ trunk/django/django/core/meta/__init__.py   (working copy)
214@@ -777,8 +777,8 @@
215     pk_set = bool(pk_val)
216     record_exists = True
217     if pk_set:
218-        # Determine whether a record with the primary key already exists.
219-        cursor.execute("SELECT 1 FROM %s WHERE %s=%%s LIMIT 1" % (opts.db_table, opts.pk.column), [pk_val])
220+        # Determine whether a record with the primary key already exists.
221+        cursor.execute("SELECT 1 FROM %s WHERE %s=%%s" % (opts.db_table, opts.pk.column), [pk_val])
222         # If it does already exist, do an UPDATE.
223         if cursor.fetchone():
224             db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.column), False)) for f in non_pks]
225@@ -1102,10 +1102,11 @@
226     kwargs['select'] = kwargs.get('select', {}).items()
227 
228     cursor = db.db.cursor()
229-    select, sql, params = function_get_sql_clause(opts, **kwargs)
230-    cursor.execute("SELECT " + (kwargs.get('distinct') and "DISTINCT " or "") + ",".join(select) + sql, params)
231+    select, sql, params, full_query = function_get_sql_clause(opts, **kwargs)
232+    cursor.execute(full_query, params)
233     fill_cache = kwargs.get('select_related')
234     index_end = len(opts.fields)
235+   
236     while 1:
237         rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
238         if not rows:
239@@ -1127,7 +1128,7 @@
240     kwargs['offset'] = None
241     kwargs['limit'] = None
242     kwargs['select_related'] = False
243-    _, sql, params = function_get_sql_clause(opts, **kwargs)
244+    _, sql, params, full_query = function_get_sql_clause(opts, **kwargs)
245     cursor = db.db.cursor()
246     cursor.execute("SELECT COUNT(*)" + sql, params)
247     return cursor.fetchone()[0]
248@@ -1144,7 +1145,7 @@
249         fields = [f.column for f in opts.fields]
250 
251     cursor = db.db.cursor()
252-    _, sql, params = function_get_sql_clause(opts, **kwargs)
253+    _, sql, params, full_query = function_get_sql_clause(opts, **kwargs)
254     select = ['%s.%s' % (opts.db_table, f) for f in fields]
255     cursor.execute("SELECT " + (kwargs.get('distinct') and "DISTINCT " or "") + ",".join(select) + sql, params)
256     while 1:
257@@ -1171,9 +1172,9 @@
258                 new_prefix = '%s%s' % (db_table, len(cache_tables_seen))
259                 tables.append('%s %s' % (db_table, new_prefix))
260                 db_table = new_prefix
261-            cache_tables_seen.append(db_table)
262+            cache_tables_seen.append(db_table)
263             where.append('%s.%s = %s.%s' % (old_prefix, f.column, db_table, f.rel.get_related_field().column))
264-            select.extend(['%s.%s' % (db_table, f2.column) for f2 in f.rel.to.fields])
265+            select.extend(['%s.%s as "%s.%s"' % (db_table, f2.column, db_table, f2.column) for f2 in f.rel.to.fields])
266             _fill_table_cache(f.rel.to, select, tables, where, db_table, cache_tables_seen)
267 
268 def _throw_bad_kwarg_error(kwarg):
269@@ -1188,7 +1189,7 @@
270     # is specifically a list of where clauses to use for JOINs. This
271     # distinction is necessary because of support for "_or".
272 
273-    # table_count is used to ensure table aliases are unique.
274+    # table_count is used to ensure table aliases are unique.   
275     tables, join_where, where, params = [], [], [], []
276     for kwarg, kwarg_value in kwarg_items:
277         if kwarg in ('order_by', 'limit', 'offset', 'select_related', 'distinct', 'select', 'tables', 'where', 'params'):
278@@ -1333,14 +1334,58 @@
279                 order_by.append('%s%s ASC' % (table_prefix, orderfield2column(f, opts)))
280     order_by = ", ".join(order_by)
281 
282-    # LIMIT and OFFSET clauses
283-    if kwargs.get('limit') is not None:
284-        limit_sql = " %s " % db.get_limit_offset_sql(kwargs['limit'], kwargs.get('offset'))
285-    else:
286-        assert kwargs.get('offset') is None, "'offset' is not allowed without 'limit'"
287-        limit_sql = ""
288 
289-    return select, " FROM " + ",".join(tables) + (where and " WHERE " + " AND ".join(where) or "") + (order_by and " ORDER BY " + order_by or "") + limit_sql, params
290+    sql = " FROM " + ",".join(tables) + (where and " WHERE " + " AND ".join(where) or "") + (order_by and " ORDER BY " + order_by or "")
291+
292+    if (db.DATABASE_ENGINE != 'oracle'):
293+        # LIMIT and OFFSET clauses
294+        if kwargs.get('limit') is not None:
295+            limit_sql = " %s " % db.get_limit_offset_sql(kwargs['limit'], kwargs.get('offset'))
296+        else:
297+            assert kwargs.get('offset') is None, "'offset' is not allowed without 'limit'"
298+            limit_sql = ""
299+
300+
301+        full_query = "SELECT " + (kwargs.get('distinct') and "DISTINCT " or "") + ",".join(select) + sql + limit_sql
302+        return select, sql + limit_sql, params, full_query
303+    else:
304+        # To support limits and offsets, Oracle requires some funky rewriting of an otherwise normal looking query.
305+       
306+        select_clause = ",".join(select)
307+        distinct = (kwargs.get('distinct') and "DISTINCT " or "")
308+        from_clause = ",".join(tables)
309+        where_clause = (where and " WHERE " + " AND ".join(where) or "")
310+       
311+        if order_by:           
312+            order_by_clause = " OVER (ORDER BY %s )" % (order_by)
313+        else:
314+            #Oracle's row_number() function always requires an order-by clause.
315+            #So we need to define a default order-by, since none was provided.
316+            order_by_clause = " OVER (ORDER BY %s.%s)" % (opts.db_table, opts.fields[0].name)           
317+       
318+        # limit_and_offset_clause
319+        limit = kwargs.get('limit',0)
320+        offset = kwargs.get('offset',0)
321+       
322+        limit_and_offset_clause = ''
323+        if limit:
324+            limit = int(limit)
325+            offset = int(offset)
326+            limit_and_offset_clause = "WHERE rn > %s AND rn <= %s" % (offset, limit+offset)
327+        else:
328+            limit_and_offset_clause = "WHERE rn > %s" % (offset)
329+                                 
330+        full_query = """SELECT * FROM
331+                         (SELECT %s                 
332+                          %s,
333+                          ROW_NUMBER() %s AS rn
334+                          FROM %s
335+                          %s
336+                         )
337+                         %s
338+                     """ % (distinct, select_clause, order_by_clause, from_clause, where_clause, limit_and_offset_clause)
339+        return select, sql, params, full_query                               
340+
341 
342 def function_get_in_bulk(opts, klass, *args, **kwargs):
343     id_list = args and args[0] or kwargs['id_list']
344@@ -1366,7 +1411,7 @@
345     kwargs['order_by'] = [] # Clear this because it'll mess things up otherwise.
346     if field.null:
347         kwargs.setdefault('where', []).append('%s.%s IS NOT NULL' % (opts.db_table, field.column))
348-    select, sql, params = function_get_sql_clause(opts, **kwargs)
349+    select, sql, params, full_query = function_get_sql_clause(opts, **kwargs)
350     sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1' % (db.get_date_trunc_sql(kind, '%s.%s' % (opts.db_table, field.column)), sql)
351     cursor = db.db.cursor()
352     cursor.execute(sql, params)
353--- trunk/django/django/core/meta/fields.py (revision 656)
354+++ trunk/django/django/core/meta/fields.py (working copy)
355@@ -1,4 +1,5 @@
356 from django.conf import settings
357+from django.core import db
358 from django.core import formfields, validators
359 from django.core.exceptions import ObjectDoesNotExist
360 from django.utils.functional import curry
361@@ -120,6 +121,12 @@
362 
363     def get_db_prep_save(self, value):
364         "Returns field's value prepared for saving into a database."
365+       
366+        # Oracle treats empty strings ('') the same as NULLs.
367+        # To get around this wart, we need to change it to something else...
368+        if value == '':
369+            string_replacement = getattr(db,'EMPTY_STR_EQUIV','')
370+            value = string_replacement
371         return value
372 
373     def get_db_prep_lookup(self, lookup_type, value):
374@@ -129,7 +136,12 @@
375         elif lookup_type in ('range', 'in'):
376             return value
377         elif lookup_type == 'year':
378-            return ['%s-01-01' % value, '%s-12-31' % value]
379+            if settings.DATABASE_ENGINE == 'oracle':             
380+                from_dt = datetime.date(int(value), 01, 01)
381+                to_dt = datetime.date(int(value), 12, 31)
382+                return [from_dt, to_dt]
383+            else:           
384+                return ['%s-01-01' % value, '%s-12-31' % value]
385         elif lookup_type in ('contains', 'icontains'):
386             return ["%%%s%%" % prep_for_like_query(value)]
387         elif lookup_type == 'iexact':
388@@ -287,6 +299,14 @@
389         kwargs['blank'] = True
390         Field.__init__(self, *args, **kwargs)
391 
392+    def get_db_prep_lookup(self, lookup_type, value):
393+        if settings.DATABASE_ENGINE == 'oracle':
394+            if value == 'True':
395+                value = 1
396+            elif value == 'False':
397+                value = 0
398+        return Field.get_db_prep_lookup(self, lookup_type, value)
399+
400     def get_manipulator_field_objs(self):
401         return [formfields.CheckboxField]
402 
403@@ -305,12 +325,30 @@
404         if auto_now or auto_now_add:
405             kwargs['editable'] = False
406         Field.__init__(self, verbose_name, name, **kwargs)
407-
408-    def get_db_prep_lookup(self, lookup_type, value):
409-        if lookup_type == 'range':
410-            value = [str(v) for v in value]
411-        else:
412-            value = str(value)
413+
414+    def get_db_prep_lookup(self, lookup_type, value):           
415+        # Oracle driver can deal with datetime.time objects natively
416+        # and doesn't need to convert to string
417+        if settings.DATABASE_ENGINE == 'oracle':       
418+            if lookup_type == 'range':
419+                value = [v for v in value]
420+            #elif lookup_type == 'day':
421+            #    value = int(value)
422+            elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte', 'ne'):
423+                if type(value) == str:
424+                    pattern = r'^\d\d\d\d-\d\d-\d\d$' # "YYYY-MM-DD"
425+                    from re import search
426+                    if search(pattern, value) != None:
427+                        year, month, day = map(int, value.split('-'))
428+                        value = datetime.date(year, month, day)
429+            else:
430+                value = value
431+        else:
432+            if lookup_type == 'range':
433+                value = [str(v) for v in value]
434+            else:
435+                value = str(value)           
436+           
437         return Field.get_db_prep_lookup(self, lookup_type, value)
438 
439     def pre_save(self, value, add):
440@@ -321,22 +359,30 @@
441     def get_db_prep_save(self, value):
442         # Casts dates into string format for entry into database.
443         if value is not None:
444-            value = value.strftime('%Y-%m-%d')
445-        return Field.get_db_prep_save(self, value)
446+            if settings.DATABASE_ENGINE != 'oracle':
447+            #Oracle does not need a string conversion
448+                value = value.strftime('%Y-%m-%d')
449+        return Field.get_db_prep_save(self, value)
450 
451     def get_manipulator_field_objs(self):
452         return [formfields.DateField]
453 
454 class DateTimeField(DateField):
455     def get_db_prep_save(self, value):
456-        # Casts dates into string format for entry into database.
457-        if value is not None:
458-            # MySQL will throw a warning if microseconds are given, because it
459-            # doesn't support microseconds.
460-            if settings.DATABASE_ENGINE == 'mysql':
461-                value = value.replace(microsecond=0)
462-            value = str(value)
463-        return Field.get_db_prep_save(self, value)
464+        # Casts dates into string format for entry into database.
465+        if value is not None:
466+            # MySQL will throw a warning if microseconds are given, because it
467+            # doesn't support microseconds.
468+            if settings.DATABASE_ENGINE == 'mysql':
469+                value = value.replace(microsecond=0)
470+                value = str(value)
471+            # Oracle will choke if microseconds are given...
472+            elif settings.DATABASE_ENGINE == 'oracle':
473+                value = value.replace(microsecond=0)
474+            else:
475+                value = str(value)
476+           
477+        return Field.get_db_prep_save(self, value)
478 
479     def get_manipulator_field_objs(self):
480         return [formfields.DateField, formfields.TimeField]
481@@ -513,12 +559,21 @@
482         if auto_now or auto_now_add:
483             kwargs['editable'] = False
484         Field.__init__(self, verbose_name, name, **kwargs)
485-
486+
487     def get_db_prep_lookup(self, lookup_type, value):
488-        if lookup_type == 'range':
489-            value = [str(v) for v in value]
490-        else:
491-            value = str(value)
492+        # Oracle driver can deal with datetime.time objects natively
493+        # and doesn't need to convert to string
494+        if settings.DATABASE_ENGINE == 'oracle':       
495+            if lookup_type == 'range':
496+                value = [v for v in value]
497+            else:
498+                value = value
499+        else:
500+            if lookup_type == 'range':
501+                value = [str(v) for v in value]
502+            else:
503+                value = str(value)
504+               
505         return Field.get_db_prep_lookup(self, lookup_type, value)
506 
507     def pre_save(self, value, add):
508@@ -529,11 +584,16 @@
509     def get_db_prep_save(self, value):
510         # Casts dates into string format for entry into database.
511         if value is not None:
512-            # MySQL will throw a warning if microseconds are given, because it
513-            # doesn't support microseconds.
514-            if settings.DATABASE_ENGINE == 'mysql':
515-                value = value.replace(microsecond=0)
516-            value = str(value)
517+            # MySQL will throw a warning if microseconds are given, because it
518+            # doesn't support microseconds.
519+            if settings.DATABASE_ENGINE == 'mysql':
520+                value = value.replace(microsecond=0)
521+                value = str(value)
522+            # Oracle will choke if microseconds are given...
523+            elif settings.DATABASE_ENGINE == 'oracle':
524+                value = value.replace(microsecond=0)
525+            else:
526+                value = str(value)
527         return Field.get_db_prep_save(self, value)
528 
529     def get_manipulator_field_objs(self):
530--- trunk/django/django/core/management.py  (revision 656)
531+++ trunk/django/django/core/management.py  (working copy)
532@@ -20,7 +20,7 @@
533 ADMIN_TEMPLATE_DIR = os.path.join(django.__path__[0], 'conf/admin_templates')
534 
535 def _get_packages_insert(app_label):
536-    return "INSERT INTO packages (label, name) VALUES ('%s', '%s');" % (app_label, app_label)
537+    return "INSERT INTO packages (label, name) VALUES ('%s', '%s')" % (app_label, app_label)
538 
539 def _get_permission_codename(action, opts):
540     return '%s_%s' % (action, opts.object_name.lower())
541@@ -34,11 +34,11 @@
542     return perms + list(opts.permissions)
543 
544 def _get_permission_insert(name, codename, opts):
545-    return "INSERT INTO auth_permissions (name, package, codename) VALUES ('%s', '%s', '%s');" % \
546+    return "INSERT INTO auth_permissions (name, package, codename) VALUES ('%s', '%s', '%s')" % \
547         (name.replace("'", "''"), opts.app_label, codename)
548 
549 def _get_contenttype_insert(opts):
550-    return "INSERT INTO content_types (name, package, python_module_name) VALUES ('%s', '%s', '%s');" % \
551+    return "INSERT INTO content_types (name, package, python_module_name) VALUES ('%s', '%s', '%s')" % \
552         (opts.verbose_name, opts.app_label, opts.module_name)
553 
554 def _is_valid_dir_name(s):
555@@ -85,9 +85,26 @@
556         full_statement = ['CREATE TABLE %s (' % opts.db_table]
557         for i, line in enumerate(table_output): # Combine and add commas.
558             full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
559-        full_statement.append(');')
560+        full_statement.append(')')
561         final_output.append('\n'.join(full_statement))
562+       
563+        if (db.DATABASE_ENGINE == 'oracle') & (opts.has_auto_field):
564+            # To simulate auto-incrementing primary keys in Oracle
565+                       
566+            sequence_statement = 'CREATE SEQUENCE %s_sq\n' % opts.db_table
567+            final_output.append(sequence_statement)
568+           
569+            trigger_statement = "" + \
570+                "CREATE OR REPLACE trigger %s_tr\n"    % opts.db_table + \
571+                "  before insert on %s\n"           % opts.db_table + \
572+                "    for each row\n"  + \
573+                "      begin\n" + \
574+                "        select %s_sq.NEXTVAL into :new.id from DUAL;\n" % opts.db_table + \
575+                "      end;\n"
576+            final_output.append(trigger_statement)
577+                               
578 
579+   
580     for klass in mod._MODELS:
581         opts = klass._meta
582         for f in opts.many_to_many:
583@@ -98,10 +115,24 @@
584             table_output.append('    %s_id %s NOT NULL REFERENCES %s (%s),' % \
585                 (f.rel.to.object_name.lower(), db.DATA_TYPES['IntegerField'], f.rel.to.db_table, f.rel.to.pk.column))
586             table_output.append('    UNIQUE (%s_id, %s_id)' % (opts.object_name.lower(), f.rel.to.object_name.lower()))
587-            table_output.append(');')
588+            table_output.append(')')
589             final_output.append('\n'.join(table_output))
590+           
591+            if (db.DATABASE_ENGINE == 'oracle') & (opts.has_auto_field):
592+                sequence_statement = 'CREATE SEQUENCE %s_sq\n' % f.get_m2m_db_table(opts)
593+                final_output.append(sequence_statement)   
594+                               
595+                trigger_statement = "" + \
596+                    "CREATE OR REPLACE trigger %s_tr\n"    % f.get_m2m_db_table(opts) + \
597+                    "  before insert on %s\n"           % f.get_m2m_db_table(opts) + \
598+                    "    for each row\n"  + \
599+                    "      begin\n" + \
600+                    "        select %s_sq.NEXTVAL into :new.id from DUAL;\n" % f.get_m2m_db_table(opts) + \
601+                    "      end;\n"
602+                final_output.append(trigger_statement)               
603+           
604     return final_output
605-get_sql_create.help_doc = "Prints the CREATE TABLE SQL statements for the given model module name(s)."
606+get_sql_create.help_doc = "Prints the CREATE TABLE SQL statements for the given model module name(s)."
607 get_sql_create.args = APP_ARGS
608 
609 def get_sql_delete(mod):
610@@ -116,23 +147,27 @@
611         try:
612             if cursor is not None:
613                 # Check whether the table exists.
614-                cursor.execute("SELECT 1 FROM %s LIMIT 1" % klass._meta.db_table)
615+                cursor.execute("SELECT COUNT(1) FROM %s" % klass._meta.db_table)
616         except:
617             # The table doesn't exist, so it doesn't need to be dropped.
618             db.db.rollback()
619         else:
620             output.append("DROP TABLE %s;" % klass._meta.db_table)
621+            if db.DATABASE_ENGINE == 'oracle':
622+                output.append("DROP SEQUENCE %s_sq" % klass._meta.db_table)
623+           
624     for klass in mod._MODELS:
625         opts = klass._meta
626         for f in opts.many_to_many:
627             try:
628                 if cursor is not None:
629-                    cursor.execute("SELECT 1 FROM %s LIMIT 1" % f.get_m2m_db_table(opts))
630+                    cursor.execute("SELECT COUNT(1) FROM %s" % f.get_m2m_db_table(opts))
631             except:
632                 db.db.rollback()
633             else:
634                 output.append("DROP TABLE %s;" % f.get_m2m_db_table(opts))
635-
636+                if db.DATABASE_ENGINE == 'oracle':
637+                    output.append("DROP SEQUENCE %s_sq" % f.get_m2m_db_table(opts))
638     app_label = mod._MODELS[0]._meta.app_label
639 
640     # Delete from packages, auth_permissions, content_types.
641@@ -181,12 +216,15 @@
642 
643 def get_sql_sequence_reset(mod):
644     "Returns a list of the SQL statements to reset PostgreSQL sequences for the given module."
645-    from django.core import meta
646+    from django.core import db, meta
647     output = []
648     for klass in mod._MODELS:
649         for f in klass._meta.fields:
650-            if isinstance(f, meta.AutoField):
651-                output.append("SELECT setval('%s_%s_seq', (SELECT max(%s) FROM %s));" % (klass._meta.db_table, f.column, f.column, klass._meta.db_table))
652+            if isinstance(f, meta.AutoField):
653+                if db.DATABASE_ENGINE == 'postgresql':
654+                    output.append("SELECT setval('%s_%s_seq', (SELECT max(%s) FROM %s));" % (klass._meta.db_table, f.column, f.column, klass._meta.db_table))
655+                else:
656+                    raise "Not implemented yet for %s!" % (db.DATABASE_ENGINE)
657     return output
658 get_sql_sequence_reset.help_doc = "Prints the SQL statements for resetting PostgreSQL sequences for the given model module name(s)."
659 get_sql_sequence_reset.args = APP_ARGS
660--- trunk/django/django/middleware/sessions.py  (revision 656)
661+++ trunk/django/django/middleware/sessions.py  (working copy)
662@@ -1,4 +1,4 @@
663-from django.conf.settings import SESSION_COOKIE_NAME, SESSION_COOKIE_AGE, SESSION_COOKIE_DOMAIN
664+from django.conf.settings import SESSION_COOKIE_NAME, SESSION_COOKIE_AGE, SESSION_COOKIE_DOMAIN, DATABASE_ENGINE
665 from django.models.core import sessions
666 import datetime
667 
668@@ -64,8 +64,12 @@
669             modified = False
670         if modified:
671             session_key = request.session.session_key or sessions.get_new_session_key()
672+            expire_date = datetime.datetime.now()
673+            #Oracle and Mysql can't deal with microseconds
674+            if DATABASE_ENGINE in ['oracle','mysql']:
675+                expire_date = expire_date.replace(microsecond=0)           
676             new_session = sessions.save(session_key, request.session._session,
677-                datetime.datetime.now() + datetime.timedelta(seconds=SESSION_COOKIE_AGE))
678+                expire_date + datetime.timedelta(seconds=SESSION_COOKIE_AGE))
679             # TODO: Accept variable session length and domain.
680             response.set_cookie(SESSION_COOKIE_NAME, session_key,
681                 max_age=SESSION_COOKIE_AGE, domain=SESSION_COOKIE_DOMAIN)
Back to Top