diff --git a/CHANGELOG b/CHANGELOG index 9a755a5cef..01c81d2e44 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Add support for database connection parameters to configuration URI * Use immutable datastructures for Dict and MultiSelection fields * Skip warnings for non-interactive operations * Check rule only if _check_access is set diff --git a/doc/topics/configuration.rst b/doc/topics/configuration.rst index 39c6f40751..41a87a8acc 100644 --- a/doc/topics/configuration.rst +++ b/doc/topics/configuration.rst @@ -106,7 +106,10 @@ uri Contains the URI to connect to the SQL database. The URI follows the RFC-3986_. The typical form is: - database://username:password@host:port/ + database://username:password@host:port/?param1=value1¶m2=value2 + +The parameters are database dependent, check the database documentation for a +list of valid parameters. Default: The value of the environment variable ``TRYTOND_DATABASE_URI`` or ``sqlite://`` if not set. @@ -116,15 +119,27 @@ The available databases are: PostgreSQL ********** -``pyscopg2`` supports two type of connections: +``psycopg2`` supports two type of connections: - TCP/IP connection: ``postgresql://user:password@localhost:5432/`` - Unix domain connection: ``postgresql://username:password@/`` +Please refer to `psycopg2 for the complete specification of the URI +`_. + +A list of parameters supported by PostgreSQL can be found in the +`documentation `_. + SQLite ****** -The only possible URI is: ``sqlite://`` +The URI is defined as ``sqlite://`` + +If the name of the database is ``:memory:``, the parameter ``mode`` will be set +to ``memory`` thus using a pure in-memory database. + +The recognized query parameters can be found in SQLite's +`documentation `_. path ~~~~ diff --git a/trytond/backend/postgresql/database.py b/trytond/backend/postgresql/database.py index 5597326d8f..d3f5cd4cff 100644 --- a/trytond/backend/postgresql/database.py +++ b/trytond/backend/postgresql/database.py @@ -4,8 +4,8 @@ from collections import defaultdict import time import logging import os -import urllib.parse import json +import warnings from datetime import datetime from decimal import Decimal from itertools import chain, repeat @@ -243,17 +243,11 @@ class Database(DatabaseInterface): @classmethod def _connection_params(cls, name): uri = parse_uri(config.get('database', 'uri')) + if uri.path: + warnings.warn("The path specified in the URI will be overridden") params = { - 'dbname': name, + 'dsn': uri._replace(path=name).geturl(), } - if uri.username: - params['user'] = uri.username - if uri.password: - params['password'] = urllib.parse.unquote_plus(uri.password) - if uri.hostname: - params['host'] = uri.hostname - if uri.port: - params['port'] = uri.port return params def connect(self): diff --git a/trytond/backend/sqlite/database.py b/trytond/backend/sqlite/database.py index c378940541..088f59f70d 100644 --- a/trytond/backend/sqlite/database.py +++ b/trytond/backend/sqlite/database.py @@ -7,8 +7,11 @@ import os import random import threading import time +import urllib.parse +import warnings from decimal import Decimal from weakref import WeakKeyDictionary +from werkzeug.security import safe_join try: from pysqlite2 import dbapi2 as sqlite @@ -24,7 +27,7 @@ from sql.functions import (Function, Extract, Position, Substring, Overlay, CharLength, CurrentTimestamp, Trim) from trytond.backend.database import DatabaseInterface, SQLType -from trytond.config import config +from trytond.config import config, parse_uri from trytond.transaction import Transaction __all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError'] @@ -336,16 +339,10 @@ class Database(DatabaseInterface): Database._local.memory_database = self def connect(self): - if self.name == ':memory:': - path = ':memory:' - else: - db_filename = self.name + '.sqlite' - path = os.path.join(config.get('database', 'path'), db_filename) - if not os.path.isfile(path): - raise IOError('Database "%s" doesn\'t exist!' % path) if self._conn is not None: return self - self._conn = sqlite.connect(path, + self._conn = sqlite.connect( + self._make_uri(), uri=True, detect_types=sqlite.PARSE_DECLTYPES | sqlite.PARSE_COLNAMES, factory=SQLiteConnection) self._conn.create_function('extract', 2, SQLiteExtract.extract) @@ -402,6 +399,28 @@ class Database(DatabaseInterface): self._conn.execute('PRAGMA foreign_keys = ON') return self + def _make_uri(self): + uri = config.get('database', 'uri') + base_uri = parse_uri(uri) + if base_uri.path: + warnings.warn("The path specified in the URI will be overridden") + + if self.name == ':memory:': + query_string = urllib.parse.parse_qs(base_uri.query) + query_string['mode'] = 'memory' + query = urllib.parse.urlencode(query_string, doseq=True) + db_uri = base_uri._replace(netloc='', path='/', query=query) + else: + db_path = safe_join( + config.get('database', 'path'), self.name + '.sqlite') + db_uri = base_uri._replace(path=db_path) + + # Use unparse before replacing sqlite with file because SQLite accepts + # a relative path URI like file:db/test.sqlite which doesn't conform to + # RFC8089 which urllib follows and enforces when the scheme is 'file' + db_uri = urllib.parse.urlunparse(db_uri) + return db_uri.replace('sqlite', 'file', 1) + def get_connection(self, autocommit=False, readonly=False): if self._conn is None: self.connect()