# Copyright 2016 Eficent Business and IT Consulting Services S.L. # Copyright 2016 Serpent Consulting Services Pvt. Ltd. # Copyright 2017 LasLabs Inc. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging from odoo import _, api, exceptions, fields, models from psycopg2.extensions import AsIs _logger = logging.getLogger(__name__) class TrgmIndex(models.Model): """Model for Trigram Index.""" _name = 'trgm.index' _rec_name = 'field_id' _description = 'Trigram Index' field_id = fields.Many2one( comodel_name='ir.model.fields', string='Field', required=True, help='You can either select a field of type "text" or "char".' ) index_name = fields.Char( string='Index Name', readonly=True, help='The index name is automatically generated like ' 'fieldname_indextype_idx. If the index already exists and the ' 'index is located in the same table then this index is reused. ' 'If the index is located in another table then a number is added ' 'at the end of the index name.' ) index_type = fields.Selection( selection=[('gin', 'GIN'), ('gist', 'GiST')], string='Index Type', default='gin', required=True, help='Cite from PostgreSQL documentation: "As a rule of thumb, a ' 'GIN index is faster to search than a GiST index, but slower to ' 'build or update; so GIN is better suited for static data and ' 'GiST for often-updated data."' ) @api.model_cr def _trgm_extension_exists(self): self.env.cr.execute(""" SELECT name, installed_version FROM pg_available_extensions WHERE name = 'pg_trgm' LIMIT 1; """) extension = self.env.cr.fetchone() if extension is None: return 'missing' if extension[1] is None: return 'uninstalled' return 'installed' @api.model_cr def _is_postgres_superuser(self): self.env.cr.execute("SHOW is_superuser;") superuser = self.env.cr.fetchone() return superuser is not None and superuser[0] == 'on' or False @api.model_cr def _install_trgm_extension(self): extension = self._trgm_extension_exists() if extension == 'missing': _logger.warning('To use pg_trgm you have to install the ' 'postgres-contrib module.') elif extension == 'uninstalled': if self._is_postgres_superuser(): self.env.cr.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm;") return True else: _logger.warning('To use pg_trgm you have to create the ' 'extension pg_trgm in your database or you ' 'have to be the superuser.') else: return True return False @api.model_cr_context def _auto_init(self): res = super(TrgmIndex, self)._auto_init() if self._install_trgm_extension(): _logger.info('The pg_trgm is loaded in the database and the ' 'fuzzy search can be used.') return res @api.model_cr def get_not_used_index(self, index_name, table_name, inc=1): if inc > 1: new_index_name = index_name + str(inc) else: new_index_name = index_name self.env.cr.execute(""" SELECT tablename, indexname FROM pg_indexes WHERE indexname = %(index)s; """, {'index': new_index_name}) indexes = self.env.cr.fetchone() if indexes is not None and indexes[0] == table_name: return True, index_name elif indexes is not None: return self.get_not_used_index(index_name, table_name, inc + 1) return False, new_index_name @api.multi def create_index(self): self.ensure_one() if not self._install_trgm_extension(): raise exceptions.UserError(_( 'The pg_trgm extension does not exists or cannot be ' 'installed.')) table_name = self.env[self.field_id.model_id.model]._table column_name = self.field_id.name index_type = self.index_type index_name = '%s_%s_idx' % (column_name, index_type) index_exists, index_name = self.get_not_used_index( index_name, table_name) if not index_exists: self.env.cr.execute(""" CREATE INDEX %(index)s ON %(table)s USING %(indextype)s (%(column)s %(indextype)s_trgm_ops); """, { 'table': AsIs(table_name), 'index': AsIs(index_name), 'column': AsIs(column_name), 'indextype': AsIs(index_type) }) return index_name @api.model def index_exists(self, model_name, field_name): field = self.env['ir.model.fields'].search([ ('model', '=', model_name), ('name', '=', field_name)], limit=1) if not field: return False trgm_index = self.search([('field_id', '=', field.id)], limit=1) return bool(trgm_index) @api.model def create(self, vals): rec = super(TrgmIndex, self).create(vals) rec.index_name = rec.create_index() return rec @api.multi def unlink(self): for rec in self: self.env.cr.execute(""" DROP INDEX IF EXISTS %(index)s; """, { 'index': AsIs(rec.index_name), }) return super(TrgmIndex, self).unlink()