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 25KB

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