Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merged boulder-oracle-sprint branch (r3965:5512) back into trunk. All

expected tests pass for all databases.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@5519 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit ac64e91a0cadc57f4bc5cd5d66955832320ca7a1 1 parent 553a200
Malcolm Tredinnick authored June 23, 2007

Showing 28 changed files with 1,239 additions and 172 deletions. Show diff stats Hide diff stats

  1. 2  django/contrib/admin/models.py
  2. 112  django/core/management.py
  3. 22  django/db/backends/ado_mssql/base.py
  4. 1  django/db/backends/dummy/base.py
  5. 23  django/db/backends/mysql/base.py
  6. 23  django/db/backends/mysql_old/base.py
  7. 448  django/db/backends/oracle/base.py
  8. 11  django/db/backends/oracle/client.py
  9. 326  django/db/backends/oracle/creation.py
  10. 82  django/db/backends/oracle/introspection.py
  11. 31  django/db/backends/postgresql/base.py
  12. 25  django/db/backends/postgresql_psycopg2/base.py
  13. 23  django/db/backends/sqlite3/base.py
  14. 11  django/db/backends/util.py
  15. 19  django/db/models/base.py
  16. 32  django/db/models/fields/__init__.py
  17. 5  django/db/models/fields/related.py
  18. 11  django/db/models/options.py
  19. 82  django/db/models/query.py
  20. 2  django/newforms/widgets.py
  21. 15  django/test/utils.py
  22. 3  docs/faq.txt
  23. 3  docs/install.txt
  24. 18  docs/model-api.txt
  25. 6  docs/settings.txt
  26. 0  tests/regressiontests/datatypes/__init__.py
  27. 59  tests/regressiontests/datatypes/models.py
  28. 16  tests/regressiontests/serializers_regress/tests.py
2  django/contrib/admin/models.py
@@ -9,7 +9,7 @@
9 9
 
10 10
 class LogEntryManager(models.Manager):
11 11
     def log_action(self, user_id, content_type_id, object_id, object_repr, action_flag, change_message=''):
12  
-        e = self.model(None, None, user_id, content_type_id, object_id, object_repr[:200], action_flag, change_message)
  12
+        e = self.model(None, None, user_id, content_type_id, str(object_id), object_repr[:200], action_flag, change_message)
13 13
         e.save()
14 14
 
15 15
 class LogEntry(models.Model):
112  django/core/management.py
@@ -59,12 +59,16 @@ def _is_valid_dir_name(s):
59 59
 
60 60
 def _get_installed_models(table_list):
61 61
     "Gets a set of all models that are installed, given a list of existing tables"
62  
-    from django.db import models
  62
+    from django.db import backend, models
63 63
     all_models = []
64 64
     for app in models.get_apps():
65 65
         for model in models.get_models(app):
66 66
             all_models.append(model)
67  
-    return set([m for m in all_models if m._meta.db_table in table_list])
  67
+    if backend.uses_case_insensitive_names:
  68
+        converter = str.upper
  69
+    else:
  70
+        converter = lambda x: x
  71
+    return set([m for m in all_models if converter(m._meta.db_table) in map(converter, table_list)])
68 72
 
69 73
 def _get_table_list():
70 74
     "Gets a list of all db tables that are physically installed."
@@ -100,6 +104,7 @@ def _get_sequence_list():
100 104
 def get_sql_create(app):
101 105
     "Returns a list of the CREATE TABLE SQL statements for the given app."
102 106
     from django.db import get_creation_module, models
  107
+
103 108
     data_types = get_creation_module().DATA_TYPES
104 109
 
105 110
     if not data_types:
@@ -171,15 +176,20 @@ def _get_sql_model_create(model, known_models=set()):
171 176
             rel_field = f
172 177
             data_type = f.get_internal_type()
173 178
         col_type = data_types[data_type]
  179
+        tablespace = f.db_tablespace or opts.db_tablespace
174 180
         if col_type is not None:
175 181
             # Make the definition (e.g. 'foo VARCHAR(30)') for this field.
176 182
             field_output = [style.SQL_FIELD(backend.quote_name(f.column)),
177 183
                 style.SQL_COLTYPE(col_type % rel_field.__dict__)]
178 184
             field_output.append(style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or '')))
179  
-            if f.unique:
  185
+            if f.unique and (not f.primary_key or backend.allows_unique_and_pk):
180 186
                 field_output.append(style.SQL_KEYWORD('UNIQUE'))
181 187
             if f.primary_key:
182 188
                 field_output.append(style.SQL_KEYWORD('PRIMARY KEY'))
  189
+            if tablespace and backend.supports_tablespaces and (f.unique or f.primary_key) and backend.autoindexes_primary_keys:
  190
+                # We must specify the index tablespace inline, because we
  191
+                # won't be generating a CREATE INDEX statement for this field.
  192
+                field_output.append(backend.get_tablespace_sql(tablespace, inline=True))
183 193
             if f.rel:
184 194
                 if f.rel.to in known_models:
185 195
                     field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \
@@ -203,9 +213,19 @@ def _get_sql_model_create(model, known_models=set()):
203 213
     full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(backend.quote_name(opts.db_table)) + ' (']
204 214
     for i, line in enumerate(table_output): # Combine and add commas.
205 215
         full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
206  
-    full_statement.append(');')
  216
+    full_statement.append(')')
  217
+    if opts.db_tablespace and backend.supports_tablespaces:
  218
+        full_statement.append(backend.get_tablespace_sql(opts.db_tablespace))
  219
+    full_statement.append(';')
207 220
     final_output.append('\n'.join(full_statement))
208 221
 
  222
+    if opts.has_auto_field and hasattr(backend, 'get_autoinc_sql'):
  223
+        # Add any extra SQL needed to support auto-incrementing primary keys
  224
+        autoinc_sql = backend.get_autoinc_sql(opts.db_table)
  225
+        if autoinc_sql:
  226
+            for stmt in autoinc_sql:
  227
+                final_output.append(stmt)
  228
+
209 229
     return final_output, pending_references
210 230
 
211 231
 def _get_sql_for_pending_references(model, pending_references):
@@ -213,6 +233,7 @@ def _get_sql_for_pending_references(model, pending_references):
213 233
     Get any ALTER TABLE statements to add constraints after the fact.
