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.

173 lines
5.7 KiB

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