Ticket #29444: refs-29444-oracle-part.diff
File refs-29444-oracle-part.diff, 8.2 KB (added by , 5 years ago) |
---|
-
django/db/backends/base/operations.py
diff --git a/django/db/backends/base/operations.py b/django/db/backends/base/operations.py index 51370ef2acba..251a670bafd4 100644
a b def distinct_sql(self, fields, params): 176 176 else: 177 177 return ['DISTINCT'], [] 178 178 179 def fetch_returned_insert_columns(self, cursor ):179 def fetch_returned_insert_columns(self, cursor, return_params): 180 180 """ 181 181 Given a cursor object that has just performed an INSERT...RETURNING 182 182 statement into a table, return the newly created data. -
django/db/backends/oracle/features.py
diff --git a/django/db/backends/oracle/features.py b/django/db/backends/oracle/features.py index 73a6e8668648..4b3d26d74b1a 100644
a b class DatabaseFeatures(BaseDatabaseFeatures): 10 10 has_select_for_update_of = True 11 11 select_for_update_of_column = True 12 12 can_return_columns_from_insert = True 13 can_return_multiple_columns_from_insert = True 13 14 can_introspect_autofield = True 14 15 supports_subqueries_in_group_by = False 15 16 supports_transactions = True -
django/db/backends/oracle/operations.py
diff --git a/django/db/backends/oracle/operations.py b/django/db/backends/oracle/operations.py index df3710f66baf..d3552ec643eb 100644
a b def convert_empty_bytes(value, expression, connection): 248 248 def deferrable_sql(self): 249 249 return " DEFERRABLE INITIALLY DEFERRED" 250 250 251 def fetch_returned_insert_columns(self, cursor): 252 value = cursor._insert_id_var.getvalue() 253 if value is None or value == []: 254 # cx_Oracle < 6.3 returns None, >= 6.3 returns empty list. 255 raise DatabaseError( 256 'The database did not return a new row id. Probably "ORA-1403: ' 257 'no data found" was raised internally but was hidden by the ' 258 'Oracle OCI library (see https://code.djangoproject.com/ticket/28859).' 259 ) 260 # cx_Oracle < 7 returns value, >= 7 returns list with single value. 261 return value if isinstance(value, list) else [value] 251 def fetch_returned_insert_columns(self, cursor, r_params): 252 for param in r_params: 253 value = param.bound_param.getvalue() 254 if value is None or value == []: 255 # cx_Oracle < 6.3 returns None, >= 6.3 returns empty list. 256 raise DatabaseError( 257 'The database did not return a new row id. Probably "ORA-1403: ' 258 'no data found" was raised internally but was hidden by the ' 259 'Oracle OCI library (see https://code.djangoproject.com/ticket/28859).' 260 ) 261 # cx_Oracle < 7 returns value, >= 7 returns list with single value. 262 yield value[0] if isinstance(value, list) else value 262 263 263 264 def field_cast_sql(self, db_type, internal_type): 264 265 if db_type and db_type.endswith('LOB'): … … def regex_lookup(self, lookup_type): 344 345 def return_insert_columns(self, fields): 345 346 if not fields: 346 347 return '', () 347 sql = 'RETURNING %s.%s INTO %%s' % ( 348 self.quote_name(fields[0].model._meta.db_table), 349 self.quote_name(fields[0].column), 350 ) 351 return sql, (InsertVar(fields[0]),) 348 field_names = [] 349 params = [] 350 for field in fields: 351 field_names.append("%s.%s" % ( 352 self.quote_name(field.model._meta.db_table), 353 self.quote_name(field.column) 354 )) 355 params.append(InsertVar(field)) 356 357 return ('RETURNING %s INTO %s' % ( 358 ', '.join(field_names), 359 ', '.join('%s' for _ in params) 360 ), tuple(params)) 352 361 353 362 def __foreign_key_constraints(self, table_name, recursive): 354 363 with self.connection.cursor() as cursor: -
django/db/backends/oracle/utils.py
diff --git a/django/db/backends/oracle/utils.py b/django/db/backends/oracle/utils.py index fdd6dee61729..c8f8c2aacd40 100644
a b class InsertVar: 27 27 def __init__(self, field): 28 28 internal_type = getattr(field, 'target_field', field).get_internal_type() 29 29 self.db_type = self.types.get(internal_type, str) 30 self.bound_param = None 30 31 31 32 def bind_parameter(self, cursor): 32 param = cursor.cursor.var(self.db_type) 33 cursor._insert_id_var = param 34 return param 33 self.bound_param = cursor.cursor.var(self.db_type) 34 return self.bound_param 35 35 36 36 37 37 class Oracle_datetime(datetime.datetime): -
django/db/models/sql/compiler.py
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 5193362a92cd..5f8d4165fe82 100644
a b def explain_query(self): 1160 1160 1161 1161 class SQLInsertCompiler(SQLCompiler): 1162 1162 returning_fields = None 1163 r_params = tuple() 1163 1164 1164 1165 def field_as_sql(self, field, val): 1165 1166 """ … … def as_sql(self): 1308 1309 result.append(ignore_conflicts_suffix_sql) 1309 1310 # Skip empty r_sql to allow subclasses to customize behavior for 1310 1311 # 3rd party backends. Refs #19096. 1311 r_sql, r_params = self.connection.ops.return_insert_columns(self.returning_fields)1312 r_sql, self.r_params = self.connection.ops.return_insert_columns(self.returning_fields) 1312 1313 if r_sql: 1313 1314 result.append(r_sql) 1314 params += [ r_params]1315 params += [self.r_params] 1315 1316 return [(" ".join(result), tuple(chain.from_iterable(params)))] 1316 1317 1317 1318 if can_bulk: … … def execute_sql(self, returning_fields=None): 1350 1351 'not supported on this database backend.' 1351 1352 ) 1352 1353 assert len(self.query.objs) == 1 1353 return self.connection.ops.fetch_returned_insert_columns(cursor )1354 return self.connection.ops.fetch_returned_insert_columns(cursor, self.r_params) 1354 1355 return [self.connection.ops.last_insert_id( 1355 1356 cursor, self.query.get_meta().db_table, self.query.get_meta().pk.column 1356 1357 )] -
tests/queries/test_db_returning.py
diff --git a/tests/queries/test_db_returning.py b/tests/queries/test_db_returning.py index a8ac8394d2ec..5d4342cc1a17 100644
a b 1 1 import datetime 2 import unittest 2 3 3 4 from django.db import NotSupportedError, connection 4 5 from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature … … def test_insert_returning(self): 20 21 captured_queries[-1]['sql'], 21 22 ) 22 23 23 @ skipUnlessDBFeature('can_return_multiple_columns_from_insert')24 def test_insert_returning_multiple (self):24 @unittest.skipUnless(connection.vendor == 'postgresql', "PostgreSQL specific tests") 25 def test_insert_returning_multiple__pg(self): 25 26 with CaptureQueriesContext(connection) as captured_queries: 26 27 obj = ReturningModel.objects.create() 27 28 table_name = connection.ops.quote_name(ReturningModel._meta.db_table) … … def test_insert_returning_multiple(self): 37 38 self.assertTrue(obj.pk) 38 39 self.assertIsInstance(obj.created, datetime.datetime) 39 40 41 @unittest.skipUnless(connection.vendor == 'oracle', "Oracle specific tests") 42 def test_insert_returning_multiple__oracle(self): 43 with CaptureQueriesContext(connection) as captured_queries: 44 obj = ReturningModel.objects.create() 45 table_name = connection.ops.quote_name(ReturningModel._meta.db_table) 46 self.assertIn( 47 'RETURNING %s.%s, %s.%s INTO ' % ( 48 table_name, 49 connection.ops.quote_name(ReturningModel._meta.get_field('id').column), 50 table_name, 51 connection.ops.quote_name(ReturningModel._meta.get_field('created').column), 52 ), 53 captured_queries[-1]['sql'], 54 ) 55 self.assertTrue(obj.pk) 56 self.assertIsInstance(obj.created, datetime.datetime) 57 40 58 @skipIfDBFeature('can_return_multiple_columns_from_insert') 41 59 def test_insert_returning_multiple_not_supported(self): 42 60 msg = (