214 234
     """
215 235
     from django.db import backend, get_creation_module
  236
+    from django.db.backends.util import truncate_name
216 237
     data_types = get_creation_module().DATA_TYPES
217 238
 
218 239
     final_output = []
@@ -229,7 +250,7 @@ def _get_sql_for_pending_references(model, pending_references):
229 250
                 # So we are careful with character usage here.
230 251
                 r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table))))
231 252
                 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
232  
-                    (backend.quote_name(r_table), r_name,
  253
+                    (backend.quote_name(r_table), truncate_name(r_name, backend.get_max_name_length()),
233 254
                     backend.quote_name(r_col), backend.quote_name(table), backend.quote_name(col),
234 255
                     backend.get_deferrable_sql()))
235 256
             del pending_references[model]
@@ -245,12 +266,18 @@ def _get_many_to_many_sql_for_model(model):
245 266
     final_output = []
246 267
     for f in opts.many_to_many:
247 268
         if not isinstance(f.rel, generic.GenericRel):
  269
+            tablespace = f.db_tablespace or opts.db_tablespace
  270
+            if tablespace and backend.supports_tablespaces and backend.autoindexes_primary_keys:
  271
+                tablespace_sql = ' ' + backend.get_tablespace_sql(tablespace, inline=True)
  272
+            else:
  273
+                tablespace_sql = ''
248 274
             table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
249 275
                 style.SQL_TABLE(backend.quote_name(f.m2m_db_table())) + ' (']
250  
-            table_output.append('    %s %s %s,' % \
  276
+            table_output.append('    %s %s %s%s,' % \
251 277
                 (style.SQL_FIELD(backend.quote_name('id')),
252 278
                 style.SQL_COLTYPE(data_types['AutoField']),
253  
-                style.SQL_KEYWORD('NOT NULL PRIMARY KEY')))
  279
+                style.SQL_KEYWORD('NOT NULL PRIMARY KEY'),
  280
+                tablespace_sql))
254 281
             table_output.append('    %s %s %s %s (%s)%s,' % \
255 282
                 (style.SQL_FIELD(backend.quote_name(f.m2m_column_name())),
256 283
                 style.SQL_COLTYPE(data_types[get_rel_data_type(opts.pk)] % opts.pk.__dict__),
@@ -265,17 +292,30 @@ def _get_many_to_many_sql_for_model(model):
265 292
                 style.SQL_TABLE(backend.quote_name(f.rel.to._meta.db_table)),
266 293
                 style.SQL_FIELD(backend.quote_name(f.rel.to._meta.pk.column)),
267 294
                 backend.get_deferrable_sql()))
268  
-            table_output.append('    %s (%s, %s)' % \
  295
+            table_output.append('    %s (%s, %s)%s' % \
269 296
                 (style.SQL_KEYWORD('UNIQUE'),
270 297
                 style.SQL_FIELD(backend.quote_name(f.m2m_column_name())),
271  
-                style.SQL_FIELD(backend.quote_name(f.m2m_reverse_name()))))
272  
-            table_output.append(');')
  298
+                style.SQL_FIELD(backend.quote_name(f.m2m_reverse_name())),
  299
+                tablespace_sql))
  300
+            table_output.append(')')
  301
+            if opts.db_tablespace and backend.supports_tablespaces:
  302
+                # f.db_tablespace is only for indices, so ignore its value here.
  303
+                table_output.append(backend.get_tablespace_sql(opts.db_tablespace))
  304
+            table_output.append(';')
273 305
             final_output.append('\n'.join(table_output))
  306
+
  307
+            # Add any extra SQL needed to support auto-incrementing PKs
  308
+            autoinc_sql = backend.get_autoinc_sql(f.m2m_db_table())
  309
+            if autoinc_sql:
  310
+                for stmt in autoinc_sql:
  311
+                    final_output.append(stmt)
  312
+
274 313
     return final_output
275 314
 
276 315
 def get_sql_delete(app):
277 316
     "Returns a list of the DROP TABLE SQL statements for the given app."
278 317
     from django.db import backend, connection, models, get_introspection_module
  318
+    from django.db.backends.util import truncate_name
279 319
     introspection = get_introspection_module()
280 320
 
281 321
     # This should work even if a connection isn't available
@@ -289,6 +329,10 @@ def get_sql_delete(app):
289 329
         table_names = introspection.get_table_list(cursor)
290 330
     else:
291 331
         table_names = []
  332
+    if backend.uses_case_insensitive_names:
  333
+        table_name_converter = str.upper
  334
+    else:
  335
+        table_name_converter = lambda x: x
292 336
 
293 337
     output = []
294 338
 
@@ -298,7 +342,7 @@ def get_sql_delete(app):
298 342
     references_to_delete = {}
299 343
     app_models = models.get_models(app)
300 344
     for model in app_models:
301  
-        if cursor and model._meta.db_table in table_names:
  345
+        if cursor and table_name_converter(model._meta.db_table) in table_names:
302 346
             # The table exists, so it needs to be dropped
303 347
             opts = model._meta
304 348
             for f in opts.fields:
@@ -308,7 +352,7 @@ def get_sql_delete(app):
308 352
             to_delete.add(model)
309 353
 
310 354
     for model in app_models:
311  
-        if cursor and model._meta.db_table in table_names:
  355
+        if cursor and table_name_converter(model._meta.db_table) in table_names:
312 356
             # Drop the table now
313 357
             output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
314 358
                 style.SQL_TABLE(backend.quote_name(model._meta.db_table))))
@@ -318,20 +362,26 @@ def get_sql_delete(app):
318 362
                     col = f.column
319 363
                     r_table = model._meta.db_table
320 364
                     r_col = model._meta.get_field(f.rel.field_name).column
  365
+                    r_name = '%s_refs_%s_%x' % (col, r_col, abs(hash((table, r_table))))
321 366
                     output.append('%s %s %s %s;' % \
322 367
                         (style.SQL_KEYWORD('ALTER TABLE'),
323 368
                         style.SQL_TABLE(backend.quote_name(table)),
324 369
                         style.SQL_KEYWORD(backend.get_drop_foreignkey_sql()),
325  
-                        style.SQL_FIELD(backend.quote_name('%s_refs_%s_%x' % (col, r_col, abs(hash((table, r_table))))))))
  370
+                        style.SQL_FIELD(truncate_name(r_name, backend.get_max_name_length()))))
326 371
                 del references_to_delete[model]
  372
+            if model._meta.has_auto_field and hasattr(backend, 'get_drop_sequence'):
  373
+                output.append(backend.get_drop_sequence(model._meta.db_table))
327 374
 
328 375
     # Output DROP TABLE statements for many-to-many tables.
329 376
     for model in app_models:
330 377
         opts = model._meta
331 378
         for f in opts.many_to_many:
332  
-            if cursor and f.m2m_db_table() in table_names:
  379
+            if cursor and table_name_converter(f.m2m_db_table()) in table_names:
333 380
                 output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'),
334 381
                     style.SQL_TABLE(backend.quote_name(f.m2m_db_table()))))
  382
+                if hasattr(backend, 'get_drop_sequence'):
  383
+                    output.append(backend.get_drop_sequence("%s_%s" % (model._meta.db_table, f.column)))
  384
+
335 385
 
336 386
     app_label = app_models[0]._meta.app_label
337 387
 
@@ -430,14 +480,20 @@ def get_sql_indexes_for_model(model):
430 480
     output = []
431 481
 
432 482
     for f in model._meta.fields:
433  
-        if f.db_index:
  483
+        if f.db_index and not ((f.primary_key or f.unique) and backend.autoindexes_primary_keys):
434 484
             unique = f.unique and 'UNIQUE ' or ''
  485
+            tablespace = f.db_tablespace or model._meta.db_tablespace
  486
+            if tablespace and backend.supports_tablespaces:
  487
+                tablespace_sql = ' ' + backend.get_tablespace_sql(tablespace)
  488
+            else:
  489
+                tablespace_sql = ''
435 490
             output.append(
436 491
                 style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \
437 492
                 style.SQL_TABLE(backend.quote_name('%s_%s' % (model._meta.db_table, f.column))) + ' ' + \
438 493
                 style.SQL_KEYWORD('ON') + ' ' + \
439 494
                 style.SQL_TABLE(backend.quote_name(model._meta.db_table)) + ' ' + \
440  
-                "(%s);" % style.SQL_FIELD(backend.quote_name(f.column))
  495
+                "(%s)" % style.SQL_FIELD(backend.quote_name(f.column)) + \
  496
+                "%s;" % tablespace_sql
441 497
             )
442 498
     return output
443 499
 
@@ -461,7 +517,7 @@ def _emit_post_sync_signal(created_models, verbosity, interactive):
461 517
 
462 518
 def syncdb(verbosity=1, interactive=True):
463 519
     "Creates the database tables for all apps in INSTALLED_APPS whose tables haven't already been created."
464  
-    from django.db import connection, transaction, models, get_creation_module
  520
+    from django.db import backend, connection, transaction, models, get_creation_module
465 521
     from django.conf import settings
466 522
 
467 523
     disable_termcolors()
@@ -484,6 +540,10 @@ def syncdb(verbosity=1, interactive=True):
484 540
     # Get a list of all existing database tables,
485 541
     # so we know what needs to be added.
486 542
     table_list = _get_table_list()
  543
+    if backend.uses_case_insensitive_names:
  544
+        table_name_converter = str.upper
  545
+    else:
  546
+        table_name_converter = lambda x: x
487 547
 
488 548
     # Get a list of already installed *models* so that references work right.
489 549
     seen_models = _get_installed_models(table_list)
@@ -498,7 +558,7 @@ def syncdb(verbosity=1, interactive=True):
498 558
             # Create the model's database table, if it doesn't already exist.
499 559
             if verbosity >= 2:
500 560
                 print "Processing %s.%s model" % (app_name, model._meta.object_name)
501  
-            if model._meta.db_table in table_list:
  561
+            if table_name_converter(model._meta.db_table) in table_list:
502 562
                 continue
503 563
             sql, references = _get_sql_model_create(model, seen_models)
504 564
             seen_models.add(model)
@@ -510,7 +570,7 @@ def syncdb(verbosity=1, interactive=True):
510 570
                 print "Creating table %s" % model._meta.db_table
511 571
             for statement in sql:
512 572
                 cursor.execute(statement)
513  
-            table_list.append(model._meta.db_table)
  573
+            table_list.append(table_name_converter(model._meta.db_table))
514 574
 
515 575
     # Create the m2m tables. This must be done after all tables have been created
516 576
     # to ensure that all referred tables will exist.
@@ -829,7 +889,7 @@ def inspectdb():
829 889
         except NotImplementedError:
830 890
             indexes = {}
831 891
         for i, row in enumerate(introspection_module.get_table_description(cursor, table_name)):
832  
-            att_name = row[0]
  892
+            att_name = row[0].lower()
833 893
             comment_notes = [] # Holds Field notes, to be displayed in a Python comment.
834 894
             extra_params = {}  # Holds Field parameters such as 'db_column'.
835 895
 
@@ -1322,7 +1382,7 @@ def load_data(fixture_labels, verbosity=1):
1322 1382
     # Keep a count of the installed objects and fixtures
1323 1383
     count = [0,0]
1324 1384
     models = set()
1325  
-    
  1385
+
1326 1386
     humanize = lambda dirname: dirname and "'%s'" % dirname or 'absolute path'
1327 1387
 
1328 1388
     # Get a cursor (even though we don't need one yet). This has
@@ -1400,7 +1460,7 @@ def load_data(fixture_labels, verbosity=1):
1400 1460
                     if verbosity > 1:
1401 1461
                         print "No %s fixture '%s' in %s." % \
1402 1462
                             (format, fixture_name, humanize(fixture_dir))
1403  
-                            
  1463
+
1404 1464
     if count[0] > 0:
1405 1465
         sequence_sql = backend.get_sql_sequence_reset(style, models)
1406 1466
         if sequence_sql:
@@ -1408,10 +1468,10 @@ def load_data(fixture_labels, verbosity=1):
1408 1468
                 print "Resetting sequences"
1409 1469
             for line in sequence_sql:
1410 1470
                 cursor.execute(line)
1411  
-            
  1471
+
1412 1472
     transaction.commit()
1413 1473
     transaction.leave_transaction_management()
1414  
-    
  1474
+
1415 1475
     if count[0] == 0:
1416 1476
         if verbosity > 0:
1417 1477
             print "No fixtures found."
@@ -1626,7 +1686,9 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING, argv=None):
1626 1686
         if not mod_list:
1627 1687
             parser.print_usage_and_exit()
1628 1688
         if action not in NO_SQL_TRANSACTION:
1629  
-            print style.SQL_KEYWORD("BEGIN;")
  1689
+            from django.db import backend
  1690
+            if backend.get_start_transaction_sql():
  1691
+                print style.SQL_KEYWORD(backend.get_start_transaction_sql())
1630 1692
         for mod in mod_list:
1631 1693
             if action == 'reset':
1632 1694
                 output = action_mapping[action](mod, options.interactive)
22  django/db/backends/ado_mssql/base.py
@@ -89,7 +89,14 @@ def close(self):
89 89
             self.connection.close()
90 90
             self.connection = None
91 91
 
  92
+allows_group_by_ordinal = True
  93
+allows_unique_and_pk = True
  94
+autoindexes_primary_keys = True
  95
+needs_datetime_string_cast = True
  96
+needs_upper_for_iops = False
92 97
 supports_constraints = True
  98
+supports_tablespaces = True
  99
+uses_case_insensitive_names = False
93 100
 
94 101
 def quote_name(name):
95 102
     if name.startswith('[') and name.endswith(']'):
@@ -117,6 +124,9 @@ def get_date_trunc_sql(lookup_type, field_name):
117 124
     if lookup_type=='day':
118 125
         return "Convert(datetime, Convert(varchar(12), %s))" % field_name
119 126
 
  127
+def get_datetime_cast_sql():
  128
+    return None
  129
+
120 130
 def get_limit_offset_sql(limit, offset=None):
121 131
     # TODO: This is a guess. Make sure this is correct.
122 132
     sql = "LIMIT %s" % limit
@@ -139,6 +149,18 @@ def get_drop_foreignkey_sql():
139 149
 def get_pk_default_value():
140 150
     return "DEFAULT"
141 151
 
  152
+def get_max_name_length():
  153
+    return None
  154
+
  155
+def get_start_transaction_sql():
  156
+    return "BEGIN;"
  157
+
  158
+def get_tablespace_sql(tablespace, inline=False):
  159
+    return "ON %s" % quote_name(tablespace)
  160
+
  161
+def get_autoinc_sql(table):
  162
+    return None
  163
+
142 164
 def get_sql_flush(style, tables, sequences):
143 165
     """Return a list of SQL statements required to remove all data from
