Refs #29444 -- Added support for fetching a returned non-integer insert values on Oracle.

This is currently not actively used, since the ORM will ask the
SQL compiler to only return auto fields.
This commit is contained in:
Johannes Hoppe 2019-06-10 13:56:50 +02:00 committed by Mariusz Felisiak
parent 34a88b21da
commit bc91f27a86
8 changed files with 49 additions and 9 deletions

View File

@ -311,7 +311,7 @@ class BaseDatabaseOperations:
""" """
return value return value
def return_insert_id(self): def return_insert_id(self, field):
""" """
For backends that support returning the last insert ID as part of an For backends that support returning the last insert ID as part of an
insert query, return the SQL and params to append to the INSERT query. insert query, return the SQL and params to append to the INSERT query.

View File

@ -12,7 +12,7 @@ from django.utils.encoding import force_bytes, force_str
from django.utils.functional import cached_property from django.utils.functional import cached_property
from .base import Database from .base import Database
from .utils import BulkInsertMapper, InsertIdVar, Oracle_datetime from .utils import BulkInsertMapper, InsertVar, Oracle_datetime
class DatabaseOperations(BaseDatabaseOperations): class DatabaseOperations(BaseDatabaseOperations):
@ -333,8 +333,8 @@ END;
match_option = "'i'" match_option = "'i'"
return 'REGEXP_LIKE(%%s, %%s, %s)' % match_option return 'REGEXP_LIKE(%%s, %%s, %s)' % match_option
def return_insert_id(self): def return_insert_id(self, field):
return "RETURNING %s INTO %%s", (InsertIdVar(),) return 'RETURNING %s INTO %%s', (InsertVar(field),)
def __foreign_key_constraints(self, table_name, recursive): def __foreign_key_constraints(self, table_name, recursive):
with self.connection.cursor() as cursor: with self.connection.cursor() as cursor:

View File

@ -3,14 +3,26 @@ import datetime
from .base import Database from .base import Database
class InsertIdVar: class InsertVar:
""" """
A late-binding cursor variable that can be passed to Cursor.execute A late-binding cursor variable that can be passed to Cursor.execute
as a parameter, in order to receive the id of the row created by an as a parameter, in order to receive the id of the row created by an
insert statement. insert statement.
""" """
types = {
'FloatField': Database.NATIVE_FLOAT,
'CharField': str,
'DateTimeField': Database.TIMESTAMP,
'DateField': Database.DATETIME,
'DecimalField': Database.NUMBER,
}
def __init__(self, field):
internal_type = getattr(field, 'target_field', field).get_internal_type()
self.db_type = self.types.get(internal_type, int)
def bind_parameter(self, cursor): def bind_parameter(self, cursor):
param = cursor.cursor.var(int) param = cursor.cursor.var(self.db_type)
cursor._insert_id_var = param cursor._insert_id_var = param
return param return param

View File

@ -235,7 +235,7 @@ class DatabaseOperations(BaseDatabaseOperations):
return cursor.query.decode() return cursor.query.decode()
return None return None
def return_insert_id(self): def return_insert_id(self, field):
return "RETURNING %s", () return "RETURNING %s", ()
def bulk_insert_sql(self, fields, placeholder_rows): def bulk_insert_sql(self, fields, placeholder_rows):

View File

@ -1304,7 +1304,7 @@ class SQLInsertCompiler(SQLCompiler):
if ignore_conflicts_suffix_sql: if ignore_conflicts_suffix_sql:
result.append(ignore_conflicts_suffix_sql) result.append(ignore_conflicts_suffix_sql)
col = "%s.%s" % (qn(opts.db_table), qn(opts.pk.column)) col = "%s.%s" % (qn(opts.db_table), qn(opts.pk.column))
r_fmt, r_params = self.connection.ops.return_insert_id() r_fmt, r_params = self.connection.ops.return_insert_id(opts.pk)
# Skip empty r_fmt to allow subclasses to customize behavior for # Skip empty r_fmt to allow subclasses to customize behavior for
# 3rd party backends. Refs #19096. # 3rd party backends. Refs #19096.
if r_fmt: if r_fmt:

View File

@ -353,6 +353,9 @@ backends.
:class:`~django.db.models.DateTimeField` in ``datetime_cast_date_sql()``, :class:`~django.db.models.DateTimeField` in ``datetime_cast_date_sql()``,
``datetime_extract_sql()``, etc. ``datetime_extract_sql()``, etc.
* ``DatabaseOperations.return_insert_id()`` now requires an additional
``field`` argument with the model field.
:mod:`django.contrib.admin` :mod:`django.contrib.admin`
--------------------------- ---------------------------

View File

@ -5,6 +5,10 @@ from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
class NonIntegerAutoField(models.Model):
creation_datetime = models.DateTimeField(primary_key=True)
class Square(models.Model): class Square(models.Model):
root = models.IntegerField() root = models.IntegerField()
square = models.PositiveIntegerField() square = models.PositiveIntegerField()

View File

@ -1,3 +1,4 @@
import datetime
import unittest import unittest
from django.db import connection from django.db import connection
@ -5,7 +6,7 @@ from django.db.models.fields import BooleanField, NullBooleanField
from django.db.utils import DatabaseError from django.db.utils import DatabaseError
from django.test import TransactionTestCase from django.test import TransactionTestCase
from ..models import Square from ..models import NonIntegerAutoField, Square
@unittest.skipUnless(connection.vendor == 'oracle', 'Oracle tests') @unittest.skipUnless(connection.vendor == 'oracle', 'Oracle tests')
@ -95,3 +96,23 @@ class TransactionalTests(TransactionTestCase):
self.assertIn('ORA-01017', context.exception.args[0].message) self.assertIn('ORA-01017', context.exception.args[0].message)
finally: finally:
connection.settings_dict['PASSWORD'] = old_password connection.settings_dict['PASSWORD'] = old_password
def test_non_integer_auto_field(self):
with connection.cursor() as cursor:
# Create trigger that fill non-integer auto field.
cursor.execute("""
CREATE OR REPLACE TRIGGER "TRG_FILL_CREATION_DATETIME"
BEFORE INSERT ON "BACKENDS_NONINTEGERAUTOFIELD"
FOR EACH ROW
BEGIN
:NEW.CREATION_DATETIME := SYSTIMESTAMP;
END;
""")
try:
NonIntegerAutoField._meta.auto_field = NonIntegerAutoField.creation_datetime
obj = NonIntegerAutoField.objects.create()
self.assertIsNotNone(obj.creation_datetime)
self.assertIsInstance(obj.creation_datetime, datetime.datetime)
finally:
with connection.cursor() as cursor:
cursor.execute('DROP TRIGGER "TRG_FILL_CREATION_DATETIME"')