Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

operations.py 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. import datetime
  2. import re
  3. import uuid
  4. from django.conf import settings
  5. from django.db.backends.base.operations import BaseDatabaseOperations
  6. from django.db.backends.utils import strip_quotes, truncate_name
  7. from django.db.utils import DatabaseError
  8. from django.utils import timezone
  9. from django.utils.encoding import force_bytes
  10. from django.utils.functional import cached_property
  11. from .base import Database
  12. from .utils import BulkInsertMapper, InsertIdVar, Oracle_datetime
  13. class DatabaseOperations(BaseDatabaseOperations):
  14. # Oracle uses NUMBER(11) and NUMBER(19) for integer fields.
  15. integer_field_ranges = {
  16. 'SmallIntegerField': (-99999999999, 99999999999),
  17. 'IntegerField': (-99999999999, 99999999999),
  18. 'BigIntegerField': (-9999999999999999999, 9999999999999999999),
  19. 'PositiveSmallIntegerField': (0, 99999999999),
  20. 'PositiveIntegerField': (0, 99999999999),
  21. }
  22. set_operators = {**BaseDatabaseOperations.set_operators, 'difference': 'MINUS'}
  23. # TODO: colorize this SQL code with style.SQL_KEYWORD(), etc.
  24. _sequence_reset_sql = """
  25. DECLARE
  26. table_value integer;
  27. seq_value integer;
  28. seq_name user_tab_identity_cols.sequence_name%%TYPE;
  29. BEGIN
  30. BEGIN
  31. SELECT sequence_name INTO seq_name FROM user_tab_identity_cols
  32. WHERE table_name = '%(table_name)s' AND
  33. column_name = '%(column_name)s';
  34. EXCEPTION WHEN NO_DATA_FOUND THEN
  35. seq_name := '%(no_autofield_sequence_name)s';
  36. END;
  37. SELECT NVL(MAX(%(column)s), 0) INTO table_value FROM %(table)s;
  38. SELECT NVL(last_number - cache_size, 0) INTO seq_value FROM user_sequences
  39. WHERE sequence_name = seq_name;
  40. WHILE table_value > seq_value LOOP
  41. EXECUTE IMMEDIATE 'SELECT "'||seq_name||'".nextval FROM DUAL'
  42. INTO seq_value;
  43. END LOOP;
  44. END;
  45. /"""
  46. # Oracle doesn't support string without precision; use the max string size.
  47. cast_char_field_without_max_length = 'NVARCHAR2(2000)'
  48. cast_data_types = {
  49. 'TextField': cast_char_field_without_max_length,
  50. }
  51. def cache_key_culling_sql(self):
  52. return 'SELECT cache_key FROM %s ORDER BY cache_key OFFSET %%s ROWS FETCH FIRST 1 ROWS ONLY'
  53. def date_extract_sql(self, lookup_type, field_name):
  54. if lookup_type == 'week_day':
  55. # TO_CHAR(field, 'D') returns an integer from 1-7, where 1=Sunday.
  56. return "TO_CHAR(%s, 'D')" % field_name
  57. elif lookup_type == 'week':
  58. # IW = ISO week number
  59. return "TO_CHAR(%s, 'IW')" % field_name
  60. elif lookup_type == 'quarter':
  61. return "TO_CHAR(%s, 'Q')" % field_name
  62. else:
  63. # https://docs.oracle.com/database/121/SQLRF/functions067.htm#SQLRF00639
  64. return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name)
  65. def date_trunc_sql(self, lookup_type, field_name):
  66. # https://docs.oracle.com/database/121/SQLRF/functions271.htm#SQLRF52058
  67. if lookup_type in ('year', 'month'):
  68. return "TRUNC(%s, '%s')" % (field_name, lookup_type.upper())
  69. elif lookup_type == 'quarter':
  70. return "TRUNC(%s, 'Q')" % field_name
  71. elif lookup_type == 'week':
  72. return "TRUNC(%s, 'IW')" % field_name
  73. else:
  74. return "TRUNC(%s)" % field_name
  75. # Oracle crashes with "ORA-03113: end-of-file on communication channel"
  76. # if the time zone name is passed in parameter. Use interpolation instead.
  77. # https://groups.google.com/forum/#!msg/django-developers/zwQju7hbG78/9l934yelwfsJ
  78. # This regexp matches all time zone names from the zoneinfo database.
  79. _tzname_re = re.compile(r'^[\w/:+-]+$')
  80. def _convert_field_to_tz(self, field_name, tzname):
  81. if not settings.USE_TZ:
  82. return field_name
  83. if not self._tzname_re.match(tzname):
  84. raise ValueError("Invalid time zone name: %s" % tzname)
  85. # Convert from UTC to local time, returning TIMESTAMP WITH TIME ZONE
  86. # and cast it back to TIMESTAMP to strip the TIME ZONE details.
  87. return "CAST((FROM_TZ(%s, '0:00') AT TIME ZONE '%s') AS TIMESTAMP)" % (field_name, tzname)
  88. def datetime_cast_date_sql(self, field_name, tzname):
  89. field_name = self._convert_field_to_tz(field_name, tzname)
  90. return 'TRUNC(%s)' % field_name
  91. def datetime_cast_time_sql(self, field_name, tzname):
  92. # Since `TimeField` values are stored as TIMESTAMP where only the date
  93. # part is ignored, convert the field to the specified timezone.
  94. return self._convert_field_to_tz(field_name, tzname)
  95. def datetime_extract_sql(self, lookup_type, field_name, tzname):
  96. field_name = self._convert_field_to_tz(field_name, tzname)
  97. return self.date_extract_sql(lookup_type, field_name)
  98. def datetime_trunc_sql(self, lookup_type, field_name, tzname):
  99. field_name = self._convert_field_to_tz(field_name, tzname)
  100. # https://docs.oracle.com/database/121/SQLRF/functions271.htm#SQLRF52058
  101. if lookup_type in ('year', 'month'):
  102. sql = "TRUNC(%s, '%s')" % (field_name, lookup_type.upper())
  103. elif lookup_type == 'quarter':
  104. sql = "TRUNC(%s, 'Q')" % field_name
  105. elif lookup_type == 'week':
  106. sql = "TRUNC(%s, 'IW')" % field_name
  107. elif lookup_type == 'day':
  108. sql = "TRUNC(%s)" % field_name
  109. elif lookup_type == 'hour':
  110. sql = "TRUNC(%s, 'HH24')" % field_name
  111. elif lookup_type == 'minute':
  112. sql = "TRUNC(%s, 'MI')" % field_name
  113. else:
  114. sql = "CAST(%s AS DATE)" % field_name # Cast to DATE removes sub-second precision.
  115. return sql
  116. def time_trunc_sql(self, lookup_type, field_name):
  117. # The implementation is similar to `datetime_trunc_sql` as both
  118. # `DateTimeField` and `TimeField` are stored as TIMESTAMP where
  119. # the date part of the later is ignored.
  120. if lookup_type == 'hour':
  121. sql = "TRUNC(%s, 'HH24')" % field_name
  122. elif lookup_type == 'minute':
  123. sql = "TRUNC(%s, 'MI')" % field_name
  124. elif lookup_type == 'second':
  125. sql = "CAST(%s AS DATE)" % field_name # Cast to DATE removes sub-second precision.
  126. return sql
  127. def get_db_converters(self, expression):
  128. converters = super().get_db_converters(expression)
  129. internal_type = expression.output_field.get_internal_type()
  130. if internal_type == 'TextField':
  131. converters.append(self.convert_textfield_value)
  132. elif internal_type == 'BinaryField':
  133. converters.append(self.convert_binaryfield_value)
  134. elif internal_type in ['BooleanField', 'NullBooleanField']:
  135. converters.append(self.convert_booleanfield_value)
  136. elif internal_type == 'DateTimeField':
  137. if settings.USE_TZ:
  138. converters.append(self.convert_datetimefield_value)
  139. elif internal_type == 'DateField':
  140. converters.append(self.convert_datefield_value)
  141. elif internal_type == 'TimeField':
  142. converters.append(self.convert_timefield_value)
  143. elif internal_type == 'UUIDField':
  144. converters.append(self.convert_uuidfield_value)
  145. # Oracle stores empty strings as null. If the field accepts the empty
  146. # string, undo this to adhere to the Django convention of using
  147. # the empty string instead of null.
  148. if expression.field.empty_strings_allowed:
  149. converters.append(
  150. self.convert_empty_bytes
  151. if internal_type == 'BinaryField' else
  152. self.convert_empty_string
  153. )
  154. return converters
  155. def convert_textfield_value(self, value, expression, connection):
  156. if isinstance(value, Database.LOB):
  157. value = value.read()
  158. return value
  159. def convert_binaryfield_value(self, value, expression, connection):
  160. if isinstance(value, Database.LOB):
  161. value = force_bytes(value.read())
  162. return value
  163. def convert_booleanfield_value(self, value, expression, connection):
  164. if value in (0, 1):
  165. value = bool(value)
  166. return value
  167. # cx_Oracle always returns datetime.datetime objects for
  168. # DATE and TIMESTAMP columns, but Django wants to see a
  169. # python datetime.date, .time, or .datetime.
  170. def convert_datetimefield_value(self, value, expression, connection):
  171. if value is not None:
  172. value = timezone.make_aware(value, self.connection.timezone)
  173. return value
  174. def convert_datefield_value(self, value, expression, connection):
  175. if isinstance(value, Database.Timestamp):
  176. value = value.date()
  177. return value
  178. def convert_timefield_value(self, value, expression, connection):
  179. if isinstance(value, Database.Timestamp):
  180. value = value.time()
  181. return value
  182. def convert_uuidfield_value(self, value, expression, connection):
  183. if value is not None:
  184. value = uuid.UUID(value)
  185. return value
  186. @staticmethod
  187. def convert_empty_string(value, expression, connection):
  188. return '' if value is None else value
  189. @staticmethod
  190. def convert_empty_bytes(value, expression, connection):
  191. return b'' if value is None else value
  192. def deferrable_sql(self):
  193. return " DEFERRABLE INITIALLY DEFERRED"
  194. def fetch_returned_insert_id(self, cursor):
  195. try:
  196. value = cursor._insert_id_var.getvalue()
  197. # cx_Oracle < 7 returns value, >= 7 returns list with single value.
  198. return int(value[0] if isinstance(value, list) else value)
  199. except (IndexError, TypeError):
  200. # cx_Oracle < 6.3 returns None, >= 6.3 raises IndexError.
  201. raise DatabaseError(
  202. 'The database did not return a new row id. Probably "ORA-1403: '
  203. 'no data found" was raised internally but was hidden by the '
  204. 'Oracle OCI library (see https://code.djangoproject.com/ticket/28859).'
  205. )
  206. def field_cast_sql(self, db_type, internal_type):
  207. if db_type and db_type.endswith('LOB'):
  208. return "DBMS_LOB.SUBSTR(%s)"
  209. else:
  210. return "%s"
  211. def no_limit_value(self):
  212. return None
  213. def limit_offset_sql(self, low_mark, high_mark):
  214. fetch, offset = self._get_limit_offset_params(low_mark, high_mark)
  215. return '%s%s' % (
  216. (' OFFSET %d ROWS' % offset) if offset else '',
  217. (' FETCH FIRST %d ROWS ONLY' % fetch) if fetch else '',
  218. )
  219. def last_executed_query(self, cursor, sql, params):
  220. # https://cx-oracle.readthedocs.io/en/latest/cursor.html#Cursor.statement
  221. # The DB API definition does not define this attribute.
  222. statement = cursor.statement
  223. # Unlike Psycopg's `query` and MySQLdb`'s `_last_executed`, CxOracle's
  224. # `statement` doesn't contain the query parameters. refs #20010.
  225. return super().last_executed_query(cursor, statement, params)
  226. def last_insert_id(self, cursor, table_name, pk_name):
  227. sq_name = self._get_sequence_name(cursor, strip_quotes(table_name), pk_name)
  228. cursor.execute('"%s".currval' % sq_name)
  229. return cursor.fetchone()[0]
  230. def lookup_cast(self, lookup_type, internal_type=None):
  231. if lookup_type in ('iexact', 'icontains', 'istartswith', 'iendswith'):
  232. return "UPPER(%s)"
  233. return "%s"
  234. def max_in_list_size(self):
  235. return 1000
  236. def max_name_length(self):
  237. return 30
  238. def pk_default_value(self):
  239. return "NULL"
  240. def prep_for_iexact_query(self, x):
  241. return x
  242. def process_clob(self, value):
  243. if value is None:
  244. return ''
  245. return value.read()
  246. def quote_name(self, name):
  247. # SQL92 requires delimited (quoted) names to be case-sensitive. When
  248. # not quoted, Oracle has case-insensitive behavior for identifiers, but
  249. # always defaults to uppercase.
  250. # We simplify things by making Oracle identifiers always uppercase.
  251. if not name.startswith('"') and not name.endswith('"'):
  252. name = '"%s"' % truncate_name(name.upper(), self.max_name_length())
  253. # Oracle puts the query text into a (query % args) construct, so % signs
  254. # in names need to be escaped. The '%%' will be collapsed back to '%' at
  255. # that stage so we aren't really making the name longer here.
  256. name = name.replace('%', '%%')
  257. return name.upper()
  258. def random_function_sql(self):
  259. return "DBMS_RANDOM.RANDOM"
  260. def regex_lookup(self, lookup_type):
  261. if lookup_type == 'regex':
  262. match_option = "'c'"
  263. else:
  264. match_option = "'i'"
  265. return 'REGEXP_LIKE(%%s, %%s, %s)' % match_option
  266. def return_insert_id(self):
  267. return "RETURNING %s INTO %%s", (InsertIdVar(),)
  268. def savepoint_create_sql(self, sid):
  269. return "SAVEPOINT " + self.quote_name(sid)
  270. def savepoint_rollback_sql(self, sid):
  271. return "ROLLBACK TO SAVEPOINT " + self.quote_name(sid)
  272. def _foreign_key_constraints(self, table_name, recursive=False):
  273. with self.connection.cursor() as cursor:
  274. if recursive:
  275. cursor.execute("""
  276. SELECT
  277. user_tables.table_name, rcons.constraint_name
  278. FROM
  279. user_tables
  280. JOIN
  281. user_constraints cons
  282. ON (user_tables.table_name = cons.table_name AND cons.constraint_type = ANY('P', 'U'))
  283. LEFT JOIN
  284. user_constraints rcons
  285. ON (user_tables.table_name = rcons.table_name AND rcons.constraint_type = 'R')
  286. START WITH user_tables.table_name = UPPER(%s)
  287. CONNECT BY NOCYCLE PRIOR cons.constraint_name = rcons.r_constraint_name
  288. GROUP BY
  289. user_tables.table_name, rcons.constraint_name
  290. HAVING user_tables.table_name != UPPER(%s)
  291. ORDER BY MAX(level) DESC
  292. """, (table_name, table_name))
  293. else:
  294. cursor.execute("""
  295. SELECT
  296. cons.table_name, cons.constraint_name
  297. FROM
  298. user_constraints cons
  299. WHERE
  300. cons.constraint_type = 'R'
  301. AND cons.table_name = UPPER(%s)
  302. """, (table_name,))
  303. return cursor.fetchall()
  304. def sql_flush(self, style, tables, sequences, allow_cascade=False):
  305. if tables:
  306. truncated_tables = {table.upper() for table in tables}
  307. constraints = set()
  308. # Oracle's TRUNCATE CASCADE only works with ON DELETE CASCADE
  309. # foreign keys which Django doesn't define. Emulate the
  310. # PostgreSQL behavior which truncates all dependent tables by
  311. # manually retrieving all foreign key constraints and resolving
  312. # dependencies.
  313. for table in tables:
  314. for foreign_table, constraint in self._foreign_key_constraints(table, recursive=allow_cascade):
  315. if allow_cascade:
  316. truncated_tables.add(foreign_table)
  317. constraints.add((foreign_table, constraint))
  318. sql = [
  319. "%s %s %s %s %s %s %s %s;" % (
  320. style.SQL_KEYWORD('ALTER'),
  321. style.SQL_KEYWORD('TABLE'),
  322. style.SQL_FIELD(self.quote_name(table)),
  323. style.SQL_KEYWORD('DISABLE'),
  324. style.SQL_KEYWORD('CONSTRAINT'),
  325. style.SQL_FIELD(self.quote_name(constraint)),
  326. style.SQL_KEYWORD('KEEP'),
  327. style.SQL_KEYWORD('INDEX'),
  328. ) for table, constraint in constraints
  329. ] + [
  330. "%s %s %s;" % (
  331. style.SQL_KEYWORD('TRUNCATE'),
  332. style.SQL_KEYWORD('TABLE'),
  333. style.SQL_FIELD(self.quote_name(table)),
  334. ) for table in truncated_tables
  335. ] + [
  336. "%s %s %s %s %s %s;" % (
  337. style.SQL_KEYWORD('ALTER'),
  338. style.SQL_KEYWORD('TABLE'),
  339. style.SQL_FIELD(self.quote_name(table)),
  340. style.SQL_KEYWORD('ENABLE'),
  341. style.SQL_KEYWORD('CONSTRAINT'),
  342. style.SQL_FIELD(self.quote_name(constraint)),
  343. ) for table, constraint in constraints
  344. ]
  345. # Since we've just deleted all the rows, running our sequence
  346. # ALTER code will reset the sequence to 0.
  347. sql.extend(self.sequence_reset_by_name_sql(style, sequences))
  348. return sql
  349. else:
  350. return []
  351. def sequence_reset_by_name_sql(self, style, sequences):
  352. sql = []
  353. for sequence_info in sequences:
  354. no_autofield_sequence_name = self._get_no_autofield_sequence_name(sequence_info['table'])
  355. table = self.quote_name(sequence_info['table'])
  356. column = self.quote_name(sequence_info['column'] or 'id')
  357. query = self._sequence_reset_sql % {
  358. 'no_autofield_sequence_name': no_autofield_sequence_name,
  359. 'table': table,
  360. 'column': column,
  361. 'table_name': strip_quotes(table),
  362. 'column_name': strip_quotes(column),
  363. }
  364. sql.append(query)
  365. return sql
  366. def sequence_reset_sql(self, style, model_list):
  367. from django.db import models
  368. output = []
  369. query = self._sequence_reset_sql
  370. for model in model_list:
  371. for f in model._meta.local_fields:
  372. if isinstance(f, models.AutoField):
  373. no_autofield_sequence_name = self._get_no_autofield_sequence_name(model._meta.db_table)
  374. table = self.quote_name(model._meta.db_table)
  375. column = self.quote_name(f.column)
  376. output.append(query % {
  377. 'no_autofield_sequence_name': no_autofield_sequence_name,
  378. 'table': table,
  379. 'column': column,
  380. 'table_name': strip_quotes(table),
  381. 'column_name': strip_quotes(column),
  382. })
  383. # Only one AutoField is allowed per model, so don't
  384. # continue to loop
  385. break
  386. for f in model._meta.many_to_many:
  387. if not f.remote_field.through:
  388. no_autofield_sequence_name = self._get_no_autofield_sequence_name(f.m2m_db_table())
  389. table = self.quote_name(f.m2m_db_table())
  390. column = self.quote_name('id')
  391. output.append(query % {
  392. 'no_autofield_sequence_name': no_autofield_sequence_name,
  393. 'table': table,
  394. 'column': column,
  395. 'table_name': strip_quotes(table),
  396. 'column_name': 'ID',
  397. })
  398. return output
  399. def start_transaction_sql(self):
  400. return ''
  401. def tablespace_sql(self, tablespace, inline=False):
  402. if inline:
  403. return "USING INDEX TABLESPACE %s" % self.quote_name(tablespace)
  404. else:
  405. return "TABLESPACE %s" % self.quote_name(tablespace)
  406. def adapt_datefield_value(self, value):
  407. """
  408. Transform a date value to an object compatible with what is expected
  409. by the backend driver for date columns.
  410. The default implementation transforms the date to text, but that is not
  411. necessary for Oracle.
  412. """
  413. return value
  414. def adapt_datetimefield_value(self, value):
  415. """
  416. Transform a datetime value to an object compatible with what is expected
  417. by the backend driver for datetime columns.
  418. If naive datetime is passed assumes that is in UTC. Normally Django
  419. models.DateTimeField makes sure that if USE_TZ is True passed datetime
  420. is timezone aware.
  421. """
  422. if value is None:
  423. return None
  424. # Expression values are adapted by the database.
  425. if hasattr(value, 'resolve_expression'):
  426. return value
  427. # cx_Oracle doesn't support tz-aware datetimes
  428. if timezone.is_aware(value):
  429. if settings.USE_TZ:
  430. value = timezone.make_naive(value, self.connection.timezone)
  431. else:
  432. raise ValueError("Oracle backend does not support timezone-aware datetimes when USE_TZ is False.")
  433. return Oracle_datetime.from_datetime(value)
  434. def adapt_timefield_value(self, value):
  435. if value is None:
  436. return None
  437. # Expression values are adapted by the database.
  438. if hasattr(value, 'resolve_expression'):
  439. return value
  440. if isinstance(value, str):
  441. return datetime.datetime.strptime(value, '%H:%M:%S')
  442. # Oracle doesn't support tz-aware times
  443. if timezone.is_aware(value):
  444. raise ValueError("Oracle backend does not support timezone-aware times.")
  445. return Oracle_datetime(1900, 1, 1, value.hour, value.minute,
  446. value.second, value.microsecond)
  447. def combine_expression(self, connector, sub_expressions):
  448. lhs, rhs = sub_expressions
  449. if connector == '%%':
  450. return 'MOD(%s)' % ','.join(sub_expressions)
  451. elif connector == '&':
  452. return 'BITAND(%s)' % ','.join(sub_expressions)
  453. elif connector == '|':
  454. return 'BITAND(-%(lhs)s-1,%(rhs)s)+%(lhs)s' % {'lhs': lhs, 'rhs': rhs}
  455. elif connector == '<<':
  456. return '(%(lhs)s * POWER(2, %(rhs)s))' % {'lhs': lhs, 'rhs': rhs}
  457. elif connector == '>>':
  458. return 'FLOOR(%(lhs)s / POWER(2, %(rhs)s))' % {'lhs': lhs, 'rhs': rhs}
  459. elif connector == '^':
  460. return 'POWER(%s)' % ','.join(sub_expressions)
  461. return super().combine_expression(connector, sub_expressions)
  462. def _get_no_autofield_sequence_name(self, table):
  463. """
  464. Manually created sequence name to keep backward compatibility for
  465. AutoFields that aren't Oracle identity columns.
  466. """
  467. name_length = self.max_name_length() - 3
  468. return '%s_SQ' % truncate_name(strip_quotes(table), name_length).upper()
  469. def _get_sequence_name(self, cursor, table, pk_name):
  470. cursor.execute("""
  471. SELECT sequence_name
  472. FROM user_tab_identity_cols
  473. WHERE table_name = UPPER(%s)
  474. AND column_name = UPPER(%s)""", [table, pk_name])
  475. row = cursor.fetchone()
  476. return self._get_no_autofield_sequence_name(table) if row is None else row[0]
  477. def bulk_insert_sql(self, fields, placeholder_rows):
  478. query = []
  479. for row in placeholder_rows:
  480. select = []
  481. for i, placeholder in enumerate(row):
  482. # A model without any fields has fields=[None].
  483. if fields[i]:
  484. internal_type = getattr(fields[i], 'target_field', fields[i]).get_internal_type()
  485. placeholder = BulkInsertMapper.types.get(internal_type, '%s') % placeholder
  486. # Add columns aliases to the first select to avoid "ORA-00918:
  487. # column ambiguously defined" when two or more columns in the
  488. # first select have the same value.
  489. if not query:
  490. placeholder = '%s col_%s' % (placeholder, i)
  491. select.append(placeholder)
  492. query.append('SELECT %s FROM DUAL' % ', '.join(select))
  493. # Bulk insert to tables with Oracle identity columns causes Oracle to
  494. # add sequence.nextval to it. Sequence.nextval cannot be used with the
  495. # UNION operator. To prevent incorrect SQL, move UNION to a subquery.
  496. return 'SELECT * FROM (%s)' % ' UNION ALL '.join(query)
  497. def subtract_temporals(self, internal_type, lhs, rhs):
  498. if internal_type == 'DateField':
  499. lhs_sql, lhs_params = lhs
  500. rhs_sql, rhs_params = rhs
  501. return "NUMTODSINTERVAL(%s - %s, 'DAY')" % (lhs_sql, rhs_sql), lhs_params + rhs_params
  502. return super().subtract_temporals(internal_type, lhs, rhs)
  503. def bulk_batch_size(self, fields, objs):
  504. """Oracle restricts the number of parameters in a query."""
  505. if fields:
  506. return self.connection.features.max_query_params // len(fields)
  507. return len(objs)
  508. @cached_property
  509. def compiler_module(self):
  510. if self.connection.features.has_fetch_offset_support:
  511. return super().compiler_module
  512. return 'django.db.backends.oracle.compiler'