144 166
     all tables in the database (without actually removing the tables
1  django/db/backends/dummy/base.py
@@ -33,6 +33,7 @@ def close(self):
33 33
         pass # close()
34 34
 
35 35
 supports_constraints = False
  36
+supports_tablespaces = False
36 37
 quote_name = complain
37 38
 dictfetchone = complain
38 39
 dictfetchmany = complain
23  django/db/backends/mysql/base.py
@@ -134,7 +134,14 @@ def get_server_version(self):
134 134
             self.server_version = tuple([int(x) for x in m.groups()])
135 135
         return self.server_version
136 136
 
  137
+allows_group_by_ordinal = True
  138
+allows_unique_and_pk = True
  139
+autoindexes_primary_keys = False
  140
+needs_datetime_string_cast = True     # MySQLdb requires a typecast for dates
  141
+needs_upper_for_iops = False
137 142
 supports_constraints = True
  143
+supports_tablespaces = False
  144
+uses_case_insensitive_names = False
138 145
 
139 146
 def quote_name(name):
140 147
     if name.startswith("`") and name.endswith("`"):
@@ -167,6 +174,9 @@ def get_date_trunc_sql(lookup_type, field_name):
167 174
         sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
168 175
     return sql
169 176
 
  177
+def get_datetime_cast_sql():
  178
+    return None
  179
+
170 180
 def get_limit_offset_sql(limit, offset=None):
171 181
     sql = "LIMIT "
172 182
     if offset and offset != 0:
@@ -188,11 +198,20 @@ def get_drop_foreignkey_sql():
188 198
 def get_pk_default_value():
189 199
     return "DEFAULT"
190 200
 
  201
+def get_max_name_length():
  202
+    return None;
  203
+
  204
+def get_start_transaction_sql():
  205
+    return "BEGIN;"
  206
+
  207
+def get_autoinc_sql(table):
  208
+    return None
  209
+
191 210
 def get_sql_flush(style, tables, sequences):
192 211
     """Return a list of SQL statements required to remove all data from
193 212
     all tables in the database (without actually removing the tables
194 213
     themselves) and put the database in an empty 'initial' state
195  
-    
  214
+
196 215
     """
197 216
     # NB: The generated SQL below is specific to MySQL
198 217
     # 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
@@ -204,7 +223,7 @@ def get_sql_flush(style, tables, sequences):
204 223
                  style.SQL_FIELD(quote_name(table))
205 224
                 )  for table in tables] + \
