From 26a8b438b6cc94241354af9fe218d8c592e17cfa Mon Sep 17 00:00:00 2001 From: Xavier Morel <xmo@odoo.com> Date: Mon, 23 Nov 2015 14:17:21 +0100 Subject: [PATCH] [IMP] provide pg connection information as keywords When the database_or_uri is not a URI, rather than generate a dsn string just collect the various psycopg2.connect keyword arguments in a dictionary, and return that. This way psycopg2/libpq is the one doing any necessary processing on the various parameters. Fixes #3865 and fixes #6958 in a more resilient manner than manually escaping dsn-injected values so database names or passwords containing spaces or non-ascii characters work correctly. In case a URI is provided to the dsn() function, return it as the `dsn` keyword to get the same behaviour as previously. Since we're checking dsns by equality, a dict should work just as well as a string. --- openerp/sql_db.py | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/openerp/sql_db.py b/openerp/sql_db.py index 4aef9549ff4b..fe0502a4fa9a 100644 --- a/openerp/sql_db.py +++ b/openerp/sql_db.py @@ -13,6 +13,8 @@ from functools import wraps import logging import urlparse import uuid + +import psycopg2 import psycopg2.extras import psycopg2.extensions from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT, ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_REPEATABLE_READ @@ -527,7 +529,11 @@ class ConnectionPool(object): _logger.debug(('%r ' + msg), self, *args) @locked - def borrow(self, dsn): + def borrow(self, connection_info): + """ + :param dict connection_info: dict of psql connection keywords + :rtype: PsycoConnection + """ # free dead and leaked connections for i, (cnx, _) in tools.reverse_enumerate(self._connections): if cnx.closed: @@ -541,7 +547,7 @@ class ConnectionPool(object): _logger.info('%r: Free leaked connection to %r', self, cnx.dsn) for i, (cnx, used) in enumerate(self._connections): - if not used and cnx._original_dsn == dsn: + if not used and cnx._original_dsn == connection_info: try: cnx.reset() except psycopg2.OperationalError: @@ -570,11 +576,13 @@ class ConnectionPool(object): raise PoolError('The Connection Pool Is Full') try: - result = psycopg2.connect(dsn=dsn, connection_factory=PsycoConnection) + result = psycopg2.connect( + connection_factory=PsycoConnection, + **connection_info) except psycopg2.Error: _logger.info('Connection to the database failed') raise - result._original_dsn = dsn + result._original_dsn = connection_info self._connections.append((result, True)) self._debug('Create new connection') return result @@ -639,8 +647,17 @@ class Connection(object): except Exception: return False -def dsn(db_or_uri): - """parse the given `db_or_uri` and return a 2-tuple (dbname, uri)""" +def connection_info_for(db_or_uri): + """ parse the given `db_or_uri` and return a 2-tuple (dbname, connection_params) + + Connection params are either a dictionary with a single key ``dsn`` + containing a connection URI, or a dictionary containing connection + parameter keywords which psycopg2 can build a key/value connection string + (dsn) from + + :param str db_or_uri: database name or postgres dsn + :rtype: (str, dict) + """ if db_or_uri.startswith(('postgresql://', 'postgres://')): # extract db from uri us = urlparse.urlsplit(db_or_uri) @@ -650,15 +667,15 @@ def dsn(db_or_uri): db_name = us.username else: db_name = us.hostname - return db_name, db_or_uri + return db_name, {'dsn': db_or_uri} - _dsn = '' + connection_info = {'database': db_or_uri} for p in ('host', 'port', 'user', 'password'): cfg = tools.config['db_' + p] if cfg: - _dsn += '%s=%s ' % (p, cfg) + connection_info[p] = cfg - return db_or_uri, '%sdbname=%s' % (_dsn, db_or_uri) + return db_or_uri, connection_info _Pool = None @@ -667,16 +684,16 @@ def db_connect(to, allow_uri=False): if _Pool is None: _Pool = ConnectionPool(int(tools.config['db_maxconn'])) - db, uri = dsn(to) + db, info = connection_info_for(to) if not allow_uri and db != to: raise ValueError('URI connections not allowed') - return Connection(_Pool, db, uri) + return Connection(_Pool, db, info) def close_db(db_name): """ You might want to call openerp.modules.registry.RegistryManager.delete(db_name) along this function.""" global _Pool if _Pool: - _Pool.close_all(dsn(db_name)[1]) + _Pool.close_all(connection_info_for(db_name)[1]) def close_all(): global _Pool -- GitLab