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.

base.py 10 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. """
  2. PostgreSQL database backend for Django.
  3. Requires psycopg 2: http://initd.org/projects/psycopg2
  4. """
  5. import warnings
  6. from django.conf import settings
  7. from django.core.exceptions import ImproperlyConfigured
  8. from django.db import DEFAULT_DB_ALIAS
  9. from django.db.backends.base.base import BaseDatabaseWrapper
  10. from django.db.backends.base.validation import BaseDatabaseValidation
  11. from django.db.utils import DatabaseError as WrappedDatabaseError
  12. from django.utils.encoding import force_str
  13. from django.utils.functional import cached_property
  14. from django.utils.safestring import SafeBytes, SafeText
  15. try:
  16. import psycopg2 as Database
  17. import psycopg2.extensions
  18. import psycopg2.extras
  19. except ImportError as e:
  20. raise ImproperlyConfigured("Error loading psycopg2 module: %s" % e)
  21. def psycopg2_version():
  22. version = psycopg2.__version__.split(' ', 1)[0]
  23. return tuple(int(v) for v in version.split('.') if v.isdigit())
  24. PSYCOPG2_VERSION = psycopg2_version()
  25. if PSYCOPG2_VERSION < (2, 4, 5):
  26. raise ImproperlyConfigured("psycopg2_version 2.4.5 or newer is required; you have %s" % psycopg2.__version__)
  27. # Some of these import psycopg2, so import them after checking if it's installed.
  28. from .client import DatabaseClient # isort:skip
  29. from .creation import DatabaseCreation # isort:skip
  30. from .features import DatabaseFeatures # isort:skip
  31. from .introspection import DatabaseIntrospection # isort:skip
  32. from .operations import DatabaseOperations # isort:skip
  33. from .schema import DatabaseSchemaEditor # isort:skip
  34. from .utils import utc_tzinfo_factory # isort:skip
  35. from .version import get_version # isort:skip
  36. DatabaseError = Database.DatabaseError
  37. IntegrityError = Database.IntegrityError
  38. psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
  39. psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY)
  40. psycopg2.extensions.register_adapter(SafeBytes, psycopg2.extensions.QuotedString)
  41. psycopg2.extensions.register_adapter(SafeText, psycopg2.extensions.QuotedString)
  42. psycopg2.extras.register_uuid()
  43. # Register support for inet[] manually so we don't have to handle the Inet()
  44. # object on load all the time.
  45. INETARRAY_OID = 1041
  46. INETARRAY = psycopg2.extensions.new_array_type(
  47. (INETARRAY_OID,),
  48. 'INETARRAY',
  49. psycopg2.extensions.UNICODE,
  50. )
  51. psycopg2.extensions.register_type(INETARRAY)
  52. class DatabaseWrapper(BaseDatabaseWrapper):
  53. vendor = 'postgresql'
  54. # This dictionary maps Field objects to their associated PostgreSQL column
  55. # types, as strings. Column-type strings can contain format strings; they'll
  56. # be interpolated against the values of Field.__dict__ before being output.
  57. # If a column type is set to None, it won't be included in the output.
  58. data_types = {
  59. 'AutoField': 'serial',
  60. 'BinaryField': 'bytea',
  61. 'BooleanField': 'boolean',
  62. 'CharField': 'varchar(%(max_length)s)',
  63. 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
  64. 'DateField': 'date',
  65. 'DateTimeField': 'timestamp with time zone',
  66. 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
  67. 'DurationField': 'interval',
  68. 'FileField': 'varchar(%(max_length)s)',
  69. 'FilePathField': 'varchar(%(max_length)s)',
  70. 'FloatField': 'double precision',
  71. 'IntegerField': 'integer',
  72. 'BigIntegerField': 'bigint',
  73. 'IPAddressField': 'inet',
  74. 'GenericIPAddressField': 'inet',
  75. 'NullBooleanField': 'boolean',
  76. 'OneToOneField': 'integer',
  77. 'PositiveIntegerField': 'integer',
  78. 'PositiveSmallIntegerField': 'smallint',
  79. 'SlugField': 'varchar(%(max_length)s)',
  80. 'SmallIntegerField': 'smallint',
  81. 'TextField': 'text',
  82. 'TimeField': 'time',
  83. 'UUIDField': 'uuid',
  84. }
  85. data_type_check_constraints = {
  86. 'PositiveIntegerField': '"%(column)s" >= 0',
  87. 'PositiveSmallIntegerField': '"%(column)s" >= 0',
  88. }
  89. operators = {
  90. 'exact': '= %s',
  91. 'iexact': '= UPPER(%s)',
  92. 'contains': 'LIKE %s',
  93. 'icontains': 'LIKE UPPER(%s)',
  94. 'regex': '~ %s',
  95. 'iregex': '~* %s',
  96. 'gt': '> %s',
  97. 'gte': '>= %s',
  98. 'lt': '< %s',
  99. 'lte': '<= %s',
  100. 'startswith': 'LIKE %s',
  101. 'endswith': 'LIKE %s',
  102. 'istartswith': 'LIKE UPPER(%s)',
  103. 'iendswith': 'LIKE UPPER(%s)',
  104. }
  105. # The patterns below are used to generate SQL pattern lookup clauses when
  106. # the right-hand side of the lookup isn't a raw string (it might be an expression
  107. # or the result of a bilateral transformation).
  108. # In those cases, special characters for LIKE operators (e.g. \, *, _) should be
  109. # escaped on database side.
  110. #
  111. # Note: we use str.format() here for readability as '%' is used as a wildcard for
  112. # the LIKE operator.
  113. pattern_esc = r"REPLACE(REPLACE(REPLACE({}, '\', '\\'), '%%', '\%%'), '_', '\_')"
  114. pattern_ops = {
  115. 'contains': "LIKE '%%' || {} || '%%'",
  116. 'icontains': "LIKE '%%' || UPPER({}) || '%%'",
  117. 'startswith': "LIKE {} || '%%'",
  118. 'istartswith': "LIKE UPPER({}) || '%%'",
  119. 'endswith': "LIKE '%%' || {}",
  120. 'iendswith': "LIKE '%%' || UPPER({})",
  121. }
  122. Database = Database
  123. SchemaEditorClass = DatabaseSchemaEditor
  124. def __init__(self, *args, **kwargs):
  125. super(DatabaseWrapper, self).__init__(*args, **kwargs)
  126. self.features = DatabaseFeatures(self)
  127. self.ops = DatabaseOperations(self)
  128. self.client = DatabaseClient(self)
  129. self.creation = DatabaseCreation(self)
  130. self.introspection = DatabaseIntrospection(self)
  131. self.validation = BaseDatabaseValidation(self)
  132. def get_connection_params(self):
  133. settings_dict = self.settings_dict
  134. # None may be used to connect to the default 'postgres' db
  135. if settings_dict['NAME'] == '':
  136. raise ImproperlyConfigured(
  137. "settings.DATABASES is improperly configured. "
  138. "Please supply the NAME value.")
  139. conn_params = {
  140. 'database': settings_dict['NAME'] or 'postgres',
  141. }
  142. conn_params.update(settings_dict['OPTIONS'])
  143. conn_params.pop('isolation_level', None)
  144. if settings_dict['USER']:
  145. conn_params['user'] = settings_dict['USER']
  146. if settings_dict['PASSWORD']:
  147. conn_params['password'] = force_str(settings_dict['PASSWORD'])
  148. if settings_dict['HOST']:
  149. conn_params['host'] = settings_dict['HOST']
  150. if settings_dict['PORT']:
  151. conn_params['port'] = settings_dict['PORT']
  152. return conn_params
  153. def get_new_connection(self, conn_params):
  154. connection = Database.connect(**conn_params)
  155. # self.isolation_level must be set:
  156. # - after connecting to the database in order to obtain the database's
  157. # default when no value is explicitly specified in options.
  158. # - before calling _set_autocommit() because if autocommit is on, that
  159. # will set connection.isolation_level to ISOLATION_LEVEL_AUTOCOMMIT.
  160. options = self.settings_dict['OPTIONS']
  161. try:
  162. self.isolation_level = options['isolation_level']
  163. except KeyError:
  164. self.isolation_level = connection.isolation_level
  165. else:
  166. # Set the isolation level to the value from OPTIONS.
  167. if self.isolation_level != connection.isolation_level:
  168. connection.set_session(isolation_level=self.isolation_level)
  169. return connection
  170. def init_connection_state(self):
  171. self.connection.set_client_encoding('UTF8')
  172. conn_timezone_name = self.connection.get_parameter_status('TimeZone')
  173. if self.timezone_name and conn_timezone_name != self.timezone_name:
  174. cursor = self.connection.cursor()
  175. try:
  176. cursor.execute(self.ops.set_time_zone_sql(), [self.timezone_name])
  177. finally:
  178. cursor.close()
  179. # Commit after setting the time zone (see #17062)
  180. if not self.get_autocommit():
  181. self.connection.commit()
  182. def create_cursor(self):
  183. cursor = self.connection.cursor()
  184. cursor.tzinfo_factory = utc_tzinfo_factory if settings.USE_TZ else None
  185. return cursor
  186. def _set_autocommit(self, autocommit):
  187. with self.wrap_database_errors:
  188. self.connection.autocommit = autocommit
  189. def check_constraints(self, table_names=None):
  190. """
  191. To check constraints, we set constraints to immediate. Then, when, we're done we must ensure they
  192. are returned to deferred.
  193. """
  194. self.cursor().execute('SET CONSTRAINTS ALL IMMEDIATE')
  195. self.cursor().execute('SET CONSTRAINTS ALL DEFERRED')
  196. def is_usable(self):
  197. try:
  198. # Use a psycopg cursor directly, bypassing Django's utilities.
  199. self.connection.cursor().execute("SELECT 1")
  200. except Database.Error:
  201. return False
  202. else:
  203. return True
  204. @property
  205. def _nodb_connection(self):
  206. nodb_connection = super(DatabaseWrapper, self)._nodb_connection
  207. try:
  208. nodb_connection.ensure_connection()
  209. except (DatabaseError, WrappedDatabaseError):
  210. warnings.warn(
  211. "Normally Django will use a connection to the 'postgres' database "
  212. "to avoid running initialization queries against the production "
  213. "database when it's not needed (for example, when running tests). "
  214. "Django was unable to create a connection to the 'postgres' database "
  215. "and will use the default database instead.",
  216. RuntimeWarning
  217. )
  218. settings_dict = self.settings_dict.copy()
  219. settings_dict['NAME'] = settings.DATABASES[DEFAULT_DB_ALIAS]['NAME']
  220. nodb_connection = self.__class__(
  221. self.settings_dict.copy(),
  222. alias=self.alias,
  223. allow_thread_sharing=False)
  224. return nodb_connection
  225. @cached_property
  226. def psycopg2_version(self):
  227. return PSYCOPG2_VERSION
  228. @cached_property
  229. def pg_version(self):
  230. with self.temporary_connection():
  231. return get_version(self.connection)