mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	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:
		
				
					committed by
					
						 Mariusz Felisiak
						Mariusz Felisiak
					
				
			
			
				
	
			
			
			
						parent
						
							34a88b21da
						
					
				
				
					commit
					bc91f27a86
				
			| @@ -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. | ||||||
|   | |||||||
| @@ -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: | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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): | ||||||
|   | |||||||
| @@ -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: | ||||||
|   | |||||||
| @@ -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` | ||||||
| --------------------------- | --------------------------- | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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() | ||||||
|   | |||||||
| @@ -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"') | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user