Code

OracleBranch: django-oracle-rev5392.diff

File django-oracle-rev5392.diff, 96.1 KB (added by bouldersprinters, 7 years ago)

Patch to add Oracle support to trunk as of trunk rev 5392

Line 
1Index: django/test/utils.py
2===================================================================
3--- django/test/utils.py        (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
4+++ django/test/utils.py        (working copy)
5@@ -1,6 +1,6 @@
6 import sys, time
7 from django.conf import settings
8-from django.db import connection, transaction, backend
9+from django.db import connection, backend, get_creation_module
10 from django.core import management, mail
11 from django.dispatch import dispatcher
12 from django.test import signals
13@@ -88,6 +88,12 @@
14     return ''
15 
16 def create_test_db(verbosity=1, autoclobber=False):
17+    # If the database backend wants to create the test DB itself, let it
18+    creation_module = get_creation_module()
19+    if hasattr(creation_module, "create_test_db"):
20+        creation_module.create_test_db(settings, connection, backend, verbosity, autoclobber)
21+        return
22+   
23     if verbosity >= 1:
24         print "Creating test database..."
25     # If we're using SQLite, it's more convenient to test against an
26@@ -142,6 +148,12 @@
27     cursor = connection.cursor()
28 
29 def destroy_test_db(old_database_name, verbosity=1):
30+    # If the database wants to drop the test DB itself, let it
31+    creation_module = get_creation_module()
32+    if hasattr(creation_module, "destroy_test_db"):
33+        creation_module.destroy_test_db(settings, connection, backend, old_database_name, verbosity)
34+        return
35+   
36     # Unless we're using SQLite, remove the test database to clean up after
37     # ourselves. Connect to the previous database (not the test database)
38     # to do so, because it's not allowed to delete a database while being
39Index: django/db/models/base.py
40===================================================================
41--- django/db/models/base.py    (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
42+++ django/db/models/base.py    (working copy)
43@@ -96,9 +96,9 @@
44 
45     def __init__(self, *args, **kwargs):
46         dispatcher.send(signal=signals.pre_init, sender=self.__class__, args=args, kwargs=kwargs)
47-       
48+
49         # There is a rather weird disparity here; if kwargs, it's set, then args
50-        # overrides it. It should be one or the other; don't duplicate the work
51+        # overrides it. It should be one or the other; don't duplicate the work
52         # The reason for the kwargs check is that standard iterator passes in by
53         # args, and nstantiation for iteration is 33% faster.
54         args_len = len(args)
55@@ -122,10 +122,10 @@
56                 # Maintain compatibility with existing calls.
57                 if isinstance(field.rel, ManyToOneRel):
58                     kwargs.pop(field.attname, None)
59-       
60+
61         # Now we're left with the unprocessed fields that *must* come from
62         # keywords, or default.
63-       
64+
65         for field in fields_iter:
66             if kwargs:
67                 if isinstance(field.rel, ManyToOneRel):
68@@ -147,7 +147,7 @@
69                             try:
70                                 val = getattr(rel_obj, field.rel.get_related_field().attname)
71                             except AttributeError:
72-                                raise TypeError("Invalid value: %r should be a %s instance, not a %s" %
73+                                raise TypeError("Invalid value: %r should be a %s instance, not a %s" %
74                                     (field.name, field.rel.to, type(rel_obj)))
75                 else:
76                     val = kwargs.pop(field.attname, field.get_default())
77@@ -210,17 +210,18 @@
78         record_exists = True
79         if pk_set:
80             # Determine whether a record with the primary key already exists.
81-            cursor.execute("SELECT 1 FROM %s WHERE %s=%%s LIMIT 1" % \
82-                (backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)), [pk_val])
83+            cursor.execute("SELECT COUNT(*) FROM %s WHERE %s=%%s" % \
84+                (backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)),
85+                self._meta.pk.get_db_prep_lookup('exact', pk_val))
86             # If it does already exist, do an UPDATE.
87-            if cursor.fetchone():
88+            if cursor.fetchone()[0] > 0:
89                 db_values = [f.get_db_prep_save(f.pre_save(self, False)) for f in non_pks]
90                 if db_values:
91                     cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % \
92                         (backend.quote_name(self._meta.db_table),
93                         ','.join(['%s=%%s' % backend.quote_name(f.column) for f in non_pks]),
94                         backend.quote_name(self._meta.pk.column)),
95-                        db_values + [pk_val])
96+                        db_values + self._meta.pk.get_db_prep_lookup('exact', pk_val))
97             else:
98                 record_exists = False
99         if not pk_set or not record_exists:
100Index: django/db/models/options.py
101===================================================================
102--- django/db/models/options.py (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
103+++ django/db/models/options.py (working copy)
104@@ -13,7 +13,7 @@
105 
106 DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
107                  'unique_together', 'permissions', 'get_latest_by',
108-                 'order_with_respect_to', 'app_label')
109+                 'order_with_respect_to', 'app_label', 'db_tablespace')
110 
111 class Options(object):
112     def __init__(self, meta):
113@@ -27,6 +27,7 @@
114         self.object_name, self.app_label = None, None
115         self.get_latest_by = None
116         self.order_with_respect_to = None
117+        self.db_tablespace = None
118         self.admin = None
119         self.meta = meta
120         self.pk = None
121@@ -59,6 +60,8 @@
122         del self.meta
123 
124     def _prepare(self, model):
125+        from django.db import backend
126+        from django.db.backends.util import truncate_name
127         if self.order_with_respect_to:
128             self.order_with_respect_to = self.get_field(self.order_with_respect_to)
129             self.ordering = ('_order',)
130@@ -73,6 +76,8 @@
131         # If the db_table wasn't provided, use the app_label + module_name.
132         if not self.db_table:
133             self.db_table = "%s_%s" % (self.app_label, self.module_name)
134+            self.db_table = truncate_name(self.db_table,
135+                                          backend.get_max_name_length())
136 
137     def add_field(self, field):
138         # Insert the given field in the order in which it was created, using
139@@ -88,10 +93,10 @@
140 
141     def __repr__(self):
142         return '<Options for %s>' % self.object_name
143-       
144+
145     def __str__(self):
146         return "%s.%s" % (self.app_label, self.module_name)
147-       
148+
149     def get_field(self, name, many_to_many=True):
150         "Returns the requested field by name. Raises FieldDoesNotExist on error."
151         to_search = many_to_many and (self.fields + self.many_to_many) or self.fields
152Index: django/db/models/fields/__init__.py
153===================================================================
154--- django/db/models/fields/__init__.py (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
155+++ django/db/models/fields/__init__.py (working copy)
156@@ -74,12 +74,16 @@
157         core=False, rel=None, default=NOT_PROVIDED, editable=True, serialize=True,
158         prepopulate_from=None, unique_for_date=None, unique_for_month=None,
159         unique_for_year=None, validator_list=None, choices=None, radio_admin=None,
160-        help_text='', db_column=None):
161+        help_text='', db_column=None, db_tablespace=None):
162         self.name = name
163         self.verbose_name = verbose_name
164         self.primary_key = primary_key
165         self.maxlength, self.unique = maxlength, unique
166         self.blank, self.null = blank, null
167+        # Oracle treats the empty string ('') as null, so coerce the null
168+        # option whenever '' is a possible value.
169+        if self.empty_strings_allowed and settings.DATABASE_ENGINE == 'oracle':
170+            self.null = True
171         self.core, self.rel, self.default = core, rel, default
172         self.editable = editable
173         self.serialize = serialize
174@@ -91,6 +95,7 @@
175         self.radio_admin = radio_admin
176         self.help_text = help_text
177         self.db_column = db_column
178+        self.db_tablespace = db_tablespace
179 
180         # Set db_index to True if the field has a relationship and doesn't explicitly set db_index.
181         self.db_index = db_index
182@@ -875,10 +880,18 @@
183         Field.__init__(self, verbose_name, name, **kwargs)
184 
185     def get_db_prep_lookup(self, lookup_type, value):
186+        if settings.DATABASE_ENGINE == 'oracle':
187+            # Oracle requires a date in order to parse.
188+            def prep(value):
189+                if isinstance(value, datetime.time):
190+                    value = datetime.datetime.combine(datetime.date(1900, 1, 1), value)
191+                return str(value)
192+        else:
193+            prep = str
194         if lookup_type == 'range':
195-            value = [str(v) for v in value]
196+            value = [prep(v) for v in value]
197         else:
198-            value = str(value)
199+            value = prep(value)
200         return Field.get_db_prep_lookup(self, lookup_type, value)
201 
202     def pre_save(self, model_instance, add):
203@@ -896,6 +909,14 @@
204             # doesn't support microseconds.
205             if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
206                 value = value.replace(microsecond=0)
207+            if settings.DATABASE_ENGINE == 'oracle':
208+                # cx_Oracle expects a datetime.datetime to persist into TIMESTAMP field.
209+                if isinstance(value, datetime.time):
210+                    value = datetime.datetime(1900, 1, 1, value.hour, value.minute,
211+                                              value.second, value.microsecond)
212+                elif isinstance(value, basestring):
213+                    value = datetime.datetime(*(time.strptime(value, '%H:%M:%S')[:6]))
214+            else:
215             value = str(value)
216         return Field.get_db_prep_save(self, value)
217 
218Index: django/db/models/fields/related.py
219===================================================================
220--- django/db/models/fields/related.py  (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
221+++ django/db/models/fields/related.py  (working copy)
222@@ -335,10 +335,7 @@
223                     (target_col_name, self.join_table, source_col_name,
224                     target_col_name, ",".join(['%s'] * len(new_ids))),
225                     [self._pk_val] + list(new_ids))
226-                if cursor.rowcount is not None and cursor.rowcount != 0:
227-                    existing_ids = set([row[0] for row in cursor.fetchmany(cursor.rowcount)])
228-                else:
229-                    existing_ids = set()
230+                existing_ids = set([row[0] for row in cursor.fetchall()])
231 
232                 # Add the ones that aren't there already
233                 for obj_id in (new_ids - existing_ids):
234Index: django/db/models/query.py
235===================================================================
236--- django/db/models/query.py   (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
237+++ django/db/models/query.py   (working copy)
238@@ -4,6 +4,7 @@
239 from django.dispatch import dispatcher
240 from django.utils.datastructures import SortedDict
241 from django.contrib.contenttypes import generic
242+import datetime
243 import operator
244 import re
245 
246@@ -77,7 +78,7 @@
247     else:
248         return backend.quote_name(word)
249 
250-class QuerySet(object):
251+class _QuerySet(object):
252     "Represents a lazy database lookup for a set of objects"
253     def __init__(self, model=None):
254         self.model = model
255@@ -181,13 +182,18 @@
256 
257         cursor = connection.cursor()
258         cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
259+
260         fill_cache = self._select_related
261-        index_end = len(self.model._meta.fields)
262+        fields = self.model._meta.fields
263+        index_end = len(fields)
264+        has_resolve_columns = hasattr(self, 'resolve_columns')
265         while 1:
266             rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
267             if not rows:
268                 raise StopIteration
269             for row in rows:
270+                if has_resolve_columns:
271+                    row = self.resolve_columns(row, fields)
272                 if fill_cache:
273                     obj, index_end = get_cached_row(klass=self.model, row=row,
274                                                     index_start=0, max_depth=self._max_related_depth)
275@@ -551,11 +557,18 @@
276 
277         return select, " ".join(sql), params
278 
279+# Use the backend's QuerySet class if it defines one, otherwise use _QuerySet.
280+if hasattr(backend, 'get_query_set_class'):
281+    QuerySet = backend.get_query_set_class(_QuerySet)
282+else:
283+    QuerySet = _QuerySet
284+
285 class ValuesQuerySet(QuerySet):
286     def __init__(self, *args, **kwargs):
287         super(ValuesQuerySet, self).__init__(*args, **kwargs)
288-        # select_related isn't supported in values().
289+        # select_related and select aren't supported in values().
290         self._select_related = False
291+        self._select = {}
292 
293     def iterator(self):
294         try:
295@@ -565,35 +578,24 @@
296 
297         # self._fields is a list of field names to fetch.
298         if self._fields:
299-            #columns = [self.model._meta.get_field(f, many_to_many=False).column for f in self._fields]
300-            if not self._select:
301-                columns = [self.model._meta.get_field(f, many_to_many=False).column for f in self._fields]
302-            else:
303-                columns = []
304-                for f in self._fields:
305-                    if f in [field.name for field in self.model._meta.fields]:
306-                        columns.append( self.model._meta.get_field(f, many_to_many=False).column )
307-                    elif not self._select.has_key( f ):
308-                        raise FieldDoesNotExist, '%s has no field named %r' % ( self.model._meta.object_name, f )
309-
310-            field_names = self._fields
311+            fields = [self.model._meta.get_field(f, many_to_many=False) for f in self._fields]
312         else: # Default to all fields.
313-            columns = [f.column for f in self.model._meta.fields]
314-            field_names = [f.attname for f in self.model._meta.fields]
315+            fields = self.model._meta.fields
316+        columns = [f.column for f in fields]
317+        field_names = [f.attname for f in fields]
318 
319         select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns]
320-
321-        # Add any additional SELECTs.
322-        if self._select:
323-            select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select.items()])
324-
325         cursor = connection.cursor()
326         cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
327+
328+        has_resolve_columns = hasattr(self, 'resolve_columns')
329         while 1:
330             rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
331             if not rows:
332                 raise StopIteration
333             for row in rows:
334+                if has_resolve_columns:
335+                    row = self.resolve_columns(row, fields)
336                 yield dict(zip(field_names, row))
337 
338     def _clone(self, klass=None, **kwargs):
339@@ -604,26 +606,50 @@
340 class DateQuerySet(QuerySet):
341     def iterator(self):
342         from django.db.backends.util import typecast_timestamp
343+        from django.db.models.fields import DateTimeField
344         self._order_by = () # Clear this because it'll mess things up otherwise.
345         if self._field.null:
346             self._where.append('%s.%s IS NOT NULL' % \
347                 (backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column)))
348-
349         try:
350             select, sql, params = self._get_sql_clause()
351         except EmptyResultSet:
352             raise StopIteration
353 
354-        sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1 %s' % \
355+        table_name = backend.quote_name(self.model._meta.db_table)
356+        field_name = backend.quote_name(self._field.column)
357+
358+        if backend.allows_group_by_ordinal:
359+            group_by = '1'
360+        else:
361+            group_by = backend.get_date_trunc_sql(self._kind,
362+                                                  '%s.%s' % (table_name, field_name))
363+
364+        sql = 'SELECT %s %s GROUP BY %s ORDER BY 1 %s' % \
365             (backend.get_date_trunc_sql(self._kind, '%s.%s' % (backend.quote_name(self.model._meta.db_table),
366-            backend.quote_name(self._field.column))), sql, self._order)
367+            backend.quote_name(self._field.column))), sql, group_by, self._order)
368         cursor = connection.cursor()
369         cursor.execute(sql, params)
370-        # We have to manually run typecast_timestamp(str()) on the results, because
371-        # MySQL doesn't automatically cast the result of date functions as datetime
372-        # objects -- MySQL returns the values as strings, instead.
373-        return [typecast_timestamp(str(row[0])) for row in cursor.fetchall()]
374 
375+        has_resolve_columns = hasattr(self, 'resolve_columns')
376+        needs_datetime_string_cast = backend.needs_datetime_string_cast
377+        dates = []
378+        # It would be better to use self._field here instead of DateTimeField(),
379+        # but in Oracle that will result in a list of datetime.date instead of
380+        # datetime.datetime.
381+        fields = [DateTimeField()]
382+        while 1:
383+            rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
384+            if not rows:
385+                return dates
386+            for row in rows:
387+                date = row[0]
388+                if has_resolve_columns:
389+                    date = self.resolve_columns([date], fields)[0]
390+                elif needs_datetime_string_cast:
391+                    date = typecast_timestamp(str(date))
392+                dates.append(date)
393+
394     def _clone(self, klass=None, **kwargs):
395         c = super(DateQuerySet, self)._clone(klass, **kwargs)
396         c._field = self._field
397@@ -730,8 +756,17 @@
398     if table_prefix.endswith('.'):
399         table_prefix = backend.quote_name(table_prefix[:-1])+'.'
400     field_name = backend.quote_name(field_name)
401+    if type(value) == datetime.datetime and backend.get_datetime_cast_sql():
402+        cast_sql = backend.get_datetime_cast_sql()
403+    else:
404+        cast_sql = '%s'
405+    if lookup_type in ('iexact', 'icontains', 'istartswith', 'iendswith') and backend.needs_upper_for_iops:
406+        format = 'UPPER(%s%s) %s'
407+    else:
408+        format = '%s%s %s'
409     try:
410-        return '%s%s %s' % (table_prefix, field_name, (backend.OPERATOR_MAPPING[lookup_type] % '%s'))
411+        return format % (table_prefix, field_name,
412+                         backend.OPERATOR_MAPPING[lookup_type] % cast_sql)
413     except KeyError:
414         pass
415     if lookup_type == 'in':
416Index: django/db/backends/ado_mssql/base.py
417===================================================================
418--- django/db/backends/ado_mssql/base.py        (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
419+++ django/db/backends/ado_mssql/base.py        (working copy)
420@@ -89,7 +89,14 @@
421             self.connection.close()
422             self.connection = None
423 
424+allows_group_by_ordinal = True
425+allows_unique_and_pk = True
426+autoindexes_primary_keys = True
427+needs_datetime_string_cast = True
428+needs_upper_for_iops = False
429 supports_constraints = True
430+supports_tablespaces = True
431+uses_case_insensitive_names = False
432 
433 def quote_name(name):
434     if name.startswith('[') and name.endswith(']'):
435@@ -117,6 +124,9 @@
436     if lookup_type=='day':
437         return "Convert(datetime, Convert(varchar(12), %s))" % field_name
438 
439+def get_datetime_cast_sql():
440+    return None
441+
442 def get_limit_offset_sql(limit, offset=None):
443     # TODO: This is a guess. Make sure this is correct.
444     sql = "LIMIT %s" % limit
445@@ -139,6 +149,18 @@
446 def get_pk_default_value():
447     return "DEFAULT"
448 
449+def get_max_name_length():
450+    return None
451+
452+def get_start_transaction_sql():
453+    return "BEGIN;"
454+
455+def get_tablespace_sql(tablespace, inline=False):
456+    return "ON %s" % quote_name(tablespace)
457+
458+def get_autoinc_sql(table):
459+    return None
460+
461 def get_sql_flush(style, tables, sequences):
462     """Return a list of SQL statements required to remove all data from
463     all tables in the database (without actually removing the tables
464Index: django/db/backends/postgresql/base.py
465===================================================================
466--- django/db/backends/postgresql/base.py       (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
467+++ django/db/backends/postgresql/base.py       (working copy)
468@@ -87,7 +87,7 @@
469         global postgres_version
470         if not postgres_version:
471             cursor.execute("SELECT version()")
472-            postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')]       
473+            postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')]
474         if settings.DEBUG:
475             return util.CursorDebugWrapper(cursor, self)
476         return cursor
477@@ -105,7 +105,14 @@
478             self.connection.close()
479             self.connection = None
480 
481+allows_group_by_ordinal = True
482+allows_unique_and_pk = True
483+autoindexes_primary_keys = True
484+needs_datetime_string_cast = True
485+needs_upper_for_iops = False
486 supports_constraints = True
487+supports_tablespaces = False
488+uses_case_insensitive_names = False
489 
490 def quote_name(name):
491     if name.startswith('"') and name.endswith('"'):
492@@ -138,6 +145,9 @@
493     # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
494     return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name)
495 
496+def get_datetime_cast_sql():
497+    return None
498+
499 def get_limit_offset_sql(limit, offset=None):
500     sql = "LIMIT %s" % limit
501     if offset and offset != 0:
502@@ -149,7 +159,7 @@
503 
504 def get_deferrable_sql():
505     return " DEFERRABLE INITIALLY DEFERRED"
506-   
507+
508 def get_fulltext_search_sql(field_name):
509     raise NotImplementedError
510 
511@@ -159,12 +169,21 @@
512 def get_pk_default_value():
513     return "DEFAULT"
514 
515+def get_max_name_length():
516+    return None
517+
518+def get_start_transaction_sql():
519+    return "BEGIN;"
520+
521+def get_autoinc_sql(table):
522+    return None
523+
524 def get_sql_flush(style, tables, sequences):
525     """Return a list of SQL statements required to remove all data from
526     all tables in the database (without actually removing the tables
527     themselves) and put the database in an empty 'initial' state
528-   
529-    """   
530+
531+    """
532     if tables:
533         if postgres_version[0] >= 8 and postgres_version[1] >= 1:
534             # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to* in order to be able to
535@@ -175,7 +194,7 @@
536                  style.SQL_FIELD(', '.join([quote_name(table) for table in tables]))
537             )]
538         else:
539-            # Older versions of Postgres can't do TRUNCATE in a single call, so they must use
540+            # Older versions of Postgres can't do TRUNCATE in a single call, so they must use
541             # a simple delete.
542             sql = ['%s %s %s;' % \
543                     (style.SQL_KEYWORD('DELETE'),
544@@ -238,7 +257,7 @@
545                 style.SQL_KEYWORD('FROM'),
546                 style.SQL_TABLE(f.m2m_db_table())))
547     return output
548-       
549+
550 # Register these custom typecasts, because Django expects dates/times to be
551 # in Python's native (standard-library) datetime/time format, whereas psycopg
552 # use mx.DateTime by default.
553Index: django/db/backends/sqlite3/base.py
554===================================================================
555--- django/db/backends/sqlite3/base.py  (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
556+++ django/db/backends/sqlite3/base.py  (working copy)
557@@ -107,7 +107,14 @@
558     def convert_query(self, query, num_params):
559         return query % tuple("?" * num_params)
560 
561+allows_group_by_ordinal = True
562+allows_unique_and_pk = True
563+autoindexes_primary_keys = True
564+needs_datetime_string_cast = True
565+needs_upper_for_iops = False
566 supports_constraints = False
567+supports_tablespaces = False
568+uses_case_insensitive_names = False
569 
570 def quote_name(name):
571     if name.startswith('"') and name.endswith('"'):
572@@ -139,6 +146,9 @@
573     # sqlite doesn't support DATE_TRUNC, so we fake it as above.
574     return 'django_date_trunc("%s", %s)' % (lookup_type.lower(), field_name)
575 
576+def get_datetime_cast_sql():
577+    return None
578+
579 def get_limit_offset_sql(limit, offset=None):
580     sql = "LIMIT %s" % limit
581     if offset and offset != 0:
582@@ -160,11 +170,20 @@
583 def get_pk_default_value():
584     return "NULL"
585 
586+def get_max_name_length():
587+    return None
588+
589+def get_start_transaction_sql():
590+    return "BEGIN;"
591+
592+def get_autoinc_sql(table):
593+    return None
594+
595 def get_sql_flush(style, tables, sequences):
596     """Return a list of SQL statements required to remove all data from
597     all tables in the database (without actually removing the tables
598     themselves) and put the database in an empty 'initial' state
599-   
600+
601     """
602     # NB: The generated SQL below is specific to SQLite
603     # Note: The DELETE FROM... SQL generated below works for SQLite databases
604@@ -182,7 +201,7 @@
605     "Returns a list of the SQL statements to reset sequences for the given models."
606     # No sequence reset required
607     return []
608-   
609+
610 def _sqlite_date_trunc(lookup_type, dt):
611     try:
612         dt = util.typecast_timestamp(dt)
613Index: django/db/backends/util.py
614===================================================================
615--- django/db/backends/util.py  (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
616+++ django/db/backends/util.py  (working copy)
617@@ -1,4 +1,5 @@
618 import datetime
619+import md5
620 from time import time
621 
622 try:
623@@ -107,6 +108,16 @@
624         return None
625     return str(d)
626 
627+def truncate_name(name, length=None):
628+    """Shortens a string to a repeatable mangled version with the given length.
629+    """
630+    if length is None or len(name) <= length:
631+        return name
632+
633+    hash = md5.md5(name).hexdigest()[:4]
634+
635+    return '%s%s' % (name[:length-4], hash)
636+
637 ##################################################################################
638 # Helper functions for dictfetch* for databases that don't natively support them #
639 ##################################################################################
640Index: django/db/backends/mysql/base.py
641===================================================================
642--- django/db/backends/mysql/base.py    (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
643+++ django/db/backends/mysql/base.py    (working copy)
644@@ -134,7 +134,14 @@
645             self.server_version = tuple([int(x) for x in m.groups()])
646         return self.server_version
647 
648+allows_group_by_ordinal = True
649+allows_unique_and_pk = True
650+autoindexes_primary_keys = False
651+needs_datetime_string_cast = True     # MySQLdb requires a typecast for dates
652+needs_upper_for_iops = False
653 supports_constraints = True
654+supports_tablespaces = False
655+uses_case_insensitive_names = False
656 
657 def quote_name(name):
658     if name.startswith("`") and name.endswith("`"):
659@@ -167,6 +174,9 @@
660         sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
661     return sql
662 
663+def get_datetime_cast_sql():
664+    return None
665+
666 def get_limit_offset_sql(limit, offset=None):
667     sql = "LIMIT "
668     if offset and offset != 0:
669@@ -188,11 +198,20 @@
670 def get_pk_default_value():
671     return "DEFAULT"
672 
673+def get_max_name_length():
674+    return 64;
675+
676+def get_start_transaction_sql():
677+    return "BEGIN;"
678+
679+def get_autoinc_sql(table):
680+    return None
681+
682 def get_sql_flush(style, tables, sequences):
683     """Return a list of SQL statements required to remove all data from
684     all tables in the database (without actually removing the tables
685     themselves) and put the database in an empty 'initial' state
686-   
687+
688     """
689     # NB: The generated SQL below is specific to MySQL
690     # 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
691@@ -204,7 +223,7 @@
692                  style.SQL_FIELD(quote_name(table))
693                 )  for table in tables] + \
694               ['SET FOREIGN_KEY_CHECKS = 1;']
695-             
696+
697         # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
698         # to reset sequence indices
699         sql.extend(["%s %s %s %s %s;" % \
700Index: django/db/backends/oracle/base.py
701===================================================================
702--- django/db/backends/oracle/base.py   (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
703+++ django/db/backends/oracle/base.py   (working copy)
704@@ -4,13 +4,17 @@
705 Requires cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/
706 """
707 
708+from django.conf import settings
709 from django.db.backends import util
710 try:
711     import cx_Oracle as Database
712 except ImportError, e:
713     from django.core.exceptions import ImproperlyConfigured
714     raise ImproperlyConfigured, "Error loading cx_Oracle module: %s" % e
715+import datetime
716+from django.utils.datastructures import SortedDict
717 
718+
719 DatabaseError = Database.Error
720 IntegrityError = Database.IntegrityError
721 
722@@ -31,7 +35,6 @@
723         return self.connection is not None
724 
725     def cursor(self):
726-        from django.conf import settings
727         if not self._valid_connection():
728             if len(settings.DATABASE_HOST.strip()) == 0:
729                 settings.DATABASE_HOST = 'localhost'
730@@ -41,25 +44,37 @@
731             else:
732                 conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME)
733                 self.connection = Database.connect(conn_string, **self.options)
734-        return FormatStylePlaceholderCursor(self.connection)
735+        cursor = FormatStylePlaceholderCursor(self.connection)
736+        # default arraysize of 1 is highly sub-optimal
737+        cursor.arraysize = 100
738+        # set oracle date to ansi date format
739+        cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD'")
740+        cursor.execute("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'")
741+        if settings.DEBUG:
742+            return util.CursorDebugWrapper(cursor, self)
743+        return cursor
744 
745     def _commit(self):
746         if self.connection is not None:
747-            self.connection.commit()
748+            return self.connection.commit()
749 
750     def _rollback(self):
751         if self.connection is not None:
752-            try:
753-                self.connection.rollback()
754-            except Database.NotSupportedError:
755-                pass
756+            return self.connection.rollback()
757 
758     def close(self):
759         if self.connection is not None:
760             self.connection.close()
761             self.connection = None
762 
763+allows_group_by_ordinal = False
764+allows_unique_and_pk = False        # Suppress UNIQUE/PK for Oracle (ORA-02259)
765+autoindexes_primary_keys = True
766+needs_datetime_string_cast = False
767+needs_upper_for_iops = True
768 supports_constraints = True
769+supports_tablespaces = True
770+uses_case_insensitive_names = True
771 
772 class FormatStylePlaceholderCursor(Database.Cursor):
773     """
774@@ -67,45 +82,75 @@
775     This fixes it -- but note that if you want to use a literal "%s" in a query,
776     you'll need to use "%%s".
777     """
778+    def _rewrite_args(self, query, params=None):
779+        if params is None:
780+            params = []
781+        else:
782+            # cx_Oracle can't handle unicode parameters, so cast to str for now
783+            for i, param in enumerate(params):
784+                if type(param) == unicode:
785+                    try:
786+                        params[i] = param.encode('utf-8')
787+                    except UnicodeError:
788+                        params[i] = str(param)
789+        args = [(':arg%d' % i) for i in range(len(params))]
790+        query = query % tuple(args)
791+        # cx_Oracle wants no trailing ';' for SQL statements.  For PL/SQL, it
792+        # it does want a trailing ';' but not a trailing '/'.  However, these
793+        # characters must be included in the original query in case the query
794+        # is being passed to SQL*Plus.
795+        if query.endswith(';') or query.endswith('/'):
796+            query = query[:-1]
797+        return query, params
798+
799     def execute(self, query, params=None):
800-        if params is None: params = []
801-        query = self.convert_arguments(query, len(params))
802+        query, params = self._rewrite_args(query, params)
803         return Database.Cursor.execute(self, query, params)
804 
805     def executemany(self, query, params=None):
806-        if params is None: params = []
807-        query = self.convert_arguments(query, len(params[0]))
808+        query, params = self._rewrite_args(query, params)
809         return Database.Cursor.executemany(self, query, params)
810 
811-    def convert_arguments(self, query, num_params):
812-        # replace occurances of "%s" with ":arg" - Oracle requires colons for parameter placeholders.
813-        args = [':arg' for i in range(num_params)]
814-        return query % tuple(args)
815-
816 def quote_name(name):
817-    return name
818+    # SQL92 requires delimited (quoted) names to be case-sensitive.  When
819+    # not quoted, Oracle has case-insensitive behavior for identifiers, but
820+    # always defaults to uppercase.
821+    # We simplify things by making Oracle identifiers always uppercase.
822+    if not name.startswith('"') and not name.endswith('"'):
823+        name = '"%s"' % util.truncate_name(name.upper(), get_max_name_length())
824+    return name.upper()
825 
826 dictfetchone = util.dictfetchone
827 dictfetchmany = util.dictfetchmany
828 dictfetchall  = util.dictfetchall
829 
830 def get_last_insert_id(cursor, table_name, pk_name):
831-    query = "SELECT %s_sq.currval from dual" % table_name
832-    cursor.execute(query)
833+    sq_name = util.truncate_name(table_name, get_max_name_length()-3)
834+    cursor.execute('SELECT %s_sq.currval FROM dual' % sq_name)
835     return cursor.fetchone()[0]
836 
837 def get_date_extract_sql(lookup_type, table_name):
838     # lookup_type is 'year', 'month', 'day'
839-    # http://www.psoug.org/reference/date_func.html
840+    # http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions42a.htm#1017163
841     return "EXTRACT(%s FROM %s)" % (lookup_type, table_name)
842 
843 def get_date_trunc_sql(lookup_type, field_name):
844-    return "EXTRACT(%s FROM TRUNC(%s))" % (lookup_type, field_name)
845+    # lookup_type is 'year', 'month', 'day'
846+    # Oracle uses TRUNC() for both dates and numbers.
847+    # http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions155a.htm#SQLRF06151
848+    if lookup_type == 'day':
849+        sql = 'TRUNC(%s)' % (field_name,)
850+    else:
851+        sql = "TRUNC(%s, '%s')" % (field_name, lookup_type)
852+    return sql
853 
854+def get_datetime_cast_sql():
855+    return "TO_TIMESTAMP(%s, 'YYYY-MM-DD HH24:MI:SS.FF')"
856+
857 def get_limit_offset_sql(limit, offset=None):
858     # Limits and offset are too complicated to be handled here.
859-    # Instead, they are handled in django/db/query.py.
860-    pass
861+    # Instead, they are handled in django/db/backends/oracle/query.py.
862+    return ""
863 
864 def get_random_function_sql():
865     return "DBMS_RANDOM.RANDOM"
866@@ -117,40 +162,363 @@
867     raise NotImplementedError
868 
869 def get_drop_foreignkey_sql():
870-    return "DROP FOREIGN KEY"
871+    return "DROP CONSTRAINT"
872 
873 def get_pk_default_value():
874     return "DEFAULT"
875 
876+def get_max_name_length():
877+    return 30
878+
879+def get_start_transaction_sql():
880+    return None
881+
882+def get_tablespace_sql(tablespace, inline=False):
883+    return "%sTABLESPACE %s" % ((inline and "USING INDEX " or ""), quote_name(tablespace))
884+
885+def get_autoinc_sql(table):
886+    # To simulate auto-incrementing primary keys in Oracle, we have to
887+    # create a sequence and a trigger.
888+    sq_name = get_sequence_name(table)
889+    tr_name = get_trigger_name(table)
890+    sequence_sql = 'CREATE SEQUENCE %s;' % sq_name
891+    trigger_sql = """CREATE OR REPLACE TRIGGER %s
892+  BEFORE INSERT ON %s
893+  FOR EACH ROW
894+  WHEN (new.id IS NULL)
895+    BEGIN
896+      SELECT %s.nextval INTO :new.id FROM dual;
897+    END;
898+    /""" % (tr_name, quote_name(table), sq_name)
899+    return sequence_sql, trigger_sql
900+
901+def get_drop_sequence(table):
902+    return "DROP SEQUENCE %s;" % quote_name(get_sequence_name(table))
903+
904+def _get_sequence_reset_sql():
905+    # TODO: colorize this SQL code with style.SQL_KEYWORD(), etc.
906+    return """
907+        DECLARE
908+            startvalue integer;
909+            cval integer;
910+        BEGIN
911+            LOCK TABLE %(table)s IN SHARE MODE;
912+            SELECT NVL(MAX(id), 0) INTO startvalue FROM %(table)s;
913+            SELECT %(sequence)s.nextval INTO cval FROM dual;
914+            cval := startvalue - cval;
915+            IF cval != 0 THEN
916+                EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s MINVALUE 0 INCREMENT BY '||cval;
917+                SELECT %(sequence)s.nextval INTO cval FROM dual;
918+                EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s INCREMENT BY 1';
919+            END IF;
920+            COMMIT;
921+        END;
922+        /"""
923+
924 def get_sql_flush(style, tables, sequences):
925     """Return a list of SQL statements required to remove all data from
926     all tables in the database (without actually removing the tables
927     themselves) and put the database in an empty 'initial' state
928     """
929-    # Return a list of 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
930-    # TODO - SQL not actually tested against Oracle yet!
931-    # TODO - autoincrement indices reset required? See other get_sql_flush() implementations
932-    sql = ['%s %s;' % \
933-            (style.SQL_KEYWORD('TRUNCATE'),
934+    # Return a list of 'TRUNCATE x;', 'TRUNCATE y;',
935+    # 'TRUNCATE z;'... style SQL statements
936+    if tables:
937+        # Oracle does support TRUNCATE, but it seems to get us into
938+        # FK referential trouble, whereas DELETE FROM table works.
939+        sql = ['%s %s %s;' % \
940+                (style.SQL_KEYWORD('DELETE'),
941+                 style.SQL_KEYWORD('FROM'),
942              style.SQL_FIELD(quote_name(table))
943              )  for table in tables]
944+        # Since we've just deleted all the rows, running our sequence
945+        # ALTER code will reset the sequence to 0.
946+        for sequence_info in sequences:
947+            table_name = sequence_info['table']
948+            seq_name = get_sequence_name(table_name)
949+            query = _get_sequence_reset_sql() % {'sequence':seq_name,
950+                                                 'table':quote_name(table_name)}
951+            sql.append(query)
952+        return sql
953+    else:
954+        return []
955 
956+def get_sequence_name(table):
957+    name_length = get_max_name_length() - 3
958+    return '%s_SQ' % util.truncate_name(table, name_length).upper()
959+
960 def get_sql_sequence_reset(style, model_list):
961     "Returns a list of the SQL statements to reset sequences for the given models."
962-    # No sequence reset required
963-    return []
964+    from django.db import models
965+    output = []
966+    query = _get_sequence_reset_sql()
967+    for model in model_list:
968+        for f in model._meta.fields:
969+            if isinstance(f, models.AutoField):
970+                sequence_name = get_sequence_name(model._meta.db_table)
971+                output.append(query % {'sequence':sequence_name,
972+                                       'table':model._meta.db_table})
973+                break # Only one AutoField is allowed per model, so don't bother continuing.
974+        for f in model._meta.many_to_many:
975+            sequence_name = get_sequence_name(f.m2m_db_table())
976+            output.append(query % {'sequence':sequence_name,
977+                                   'table':f.m2m_db_table()})
978+    return output
979 
980+def get_trigger_name(table):
981+    name_length = get_max_name_length() - 3
982+    return '%s_TR' % util.truncate_name(table, name_length).upper()
983+
984+def get_query_set_class(DefaultQuerySet):
985+    "Create a custom QuerySet class for Oracle."
986+
987+    from django.db import backend, connection
988+    from django.db.models.query import EmptyResultSet, GET_ITERATOR_CHUNK_SIZE, quote_only_if_word
989+
990+    class OracleQuerySet(DefaultQuerySet):
991+
992+        def iterator(self):
993+            "Performs the SELECT database lookup of this QuerySet."
994+
995+            from django.db.models.query import get_cached_row
996+
997+            # self._select is a dictionary, and dictionaries' key order is
998+            # undefined, so we convert it to a list of tuples.
999+            extra_select = self._select.items()
1000+
1001+            full_query = None
1002+
1003+            try:
1004+                try:
1005+                    select, sql, params, full_query = self._get_sql_clause(get_full_query=True)
1006+                except TypeError:
1007+                    select, sql, params = self._get_sql_clause()
1008+            except EmptyResultSet:
1009+                raise StopIteration
1010+            if not full_query:
1011+                full_query = "SELECT %s%s\n%s" % \
1012+                             ((self._distinct and "DISTINCT " or ""),
1013+                              ', '.join(select), sql)
1014+
1015+            cursor = connection.cursor()
1016+            cursor.execute(full_query, params)
1017+
1018+            fill_cache = self._select_related
1019+            fields = self.model._meta.fields
1020+            index_end = len(fields)
1021+
1022+            # so here's the logic;
1023+            # 1. retrieve each row in turn
1024+            # 2. convert NCLOBs
1025+
1026+            while 1:
1027+                rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
1028+                if not rows:
1029+                    raise StopIteration
1030+                for row in rows:
1031+                    row = self.resolve_columns(row, fields)
1032+                    if fill_cache:
1033+                        obj, index_end = get_cached_row(klass=self.model, row=row,
1034+                                                        index_start=0, max_depth=self._max_related_depth)
1035+                    else:
1036+                        obj = self.model(*row[:index_end])
1037+                    for i, k in enumerate(extra_select):
1038+                        setattr(obj, k[0], row[index_end+i])
1039+                    yield obj
1040+
1041+
1042+        def _get_sql_clause(self, get_full_query=False):
1043+            from django.db.models.query import fill_table_cache, \
1044+                handle_legacy_orderlist, orderfield2column
1045+
1046+            opts = self.model._meta
1047+
1048+            # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z.
1049+            select = ["%s.%s" % (backend.quote_name(opts.db_table), backend.quote_name(f.column)) for f in opts.fields]
1050+            tables = [quote_only_if_word(t) for t in self._tables]
1051+            joins = SortedDict()
1052+            where = self._where[:]
1053+            params = self._params[:]
1054+
1055+            # Convert self._filters into SQL.
1056+            joins2, where2, params2 = self._filters.get_sql(opts)
1057+            joins.update(joins2)
1058+            where.extend(where2)
1059+            params.extend(params2)
1060+
1061+            # Add additional tables and WHERE clauses based on select_related.
1062+            if self._select_related:
1063+                fill_table_cache(opts, select, tables, where, opts.db_table, [opts.db_table])
1064+
1065+            # Add any additional SELECTs.
1066+            if self._select:
1067+                select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select.items()])
1068+
1069+            # Start composing the body of the SQL statement.
1070+            sql = [" FROM", backend.quote_name(opts.db_table)]
1071+
1072+            # Compose the join dictionary into SQL describing the joins.
1073+            if joins:
1074+                sql.append(" ".join(["%s %s %s ON %s" % (join_type, table, alias, condition)
1075+                                for (alias, (table, join_type, condition)) in joins.items()]))
1076+
1077+            # Compose the tables clause into SQL.
1078+            if tables:
1079+                sql.append(", " + ", ".join(tables))
1080+
1081+            # Compose the where clause into SQL.
1082+            if where:
1083+                sql.append(where and "WHERE " + " AND ".join(where))
1084+
1085+            # ORDER BY clause
1086+            order_by = []
1087+            if self._order_by is not None:
1088+                ordering_to_use = self._order_by
1089+            else:
1090+                ordering_to_use = opts.ordering
1091+            for f in handle_legacy_orderlist(ordering_to_use):
1092+                if f == '?': # Special case.
1093+                    order_by.append(backend.get_random_function_sql())
1094+                else:
1095+                    if f.startswith('-'):
1096+                        col_name = f[1:]
1097+                        order = "DESC"
1098+                    else:
1099+                        col_name = f
1100+                        order = "ASC"
1101+                    if "." in col_name:
1102+                        table_prefix, col_name = col_name.split('.', 1)
1103+                        table_prefix = backend.quote_name(table_prefix) + '.'
1104+                    else:
1105+                        # Use the database table as a column prefix if it wasn't given,
1106+                        # and if the requested column isn't a custom SELECT.
1107+                        if "." not in col_name and col_name not in (self._select or ()):
1108+                            table_prefix = backend.quote_name(opts.db_table) + '.'
1109+                        else:
1110+                            table_prefix = ''
1111+                    order_by.append('%s%s %s' % (table_prefix, backend.quote_name(orderfield2column(col_name, opts)), order))
1112+            if order_by:
1113+                sql.append("ORDER BY " + ", ".join(order_by))
1114+
1115+            # Look for column name collisions in the select elements
1116+            # and fix them with an AS alias.  This allows us to do a
1117+            # SELECT * later in the paging query.
1118+            cols = [clause.split('.')[-1] for clause in select]
1119+            for index, col in enumerate(cols):
1120+                if cols.count(col) > 1:
1121+                    col = '%s%d' % (col.replace('"', ''), index)
1122+                    cols[index] = col
1123+                    select[index] = '%s AS %s' % (select[index], col)
1124+
1125+            # LIMIT and OFFSET clauses
1126+            # To support limits and offsets, Oracle requires some funky rewriting of an otherwise normal looking query.
1127+            select_clause = ",".join(select)
1128+            distinct = (self._distinct and "DISTINCT " or "")
1129+
1130+            if order_by:
1131+                order_by_clause = " OVER (ORDER BY %s )" % (", ".join(order_by))
1132+            else:
1133+                #Oracle's row_number() function always requires an order-by clause.
1134+                #So we need to define a default order-by, since none was provided.
1135+                order_by_clause = " OVER (ORDER BY %s.%s)" % \
1136+                    (backend.quote_name(opts.db_table),
1137+                    backend.quote_name(opts.fields[0].db_column or opts.fields[0].column))
1138+            # limit_and_offset_clause
1139+            if self._limit is None:
1140+                assert self._offset is None, "'offset' is not allowed without 'limit'"
1141+
1142+            if self._offset is not None:
1143+                offset = int(self._offset)
1144+            else:
1145+                offset = 0
1146+            if self._limit is not None:
1147+                limit = int(self._limit)
1148+            else:
1149+                limit = None
1150+
1151+            limit_and_offset_clause = ''
1152+            if limit is not None:
1153+                limit_and_offset_clause = "WHERE rn > %s AND rn <= %s" % (offset, limit+offset)
1154+            elif offset:
1155+                limit_and_offset_clause = "WHERE rn > %s" % (offset)
1156+
1157+            if len(limit_and_offset_clause) > 0:
1158+                fmt = \
1159+"""SELECT * FROM
1160+  (SELECT %s%s,
1161+          ROW_NUMBER()%s AS rn
1162+   %s)
1163+%s"""
1164+                full_query = fmt % (distinct, select_clause,
1165+                                    order_by_clause, ' '.join(sql).strip(),
1166+                                    limit_and_offset_clause)
1167+            else:
1168+                full_query = None
1169+
1170+            if get_full_query:
1171+                return select, " ".join(sql), params, full_query
1172+            else:
1173+                return select, " ".join(sql), params
1174+
1175+        def resolve_columns(self, row, fields=()):
1176+            from django.db.models.fields import DateField, DateTimeField, \
1177+                TimeField, BooleanField, NullBooleanField, DecimalField
1178+            values = []
1179+            for value, field in map(None, row, fields):
1180+                if isinstance(value, Database.LOB):
1181+                    value = value.read()
1182+                # Oracle stores empty strings as null. We need to undo this in
1183+                # order to adhere to the Django convention of using the empty
1184+                # string instead of null, but only if the field accepts the
1185+                # empty string.
1186+                if value is None and field.empty_strings_allowed:
1187+                    value = ''
1188+                # Convert 1 or 0 to True or False
1189+                elif value in (1, 0) and isinstance(field, (BooleanField, NullBooleanField)):
1190+                    value = bool(value)
1191+                # Convert floats to decimals
1192+                elif value is not None and isinstance(field, DecimalField):
1193+                    value = util.typecast_decimal(field.format_number(value))
1194+                # cx_Oracle always returns datetime.datetime objects for
1195+                # DATE and TIMESTAMP columns, but Django wants to see a
1196+                # python datetime.date, .time, or .datetime.  We use the type
1197+                # of the Field to determine which to cast to, but it's not
1198+                # always available.
1199+                # As a workaround, we cast to date if all the time-related
1200+                # values are 0, or to time if the date is 1/1/1900.
1201+                # This could be cleaned a bit by adding a method to the Field
1202+                # classes to normalize values from the database (the to_python
1203+                # method is used for validation and isn't what we want here).
1204+                elif isinstance(value, Database.Timestamp):
1205+                    # In Python 2.3, the cx_Oracle driver returns its own
1206+                    # Timestamp object that we must convert to a datetime class.
1207+                    if not isinstance(value, datetime.datetime):
1208+                        value = datetime.datetime(value.year, value.month, value.day, value.hour,
1209+                                                  value.minute, value.second, value.fsecond)
1210+                    if isinstance(field, DateTimeField):
1211+                        pass  # DateTimeField subclasses DateField so must be checked first.
1212+                    elif isinstance(field, DateField):
1213+                        value = value.date()
1214+                    elif isinstance(field, TimeField) or (value.year == 1900 and value.month == value.day == 1):
1215+                        value = value.time()
1216+                    elif value.hour == value.minute == value.second == value.microsecond == 0:
1217+                        value = value.date()
1218+                values.append(value)
1219+            return values
1220+
1221+    return OracleQuerySet
1222+
1223+
1224 OPERATOR_MAPPING = {
1225     'exact': '= %s',
1226-    'iexact': 'LIKE %s',
1227-    'contains': 'LIKE %s',
1228-    'icontains': 'LIKE %s',
1229+    'iexact': '= UPPER(%s)',
1230+    'contains': "LIKE %s ESCAPE '\\'",
1231+    'icontains': "LIKE UPPER(%s) ESCAPE '\\'",
1232     'gt': '> %s',
1233     'gte': '>= %s',
1234     'lt': '< %s',
1235     'lte': '<= %s',
1236-    'startswith': 'LIKE %s',
1237-    'endswith': 'LIKE %s',
1238-    'istartswith': 'LIKE %s',
1239-    'iendswith': 'LIKE %s',
1240+    'startswith': "LIKE %s ESCAPE '\\'",
1241+    'endswith': "LIKE %s ESCAPE '\\'",
1242+    'istartswith': "LIKE UPPER(%s) ESCAPE '\\'",
1243+    'iendswith': "LIKE UPPER(%s) ESCAPE '\\'",
1244 }
1245Index: django/db/backends/oracle/client.py
1246===================================================================
1247--- django/db/backends/oracle/client.py (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
1248+++ django/db/backends/oracle/client.py (working copy)
1249@@ -2,9 +2,10 @@
1250 import os
1251 
1252 def runshell():
1253-    args = ''
1254-    args += settings.DATABASE_USER
1255+    dsn = settings.DATABASE_USER
1256     if settings.DATABASE_PASSWORD:
1257-        args += "/%s" % settings.DATABASE_PASSWORD
1258-    args += "@%s" % settings.DATABASE_NAME
1259-    os.execvp('sqlplus', args)
1260+        dsn += "/%s" % settings.DATABASE_PASSWORD
1261+    if settings.DATABASE_NAME:
1262+        dsn += "@%s" % settings.DATABASE_NAME
1263+    args = ["sqlplus", "-L", dsn]
1264+    os.execvp("sqlplus", args)
1265Index: django/db/backends/oracle/introspection.py
1266===================================================================
1267--- django/db/backends/oracle/introspection.py  (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
1268+++ django/db/backends/oracle/introspection.py  (working copy)
1269@@ -1,14 +1,19 @@
1270+from django.db.backends.oracle.base import quote_name
1271 import re
1272+import cx_Oracle
1273 
1274+
1275 foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)")
1276 
1277 def get_table_list(cursor):
1278     "Returns a list of table names in the current database."
1279     cursor.execute("SELECT TABLE_NAME FROM USER_TABLES")
1280-    return [row[0] for row in cursor.fetchall()]
1281+    return [row[0].upper() for row in cursor.fetchall()]
1282 
1283 def get_table_description(cursor, table_name):
1284-    return table_name
1285+    "Returns a description of the table, with the DB-API cursor.description interface."
1286+    cursor.execute("SELECT * FROM %s WHERE ROWNUM < 2" % quote_name(table_name))
1287+    return cursor.description
1288 
1289 def _name_to_index(cursor, table_name):
1290     """
1291@@ -22,8 +27,25 @@
1292     Returns a dictionary of {field_index: (field_index_other_table, other_table)}
1293     representing all relationships to the given table. Indexes are 0-based.
1294     """
1295-    raise NotImplementedError
1296+    cursor.execute("""
1297+SELECT ta.column_id - 1, tb.table_name, tb.column_id - 1
1298+FROM   user_constraints, USER_CONS_COLUMNS ca, USER_CONS_COLUMNS cb,
1299+       user_tab_cols ta, user_tab_cols tb
1300+WHERE  user_constraints.table_name = %s AND
1301+       ta.table_name = %s AND
1302+       ta.column_name = ca.column_name AND
1303+       ca.table_name = %s AND
1304+       user_constraints.constraint_name = ca.constraint_name AND
1305+       user_constraints.r_constraint_name = cb.constraint_name AND
1306+       cb.table_name = tb.table_name AND
1307+       cb.column_name = tb.column_name AND
1308+       ca.position = cb.position""", [table_name, table_name, table_name])
1309 
1310+    relations = {}
1311+    for row in cursor.fetchall():
1312+        relations[row[0]] = (row[2], row[1])
1313+    return relations
1314+
1315 def get_indexes(cursor, table_name):
1316     """
1317     Returns a dictionary of fieldname -> infodict for the given table,
1318@@ -31,20 +53,46 @@
1319         {'primary_key': boolean representing whether it's the primary key,
1320          'unique': boolean representing whether it's a unique index}
1321     """
1322-    raise NotImplementedError
1323+    # This query retrieves each index on the given table, including the
1324+    # first associated field name
1325+    # "We were in the nick of time; you were in great peril!"
1326+    sql = """
1327+WITH primarycols AS (
1328+ SELECT user_cons_columns.table_name, user_cons_columns.column_name, 1 AS PRIMARYCOL
1329+ FROM   user_cons_columns, user_constraints
1330+ WHERE  user_cons_columns.constraint_name = user_constraints.constraint_name AND
1331+        user_constraints.constraint_type = 'P' AND
1332+        user_cons_columns.table_name = %s),
1333+ uniquecols AS (
1334+ SELECT user_ind_columns.table_name, user_ind_columns.column_name, 1 AS UNIQUECOL
1335+ FROM   user_indexes, user_ind_columns
1336+ WHERE  uniqueness = 'UNIQUE' AND
1337+        user_indexes.index_name = user_ind_columns.index_name AND
1338+        user_ind_columns.table_name = %s)
1339+SELECT allcols.column_name, primarycols.primarycol, uniquecols.UNIQUECOL
1340+FROM   (SELECT column_name FROM primarycols UNION SELECT column_name FROM
1341+uniquecols) allcols,
1342+      primarycols, uniquecols
1343+WHERE  allcols.column_name = primarycols.column_name (+) AND
1344+      allcols.column_name = uniquecols.column_name (+)
1345+    """
1346+    cursor.execute(sql, [table_name, table_name])
1347+    indexes = {}
1348+    for row in cursor.fetchall():
1349+        # row[1] (idx.indkey) is stored in the DB as an array. It comes out as
1350+        # a string of space-separated integers. This designates the field
1351+        # indexes (1-based) of the fields that have indexes on the table.
1352+        # Here, we skip any indexes across multiple fields.
1353+        indexes[row[0]] = {'primary_key': row[1], 'unique': row[2]}
1354+    return indexes
1355 
1356-# Maps type codes to Django Field types.
1357+# Maps type objects to Django Field types.
1358 DATA_TYPES_REVERSE = {
1359-    16: 'BooleanField',
1360-    21: 'SmallIntegerField',
1361-    23: 'IntegerField',
1362-    25: 'TextField',
1363-    869: 'IPAddressField',
1364-    1043: 'CharField',
1365-    1082: 'DateField',
1366-    1083: 'TimeField',
1367-    1114: 'DateTimeField',
1368-    1184: 'DateTimeField',
1369-    1266: 'TimeField',
1370-    1700: 'DecimalField',
1371+    cx_Oracle.CLOB: 'TextField',
1372+    cx_Oracle.DATETIME: 'DateTimeField',
1373+    cx_Oracle.FIXED_CHAR: 'CharField',
1374+    cx_Oracle.NCLOB: 'TextField',
1375+    cx_Oracle.NUMBER: 'DecimalField',
1376+    cx_Oracle.STRING: 'CharField',
1377+    cx_Oracle.TIMESTAMP: 'DateTimeField',
1378 }
1379Index: django/db/backends/oracle/creation.py
1380===================================================================
1381--- django/db/backends/oracle/creation.py       (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
1382+++ django/db/backends/oracle/creation.py       (working copy)
1383@@ -1,26 +1,304 @@
1384+import sys, time
1385+from django.core import management
1386+
1387+# This dictionary maps Field objects to their associated Oracle column
1388+# types, as strings. Column-type strings can contain format strings; they'll
1389+# be interpolated against the values of Field.__dict__ before being output.
1390+# If a column type is set to None, it won't be included in the output.
1391 DATA_TYPES = {
1392-    'AutoField':         'number(38)',
1393-    'BooleanField':      'number(1)',
1394-    'CharField':         'varchar2(%(maxlength)s)',
1395-    'CommaSeparatedIntegerField': 'varchar2(%(maxlength)s)',
1396-    'DateField':         'date',
1397-    'DateTimeField':     'date',
1398-    'DecimalField':      'number(%(max_digits)s, %(decimal_places)s)',
1399-    'FileField':         'varchar2(100)',
1400-    'FilePathField':     'varchar2(100)',
1401-    'FloatField':        'double precision',
1402-    'ImageField':        'varchar2(100)',
1403-    'IntegerField':      'integer',
1404-    'IPAddressField':    'char(15)',
1405+    'AutoField':                    'NUMBER(11)',
1406+    'BooleanField':                 'NUMBER(1) CHECK (%(column)s IN (0,1))',
1407+    'CharField':                    'VARCHAR2(%(maxlength)s)',
1408+    'CommaSeparatedIntegerField':   'VARCHAR2(%(maxlength)s)',
1409+    'DateField':                    'DATE',
1410+    'DateTimeField':                'TIMESTAMP',
1411+    'DecimalField':                 'NUMBER(%(max_digits)s, %(decimal_places)s)',
1412+    'FileField':                    'VARCHAR2(100)',
1413+    'FilePathField':                'VARCHAR2(100)',
1414+    'FloatField':                   'DOUBLE PRECISION',
1415+    'ImageField':                   'VARCHAR2(100)',
1416+    'IntegerField':                 'NUMBER(11)',
1417+    'IPAddressField':               'VARCHAR2(15)',
1418     'ManyToManyField':   None,
1419-    'NullBooleanField':  'integer',
1420-    'OneToOneField':     'integer',
1421-    'PhoneNumberField':  'varchar(20)',
1422-    'PositiveIntegerField': 'integer',
1423-    'PositiveSmallIntegerField': 'smallint',
1424-    'SlugField':         'varchar(50)',
1425-    'SmallIntegerField': 'smallint',
1426-    'TextField':         'long',
1427-    'TimeField':         'timestamp',
1428-    'USStateField':      'varchar(2)',
1429+    'NullBooleanField':             'NUMBER(1) CHECK ((%(column)s IN (0,1)) OR (%(column)s IS NULL))',
1430+    'OneToOneField':                'NUMBER(11)',
1431+    'PhoneNumberField':             'VARCHAR2(20)',
1432+    'PositiveIntegerField':         'NUMBER(11) CHECK (%(column)s >= 0)',
1433+    'PositiveSmallIntegerField':    'NUMBER(11) CHECK (%(column)s >= 0)',
1434+    'SlugField':                    'VARCHAR2(50)',
1435+    'SmallIntegerField':            'NUMBER(11)',
1436+    'TextField':                    'NCLOB',
1437+    'TimeField':                    'TIMESTAMP',
1438+    'URLField':                     'VARCHAR2(200)',
1439+    'USStateField':                 'CHAR(2)',
1440 }
1441+
1442+TEST_DATABASE_PREFIX = 'test_'
1443+PASSWORD = 'Im_a_lumberjack'
1444+REMEMBER = {}
1445+
1446+
1447+def create_test_db(settings, connection, backend, verbosity=1, autoclobber=False):
1448+
1449+    TEST_DATABASE_NAME = _test_database_name(settings)
1450+    TEST_DATABASE_USER = _test_database_user(settings)
1451+    TEST_DATABASE_PASSWD = _test_database_passwd(settings)
1452+    TEST_DATABASE_TBLSPACE = _test_database_tblspace(settings)
1453+    TEST_DATABASE_TBLSPACE_TMP = _test_database_tblspace_tmp(settings)
1454+
1455+    parameters = {
1456+        'dbname': TEST_DATABASE_NAME,
1457+        'user': TEST_DATABASE_USER,
1458+        'password': TEST_DATABASE_PASSWD,
1459+        'tblspace': TEST_DATABASE_TBLSPACE,
1460+        'tblspace_temp': TEST_DATABASE_TBLSPACE_TMP,
1461+       }
1462+
1463+    REMEMBER['user'] = settings.DATABASE_USER
1464+    REMEMBER['passwd'] = settings.DATABASE_PASSWORD
1465+
1466+    cursor = connection.cursor()
1467+    if _test_database_create(settings):
1468+        if verbosity >= 1:
1469+            print 'Creating test database...'
1470+        try:
1471+            _create_test_db(cursor, parameters, verbosity)
1472+        except Exception, e:
1473+            sys.stderr.write("Got an error creating the test database: %s\n" % e)
1474+            if not autoclobber:
1475+                confirm = raw_input("It appears the test database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_NAME)
1476+            if autoclobber or confirm == 'yes':
1477+                try:
1478+                    if verbosity >= 1:
1479+                        print "Destroying old test database..."
1480+                    _destroy_test_db(cursor, parameters, verbosity)
1481+                    if verbosity >= 1:
1482+                        print "Creating test database..."
1483+                    _create_test_db(cursor, parameters, verbosity)
1484+                except Exception, e:
1485+                    sys.stderr.write("Got an error recreating the test database: %s\n" % e)
1486+                    sys.exit(2)
1487+            else:
1488+                print "Tests cancelled."
1489+                sys.exit(1)
1490+
1491+    if _test_user_create(settings):
1492+        if verbosity >= 1:
1493+            print "Creating test user..."
1494+        try:
1495+            _create_test_user(cursor, parameters, verbosity)
1496+        except Exception, e:
1497+            sys.stderr.write("Got an error creating the test user: %s\n" % e)
1498+            if not autoclobber:
1499+                confirm = raw_input("It appears the test user, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_USER)
1500+            if autoclobber or confirm == 'yes':
1501+                try:
1502+                    if verbosity >= 1:
1503+                        print "Destroying old test user..."
1504+                    _destroy_test_user(cursor, parameters, verbosity)
1505+                    if verbosity >= 1:
1506+                        print "Creating test user..."
1507+                    _create_test_user(cursor, parameters, verbosity)
1508+                except Exception, e:
1509+                    sys.stderr.write("Got an error recreating the test user: %s\n" % e)
1510+                    sys.exit(2)
1511+            else:
1512+                print "Tests cancelled."
1513+                sys.exit(1)
1514+
1515+    connection.close()
1516+    settings.DATABASE_USER = TEST_DATABASE_USER
1517+    settings.DATABASE_PASSWORD = TEST_DATABASE_PASSWD
1518+
1519+    management.syncdb(verbosity, interactive=False)
1520+
1521+    # Get a cursor (even though we don't need one yet). This has
1522+    # the side effect of initializing the test database.
1523+    cursor = connection.cursor()
1524+
1525+
1526+def destroy_test_db(settings, connection, backend, old_database_name, verbosity=1):
1527+    connection.close()
1528+
1529+    TEST_DATABASE_NAME = _test_database_name(settings)
1530+    TEST_DATABASE_USER = _test_database_user(settings)
1531+    TEST_DATABASE_PASSWD = _test_database_passwd(settings)
1532+    TEST_DATABASE_TBLSPACE = _test_database_tblspace(settings)
1533+    TEST_DATABASE_TBLSPACE_TMP = _test_database_tblspace_tmp(settings)
1534+
1535+    settings.DATABASE_NAME = old_database_name
1536+    settings.DATABASE_USER = REMEMBER['user']
1537+    settings.DATABASE_PASSWORD = REMEMBER['passwd']
1538+
1539+    parameters = {
1540+        'dbname': TEST_DATABASE_NAME,
1541+        'user': TEST_DATABASE_USER,
1542+        'password': TEST_DATABASE_PASSWD,
1543+        'tblspace': TEST_DATABASE_TBLSPACE,
1544+        'tblspace_temp': TEST_DATABASE_TBLSPACE_TMP,
1545+       }
1546+
1547+    REMEMBER['user'] = settings.DATABASE_USER
1548+    REMEMBER['passwd'] = settings.DATABASE_PASSWORD
1549+
1550+    cursor = connection.cursor()
1551+    time.sleep(1) # To avoid "database is being accessed by other users" errors.
1552+    if _test_user_create(settings):
1553+        if verbosity >= 1:
1554+            print 'Destroying test user...'
1555+        _destroy_test_user(cursor, parameters, verbosity)
1556+    if _test_database_create(settings):
1557+        if verbosity >= 1:
1558+            print 'Destroying test database...'
1559+        _destroy_test_db(cursor, parameters, verbosity)
1560+    connection.close()
1561+
1562+
1563+def _create_test_db(cursor, parameters, verbosity):
1564+    if verbosity >= 2:
1565+        print "_create_test_db(): dbname = %s" % parameters['dbname']
1566+    statements = [
1567+        """CREATE TABLESPACE %(tblspace)s
1568+           DATAFILE '%(tblspace)s.dbf' SIZE 20M
1569+           REUSE AUTOEXTEND ON NEXT 10M MAXSIZE 100M
1570+        """,
1571+        """CREATE TEMPORARY TABLESPACE %(tblspace_temp)s
1572+           TEMPFILE '%(tblspace_temp)s.dbf' SIZE 20M
1573+           REUSE AUTOEXTEND ON NEXT 10M MAXSIZE 100M
1574+        """,
1575+    ]
1576+    _execute_statements(cursor, statements, parameters, verbosity)
1577+
1578+
1579+def _create_test_user(cursor, parameters, verbosity):
1580+    if verbosity >= 2:
1581+        print "_create_test_user(): username = %s" % parameters['user']
1582+    statements = [
1583+        """CREATE USER %(user)s
1584+           IDENTIFIED BY %(password)s
1585+           DEFAULT TABLESPACE %(tblspace)s
1586+           TEMPORARY TABLESPACE %(tblspace_temp)s
1587+        """,
1588+        """GRANT CONNECT, RESOURCE TO %(user)s""",
1589+    ]
1590+    _execute_statements(cursor, statements, parameters, verbosity)
1591+
1592+
1593+def _destroy_test_db(cursor, parameters, verbosity):
1594+    if verbosity >= 2:
1595+        print "_destroy_test_db(): dbname=%s" % parameters['dbname']
1596+    statements = [
1597+        'DROP TABLESPACE %(tblspace)s INCLUDING CONTENTS AND DATAFILES CASCADE CONSTRAINTS',
1598+        'DROP TABLESPACE %(tblspace_temp)s INCLUDING CONTENTS AND DATAFILES CASCADE CONSTRAINTS',
1599+        ]
1600+    _execute_statements(cursor, statements, parameters, verbosity)
1601+
1602+
1603+def _destroy_test_user(cursor, parameters, verbosity):
1604+    if verbosity >= 2:
1605+        print "_destroy_test_user(): user=%s" % parameters['user']
1606+        print "Be patient.  This can take some time..."
1607+    statements = [
1608+        'DROP USER %(user)s CASCADE',
1609+    ]
1610+    _execute_statements(cursor, statements, parameters, verbosity)
1611+
1612+
1613+def _execute_statements(cursor, statements, parameters, verbosity):
1614+    for template in statements:
1615+        stmt = template % parameters
1616+        if verbosity >= 2:
1617+            print stmt
1618+        try:
1619+            cursor.execute(stmt)
1620+        except Exception, err:
1621+            sys.stderr.write("Failed (%s)\n" % (err))
1622+            raise
1623+
1624+
1625+def _test_database_name(settings):
1626+    name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
1627+    try:
1628+        if settings.TEST_DATABASE_NAME:
1629+            name = settings.TEST_DATABASE_NAME
1630+    except AttributeError:
1631+        pass
1632+    except:
1633+        raise
1634+    return name
1635+
1636+
1637+def _test_database_create(settings):
1638+    name = True
1639+    try:
1640+        if settings.TEST_DATABASE_CREATE:
1641+            name = True
1642+        else:
1643+            name = False
1644+    except AttributeError:
1645+        pass
1646+    except:
1647+        raise
1648+    return name
1649+
1650+
1651+def _test_user_create(settings):
1652+    name = True
1653+    try:
1654+        if settings.TEST_USER_CREATE:
1655+            name = True
1656+        else:
1657+            name = False
1658+    except AttributeError:
1659+        pass
1660+    except:
1661+        raise
1662+    return name
1663+
1664+
1665+def _test_database_user(settings):
1666+    name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
1667+    try:
1668+        if settings.TEST_DATABASE_USER:
1669+            name = settings.TEST_DATABASE_USER
1670+    except AttributeError:
1671+        pass
1672+    except:
1673+        raise
1674+    return name
1675+
1676+
1677+def _test_database_passwd(settings):
1678+    name = PASSWORD
1679+    try:
1680+        if settings.TEST_DATABASE_PASSWD:
1681+            name = settings.TEST_DATABASE_PASSWD
1682+    except AttributeError:
1683+        pass
1684+    except:
1685+        raise
1686+    return name
1687+
1688+
1689+def _test_database_tblspace(settings):
1690+    name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
1691+    try:
1692+        if settings.TEST_DATABASE_TBLSPACE:
1693+            name = settings.TEST_DATABASE_TBLSPACE
1694+    except AttributeError:
1695+        pass
1696+    except:
1697+        raise
1698+    return name
1699+
1700+
1701+def _test_database_tblspace_tmp(settings):
1702+    name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME + '_temp'
1703+    try:
1704+        if settings.TEST_DATABASE_TBLSPACE_TMP:
1705+            name = settings.TEST_DATABASE_TBLSPACE_TMP
1706+    except AttributeError:
1707+        pass
1708+    except:
1709+        raise
1710+    return name
1711Index: django/db/backends/postgresql_psycopg2/base.py
1712===================================================================
1713--- django/db/backends/postgresql_psycopg2/base.py      (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
1714+++ django/db/backends/postgresql_psycopg2/base.py      (working copy)
1715@@ -55,7 +55,7 @@
1716         global postgres_version
1717         if not postgres_version:
1718             cursor.execute("SELECT version()")
1719-            postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')]       
1720+            postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')]
1721         if settings.DEBUG:
1722             return util.CursorDebugWrapper(cursor, self)
1723         return cursor
1724@@ -73,7 +73,14 @@
1725             self.connection.close()
1726             self.connection = None
1727 
1728+allows_group_by_ordinal = True
1729+allows_unique_and_pk = True
1730+autoindexes_primary_keys = True
1731+needs_datetime_string_cast = False
1732+needs_upper_for_iops = False
1733 supports_constraints = True
1734+supports_tablespaces = False
1735+uses_case_insensitive_names = True
1736 
1737 def quote_name(name):
1738     if name.startswith('"') and name.endswith('"'):
1739@@ -98,6 +105,9 @@
1740     # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
1741     return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name)
1742 
1743+def get_datetime_cast_sql():
1744+    return None
1745+
1746 def get_limit_offset_sql(limit, offset=None):
1747     sql = "LIMIT %s" % limit
1748     if offset and offset != 0:
1749@@ -119,6 +129,15 @@
1750 def get_pk_default_value():
1751     return "DEFAULT"
1752 
1753+def get_max_name_length():
1754+    return None
1755+
1756+def get_start_transaction_sql():
1757+    return "BEGIN;"
1758+
1759+def get_autoinc_sql(table):
1760+    return None
1761+
1762 def get_sql_flush(style, tables, sequences):
1763     """Return a list of SQL statements required to remove all data from
1764     all tables in the database (without actually removing the tables
1765@@ -139,7 +158,7 @@
1766                      style.SQL_KEYWORD('FROM'),
1767                      style.SQL_FIELD(quote_name(table))
1768                      ) for table in tables]
1769-                     
1770+
1771         # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements
1772         # to reset sequence indices
1773         for sequence in sequences:
1774@@ -195,7 +214,7 @@
1775                 style.SQL_KEYWORD('FROM'),
1776                 style.SQL_TABLE(f.m2m_db_table())))
1777     return output
1778-       
1779+
1780 OPERATOR_MAPPING = {
1781     'exact': '= %s',
1782     'iexact': 'ILIKE %s',
1783Index: django/db/backends/dummy/base.py
1784===================================================================
1785--- django/db/backends/dummy/base.py    (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
1786+++ django/db/backends/dummy/base.py    (working copy)
1787@@ -30,6 +30,7 @@
1788         pass # close()
1789 
1790 supports_constraints = False
1791+supports_tablespaces = False
1792 quote_name = complain
1793 dictfetchone = complain
1794 dictfetchmany = complain
1795Index: django/core/management.py
1796===================================================================
1797--- django/core/management.py   (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
1798+++ django/core/management.py   (working copy)
1799@@ -55,12 +55,16 @@
1800 
1801 def _get_installed_models(table_list):
1802     "Gets a set of all models that are installed, given a list of existing tables"
1803-    from django.db import models
1804+    from django.db import backend, models
1805     all_models = []
1806     for app in models.get_apps():
1807         for model in models.get_models(app):
1808             all_models.append(model)
1809-    return set([m for m in all_models if m._meta.db_table in table_list])
1810+    if backend.uses_case_insensitive_names:
1811+        converter = str.upper
1812+    else:
1813+        converter = lambda x: x
1814+    return set([m for m in all_models if converter(m._meta.db_table) in map(converter, table_list)])
1815 
1816 def _get_table_list():
1817     "Gets a list of all db tables that are physically installed."
1818@@ -104,6 +108,7 @@
1819 def get_sql_create(app):
1820     "Returns a list of the CREATE TABLE SQL statements for the given app."
1821     from django.db import get_creation_module, models
1822+
1823     data_types = get_creation_module().DATA_TYPES
1824 
1825     if not data_types:
1826@@ -175,15 +180,20 @@
1827             rel_field = f
1828             data_type = f.get_internal_type()
1829         col_type = data_types[data_type]
1830+        tablespace = f.db_tablespace or opts.db_tablespace
1831         if col_type is not None:
1832             # Make the definition (e.g. 'foo VARCHAR(30)') for this field.
1833             field_output = [style.SQL_FIELD(backend.quote_name(f.column)),
1834                 style.SQL_COLTYPE(col_type % rel_field.__dict__)]
1835             field_output.append(style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or '')))
1836-            if f.unique:
1837+            if f.unique and (not f.primary_key or backend.allows_unique_and_pk):
1838                 field_output.append(style.SQL_KEYWORD('UNIQUE'))
1839             if f.primary_key:
1840                 field_output.append(style.SQL_KEYWORD('PRIMARY KEY'))
1841+            if tablespace and backend.supports_tablespaces and (f.unique or f.primary_key) and backend.autoindexes_primary_keys:
1842+                # We must specify the index tablespace inline, because we
1843+                # won't be generating a CREATE INDEX statement for this field.
1844+                field_output.append(backend.get_tablespace_sql(tablespace, inline=True))
1845             if f.rel:
1846                 if f.rel.to in known_models:
1847                     field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \
1848@@ -207,9 +217,19 @@
1849     full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(backend.quote_name(opts.db_table)) + ' (']
1850     for i, line in enumerate(table_output): # Combine and add commas.
1851         full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
1852-    full_statement.append(');')
1853+    full_statement.append(')')
1854+    if opts.db_tablespace and backend.supports_tablespaces:
1855+        full_statement.append(backend.get_tablespace_sql(opts.db_tablespace))
1856+    full_statement.append(';')
1857     final_output.append('\n'.join(full_statement))
1858 
1859+    if opts.has_auto_field and hasattr(backend, 'get_autoinc_sql'):
1860+        # Add any extra SQL needed to support auto-incrementing primary keys
1861+        autoinc_sql = backend.get_autoinc_sql(opts.db_table)
1862+        if autoinc_sql:
1863+            for stmt in autoinc_sql:
1864+                final_output.append(stmt)
1865+
1866     return final_output, pending_references
1867 
1868 def _get_sql_for_pending_references(model, pending_references):
1869@@ -217,6 +237,7 @@
1870     Get any ALTER TABLE statements to add constraints after the fact.
1871     """
1872     from django.db import backend, get_creation_module
1873+    from django.db.backends.util import truncate_name
1874     data_types = get_creation_module().DATA_TYPES
1875 
1876     final_output = []
1877@@ -229,11 +250,9 @@
1878                 r_col = f.column
1879                 table = opts.db_table
1880                 col = opts.get_field(f.rel.field_name).column
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+                r_name = '%s_refs_%s_%s_%s' % (r_col, col, r_table, table)
1885                 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
1886-                    (backend.quote_name(r_table), r_name,
1887+                    (backend.quote_name(r_table), truncate_name(r_name, backend.get_max_name_length()),
1888                     backend.quote_name(r_col), backend.quote_name(table), backend.quote_name(col),
1889                     backend.get_deferrable_sql()))
1890             del pending_references[model]
1891@@ -249,12 +268,18 @@
1892     final_output = []
1893     for f in opts.many_to_many:
1894         if not isinstance(f.rel, generic.GenericRel):
1895+            tablespace = f.db_tablespace or opts.db_tablespace
1896+            if tablespace and backend.supports_tablespaces and backend.autoindexes_primary_keys:
1897+                tablespace_sql = ' ' + backend.get_tablespace_sql(tablespace, inline=True)
1898+            else:
1899+                tablespace_sql = ''
1900             table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
1901                 style.SQL_TABLE(backend.quote_name(f.m2m_db_table())) + ' (']
1902-            table_output.append('    %s %s %s,' % \
1903+            table_output.append('    %s %s %s%s,' % \
1904                 (style.SQL_FIELD(backend.quote_name('id')),
1905                 style.SQL_COLTYPE(data_types['AutoField']),
1906-                style.SQL_KEYWORD('NOT NULL PRIMARY KEY')))
1907+                style.SQL_KEYWORD('NOT NULL PRIMARY KEY'),
1908+                tablespace_sql))
1909             table_output.append('    %s %s %s %s (%s)%s,' % \
1910                 (style.SQL_FIELD(backend.quote_name(f.m2m_column_name())),
1911                 style.SQL_COLTYPE(data_types[get_rel_data_type(opts.pk)] % opts.pk.__dict__),
1912@@ -269,17 +294,30 @@
1913                 style.SQL_TABLE(backend.quote_name(f.rel.to._meta.db_table)),
1914                 style.SQL_FIELD(backend.quote_name(f.rel.to._meta.pk.column)),
1915                 backend.get_deferrable_sql()))
1916-            table_output.append('    %s (%s, %s)' % \
1917+            table_output.append('    %s (%s, %s)%s' % \
1918                 (style.SQL_KEYWORD('UNIQUE'),
1919                 style.SQL_FIELD(backend.quote_name(f.m2m_column_name())),
1920-                style.SQL_FIELD(backend.quote_name(f.m2m_reverse_name()))))
1921-            table_output.append(');')
1922+                style.SQL_FIELD(backend.quote_name(f.m2m_reverse_name())),
1923+                tablespace_sql))
1924+            table_output.append(')')
1925+            if opts.db_tablespace and backend.supports_tablespaces:
1926+                # f.db_tablespace is only for indices, so ignore its value here.
1927+                table_output.append(backend.get_tablespace_sql(opts.db_tablespace))
1928+            table_output.append(';')
1929             final_output.append('\n'.join(table_output))
1930+
1931+            # Add any extra SQL needed to support auto-incrementing PKs
1932+            autoinc_sql = backend.get_autoinc_sql(f.m2m_db_table())
1933+            if autoinc_sql:
1934+                for stmt in autoinc_sql:
1935+                    final_output.append(stmt)
1936+
1937     return final_output
1938 
1939 def get_sql_delete(app):
1940     "Returns a list of the DROP TABLE SQL statements for the given app."
1941     from django.db import backend, connection, models, get_introspection_module
1942+    from django.db.backends.util import truncate_name
1943     introspection = get_introspection_module()
1944 
1945     # This should work even if a connection isn't available
1946@@ -293,6 +331,10 @@
1947         table_names = introspection.get_table_list(cursor)
1948     else:
1949         table_names = []
1950+    if backend.uses_case_insensitive_names:
1951+        table_name_converter = str.upper
1952+    else:
1953+        table_name_converter = lambda x: x
1954 
1955     output = []
1956 
1957@@ -302,7 +344,7 @@
1958     references_to_delete = {}
1959     app_models = models.get_models(app)
1960     for model in app_models:
1961-        if cursor and model._meta.db_table in table_names:
1962+        if cursor and table_name_converter(model._meta.db_table) in table_names:
1963             # The table exists, so it needs to be dropped
1964             opts = model._meta
1965             for f in opts.fields:
1966@@ -312,7 +354,7 @@
1967             to_delete.add(model)
1968 
1969     for model in app_models:
1970-        if cursor and model._meta.db_table in table_names:
1971+        if cursor and table_name_converter(model._meta.db_table) in table_names:
1972             # Drop the table now
1973             output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
1974                 style.SQL_TABLE(backend.quote_name(model._meta.db_table))))
1975@@ -322,21 +364,27 @@
1976                     col = f.column
1977                     r_table = model._meta.db_table
1978                     r_col = model._meta.get_field(f.rel.field_name).column
1979+                    r_name = '%s_refs_%s_%s_%s' % (col, r_col, table, r_table)
1980                     output.append('%s %s %s %s;' % \
1981                         (style.SQL_KEYWORD('ALTER TABLE'),
1982                         style.SQL_TABLE(backend.quote_name(table)),
1983                         style.SQL_KEYWORD(backend.get_drop_foreignkey_sql()),
1984-                        style.SQL_FIELD(backend.quote_name('%s_refs_%s_%x' % (col, r_col, abs(hash((table, r_table))))))))
1985+                        style.SQL_FIELD(truncate_name(r_name, backend.get_max_name_length()))))
1986                 del references_to_delete[model]
1987+            if model._meta.has_auto_field and hasattr(backend, 'get_drop_sequence'):
1988+                output.append(backend.get_drop_sequence(model._meta.db_table))
1989 
1990     # Output DROP TABLE statements for many-to-many tables.
1991     for model in app_models:
1992         opts = model._meta
1993         for f in opts.many_to_many:
1994-            if cursor and f.m2m_db_table() in table_names:
1995+            if cursor and table_name_converter(f.m2m_db_table()) in table_names:
1996                 output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'),
1997                     style.SQL_TABLE(backend.quote_name(f.m2m_db_table()))))
1998+                if hasattr(backend, 'get_drop_sequence'):
1999+                    output.append(backend.get_drop_sequence("%s_%s" % (model._meta.db_table, f.column)))
2000 
2001+
2002     app_label = app_models[0]._meta.app_label
2003 
2004     # Close database connection explicitly, in case this output is being piped
2005@@ -431,17 +479,24 @@
2006 def get_sql_indexes_for_model(model):
2007     "Returns the CREATE INDEX SQL statements for a single model"
2008     from django.db import backend
2009+    from django.db.backends.util import truncate_name
2010     output = []
2011 
2012     for f in model._meta.fields:
2013-        if f.db_index:
2014+        if f.db_index and not ((f.primary_key or f.unique) and backend.autoindexes_primary_keys):
2015             unique = f.unique and 'UNIQUE ' or ''
2016+            tablespace = f.db_tablespace or model._meta.db_tablespace
2017+            if tablespace and backend.supports_tablespaces:
2018+                tablespace_sql = ' ' + backend.get_tablespace_sql(tablespace)
2019+            else:
2020+                tablespace_sql = ''
2021             output.append(
2022                 style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \
2023                 style.SQL_TABLE(backend.quote_name('%s_%s' % (model._meta.db_table, f.column))) + ' ' + \
2024                 style.SQL_KEYWORD('ON') + ' ' + \
2025                 style.SQL_TABLE(backend.quote_name(model._meta.db_table)) + ' ' + \
2026-                "(%s);" % style.SQL_FIELD(backend.quote_name(f.column))
2027+                "(%s)" % style.SQL_FIELD(backend.quote_name(f.column)) + \
2028+                "%s;" % tablespace_sql
2029             )
2030     return output
2031 
2032@@ -465,7 +520,7 @@
2033 
2034 def syncdb(verbosity=1, interactive=True):
2035     "Creates the database tables for all apps in INSTALLED_APPS whose tables haven't already been created."
2036-    from django.db import connection, transaction, models, get_creation_module
2037+    from django.db import backend, connection, transaction, models, get_creation_module
2038     from django.conf import settings
2039 
2040     disable_termcolors()
2041@@ -488,6 +543,10 @@
2042     # Get a list of all existing database tables,
2043     # so we know what needs to be added.
2044     table_list = _get_table_list()
2045+    if backend.uses_case_insensitive_names:
2046+        table_name_converter = str.upper
2047+    else:
2048+        table_name_converter = lambda x: x
2049 
2050     # Get a list of already installed *models* so that references work right.
2051     seen_models = _get_installed_models(table_list)
2052@@ -502,7 +561,7 @@
2053             # Create the model's database table, if it doesn't already exist.
2054             if verbosity >= 2:
2055                 print "Processing %s.%s model" % (app_name, model._meta.object_name)
2056-            if model._meta.db_table in table_list:
2057+            if table_name_converter(model._meta.db_table) in table_list:
2058                 continue
2059             sql, references = _get_sql_model_create(model, seen_models)
2060             seen_models.add(model)
2061@@ -514,7 +573,7 @@
2062                 print "Creating table %s" % model._meta.db_table
2063             for statement in sql:
2064                 cursor.execute(statement)
2065-            table_list.append(model._meta.db_table)
2066+            table_list.append(table_name_converter(model._meta.db_table))
2067 
2068     # Create the m2m tables. This must be done after all tables have been created
2069     # to ensure that all referred tables will exist.
2070@@ -833,7 +892,7 @@
2071         except NotImplementedError:
2072             indexes = {}
2073         for i, row in enumerate(introspection_module.get_table_description(cursor, table_name)):
2074-            att_name = row[0]
2075+            att_name = row[0].lower()
2076             comment_notes = [] # Holds Field notes, to be displayed in a Python comment.
2077             extra_params = {}  # Holds Field parameters such as 'db_column'.
2078 
2079@@ -1326,7 +1385,7 @@
2080     # Keep a count of the installed objects and fixtures
2081     count = [0,0]
2082     models = set()
2083-   
2084+
2085     humanize = lambda dirname: dirname and "'%s'" % dirname or 'absolute path'
2086 
2087     # Get a cursor (even though we don't need one yet). This has
2088@@ -1630,7 +1689,9 @@
2089         if not mod_list:
2090             parser.print_usage_and_exit()
2091         if action not in NO_SQL_TRANSACTION:
2092-            print style.SQL_KEYWORD("BEGIN;")
2093+            from django.db import backend
2094+            if backend.get_start_transaction_sql():
2095+                print style.SQL_KEYWORD(backend.get_start_transaction_sql())
2096         for mod in mod_list:
2097             if action == 'reset':
2098                 output = action_mapping[action](mod, options.interactive)
2099Index: django/contrib/admin/models.py
2100===================================================================
2101--- django/contrib/admin/models.py      (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
2102+++ django/contrib/admin/models.py      (working copy)
2103@@ -9,7 +9,7 @@
2104 
2105 class LogEntryManager(models.Manager):
2106     def log_action(self, user_id, content_type_id, object_id, object_repr, action_flag, change_message=''):
2107-        e = self.model(None, None, user_id, content_type_id, object_id, object_repr[:200], action_flag, change_message)
2108+        e = self.model(None, None, user_id, content_type_id, str(object_id), object_repr[:200], action_flag, change_message)
2109         e.save()
2110 
2111 class LogEntry(models.Model):
2112Index: docs/install.txt
2113===================================================================
2114--- docs/install.txt    (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
2115+++ docs/install.txt    (working copy)
2116@@ -62,6 +62,8 @@
2117 
2118 * If you're using SQLite, you'll need pysqlite_. Use version 2.0.3 or higher.
2119 
2120+* If you're using Oracle, you'll need cx_Oracle_, version 4.3.1 or higher.
2121+
2122 .. _PostgreSQL: http://www.postgresql.org/
2123 .. _MySQL: http://www.mysql.com/
2124 .. _Django's ticket system: http://code.djangoproject.com/report/1
2125@@ -71,6 +73,7 @@
2126 .. _SQLite: http://www.sqlite.org/
2127 .. _pysqlite: http://initd.org/tracker/pysqlite
2128 .. _MySQL backend: ../databases/
2129+.. _cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/
2130 
2131 Remove any old versions of Django
2132 =================================
2133Index: docs/settings.txt
2134===================================================================
2135--- docs/settings.txt   (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
2136+++ docs/settings.txt   (working copy)
2137@@ -245,8 +245,8 @@
2138 Default: ``''`` (Empty string)
2139 
2140 Which database backend to use. Either ``'postgresql_psycopg2'``,
2141-``'postgresql'``, ``'mysql'``,  ``'mysql_old'``, ``'sqlite3'`` or
2142-``'ado_mssql'``.
2143+``'postgresql'``, ``'mysql'``,  ``'mysql_old'``, ``'sqlite3'``,
2144+``'oracle'``, or ``'ado_mssql'``.
2145 
2146 DATABASE_HOST
2147 -------------
2148Index: docs/model-api.txt
2149===================================================================
2150--- docs/model-api.txt  (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
2151+++ docs/model-api.txt  (working copy)
2152@@ -487,6 +487,10 @@
2153 possible values for "no data;" Django convention is to use the empty
2154 string, not ``NULL``.
2155 
2156+Due to database limitations when using the Oracle backend, the ``null=True``
2157+option will be coerced for string-based fields, and the value ``NULL`` will
2158+be stored to denote the empty string.
2159+
2160 ``blank``
2161 ~~~~~~~~~
2162 
2163@@ -581,6 +585,13 @@
2164 If ``True``, ``django-admin.py sqlindexes`` will output a ``CREATE INDEX``
2165 statement for this field.
2166 
2167+``db_tablespace``
2168+~~~~~~~~~~~~~~~~~
2169+
2170+If this field is indexed, the name of the database tablespace to use for the
2171+index. The default is the ``db_tablespace`` of the model, if any. If the
2172+backend doesn't support tablespaces, this option is ignored.
2173+
2174 ``default``
2175 ~~~~~~~~~~~
2176 
2177@@ -991,6 +1002,12 @@
2178 that aren't allowed in Python variable names -- notably, the hyphen --
2179 that's OK. Django quotes column and table names behind the scenes.
2180 
2181+``db_tablespace``
2182+-----------------
2183+
2184+The name of the database tablespace to use for the model. If the backend
2185+doesn't support tablespaces, this option is ignored.
2186+
2187 ``get_latest_by``
2188 -----------------
2189 
2190Index: docs/faq.txt
2191===================================================================
2192--- docs/faq.txt        (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
2193+++ docs/faq.txt        (working copy)
2194@@ -301,7 +301,7 @@
2195 
2196 If you want to use Django with a database, which is probably the case, you'll
2197 also need a database engine. PostgreSQL_ is recommended, because we're
2198-PostgreSQL fans, and MySQL_ and `SQLite 3`_ are also supported.
2199+PostgreSQL fans, and MySQL_, `SQLite 3`_, and Oracle_ are also supported.
2200 
2201 .. _Python: http://www.python.org/
2202 .. _Apache 2: http://httpd.apache.org/
2203@@ -310,6 +310,7 @@
2204 .. _PostgreSQL: http://www.postgresql.org/
2205 .. _MySQL: http://www.mysql.com/
2206 .. _`SQLite 3`: http://www.sqlite.org/
2207+.. _Oracle: http://www.oracle.com/
2208 
2209 Do I lose anything by using Python 2.3 versus newer Python versions, such as Python 2.5?
2210 ----------------------------------------------------------------------------------------
2211
2212Property changes on: tests/modeltests/datatypes
2213___________________________________________________________________
2214Name: svn:ignore
2215   + *.pyc
2216
2217
2218Index: tests/modeltests/datatypes/__init__.py
2219===================================================================
2220
2221Property changes on: tests/modeltests/datatypes/__init__.py
2222___________________________________________________________________
2223Name: svn:eol-style
2224   + native
2225
2226Index: tests/modeltests/datatypes/models.py
2227===================================================================
2228--- tests/modeltests/datatypes/models.py        (.../http://code.djangoproject.com/svn/django/trunk)    (revision 0)
2229+++ tests/modeltests/datatypes/models.py        (revision 5392)
2230@@ -0,0 +1,60 @@
2231+"""
2232+1. Simple data types testing.
2233+
2234+This is a basic model to test saving and loading boolean and date-related
2235+types, which in the past were problematic for some database backends.
2236+"""
2237+
2238+from django.db import models
2239+
2240+class Donut(models.Model):
2241+    name = models.CharField(maxlength=100)
2242+    is_frosted = models.BooleanField(default=False)
2243+    has_sprinkles = models.NullBooleanField()
2244+    baked_date = models.DateField(null=True)
2245+    baked_time = models.TimeField(null=True)
2246+    consumed_at = models.DateTimeField(null=True)
2247+
2248+    class Meta:
2249+        ordering = ('consumed_at',)
2250+
2251+    def __str__(self):
2252+        return self.name
2253+
2254+__test__ = {'API_TESTS': """
2255+# No donuts are in the system yet.
2256+>>> Donut.objects.all()
2257+[]
2258+
2259+>>> d = Donut(name='Apple Fritter')
2260+
2261+# Ensure we're getting True and False, not 0 and 1
2262+>>> d.is_frosted
2263+False
2264+>>> d.has_sprinkles
2265+>>> d.has_sprinkles = True
2266+>>> d.has_sprinkles
2267+True
2268+>>> d.save()
2269+>>> d2 = Donut.objects.all()[0]
2270+>>> d2
2271+<Donut: Apple Fritter>
2272+>>> d2.is_frosted
2273+False
2274+>>> d2.has_sprinkles
2275+True
2276+
2277+>>> import datetime
2278+>>> d2.baked_date = datetime.date(year=1938, month=6, day=4)
2279+>>> d2.baked_time = datetime.time(hour=5, minute=30)
2280+>>> d2.consumed_at = datetime.datetime(year=2007, month=4, day=20, hour=16, minute=19, second=59)
2281+>>> d2.save()
2282+
2283+>>> d3 = Donut.objects.all()[0]
2284+>>> d3.baked_date
2285+datetime.date(1938, 6, 4)
2286+>>> d3.baked_time
2287+datetime.time(5, 30)
2288+>>> d3.consumed_at
2289+datetime.datetime(2007, 4, 20, 16, 19, 59)
2290+"""}
2291
2292Property changes on: tests/modeltests/datatypes/models.py
2293___________________________________________________________________
2294Name: svn:eol-style
2295   + native
2296
2297Index: tests/modeltests/lookup/models.py
2298===================================================================
2299--- tests/modeltests/lookup/models.py   (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
2300+++ tests/modeltests/lookup/models.py   (working copy)
2301@@ -131,27 +131,6 @@
2302 [('headline', 'Article 7'), ('id', 7)]
2303 [('headline', 'Article 1'), ('id', 1)]
2304 
2305-
2306-# you can use values() even on extra fields
2307->>> for d in Article.objects.extra( select={'id_plus_one' : 'id + 1'} ).values('id', 'id_plus_one'):
2308-...     i = d.items()
2309-...     i.sort()
2310-...     i
2311-[('id', 5), ('id_plus_one', 6)]
2312-[('id', 6), ('id_plus_one', 7)]
2313-[('id', 4), ('id_plus_one', 5)]
2314-[('id', 2), ('id_plus_one', 3)]
2315-[('id', 3), ('id_plus_one', 4)]
2316-[('id', 7), ('id_plus_one', 8)]
2317-[('id', 1), ('id_plus_one', 2)]
2318-
2319-# however, an exception FieldDoesNotExist will still be thrown
2320-# if you try to access non-existent field (field that is neither on the model nor extra)
2321->>> Article.objects.extra( select={'id_plus_one' : 'id + 1'} ).values('id', 'id_plus_two')
2322-Traceback (most recent call last):
2323-    ...
2324-FieldDoesNotExist: Article has no field named 'id_plus_two'
2325-
2326 # if you don't specify which fields, all are returned
2327 >>> list(Article.objects.filter(id=5).values()) == [{'id': 5, 'headline': 'Article 5', 'pub_date': datetime(2005, 8, 1, 9, 0)}]
2328 True
2329Index: tests/regressiontests/serializers_regress/tests.py
2330===================================================================
2331--- tests/regressiontests/serializers_regress/tests.py  (.../http://code.djangoproject.com/svn/django/trunk)    (revision 5393)
2332+++ tests/regressiontests/serializers_regress/tests.py  (working copy)
2333@@ -14,6 +14,7 @@
2334 from django.core import serializers
2335 from django.db import transaction
2336 from django.core import management
2337+from django.conf import settings
2338 
2339 from models import *
2340 try:
2341@@ -115,10 +116,13 @@
2342     (data_obj, 31, DateTimeData, None),
2343     (data_obj, 40, EmailData, "hovercraft@example.com"),
2344     (data_obj, 41, EmailData, None),
2345+    (data_obj, 42, EmailData, ""),
2346     (data_obj, 50, FileData, 'file:///foo/bar/whiz.txt'),
2347     (data_obj, 51, FileData, None),
2348+    (data_obj, 52, FileData, ""),
2349     (data_obj, 60, FilePathData, "/foo/bar/whiz.txt"),
2350     (data_obj, 61, FilePathData, None),
2351+    (data_obj, 62, FilePathData, ""),
2352     (data_obj, 70, DecimalData, decimal.Decimal('12.345')),
2353     (data_obj, 71, DecimalData, decimal.Decimal('-12.345')),
2354     (data_obj, 72, DecimalData, decimal.Decimal('0.0')),
2355@@ -134,6 +138,7 @@
2356     #(XX, ImageData
2357     (data_obj, 90, IPAddressData, "127.0.0.1"),
2358     (data_obj, 91, IPAddressData, None),
2359+    (data_obj, 92, IPAddressData, ""),
2360     (data_obj, 100, NullBooleanData, True),
2361     (data_obj, 101, NullBooleanData, False),
2362     (data_obj, 102, NullBooleanData, None),
2363@@ -145,6 +150,7 @@
2364     (data_obj, 131, PositiveSmallIntegerData, None),
2365     (data_obj, 140, SlugData, "this-is-a-slug"),
2366     (data_obj, 141, SlugData, None),
2367+    (data_obj, 142, SlugData, ""),
2368     (data_obj, 150, SmallData, 12),
2369     (data_obj, 151, SmallData, -12),
2370     (data_obj, 152, SmallData, 0),
2371@@ -159,8 +165,10 @@
2372     (data_obj, 171, TimeData, None),
2373     (data_obj, 180, USStateData, "MA"),
2374     (data_obj, 181, USStateData, None),
2375+    (data_obj, 182, USStateData, ""),
2376     (data_obj, 190, XMLData, "<foo></foo>"),
2377     (data_obj, 191, XMLData, None),
2378+    (data_obj, 192, XMLData, ""),
2379 
2380     (generic_obj, 200, GenericData, ['Generic Object 1', 'tag1', 'tag2']),
2381     (generic_obj, 201, GenericData, ['Generic Object 2', 'tag2', 'tag3']),
2382@@ -240,6 +248,15 @@
2383 #     (pk_obj, 790, XMLPKData, "<foo></foo>"),
2384 ]
2385 
2386+# Because Oracle treats the empty string as NULL, Oracle is expected to fail
2387+# when field.empty_strings_allowed is True and the value is None; skip these
2388+# tests.
2389+if settings.DATABASE_ENGINE == 'oracle':
2390+    test_data = [data for data in test_data
2391+                 if not (data[0] == data_obj and
2392+                         data[2]._meta.get_field('data').empty_strings_allowed and
2393+                         data[3] is None)]
2394+
2395 # Dynamically create serializer tests to ensure that all
2396 # registered serializers are automatically tested.
2397 class SerializerTests(unittest.TestCase):