Django

Code

Ticket #87: unified_diff_oracle_backend_rev_656.txt

File unified_diff_oracle_backend_rev_656.txt, 29.3 kB (added by Jason Huggns, 3 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)