Ticket #29444: refs-29444-oracle-part.diff

File refs-29444-oracle-part.diff, 8.2 KB (added by felixxm, 3 months 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): 
    176176        else:
    177177            return ['DISTINCT'], []
    178178
    179     def fetch_returned_insert_columns(self, cursor):
     179    def fetch_returned_insert_columns(self, cursor, return_params):
    180180        """
    181181        Given a cursor object that has just performed an INSERT...RETURNING
    182182        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): 
    1010    has_select_for_update_of = True
    1111    select_for_update_of_column = True
    1212    can_return_columns_from_insert = True
     13    can_return_multiple_columns_from_insert = True
    1314    can_introspect_autofield = True
    1415    supports_subqueries_in_group_by = False
    1516    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): 
    248248    def deferrable_sql(self):
    249249        return " DEFERRABLE INITIALLY DEFERRED"
    250250
    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
    262263
    263264    def field_cast_sql(self, db_type, internal_type):
    264265        if db_type and db_type.endswith('LOB'):
    def regex_lookup(self, lookup_type): 
    344345    def return_insert_columns(self, fields):
    345346        if not fields:
    346347            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))
    352361
    353362    def __foreign_key_constraints(self, table_name, recursive):
    354363        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: 
    2727    def __init__(self, field):
    2828        internal_type = getattr(field, 'target_field', field).get_internal_type()
    2929        self.db_type = self.types.get(internal_type, str)
     30        self.bound_param = None
    3031
    3132    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
    3535
    3636
    3737class 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): 
    11601160
    11611161class SQLInsertCompiler(SQLCompiler):
    11621162    returning_fields = None
     1163    r_params = tuple()
    11631164
    11641165    def field_as_sql(self, field, val):
    11651166        """
    def as_sql(self): 
    13081309                result.append(ignore_conflicts_suffix_sql)
    13091310            # Skip empty r_sql to allow subclasses to customize behavior for
    13101311            # 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)
    13121313            if r_sql:
    13131314                result.append(r_sql)
    1314                 params += [r_params]
     1315                params += [self.r_params]
    13151316            return [(" ".join(result), tuple(chain.from_iterable(params)))]
    13161317
    13171318        if can_bulk:
    def execute_sql(self, returning_fields=None): 
    13501351                        'not supported on this database backend.'
    13511352                    )
    13521353                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)
    13541355            return [self.connection.ops.last_insert_id(
    13551356                cursor, self.query.get_meta().db_table, self.query.get_meta().pk.column
    13561357            )]
  • 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  
    11import datetime
     2import unittest
    23
    34from django.db import NotSupportedError, connection
    45from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
    def test_insert_returning(self): 
    2021            captured_queries[-1]['sql'],
    2122        )
    2223
    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):
    2526        with CaptureQueriesContext(connection) as captured_queries:
    2627            obj = ReturningModel.objects.create()
    2728        table_name = connection.ops.quote_name(ReturningModel._meta.db_table)
    def test_insert_returning_multiple(self): 
    3738        self.assertTrue(obj.pk)
    3839        self.assertIsInstance(obj.created, datetime.datetime)
    3940
     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
    4058    @skipIfDBFeature('can_return_multiple_columns_from_insert')
    4159    def test_insert_returning_multiple_not_supported(self):
    4260        msg = (
Back to Top