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.

174 lines
5.6 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. 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 reused. '
  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_cr
  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_cr
  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_cr
  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. @api.model_cr_context
  75. def _auto_init(self):
  76. res = super(TrgmIndex, self)._auto_init()
  77. if self._install_trgm_extension():
  78. _logger.info('The pg_trgm is loaded in the database and the '
  79. 'fuzzy search can be used.')
  80. return res
  81. @api.model_cr
  82. def get_not_used_index(self, index_name, table_name, inc=1):
  83. if inc > 1:
  84. new_index_name = index_name + str(inc)
  85. else:
  86. new_index_name = index_name
  87. self.env.cr.execute("""
  88. SELECT tablename, indexname
  89. FROM pg_indexes
  90. WHERE indexname = %(index)s;
  91. """, {'index': new_index_name})
  92. indexes = self.env.cr.fetchone()
  93. if indexes is not None and indexes[0] == table_name:
  94. return True, index_name
  95. elif indexes is not None:
  96. return self.get_not_used_index(index_name, table_name,
  97. inc + 1)
  98. return False, new_index_name
  99. @api.multi
  100. def create_index(self):
  101. self.ensure_one()
  102. if not self._install_trgm_extension():
  103. raise exceptions.UserError(_(
  104. 'The pg_trgm extension does not exists or cannot be '
  105. 'installed.'))
  106. table_name = self.env[self.field_id.model_id.model]._table
  107. column_name = self.field_id.name
  108. index_type = self.index_type
  109. index_name = '%s_%s_idx' % (column_name, index_type)
  110. index_exists, index_name = self.get_not_used_index(
  111. index_name, table_name)
  112. if not index_exists:
  113. self.env.cr.execute("""
  114. CREATE INDEX %(index)s
  115. ON %(table)s
  116. USING %(indextype)s (%(column)s %(indextype)s_trgm_ops);
  117. """, {
  118. 'table': AsIs(table_name),
  119. 'index': AsIs(index_name),
  120. 'column': AsIs(column_name),
  121. 'indextype': AsIs(index_type)
  122. })
  123. return index_name
  124. @api.model
  125. def index_exists(self, model_name, field_name):
  126. field = self.env['ir.model.fields'].search([
  127. ('model', '=', model_name), ('name', '=', field_name)], limit=1)
  128. if not field:
  129. return False
  130. trgm_index = self.search([('field_id', '=', field.id)], limit=1)
  131. return bool(trgm_index)
  132. @api.model
  133. def create(self, vals):
  134. rec = super(TrgmIndex, self).create(vals)
  135. rec.index_name = rec.create_index()
  136. return rec
  137. @api.multi
  138. def unlink(self):
  139. for rec in self:
  140. self.env.cr.execute("""
  141. DROP INDEX IF EXISTS %(index)s;
  142. """, {
  143. 'index': AsIs(rec.index_name),
  144. })
  145. return super(TrgmIndex, self).unlink()