206 225
               ['SET FOREIGN_KEY_CHECKS = 1;']
207  
-              
  226
+
208 227
         # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
209 228
         # to reset sequence indices
210 229
         sql.extend(["%s %s %s %s %s;" % \
23  django/db/backends/mysql_old/base.py
@@ -135,7 +135,14 @@ def get_server_version(self):
135 135
             self.server_version = tuple([int(x) for x in m.groups()])
136 136
         return self.server_version
137 137
 
  138
+allows_group_by_ordinal = True
  139
+allows_unique_and_pk = True
  140
+autoindexes_primary_keys = False
  141
+needs_datetime_string_cast = True     # MySQLdb requires a typecast for dates
  142
+needs_upper_for_iops = False
138 143
 supports_constraints = True
  144
+supports_tablespaces = False
  145
+uses_case_insensitive_names = False
139 146
 
140 147
 def quote_name(name):
141 148
     if name.startswith("`") and name.endswith("`"):
@@ -168,6 +175,9 @@ def get_date_trunc_sql(lookup_type, field_name):
168 175
         sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
169 176
     return sql
170 177
 
  178
+def get_datetime_cast_sql():
  179
+    return None
  180
+
171 181
 def get_limit_offset_sql(limit, offset=None):
172 182
     sql = "LIMIT "
173 183
     if offset and offset != 0:
@@ -189,11 +199,20 @@ def get_drop_foreignkey_sql():
189 199
 def get_pk_default_value():
190 200
     return "DEFAULT"
191 201
 
  202
+def get_max_name_length():
  203
+    return None;
  204
+
  205
+def get_start_transaction_sql():
  206
+    return "BEGIN;"
  207
+
  208
+def get_autoinc_sql(table):
  209
+    return None
  210
+
192 211
 def get_sql_flush(style, tables, sequences):
193 212
     """Return a list of SQL statements required to remove all data from
194 213
     all tables in the database (without actually removing the tables
195 214
     themselves) and put the database in an empty 'initial' state
196  
-    
  215
+
197 216
     """
198 217
     # NB: The generated SQL below is specific to MySQL
199 218
     # 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
@@ -205,7 +224,7 @@ def get_sql_flush(style, tables, sequences):
205 224
                  style.SQL_FIELD(quote_name(table))
206 225
                 )  for table in tables] + \
207 226
               ['SET FOREIGN_KEY_CHECKS = 1;']
208  
-              
  227
+
209 228
         # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
210 229
         # to reset sequence indices
