diff --git a/sql_request_abstract/__init__.py b/sql_request_abstract/__init__.py index 0650744f6..c89ecf0f1 100644 --- a/sql_request_abstract/__init__.py +++ b/sql_request_abstract/__init__.py @@ -1 +1,2 @@ from . import models +from . import sql_db diff --git a/sql_request_abstract/models/sql_request_mixin.py b/sql_request_abstract/models/sql_request_mixin.py index 07f36f516..0f333309e 100644 --- a/sql_request_abstract/models/sql_request_mixin.py +++ b/sql_request_abstract/models/sql_request_mixin.py @@ -12,8 +12,10 @@ from io import BytesIO from psycopg2 import ProgrammingError from psycopg2.sql import SQL -from odoo import _, api, fields, models -from odoo.exceptions import UserError +from odoo import _, api, fields, models, tools +from odoo.exceptions import UserError, ValidationError + +from ..sql_db import get_external_cursor logger = logging.getLogger(__name__) @@ -96,6 +98,26 @@ class SQLRequestMixin(models.AbstractModel): column2="user_id", default=_default_user_ids, ) + use_external_database = fields.Boolean( + help=( + "If filled, the query will be executed against an external " + "database, configured in Odoo main configuration file. " + ) + ) + + @api.constrains("use_external_database") + def check_external_config(self): + external_db_records = self.filtered(lambda rec: rec.use_external_database) + if external_db_records: + external_db_name = tools.config.get("external_db_name") + if not external_db_name: + raise ValidationError( + _( + "You can't use an external database as there are no such " + "configuration about this. Please contact your Odoo administrator" + " to solve this issue." + ) + ) has_group_changed = fields.Boolean( copy=False, @@ -202,42 +224,54 @@ class SQLRequestMixin(models.AbstractModel): else: raise UserError(_("Unimplemented mode : '%s'") % mode) + query_cr = self._get_cr_for_query() + if rollback: - rollback_name = self._create_savepoint() + rollback_name = self._create_savepoint(query_cr) try: if mode == "stdout": output = BytesIO() - self.env.cr.copy_expert(query, output) + query_cr.copy_expert(query, output) res = base64.b64encode(output.getvalue()) output.close() else: - self.env.cr.execute(query) + query_cr.execute(query) if mode == "fetchall": - res = self.env.cr.fetchall() + res = query_cr.fetchall() if header: colnames = [desc[0] for desc in self.env.cr.description] res.insert(0, colnames) elif mode == "fetchone": - res = self.env.cr.fetchone() + res = query_cr.fetchone() finally: - self._rollback_savepoint(rollback_name) + self._rollback_savepoint(rollback_name, query_cr) return res # Private Section + def _get_cr_for_query(self): + self.ensure_one() + if self.use_external_database: + return get_external_cursor() + else: + return self.env.cr + @api.model - def _create_savepoint(self): + def _create_savepoint(self, cr): rollback_name = "{}_{}".format(self._name.replace(".", "_"), uuid.uuid1().hex) # pylint: disable=sql-injection req = "SAVEPOINT %s" % (rollback_name) - self.env.cr.execute(req) + cr.execute(req) return rollback_name @api.model - def _rollback_savepoint(self, rollback_name): + def _rollback_savepoint(self, rollback_name, cr): # pylint: disable=sql-injection req = "ROLLBACK TO SAVEPOINT %s" % (rollback_name) - self.env.cr.execute(req) + cr.execute(req) + # close external database cursor + if self.env.cr != cr: + cr.close() @api.model def _check_materialized_view_available(self): diff --git a/sql_request_abstract/readme/CONFIGURE.rst b/sql_request_abstract/readme/CONFIGURE.rst new file mode 100644 index 000000000..83a27c875 --- /dev/null +++ b/sql_request_abstract/readme/CONFIGURE.rst @@ -0,0 +1,6 @@ +To configure the use of an external database, you need to edit the main configuration file of your instance and add the external database configuration with following keys : +* external_db_user +* external_db_password +* external_db_name +* external_db_host +* external_db_port diff --git a/sql_request_abstract/sql_db.py b/sql_request_abstract/sql_db.py new file mode 100644 index 000000000..bbfa17147 --- /dev/null +++ b/sql_request_abstract/sql_db.py @@ -0,0 +1,22 @@ +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import tools +from odoo.sql_db import Connection, ConnectionPool, _Pool # noqa + + +def connection_info_for_external_database(): + db_name = tools.config.get("external_db_name") + connection_info = {"database": db_name} + for p in ("host", "port", "user", "password"): + cfg = tools.config.get("external_db_" + p) + if cfg: + connection_info[p] = cfg + return db_name, connection_info + + +def get_external_cursor(): + global _Pool + if _Pool is None: + _Pool = ConnectionPool(int(tools.config["db_maxconn"])) + db, info = connection_info_for_external_database() + return Connection(_Pool, db, info).cursor()