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.

175 lines
5.7 KiB

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