summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Hoppe <info@johanneshoppe.com>2019-06-10 13:56:50 +0200
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2019-07-08 08:53:08 +0200
commitbc91f27a86090b4c688b56cd4e37f95eebe6e969 (patch)
tree52d2e64c5e468f1aeb35ea89b244e38f17239aff
parent34a88b21dae71a26a9b136b599e5cbcf817eae16 (diff)
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.
-rw-r--r--django/db/backends/base/operations.py2
-rw-r--r--django/db/backends/oracle/operations.py6
-rw-r--r--django/db/backends/oracle/utils.py16
-rw-r--r--django/db/backends/postgresql/operations.py2
-rw-r--r--django/db/models/sql/compiler.py2
-rw-r--r--docs/releases/3.0.txt3
-rw-r--r--tests/backends/models.py4
-rw-r--r--tests/backends/oracle/tests.py23
8 files changed, 49 insertions, 9 deletions
diff --git a/django/db/backends/base/operations.py b/django/db/backends/base/operations.py
index b7e35760cb..d17dc8dcce 100644
--- a/django/db/backends/base/operations.py
+++ b/django/db/backends/base/operations.py
@@ -311,7 +311,7 @@ class BaseDatabaseOperations:
"""
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
insert query, return the SQL and params to append to the INSERT query.
diff --git a/django/db/backends/oracle/operations.py b/django/db/backends/oracle/operations.py
index 6669ca5beb..77c568e84b 100644
--- a/django/db/backends/oracle/operations.py
+++ b/django/db/backends/oracle/operations.py
@@ -12,7 +12,7 @@ from django.utils.encoding import force_bytes, force_str
from django.utils.functional import cached_property
from .base import Database
-from .utils import BulkInsertMapper, InsertIdVar, Oracle_datetime
+from .utils import BulkInsertMapper, InsertVar, Oracle_datetime
class DatabaseOperations(BaseDatabaseOperations):
@@ -333,8 +333,8 @@ END;
match_option = "'i'"
return 'REGEXP_LIKE(%%s, %%s, %s)' % match_option
- def return_insert_id(self):
- return "RETURNING %s INTO %%s", (InsertIdVar(),)
+ def return_insert_id(self, field):
+ return 'RETURNING %s INTO %%s', (InsertVar(field),)
def __foreign_key_constraints(self, table_name, recursive):
with self.connection.cursor() as cursor:
diff --git a/django/db/backends/oracle/utils.py b/django/db/backends/oracle/utils.py
index 02e3f754a8..64e69136bf 100644
--- a/django/db/backends/oracle/utils.py
+++ b/django/db/backends/oracle/utils.py
@@ -3,14 +3,26 @@ import datetime
from .base import Database
-class InsertIdVar:
+class InsertVar:
"""
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
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):
- param = cursor.cursor.var(int)
+ param = cursor.cursor.var(self.db_type)
cursor._insert_id_var = param
return param
diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py
index c502760e93..0c497edf71 100644
--- a/django/db/backends/postgresql/operations.py
+++ b/django/db/backends/postgresql/operations.py
@@ -235,7 +235,7 @@ class DatabaseOperations(BaseDatabaseOperations):
return cursor.query.decode()
return None
- def return_insert_id(self):
+ def return_insert_id(self, field):
return "RETURNING %s", ()
def bulk_insert_sql(self, fields, placeholder_rows):
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index a14f1254aa..eaccc96d7d 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -1304,7 +1304,7 @@ class SQLInsertCompiler(SQLCompiler):
if ignore_conflicts_suffix_sql:
result.append(ignore_conflicts_suffix_sql)
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
# 3rd party backends. Refs #19096.
if r_fmt:
diff --git a/docs/releases/3.0.txt b/docs/releases/3.0.txt
index c7f1a4e78b..6c25f79d7b 100644
--- a/docs/releases/3.0.txt
+++ b/docs/releases/3.0.txt
@@ -353,6 +353,9 @@ backends.
:class:`~django.db.models.DateTimeField` in ``datetime_cast_date_sql()``,
``datetime_extract_sql()``, etc.
+* ``DatabaseOperations.return_insert_id()`` now requires an additional
+ ``field`` argument with the model field.
+
:mod:`django.contrib.admin`
---------------------------
diff --git a/tests/backends/models.py b/tests/backends/models.py
index 1fa8d44e63..a2c8616cc6 100644
--- a/tests/backends/models.py
+++ b/tests/backends/models.py
@@ -5,6 +5,10 @@ from django.contrib.contenttypes.models import ContentType
from django.db import models
+class NonIntegerAutoField(models.Model):
+ creation_datetime = models.DateTimeField(primary_key=True)
+
+
class Square(models.Model):
root = models.IntegerField()
square = models.PositiveIntegerField()
diff --git a/tests/backends/oracle/tests.py b/tests/backends/oracle/tests.py
index a0d49854d9..30d981da69 100644
--- a/tests/backends/oracle/tests.py
+++ b/tests/backends/oracle/tests.py
@@ -1,3 +1,4 @@
+import datetime
import unittest
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.test import TransactionTestCase
-from ..models import Square
+from ..models import NonIntegerAutoField, Square
@unittest.skipUnless(connection.vendor == 'oracle', 'Oracle tests')
@@ -95,3 +96,23 @@ class TransactionalTests(TransactionTestCase):
self.assertIn('ORA-01017', context.exception.args[0].message)
finally:
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"')