211 230
         sql.extend(["%s %s %s %s %s;" % \
448  django/db/backends/oracle/base.py
@@ -4,12 +4,16 @@
4 4
 Requires cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/
5 5
 """
6 6
 
  7
+from django.conf import settings
7 8
 from django.db.backends import util
8 9
 try:
9 10
     import cx_Oracle as Database
10 11
 except ImportError, e:
11 12
     from django.core.exceptions import ImproperlyConfigured
12 13
     raise ImproperlyConfigured, "Error loading cx_Oracle module: %s" % e
  14
+import datetime
  15
+from django.utils.datastructures import SortedDict
  16
+
13 17
 
14 18
 DatabaseError = Database.Error
15 19
 IntegrityError = Database.IntegrityError
@@ -31,7 +35,6 @@ def _valid_connection(self):
31 35
         return self.connection is not None
32 36
 
33 37
     def cursor(self):
34  
-        from django.conf import settings
35 38
         if not self._valid_connection():
36 39
             if len(settings.DATABASE_HOST.strip()) == 0:
37 40
                 settings.DATABASE_HOST = 'localhost'
@@ -41,25 +44,37 @@ def cursor(self):
41 44
             else:
42 45
                 conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME)
43 46
                 self.connection = Database.connect(conn_string, **self.options)
44  
-        return FormatStylePlaceholderCursor(self.connection)
  47
+        cursor = FormatStylePlaceholderCursor(self.connection)
  48
+        # default arraysize of 1 is highly sub-optimal
  49
+        cursor.arraysize = 100
  50
+        # set oracle date to ansi date format
  51
+        cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD'")
  52
+        cursor.execute("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'")
  53
+        if settings.DEBUG:
  54
+            return util.CursorDebugWrapper(cursor, self)
  55
+        return cursor
45 56
 
46 57
     def _commit(self):
47 58
         if self.connection is not None:
48  
-            self.connection.commit()
  59
+            return self.connection.commit()
49 60
 
50 61
     def _rollback(self):
51 62
         if self.connection is not None:
52  
-            try:
53  
-                self.connection.rollback()
54  
-            except Database.NotSupportedError:
55  
-                pass
  63
+            return self.connection.rollback()
56 64
 
57 65
     def close(self):
58 66
         if self.connection is not None:
59 67
             self.connection.close()
60 68
             self.connection = None
61 69
 
  70
+allows_group_by_ordinal = False
  71
+allows_unique_and_pk = False        # Suppress UNIQUE/PK for Oracle (ORA-02259)
  72
+autoindexes_primary_keys = True
  73
+needs_datetime_string_cast = False
  74
+needs_upper_for_iops = True
62 75
 supports_constraints = True
  76
+supports_tablespaces = True
  77
+uses_case_insensitive_names = True
63 78
 
64 79
 class FormatStylePlaceholderCursor(Database.Cursor):
65 80
     """
@@ -67,45 +82,75 @@ class FormatStylePlaceholderCursor(Database.Cursor):
67 82
     This fixes it -- but note that if you want to use a literal "%s" in a query,
68 83
     you'll need to use "%%s".
69 84
     """
  85
+    def _rewrite_args(self, query, params=None):
  86
+        if params is None:
  87
+            params = []
  88
+        else:
  89
+            # cx_Oracle can't handle unicode parameters, so cast to str for now
  90
+            for i, param in enumerate(params):
  91
+                if type(param) == unicode:
  92
+                    try:
  93
+                        params[i] = param.encode('utf-8')
  94
+                    except UnicodeError:
  95
+                        params[i] = str(param)
  96
+        args = [(':arg%d' % i) for i in range(len(params))]
  97
+        query = query % tuple(args)
  98
+        # cx_Oracle wants no trailing ';' for SQL statements.  For PL/SQL, it
  99
+        # it does want a trailing ';' but not a trailing '/'.  However, these
  100
+        # characters must be included in the original query in case the query
  101
+        # is being passed to SQL*Plus.
  102
+        if query.endswith(';') or query.endswith('/'):
  103
+            query = query[:-1]
  104
+        return query, params
  105
+
70 106
     def execute(self, query, params=None):
71  
-        if params is None: params = []
72  
-        query = self.convert_arguments(query, len(params))
  107
+        query, params = self._rewrite_args(query, params)
73 108
         return Database.Cursor.execute(self, query, params)
74 109
 
75 110
     def executemany(self, query, params=None):
76  
-        if params is None: params = []
77  
-        query = self.convert_arguments(query, len(params[0]))
  111
+        query, params = self._rewrite_args(query, params)
78 112
         return Database.Cursor.executemany(self, query, params)
79 113
 
80  
-    def convert_arguments(self, query, num_params):
81  
-        # replace occurances of "%s" with ":arg" - Oracle requires colons for parameter placeholders.
82  
-        args = [':arg' for i in range(num_params)]
83  
-        return query % tuple(args)
84  
-
85 114
 def quote_name(name):
86  
-    return name
  115
+    # SQL92 requires delimited (quoted) names to be case-sensitive.  When
  116
+    # not quoted, Oracle has case-insensitive behavior for identifiers, but
  117
+    # always defaults to uppercase.
  118
+    # We simplify things by making Oracle identifiers always uppercase.
  119
+    if not name.startswith('"') and not name.endswith('"'):
  120
+        name = '"%s"' % util.truncate_name(name.upper(), get_max_name_length())
  121
+    return name.upper()
87 122
 
88 123
 dictfetchone = util.dictfetchone
89 124
 dictfetchmany = util.dictfetchmany
90 125
 dictfetchall  = util.dictfetchall
91 126
 
92 127
 def get_last_insert_id(cursor, table_name, pk_name):
93  
-    query = "SELECT %s_sq.currval from dual" % table_name
94  
-    cursor.execute(query)
  128
+    sq_name = util.truncate_name(table_name, get_max_name_length()-3)
  129
+    cursor.execute('SELECT %s_sq.currval FROM dual' % sq_name)
95 130
     return cursor.fetchone()[0]
96 131
 
97 132
 def get_date_extract_sql(lookup_type, table_name):
98 133
     # lookup_type is 'year', 'month', 'day'
99  
-    # http://www.psoug.org/reference/date_func.html
  134
+    # http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions42a.htm#1017163
100 135
     return "EXTRACT(%s FROM %s)" % (lookup_type, table_name)
101 136
 
102 137
 def get_date_trunc_sql(lookup_type, field_name):
103  
-    return "EXTRACT(%s FROM TRUNC(%s))" % (lookup_type, field_name)
  138
+    # lookup_type is 'year', 'month', 'day'
  139
+    # Oracle uses TRUNC() for both dates and numbers.
  140
+    # http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions155a.htm#SQLRF06151
  141
+    if lookup_type == 'day':
  142
+        sql = 'TRUNC(%s)' % (field_name,)
  143
+    else:
  144
+        sql = "TRUNC(%s, '%s')" % (field_name, lookup_type)
  145
+    return sql
  146
+
  147
+def get_datetime_cast_sql():
  148
+    return "TO_TIMESTAMP(%s, 'YYYY-MM-DD HH24:MI:SS.FF')"
104 149
 
105 150
 def get_limit_offset_sql(limit, offset=None):
106 151
     # Limits and offset are too complicated to be handled here.
107  
-    # Instead, they are handled in django/db/query.py.
108  
-    pass
  152
+    # Instead, they are handled in django/db/backends/oracle/query.py.
  153
+    return ""
109 154
 
110 155
 def get_random_function_sql():
111 156
     return "DBMS_RANDOM.RANDOM"
@@ -117,40 +162,363 @@ def get_fulltext_search_sql(field_name):
117 162
     raise NotImplementedError
118 163
 
119 164
 def get_drop_foreignkey_sql():
120  
-    return "DROP FOREIGN KEY"
  165
+    return "DROP CONSTRAINT"
121 166
 
122 167
 def get_pk_default_value():
123 168
     return "DEFAULT"
124 169
 
  170
+def get_max_name_length():
  171
+    return 30
  172
+
  173
+def get_start_transaction_sql():
  174
+    return None
  175
+
  176
+def get_tablespace_sql(tablespace, inline=False):
  177
+    return "%sTABLESPACE %s" % ((inline and "USING INDEX " or ""), quote_name(tablespace))
  178
+
  179
+def get_autoinc_sql(table):
  180
+    # To simulate auto-incrementing primary keys in Oracle, we have to
  181
+    # create a sequence and a trigger.
  182
+    sq_name = get_sequence_name(table)
  183
+    tr_name = get_trigger_name(table)
  184
+    sequence_sql = 'CREATE SEQUENCE %s;' % sq_name
  185
+    trigger_sql = """CREATE OR REPLACE TRIGGER %s
  186
+  BEFORE INSERT ON %s
  187
+  FOR EACH ROW
  188
+  WHEN (new.id IS NULL)
  189
+    BEGIN
  190
+      SELECT %s.nextval INTO :new.id FROM dual;
  191
+    END;
  192
+    /""" % (tr_name, quote_name(table), sq_name)
  193
+    return sequence_sql, trigger_sql
  194
+
  195
+def get_drop_sequence(table):
  196
+    return "DROP SEQUENCE %s;" % quote_name(get_sequence_name(table))
  197
+
  198
+def _get_sequence_reset_sql():
  199
+    # TODO: colorize this SQL code with style.SQL_KEYWORD(), etc.
  200
+    return """
  201
+        DECLARE
  202
+            startvalue integer;
  203
+            cval integer;
  204
+        BEGIN
  205
+            LOCK TABLE %(table)s IN SHARE MODE;
  206
+            SELECT NVL(MAX(id), 0) INTO startvalue FROM %(table)s;
  207
+            SELECT %(sequence)s.nextval INTO cval FROM dual;
  208
+            cval := startvalue - cval;
  209
+            IF cval != 0 THEN
  210
+                EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s MINVALUE 0 INCREMENT BY '||cval;
  211
+                SELECT %(sequence)s.nextval INTO cval FROM dual;
  212
+                EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s INCREMENT BY 1';
  213
+            END IF;
  214
+            COMMIT;
  215
+        END;
  216
+        /"""
  217
+
125 218
 def get_sql_flush(style, tables, sequences):
126 219
     """Return a list of SQL statements required to remove all data from
127 220
     all tables in the database (without actually removing the tables
128 221
     themselves) and put the database in an empty 'initial' state
129 222
     """
130  
-    # Return a list of 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
131  
-    # TODO - SQL not actually tested against Oracle yet!
132  
-    # TODO - autoincrement indices reset required? See other get_sql_flush() implementations
133  
-    sql = ['%s %s;' % \
134  
-            (style.SQL_KEYWORD('TRUNCATE'),
135  
-             style.SQL_FIELD(quote_name(table))
136  
-             )  for table in tables]
  223
+    # Return a list of 'TRUNCATE x;', 'TRUNCATE y;',
  224
+    # 'TRUNCATE z;'... style SQL statements
  225
+    if tables:
  226
+        # Oracle does support TRUNCATE, but it seems to get us into
  227
+        # FK referential trouble, whereas DELETE FROM table works.
  228
+        sql = ['%s %s %s;' % \
  229
+                (style.SQL_KEYWORD('DELETE'),
  230
+                 style.SQL_KEYWORD('FROM'),
  231
+                 style.SQL_FIELD(quote_name(table))
  232
+                 ) for table in tables]
  233
+        # Since we've just deleted all the rows, running our sequence
  234
+        # ALTER code will reset the sequence to 0.
  235
+        for sequence_info in sequences:
  236
+            table_name = sequence_info['table']
  237
+            seq_name = get_sequence_name(table_name)
  238
+            query = _get_sequence_reset_sql() % {'sequence':seq_name,
  239
+                                                 'table':quote_name(table_name)}
  240
+            sql.append(query)
  241
+        return sql
  242
+    else:
  243
+        return []
  244
+
  245
+def get_sequence_name(table):
  246
+    name_length = get_max_name_length() - 3
  247
+    return '%s_SQ' % util.truncate_name(table, name_length).upper()
137 248
 
138 249
 def get_sql_sequence_reset(style, model_list):
139 250
     "Returns a list of the SQL statements to reset sequences for the given models."
140  
-    # No sequence reset required
141  
-    return []
  251
+    from django.db import models
  252
+    output = []
  253
+    query = _get_sequence_reset_sql()
  254
+    for model in model_list:
  255
+        for f in model._meta.fields:
  256
+            if isinstance(f, models.AutoField):
  257
+                sequence_name = get_sequence_name(model._meta.db_table)
  258
+                output.append(query % {'sequence':sequence_name,
  259
+                                       'table':model._meta.db_table})
  260
+                break # Only one AutoField is allowed per model, so don't bother continuing.
  261
+        for f in model._meta.many_to_many:
  262
+            sequence_name = get_sequence_name(f.m2m_db_table())
  263
+            output.append(query % {'sequence':sequence_name,
  264
+                                   'table':f.m2m_db_table()})
  265
+    return output
  266
+
  267
+def get_trigger_name(table):
  268
+    name_length = get_max_name_length() - 3
  269
+    return '%s_TR' % util.truncate_name(table, name_length).upper()
  270
+
  271
+def get_query_set_class(DefaultQuerySet):
  272
+    "Create a custom QuerySet class for Oracle."
  273
+
  274
+    from django.db import backend, connection
  275
+    from django.db.models.query import EmptyResultSet, GET_ITERATOR_CHUNK_SIZE, quote_only_if_word
  276
+
  277
+    class OracleQuerySet(DefaultQuerySet):
  278
+
  279
+        def iterator(self):
  280
+            "Performs the SELECT database lookup of this QuerySet."
  281
+
  282
+            from django.db.models.query import get_cached_row
  283
+
  284
+            # self._select is a dictionary, and dictionaries' key order is
  285
+            # undefined, so we convert it to a list of tuples.
  286
+            extra_select = self._select.items()
  287
+
  288
+            full_query = None
  289
+
  290
+            try:
  291
+                try:
  292
+                    select, sql, params, full_query = self._get_sql_clause(get_full_query=True)
  293
+                except TypeError:
  294
+                    select, sql, params = self._get_sql_clause()
  295
+            except EmptyResultSet:
  296
+                raise StopIteration
  297
+            if not full_query:
  298
+                full_query = "SELECT %s%s\n%s" % \
  299
+                             ((self._distinct and "DISTINCT " or ""),
  300
+                              ', '.join(select), sql)
  301
+
  302
+            cursor = connection.cursor()
  303
+            cursor.execute(full_query, params)
  304
+
  305
+            fill_cache = self._select_related
  306
+            fields = self.model._meta.fields
  307
+            index_end = len(fields)
  308
+
  309
+            # so here's the logic;
  310
+            # 1. retrieve each row in turn
  311
+            # 2. convert NCLOBs
  312
+
  313
+            while 1:
  314
+                rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
  315
+                if not rows:
  316
+                    raise StopIteration
  317
+                for row in rows:
  318
+                    row = self.resolve_columns(row, fields)
  319
+                    if fill_cache:
  320
+                        obj, index_end = get_cached_row(klass=self.model, row=row,
  321
+                                                        index_start=0, max_depth=self._max_related_depth)
  322
+                    else:
  323
+                        obj = self.model(*row[:index_end])
  324
+                    for i, k in enumerate(extra_select):
  325
+                        setattr(obj, k[0], row[index_end+i])
  326
+                    yield obj
  327
+
  328
+
  329
+        def _get_sql_clause(self, get_full_query=False):
  330
+            from django.db.models.query import fill_table_cache, \
  331
+                handle_legacy_orderlist, orderfield2column
  332
+
  333
+            opts = self.model._meta
  334
+
  335
+            # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z.
  336
+            select = ["%s.%s" % (backend.quote_name(opts.db_table), backend.quote_name(f.column)) for f in opts.fields]
  337
+            tables = [quote_only_if_word(t) for t in self._tables]
  338
+            joins = SortedDict()
  339
+            where = self._where[:]
  340
+            params = self._params[:]
  341
+
  342
+            # Convert self._filters into SQL.
  343
+            joins2, where2, params2 = self._filters.get_sql(opts)
  344
+            joins.update(joins2)
  345
+            where.extend(where2)
  346
+            params.extend(params2)
  347
+
  348
+            # Add additional tables and WHERE clauses based on select_related.
  349
+            if self._select_related:
  350
+                fill_table_cache(opts, select, tables, where, opts.db_table, [opts.db_table])
  351
+
  352
+            # Add any additional SELECTs.
  353
+            if self._select:
  354
+                select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select.items()])
  355
+
  356
+            # Start composing the body of the SQL statement.
  357
+            sql = [" FROM", backend.quote_name(opts.db_table)]
  358
+
  359
+            # Compose the join dictionary into SQL describing the joins.
  360
+            if joins:
  361
+                sql.append(" ".join(["%s %s %s ON %s" % (join_type, table, alias, condition)
  362
+                                for (alias, (table, join_type, condition)) in joins.items()]))
  363
+
  364
+            # Compose the tables clause into SQL.
  365
+            if tables:
  366
+                sql.append(", " + ", ".join(tables))
  367
+
  368
+            # Compose the where clause into SQL.
  369
+            if where:
  370
+                sql.append(where and "WHERE " + " AND ".join(where))
  371
+
  372
+            # ORDER BY clause
  373
+            order_by = []
  374
+            if self._order_by is not None:
  375
+                ordering_to_use = self._order_by
  376
+            else:
  377
+                ordering_to_use = opts.ordering
  378
+            for f in handle_legacy_orderlist(ordering_to_use):
  379
+                if f == '?': # Special case.
  380
+                    order_by.append(backend.get_random_function_sql())
  381
+                else:
  382
+                    if f.startswith('-'):
  383
+                        col_name = f[1:]
  384
+                        order = "DESC"
  385
+                    else:
  386
+                        col_name = f
  387
+                        order = "ASC"
  388
+                    if "." in col_name:
  389
+                        table_prefix, col_name = col_name.split('.', 1)
  390
+                        table_prefix = backend.quote_name(table_prefix) + '.'
  391
+                    else:
  392
+                        # Use the database table as a column prefix if it wasn't given,
  393
+                        # and if the requested column isn't a custom SELECT.
  394
+                        if "." not in col_name and col_name not in (self._select or ()):
  395
+                            table_prefix = backend.quote_name(opts.db_table) + '.'
  396
+                        else:
  397
+                            table_prefix = ''
  398
+                    order_by.append('%s%s %s' % (table_prefix, backend.quote_name(orderfield2column(col_name, opts)), order))
  399
+            if order_by:
  400
+                sql.append("ORDER BY " + ", ".join(order_by))
  401
+
  402
+            # Look for column name collisions in the select elements
  403
+            # and fix them with an AS alias.  This allows us to do a
  404
+            # SELECT * later in the paging query.
  405
+            cols = [clause.split('.')[-1] for clause in select]
  406
+            for index, col in enumerate(cols):
  407
+                if cols.count(col) > 1:
  408
+                    col = '%s%d' % (col.replace('"', ''), index)
  409
+                    cols[index] = col
  410
+                    select[index] = '%s AS %s' % (select[index], col)
  411
+
  412
+            # LIMIT and OFFSET clauses
  413
+            # To support limits and offsets, Oracle requires some funky rewriting of an otherwise normal looking query.
  414
+            select_clause = ",".join(select)
  415
+            distinct = (self._distinct and "DISTINCT " or "")
  416
+
  417
+            if order_by:
  418
+                order_by_clause = " OVER (ORDER BY %s )" % (", ".join(order_by))
  419
+            else:
  420
+                #Oracle's row_number() function always requires an order-by clause.
  421
+                #So we need to define a default order-by, since none was provided.
  422
+                order_by_clause = " OVER (ORDER BY %s.%s)" % \
  423
+                    (backend.quote_name(opts.db_table),
  424
+                    backend.quote_name(opts.fields[0].db_column or opts.fields[0].column))
  425
+            # limit_and_offset_clause
  426
+            if self._limit is None:
  427
+                assert self._offset is None, "'offset' is not allowed without 'limit'"
  428
+
  429
+            if self._offset is not None:
  430
+                offset = int(self._offset)
  431
+            else:
  432
+                offset = 0
  433
+            if self._limit is not None:
  434
+                limit = int(self._limit)
  435
+            else:
  436
+                limit = None
  437
+
  438
+            limit_and_offset_clause = ''
  439
+            if limit is not None:
  440
+                limit_and_offset_clause = "WHERE rn > %s AND rn <= %s" % (offset, limit+offset)
  441
+            elif offset:
  442
+                limit_and_offset_clause = "WHERE rn > %s" % (offset)
  443
+
  444
+            if len(limit_and_offset_clause) > 0:
  445
+                fmt = \
  446
+"""SELECT * FROM
  447
+  (SELECT %s%s,
  448
+          ROW_NUMBER()%s AS rn
  449
+   %s)
  450
+%s"""
  451
+                full_query = fmt % (distinct, select_clause,
  452
+                                    order_by_clause, ' '.join(sql).strip(),
  453
+                                    limit_and_offset_clause)
  454
+            else:
  455
+                full_query = None
  456
+
  457
+            if get_full_query:
  458
+                return select, " ".join(sql), params, full_query
  459
+            else:
  460
+                return select, " ".join(sql), params
  461
+
  462
+        def resolve_columns(self, row, fields=()):
  463
+            from django.db.models.fields import DateField, DateTimeField, \
  464
+                TimeField, BooleanField, NullBooleanField, DecimalField, Field
  465
+            values = []
  466
+            for value, field in map(None, row, fields):
  467
+                if isinstance(value, Database.LOB):
  468
+                    value = value.read()
  469
+                # Oracle stores empty strings as null. We need to undo this in
  470
+                # order to adhere to the Django convention of using the empty
  471
+                # string instead of null, but only if the field accepts the
  472
+                # empty string.
  473
+                if value is None and isinstance(field, Field) and field.empty_strings_allowed:
  474
+                    value = ''
  475
+                # Convert 1 or 0 to True or False
  476
+                elif value in (1, 0) and isinstance(field, (BooleanField, NullBooleanField)):
  477
+                    value = bool(value)
  478
+                # Convert floats to decimals
  479
+                elif value is not None and isinstance(field, DecimalField):
  480
+                    value = util.typecast_decimal(field.format_number(value))
  481
+                # cx_Oracle always returns datetime.datetime objects for
  482
+                # DATE and TIMESTAMP columns, but Django wants to see a
  483
+                # python datetime.date, .time, or .datetime.  We use the type
  484
+                # of the Field to determine which to cast to, but it's not
  485
+                # always available.
  486
+                # As a workaround, we cast to date if all the time-related
  487
+                # values are 0, or to time if the date is 1/1/1900.
  488
+                # This could be cleaned a bit by adding a method to the Field
  489
+                # classes to normalize values from the database (the to_python
  490
+                # method is used for validation and isn't what we want here).
  491
+                elif isinstance(value, Database.Timestamp):
  492
+                    # In Python 2.3, the cx_Oracle driver returns its own
  493
+                    # Timestamp object that we must convert to a datetime class.
  494
+                    if not isinstance(value, datetime.datetime):
  495
+                        value = datetime.datetime(value.year, value.month, value.day, value.hour,
  496
+                                                  value.minute, value.second, value.fsecond)
  497
+                    if isinstance(field, DateTimeField):
  498
+                        pass  # DateTimeField subclasses DateField so must be checked first.
  499
+                    elif isinstance(field, DateField):
  500
+                        value = value.date()
  501
+                    elif isinstance(field, TimeField) or (value.year == 1900 and value.month == value.day == 1):
  502
+                        value = value.time()
  503
+                    elif value.hour == value.minute == value.second == value.microsecond == 0:
  504
+                        value = value.date()
  505
+                values.append(value)
  506
+            return values
  507
+
  508
+    return OracleQuerySet
  509
+
142 510
 
143 511
 OPERATOR_MAPPING = {
144 512
     'exact': '= %s',
145  
-    'iexact': 'LIKE %s',
146  
-    'contains': 'LIKE %s',
147  
-    'icontains': 'LIKE %s',
  513
+    'iexact': '= UPPER(%s)',
  514
+    'contains': "LIKE %s ESCAPE '\\'",
  515
+    'icontains': "LIKE UPPER(%s) ESCAPE '\\'",
148 516
     'gt': '> %s',
149 517
     'gte': '>= %s',
150 518
     'lt': '< %s',
151 519
     'lte': '<= %s',
152  
-    'startswith': 'LIKE %s',
153  
-    'endswith': 'LIKE %s',
154  
-    'istartswith': 'LIKE %s',
155  
-    'iendswith': 'LIKE %s',
  520
+    'startswith': "LIKE %s ESCAPE '\\'",
  521
+    'endswith': "LIKE %s ESCAPE '\\'",
  522
+    'istartswith': "LIKE UPPER(%s) ESCAPE '\\'",
  523
+    'iendswith': "LIKE UPPER(%s) ESCAPE '\\'",
156 524
 }
11  django/db/backends/oracle/client.py
@@ -2,9 +2,10 @@
2 2
 import os
3 3
 
4 4
 def runshell():
5  
-    args = ''
6  
-    args += settings.DATABASE_USER
  5
+    dsn = settings.DATABASE_USER
7 6
     if settings.DATABASE_PASSWORD:
8  
-        args += "/%s" % settings.DATABASE_PASSWORD
9  
-    args += "@%s" % settings.DATABASE_NAME
10  
-    os.execvp('sqlplus', args)
  7
+        dsn += "/%s" % settings.DATABASE_PASSWORD
  8
+    if settings.DATABASE_NAME:
  9
+        dsn += "@%s" % settings.DATABASE_NAME
  10
+    args = ["sqlplus", "-L", dsn]
  11
+    os.execvp("sqlplus", args)
326  django/db/backends/oracle/creation.py
... ...
@@ -1,26 +1,304 @@
  1
+import sys, time
  2
+from django.core import management
  3
+
  4
+# This dictionary maps Field objects to their associated Oracle column
  5
+# types, as strings. Column-type strings can contain format strings; they'll
  6
+# be interpolated against the values of Field.__dict__ before being output.
  7
+# If a column type is set to None, it won't be included in the output.
1 8
 DATA_TYPES = {
2  
-    'AutoField':         'number(38)',
3  
-    'BooleanField':      'number(1)',
4  
-    'CharField':         'varchar2(%(maxlength)s)',
5  
-    'CommaSeparatedIntegerField': 'varchar2(%(maxlength)s)',
6  
-    'DateField':         'date',
7  
-    'DateTimeField':     'date',
8  
-    'DecimalField':      'number(%(max_digits)s, %(decimal_places)s)',
9  
-    'FileField':         'varchar2(100)',
10  
-    'FilePathField':     'varchar2(100)',
11  
-    'FloatField':        'double precision',
12  
-    'ImageField':        'varchar2(100)',
13  
-    'IntegerField':      'integer',
14  
-    'IPAddressField':    'char(15)',
15  
-    'ManyToManyField':   None,
16  
-    'NullBooleanField':  'integer',
17  
-    'OneToOneField':     'integer',
18  
-    'PhoneNumberField':  'varchar(20)',
19  
-    'PositiveIntegerField': 'integer',
20  
-    'PositiveSmallIntegerField': 'smallint',
21  
-    'SlugField':         'varchar(50)',
22  
-    'SmallIntegerField': 'smallint',
23  
-    'TextField':         'long',
24  
-    'TimeField':         'timestamp',
25  
-    'USStateField':      'varchar(2)',
  9
+    'AutoField':                    'NUMBER(11)',
  10
+    'BooleanField':                 'NUMBER(1) CHECK (%(column)s IN (0,1))',
  11
+    'CharField':                    'VARCHAR2(%(maxlength)s)',
  12
+    'CommaSeparatedIntegerField':   'VARCHAR2(%(maxlength)s)',
  13
+    'DateField':                    'DATE',
  14
+    'DateTimeField':                'TIMESTAMP',
  15
+    'DecimalField':                 'NUMBER(%(max_digits)s, %(decimal_places)s)',
  16
+    'FileField':                    'VARCHAR2(100)',
  17
+    'FilePathField':                'VARCHAR2(100)',
  18
+    'FloatField':                   'DOUBLE PRECISION',
  19
+    'ImageField':                   'VARCHAR2(100)',
  20
+    'IntegerField':                 'NUMBER(11)',
  21
+    'IPAddressField':               'VARCHAR2(15)',
  22
+    'ManyToManyField':              None,
  23
+    'NullBooleanField':             'NUMBER(1) CHECK ((%(column)s IN (0,1)) OR (%(column)s IS NULL))',
  24
+    'OneToOneField':                'NUMBER(11)',
  25
+    'PhoneNumberField':             'VARCHAR2(20)',
  26
+    'PositiveIntegerField':         'NUMBER(11) CHECK (%(column)s >= 0)',
  27
+    'PositiveSmallIntegerField':    'NUMBER(11) CHECK (%(column)s >= 0)',
  28
+    'SlugField':                    'VARCHAR2(50)',
  29
+    'SmallIntegerField':            'NUMBER(11)',
  30
+    'TextField':                    'NCLOB',