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.

171 lines
5.6 KiB

  1. # -*- coding: utf-8 -*-
  2. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  3. import logging
  4. from openerp import SUPERUSER_ID, _, api, exceptions, fields, models
  5. from psycopg2.extensions import AsIs
  6. _logger = logging.getLogger(__name__)
  7. class TrgmIndex(models.Model):
  8. """Model for Trigram Index."""
  9. _name = 'trgm.index'
  10. _rec_name = 'field_id'
  11. field_id = fields.Many2one(
  12. comodel_name='ir.model.fields',
  13. string='Field',
  14. required=True,
  15. help='You can either select a field of type "text" or "char".'
  16. )
  17. index_name = fields.Char(
  18. string='Index Name',
  19. readonly=True,
  20. help='The index name is automatically generated like '
  21. 'fieldname_indextype_idx. If the index already exists and the '
  22. 'index is located in the same table then this index is resused. '
  23. 'If the index is located in another table then a number is added '
  24. 'at the end of the index name.'
  25. )
  26. index_type = fields.Selection(
  27. selection=[('gin', 'GIN'), ('gist', 'GiST')],
  28. string='Index Type',
  29. default='gin',
  30. required=True,
  31. help='Cite from PostgreSQL documentation: "As a rule of thumb, a '
  32. 'GIN index is faster to search than a GiST index, but slower to '
  33. 'build or update; so GIN is better suited for static data and '
  34. 'GiST for often-updated data."'
  35. )
  36. @api.model
  37. def _trgm_extension_exists(self):
  38. self.env.cr.execute("""
  39. SELECT name, installed_version
  40. FROM pg_available_extensions
  41. WHERE name = 'pg_trgm'
  42. LIMIT 1;
  43. """)
  44. extension = self.env.cr.fetchone()
  45. if extension is None:
  46. return 'missing'
  47. if extension[1] is None:
  48. return 'uninstalled'
  49. return 'installed'
  50. @api.model
  51. def _is_postgres_superuser(self):
  52. self.env.cr.execute("SHOW is_superuser;")
  53. superuser = self.env.cr.fetchone()
  54. return superuser is not None and superuser[0] == 'on' or False
  55. @api.model
  56. def _install_trgm_extension(self):
  57. extension = self._trgm_extension_exists()
  58. if extension == 'missing':
  59. _logger.warning('To use pg_trgm you have to install the '
  60. 'postgres-contrib module.')
  61. elif extension == 'uninstalled':
  62. if self._is_postgres_superuser():
  63. self.env.cr.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm;")
  64. return True
  65. else:
  66. _logger.warning('To use pg_trgm you have to create the '
  67. 'extension pg_trgm in your database or you '
  68. 'have to be the superuser.')
  69. else:
  70. return True
  71. return False
  72. def _auto_init(self, cr, context=None):
  73. res = super(TrgmIndex, self)._auto_init(cr, context)
  74. if self._install_trgm_extension(cr, SUPERUSER_ID, context=context):
  75. _logger.info('The pg_trgm is loaded in the database and the '
  76. 'fuzzy search can be used.')
  77. return res
  78. @api.model
  79. def get_not_used_index(self, index_name, table_name, inc=1):
  80. if inc > 1:
  81. new_index_name = index_name + str(inc)
  82. else:
  83. new_index_name = index_name
  84. self.env.cr.execute("""
  85. SELECT tablename, indexname
  86. FROM pg_indexes
  87. WHERE indexname = %(index)s;
  88. """, {'index': new_index_name})
  89. indexes = self.env.cr.fetchone()
  90. if indexes is not None and indexes[0] == table_name:
  91. return True, index_name
  92. elif indexes is not None:
  93. return self.get_not_used_index(index_name, table_name,
  94. inc + 1)
  95. return False, new_index_name
  96. @api.multi
  97. def create_index(self):
  98. self.ensure_one()
  99. if not self._install_trgm_extension():
  100. raise exceptions.UserError(_(
  101. 'The pg_trgm extension does not exists or cannot be '
  102. 'installed.'))
  103. table_name = self.env[self.field_id.model_id.model]._table
  104. column_name = self.field_id.name
  105. index_type = self.index_type
  106. index_name = '%s_%s_idx' % (column_name, index_type)
  107. index_exists, index_name = self.get_not_used_index(
  108. index_name, table_name)
  109. if not index_exists:
  110. self.env.cr.execute("""
  111. CREATE INDEX %(index)s
  112. ON %(table)s
  113. USING %(indextype)s (%(column)s %(indextype)s_trgm_ops);
  114. """, {
  115. 'table': AsIs(table_name),
  116. 'index': AsIs(index_name),
  117. 'column': AsIs(column_name),
  118. 'indextype': AsIs(index_type)
  119. })
  120. return index_name
  121. @api.model
  122. def index_exists(self, model_name, field_name):
  123. field = self.env['ir.model.fields'].search([
  124. ('model', '=', model_name), ('name', '=', field_name)], limit=1)
  125. if not field:
  126. return False
  127. trgm_index = self.search([('field_id', '=', field.id)], limit=1)
  128. return bool(trgm_index)
  129. @api.model
  130. def create(self, vals):
  131. rec = super(TrgmIndex, self).create(vals)
  132. rec.index_name = rec.create_index()
  133. return rec
  134. @api.multi
  135. def unlink(self):
  136. for rec in self:
  137. self.env.cr.execute("""
  138. DROP INDEX IF EXISTS %(index)s;
  139. """, {
  140. 'index': AsIs(rec.index_name),
  141. })
  142. return super(TrgmIndex, self).unlink()