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.
 
 
 
 

266 lines
11 KiB

  1. from __future__ import unicode_literals
  2. import datetime
  3. import uuid
  4. from django.conf import settings
  5. from django.core.exceptions import FieldError, ImproperlyConfigured
  6. from django.db import utils
  7. from django.db.backends import utils as backend_utils
  8. from django.db.backends.base.operations import BaseDatabaseOperations
  9. from django.db.models import aggregates, fields
  10. from django.utils import six, timezone
  11. from django.utils.dateparse import parse_date, parse_datetime, parse_time
  12. from django.utils.duration import duration_string
  13. try:
  14. import pytz
  15. except ImportError:
  16. pytz = None
  17. class DatabaseOperations(BaseDatabaseOperations):
  18. def bulk_batch_size(self, fields, objs):
  19. """
  20. SQLite has a compile-time default (SQLITE_LIMIT_VARIABLE_NUMBER) of
  21. 999 variables per query.
  22. If there is just single field to insert, then we can hit another
  23. limit, SQLITE_MAX_COMPOUND_SELECT which defaults to 500.
  24. """
  25. limit = 999 if len(fields) > 1 else 500
  26. return (limit // len(fields)) if len(fields) > 0 else len(objs)
  27. def check_expression_support(self, expression):
  28. bad_fields = (fields.DateField, fields.DateTimeField, fields.TimeField)
  29. bad_aggregates = (aggregates.Sum, aggregates.Avg, aggregates.Variance, aggregates.StdDev)
  30. if isinstance(expression, bad_aggregates):
  31. for expr in expression.get_source_expressions():
  32. try:
  33. output_field = expr.output_field
  34. if isinstance(output_field, bad_fields):
  35. raise NotImplementedError(
  36. 'You cannot use Sum, Avg, StdDev, and Variance '
  37. 'aggregations on date/time fields in sqlite3 '
  38. 'since date/time is saved as text.'
  39. )
  40. except FieldError:
  41. # Not every subexpression has an output_field which is fine
  42. # to ignore.
  43. pass
  44. def date_extract_sql(self, lookup_type, field_name):
  45. # sqlite doesn't support extract, so we fake it with the user-defined
  46. # function django_date_extract that's registered in connect(). Note that
  47. # single quotes are used because this is a string (and could otherwise
  48. # cause a collision with a field name).
  49. return "django_date_extract('%s', %s)" % (lookup_type.lower(), field_name)
  50. def date_interval_sql(self, timedelta):
  51. return "'%s'" % duration_string(timedelta), []
  52. def format_for_duration_arithmetic(self, sql):
  53. """Do nothing here, we will handle it in the custom function."""
  54. return sql
  55. def date_trunc_sql(self, lookup_type, field_name):
  56. # sqlite doesn't support DATE_TRUNC, so we fake it with a user-defined
  57. # function django_date_trunc that's registered in connect(). Note that
  58. # single quotes are used because this is a string (and could otherwise
  59. # cause a collision with a field name).
  60. return "django_date_trunc('%s', %s)" % (lookup_type.lower(), field_name)
  61. def _require_pytz(self):
  62. if settings.USE_TZ and pytz is None:
  63. raise ImproperlyConfigured("This query requires pytz, but it isn't installed.")
  64. def datetime_cast_date_sql(self, field_name, tzname):
  65. self._require_pytz()
  66. return "django_datetime_cast_date(%s, %%s)" % field_name, [tzname]
  67. def datetime_extract_sql(self, lookup_type, field_name, tzname):
  68. # Same comment as in date_extract_sql.
  69. self._require_pytz()
  70. return "django_datetime_extract('%s', %s, %%s)" % (
  71. lookup_type.lower(), field_name), [tzname]
  72. def datetime_trunc_sql(self, lookup_type, field_name, tzname):
  73. # Same comment as in date_trunc_sql.
  74. self._require_pytz()
  75. return "django_datetime_trunc('%s', %s, %%s)" % (
  76. lookup_type.lower(), field_name), [tzname]
  77. def time_extract_sql(self, lookup_type, field_name):
  78. # sqlite doesn't support extract, so we fake it with the user-defined
  79. # function django_time_extract that's registered in connect(). Note that
  80. # single quotes are used because this is a string (and could otherwise
  81. # cause a collision with a field name).
  82. return "django_time_extract('%s', %s)" % (lookup_type.lower(), field_name)
  83. def drop_foreignkey_sql(self):
  84. return ""
  85. def pk_default_value(self):
  86. return "NULL"
  87. def _quote_params_for_last_executed_query(self, params):
  88. """
  89. Only for last_executed_query! Don't use this to execute SQL queries!
  90. """
  91. # This function is limited both by SQLITE_LIMIT_VARIABLE_NUMBER (the
  92. # number of parameters, default = 999) and SQLITE_MAX_COLUMN (the
  93. # number of return values, default = 2000). Since Python's sqlite3
  94. # module doesn't expose the get_limit() C API, assume the default
  95. # limits are in effect and split the work in batches if needed.
  96. BATCH_SIZE = 999
  97. if len(params) > BATCH_SIZE:
  98. results = ()
  99. for index in range(0, len(params), BATCH_SIZE):
  100. chunk = params[index:index + BATCH_SIZE]
  101. results += self._quote_params_for_last_executed_query(chunk)
  102. return results
  103. sql = 'SELECT ' + ', '.join(['QUOTE(?)'] * len(params))
  104. # Bypass Django's wrappers and use the underlying sqlite3 connection
  105. # to avoid logging this query - it would trigger infinite recursion.
  106. cursor = self.connection.connection.cursor()
  107. # Native sqlite3 cursors cannot be used as context managers.
  108. try:
  109. return cursor.execute(sql, params).fetchone()
  110. finally:
  111. cursor.close()
  112. def last_executed_query(self, cursor, sql, params):
  113. # Python substitutes parameters in Modules/_sqlite/cursor.c with:
  114. # pysqlite_statement_bind_parameters(self->statement, parameters, allow_8bit_chars);
  115. # Unfortunately there is no way to reach self->statement from Python,
  116. # so we quote and substitute parameters manually.
  117. if params:
  118. if isinstance(params, (list, tuple)):
  119. params = self._quote_params_for_last_executed_query(params)
  120. else:
  121. keys = params.keys()
  122. values = tuple(params.values())
  123. values = self._quote_params_for_last_executed_query(values)
  124. params = dict(zip(keys, values))
  125. return sql % params
  126. # For consistency with SQLiteCursorWrapper.execute(), just return sql
  127. # when there are no parameters. See #13648 and #17158.
  128. else:
  129. return sql
  130. def quote_name(self, name):
  131. if name.startswith('"') and name.endswith('"'):
  132. return name # Quoting once is enough.
  133. return '"%s"' % name
  134. def no_limit_value(self):
  135. return -1
  136. def sql_flush(self, style, tables, sequences, allow_cascade=False):
  137. # NB: The generated SQL below is specific to SQLite
  138. # Note: The DELETE FROM... SQL generated below works for SQLite databases
  139. # because constraints don't exist
  140. sql = ['%s %s %s;' % (
  141. style.SQL_KEYWORD('DELETE'),
  142. style.SQL_KEYWORD('FROM'),
  143. style.SQL_FIELD(self.quote_name(table))
  144. ) for table in tables]
  145. # Note: No requirement for reset of auto-incremented indices (cf. other
  146. # sql_flush() implementations). Just return SQL at this point
  147. return sql
  148. def adapt_datetimefield_value(self, value):
  149. if value is None:
  150. return None
  151. # SQLite doesn't support tz-aware datetimes
  152. if timezone.is_aware(value):
  153. if settings.USE_TZ:
  154. value = timezone.make_naive(value, self.connection.timezone)
  155. else:
  156. raise ValueError("SQLite backend does not support timezone-aware datetimes when USE_TZ is False.")
  157. return six.text_type(value)
  158. def adapt_timefield_value(self, value):
  159. if value is None:
  160. return None
  161. # SQLite doesn't support tz-aware datetimes
  162. if timezone.is_aware(value):
  163. raise ValueError("SQLite backend does not support timezone-aware times.")
  164. return six.text_type(value)
  165. def get_db_converters(self, expression):
  166. converters = super(DatabaseOperations, self).get_db_converters(expression)
  167. internal_type = expression.output_field.get_internal_type()
  168. if internal_type == 'DateTimeField':
  169. converters.append(self.convert_datetimefield_value)
  170. elif internal_type == 'DateField':
  171. converters.append(self.convert_datefield_value)
  172. elif internal_type == 'TimeField':
  173. converters.append(self.convert_timefield_value)
  174. elif internal_type == 'DecimalField':
  175. converters.append(self.convert_decimalfield_value)
  176. elif internal_type == 'UUIDField':
  177. converters.append(self.convert_uuidfield_value)
  178. return converters
  179. def convert_datetimefield_value(self, value, expression, connection, context):
  180. if value is not None:
  181. if not isinstance(value, datetime.datetime):
  182. value = parse_datetime(value)
  183. if settings.USE_TZ:
  184. value = timezone.make_aware(value, self.connection.timezone)
  185. return value
  186. def convert_datefield_value(self, value, expression, connection, context):
  187. if value is not None:
  188. if not isinstance(value, datetime.date):
  189. value = parse_date(value)
  190. return value
  191. def convert_timefield_value(self, value, expression, connection, context):
  192. if value is not None:
  193. if not isinstance(value, datetime.time):
  194. value = parse_time(value)
  195. return value
  196. def convert_decimalfield_value(self, value, expression, connection, context):
  197. if value is not None:
  198. value = expression.output_field.format_number(value)
  199. value = backend_utils.typecast_decimal(value)
  200. return value
  201. def convert_uuidfield_value(self, value, expression, connection, context):
  202. if value is not None:
  203. value = uuid.UUID(value)
  204. return value
  205. def bulk_insert_sql(self, fields, placeholder_rows):
  206. return " UNION ALL ".join(
  207. "SELECT %s" % ", ".join(row)
  208. for row in placeholder_rows
  209. )
  210. def combine_expression(self, connector, sub_expressions):
  211. # SQLite doesn't have a power function, so we fake it with a
  212. # user-defined function django_power that's registered in connect().
  213. if connector == '^':
  214. return 'django_power(%s)' % ','.join(sub_expressions)
  215. return super(DatabaseOperations, self).combine_expression(connector, sub_expressions)
  216. def combine_duration_expression(self, connector, sub_expressions):
  217. if connector not in ['+', '-']:
  218. raise utils.DatabaseError('Invalid connector for timedelta: %s.' % connector)
  219. fn_params = ["'%s'" % connector] + sub_expressions
  220. if len(fn_params) > 3:
  221. raise ValueError('Too many params for timedelta operations.')
  222. return "django_format_dtdelta(%s)" % ', '.join(fn_params)
  223. def integer_field_range(self, internal_type):
  224. # SQLite doesn't enforce any integer constraints
  225. return (None, None)