Files
tryton/trytond/backend/postgresql/database.py
T

787 lines
25 KiB
Python
Raw Normal View History

2014-12-02 12:54:01 +01:00
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
2021-12-11 17:58:45 +01:00
import json
2015-03-15 15:52:03 +01:00
import logging
import os
2021-12-11 17:58:45 +01:00
import time
import warnings
2021-12-11 17:58:45 +01:00
from collections import defaultdict
2018-07-10 17:56:44 +02:00
from datetime import datetime
2015-03-15 15:52:03 +01:00
from decimal import Decimal
2021-03-21 16:21:05 +01:00
from itertools import chain, repeat
from threading import RLock
2015-03-15 15:52:03 +01:00
2022-04-10 19:10:14 +02:00
from psycopg2 import Binary, connect
2021-12-11 17:58:45 +01:00
from psycopg2.extensions import (
2022-04-10 19:10:14 +02:00
ISOLATION_LEVEL_REPEATABLE_READ, UNICODE, AsIs, cursor, register_adapter,
register_type)
2021-12-11 17:58:45 +01:00
from psycopg2.pool import PoolError, ThreadedConnectionPool
2021-04-12 23:07:55 +02:00
from psycopg2.sql import SQL, Identifier
2021-12-11 17:58:45 +01:00
try:
2021-12-11 17:58:45 +01:00
from psycopg2.extensions import PYDATE, PYDATETIME, PYINTERVAL, PYTIME
except ImportError:
2015-02-09 22:58:56 +01:00
PYDATE, PYDATETIME, PYTIME, PYINTERVAL = None, None, None, None
2022-04-04 00:35:38 +02:00
from psycopg2 import DataError as DatabaseDataError
2009-02-04 15:29:28 +01:00
from psycopg2 import IntegrityError as DatabaseIntegrityError
2009-02-06 10:26:38 +01:00
from psycopg2 import OperationalError as DatabaseOperationalError
from psycopg2 import ProgrammingError
from psycopg2.extras import register_default_json, register_default_jsonb
2021-12-11 17:58:45 +01:00
from sql import Cast, Flavor, For, Table
2021-03-21 16:21:05 +01:00
from sql.conditionals import Coalesce
from sql.functions import Function
2021-03-21 16:21:05 +01:00
from sql.operators import BinaryOperator, Concat
2013-09-06 22:32:37 +02:00
from trytond.backend.database import DatabaseInterface, SQLType
2015-03-15 15:52:03 +01:00
from trytond.config import config, parse_uri
2019-02-05 19:09:53 +01:00
from trytond.tools.gevent import is_gevent_monkey_patched
2015-03-15 15:52:03 +01:00
2022-04-04 00:35:38 +02:00
__all__ = [
'Database',
'DatabaseIntegrityError', 'DatabaseDataError', 'DatabaseOperationalError']
2009-02-04 15:29:28 +01:00
2015-03-05 11:32:18 +01:00
logger = logging.getLogger(__name__)
os.environ['PGTZ'] = os.environ.get('TZ', '')
2018-08-30 11:15:01 +02:00
_timeout = config.getint('database', 'timeout')
2018-07-10 17:56:44 +02:00
_minconn = config.getint('database', 'minconn', default=1)
_maxconn = config.getint('database', 'maxconn', default=64)
2020-04-01 23:56:17 +02:00
_default_name = config.get('database', 'default_name', default='template1')
2009-02-04 15:29:28 +01:00
def unescape_quote(s):
if s.startswith('"') and s.endswith('"'):
return s.strip('"').replace('""', '"')
return s
def replace_special_values(s, **mapping):
2018-06-26 19:11:19 +02:00
for name, value in mapping.items():
s = s.replace('$' + name, value)
return s
2017-02-02 12:06:55 +01:00
class LoggingCursor(cursor):
def execute(self, sql, args=None):
if logger.isEnabledFor(logging.DEBUG):
logger.debug(self.mogrify(sql, args))
cursor.execute(self, sql, args)
2017-02-02 12:06:55 +01:00
class ForSkipLocked(For):
def __str__(self):
assert not self.nowait, "Can not use both NO WAIT and SKIP LOCKED"
return super().__str__() + (' SKIP LOCKED' if not self.nowait else '')
class Unaccent(Function):
__slots__ = ()
_function = config.get('database', 'unaccent_function', default='unaccent')
2021-03-21 16:21:05 +01:00
class Similarity(Function):
__slots__ = ()
_function = config.get(
'database', 'similarity_function', default='similarity')
2021-03-21 16:21:05 +01:00
class Match(BinaryOperator):
__slots__ = ()
_operator = '@@'
class ToTsvector(Function):
__slots__ = ()
_function = 'to_tsvector'
class Setweight(Function):
__slots__ = ()
_function = 'setweight'
class TsQuery(Function):
__slots__ = ()
class ToTsQuery(TsQuery):
__slots__ = ()
_function = 'to_tsquery'
class PlainToTsQuery(TsQuery):
__slots__ = ()
_function = 'plainto_tsquery'
class PhraseToTsQuery(TsQuery):
__slots__ = ()
_function = 'phraseto_tsquery'
class WebsearchToTsQuery(TsQuery):
__slots__ = ()
_function = 'websearch_to_tsquery'
class TsRank(Function):
__slots__ = ()
_function = 'ts_rank'
2018-07-27 09:32:28 +02:00
class AdvisoryLock(Function):
_function = 'pg_advisory_xact_lock'
class TryAdvisoryLock(Function):
_function = 'pg_try_advisory_xact_lock'
2019-01-18 11:32:47 +01:00
class JSONBExtractPath(Function):
__slots__ = ()
_function = 'jsonb_extract_path'
class JSONKeyExists(BinaryOperator):
__slots__ = ()
_operator = '?'
2019-09-20 11:32:56 +02:00
class _BinaryOperatorArray(BinaryOperator):
"Binary Operator that convert list into Array"
@property
def _operands(self):
if isinstance(self.right, list):
return (self.left, None)
return super()._operands
@property
def params(self):
params = super().params
if isinstance(self.right, list):
params = params[:-1] + (self.right,)
return params
class JSONAnyKeyExist(_BinaryOperatorArray):
2019-01-18 11:32:47 +01:00
__slots__ = ()
_operator = '?|'
2019-09-20 11:32:56 +02:00
class JSONAllKeyExist(_BinaryOperatorArray):
2019-01-18 11:32:47 +01:00
__slots__ = ()
_operator = '?&'
class JSONContains(BinaryOperator):
__slots__ = ()
_operator = '@>'
2009-02-04 15:29:28 +01:00
class Database(DatabaseInterface):
index_translators = []
_lock = RLock()
2019-06-27 21:45:37 +02:00
_databases = defaultdict(dict)
2009-02-11 14:52:22 +01:00
_connpool = None
2018-05-22 23:42:35 +02:00
_list_cache = {}
_list_cache_timestamp = {}
_search_path = None
_current_user = None
_has_returning = None
_has_select_for_skip_locked = None
_has_proc = defaultdict(lambda: defaultdict(dict))
_extensions = defaultdict(dict)
2021-03-21 16:21:05 +01:00
_search_full_text_languages = defaultdict(dict)
2013-09-06 22:32:37 +02:00
flavor = Flavor(ilike=True)
2009-02-11 14:52:22 +01:00
TYPES_MAPPING = {
'BIGINT': SQLType('INT8', 'INT8'),
'BLOB': SQLType('BYTEA', 'BYTEA'),
'DATETIME': SQLType('TIMESTAMP', 'TIMESTAMP(0)'),
'FLOAT': SQLType('FLOAT8', 'FLOAT8'),
2021-03-21 16:21:05 +01:00
'FULLTEXT': SQLType('TSVECTOR', 'TSVECTOR'),
'INTEGER': SQLType('INT4', 'INT4'),
'JSON': SQLType('JSONB', 'JSONB'),
'TIMESTAMP': SQLType('TIMESTAMP', 'TIMESTAMP(6)'),
}
2020-04-01 23:56:17 +02:00
def __new__(cls, name=_default_name):
with cls._lock:
2018-07-10 17:56:44 +02:00
now = datetime.now()
2019-06-27 21:45:37 +02:00
databases = cls._databases[os.getpid()]
for database in list(databases.values()):
2018-07-10 17:56:44 +02:00
if ((now - database._last_use).total_seconds() > _timeout
and database.name != name
and not database._connpool._used):
database.close()
2019-06-27 21:45:37 +02:00
if name in databases:
inst = databases[name]
2018-07-10 17:56:44 +02:00
else:
2020-04-01 23:56:17 +02:00
if name == _default_name:
2018-07-10 17:56:44 +02:00
minconn = 0
else:
minconn = _minconn
inst = DatabaseInterface.__new__(cls, name=name)
try:
inst._connpool = ThreadedConnectionPool(
minconn, _maxconn, **cls._connection_params(name),
cursor_factory=LoggingCursor)
except Exception:
logger.error(
'connection to "%s" failed', name, exc_info=True)
raise
else:
logger.info('connection to "%s" succeeded', name)
2019-06-27 21:45:37 +02:00
databases[name] = inst
2018-07-10 17:56:44 +02:00
inst._last_use = datetime.now()
return inst
2009-02-04 15:29:28 +01:00
2020-04-01 23:56:17 +02:00
def __init__(self, name=_default_name):
2018-07-10 17:56:44 +02:00
super(Database, self).__init__(name)
2016-03-16 22:12:22 +01:00
@classmethod
def _connection_params(cls, name):
uri = parse_uri(config.get('database', 'uri'))
if uri.path and uri.path != '/':
warnings.warn("The path specified in the URI will be overridden")
params = {
'dsn': uri._replace(path=name).geturl(),
}
return params
2016-03-16 22:12:22 +01:00
def connect(self):
2009-02-04 15:29:28 +01:00
return self
def get_connection(self, autocommit=False, readonly=False):
2016-05-10 10:35:23 +02:00
for count in range(config.getint('database', 'retry'), -1, -1):
try:
conn = self._connpool.getconn()
break
except PoolError:
if count and not self._connpool.closed:
logger.info('waiting a connection')
time.sleep(1)
continue
raise
except Exception:
logger.error(
'connection to "%s" failed', self.name, exc_info=True)
raise
2022-04-10 19:10:14 +02:00
conn.set_session(
isolation_level=ISOLATION_LEVEL_REPEATABLE_READ,
readonly=readonly,
autocommit=autocommit)
return conn
def put_connection(self, connection, close=False):
self._connpool.putconn(connection, close=close)
2009-02-04 15:29:28 +01:00
def close(self):
with self._lock:
logger.info('disconnection from "%s"', self.name)
self._connpool.closeall()
2019-06-27 21:45:37 +02:00
self._databases[os.getpid()].pop(self.name)
2009-02-04 15:29:28 +01:00
@classmethod
def create(cls, connection, database_name, template='template0'):
cursor = connection.cursor()
2021-04-12 23:07:55 +02:00
cursor.execute(
SQL(
"CREATE DATABASE {} TEMPLATE {} ENCODING 'unicode'")
.format(
Identifier(database_name),
Identifier(template)))
connection.commit()
2018-05-22 23:42:35 +02:00
cls._list_cache.clear()
2009-02-04 15:29:28 +01:00
2022-09-08 13:05:06 +02:00
@classmethod
def drop(cls, connection, database_name):
cursor = connection.cursor()
2021-04-12 23:07:55 +02:00
cursor.execute(SQL("DROP DATABASE {}")
.format(Identifier(database_name)))
2022-09-08 13:05:06 +02:00
cls._list_cache.clear()
2022-10-03 00:15:12 +02:00
cls._has_proc.pop(database_name, None)
cls._search_full_text_languages.pop(database_name, None)
2009-02-04 15:29:28 +01:00
def get_version(self, connection):
version = connection.server_version
major, rest = divmod(int(version), 10000)
minor, patch = divmod(rest, 100)
return (major, minor, patch)
2011-08-21 10:20:23 +02:00
2018-05-22 23:42:35 +02:00
def list(self, hostname=None):
2009-08-25 16:58:09 +02:00
now = time.time()
timeout = config.getint('session', 'timeout')
2018-05-22 23:42:35 +02:00
res = self.__class__._list_cache.get(hostname)
timestamp = self.__class__._list_cache_timestamp.get(hostname, now)
if res and abs(timestamp - now) < timeout:
2009-08-25 16:58:09 +02:00
return res
connection = self.get_connection()
try:
cursor = connection.cursor()
cursor.execute('SELECT datname FROM pg_database '
'WHERE datistemplate = false ORDER BY datname')
res = []
for db_name, in cursor:
try:
2020-09-24 23:02:14 +02:00
conn = connect(**self._connection_params(db_name))
try:
with conn:
if self._test(conn, hostname=hostname):
res.append(db_name)
finally:
conn.close()
except Exception:
logger.debug(
'Test failed for "%s"', db_name, exc_info=True)
continue
finally:
self.put_connection(connection)
2018-05-22 23:42:35 +02:00
self.__class__._list_cache[hostname] = res
self.__class__._list_cache_timestamp[hostname] = now
2009-02-04 15:29:28 +01:00
return res
def init(self):
from trytond.modules import get_module_info
connection = self.get_connection()
try:
cursor = connection.cursor()
sql_file = os.path.join(os.path.dirname(__file__), 'init.sql')
with open(sql_file) as fp:
for line in fp.read().split(';'):
if (len(line) > 0) and (not line.isspace()):
cursor.execute(line)
2009-02-04 15:29:28 +01:00
for module in ('ir', 'res'):
state = 'not activated'
if module in ('ir', 'res'):
state = 'to activate'
info = get_module_info(module)
cursor.execute('SELECT NEXTVAL(\'ir_module_id_seq\')')
module_id = cursor.fetchone()[0]
cursor.execute('INSERT INTO ir_module '
'(id, create_uid, create_date, name, state) '
'VALUES (%s, %s, now(), %s, %s)',
(module_id, 0, module, state))
for dependency in info.get('depends', []):
cursor.execute('INSERT INTO ir_module_dependency '
'(create_uid, create_date, module, name) '
'VALUES (%s, now(), %s, %s)',
(0, module_id, dependency))
2009-02-04 15:29:28 +01:00
connection.commit()
finally:
self.put_connection(connection)
2009-02-04 15:29:28 +01:00
2018-05-22 23:42:35 +02:00
def test(self, hostname=None):
try:
connection = self.get_connection()
2018-05-22 23:42:35 +02:00
except Exception:
logger.debug('Test failed for "%s"', self.name, exc_info=True)
return False
try:
return self._test(connection, hostname=hostname)
2018-05-22 23:42:35 +02:00
finally:
self.put_connection(connection)
2016-03-16 22:12:22 +01:00
@classmethod
2018-05-22 23:42:35 +02:00
def _test(cls, connection, hostname=None):
cursor = connection.cursor()
2018-05-22 23:42:35 +02:00
tables = ('ir_model', 'ir_model_field', 'ir_ui_view', 'ir_ui_menu',
'res_user', 'res_group', 'ir_module', 'ir_module_dependency',
'ir_translation', 'ir_lang', 'ir_configuration')
cursor.execute('SELECT table_name FROM information_schema.tables '
'WHERE table_name IN %s', (tables,))
if len(cursor.fetchall()) != len(tables):
return False
if hostname:
try:
cursor.execute(
'SELECT hostname FROM ir_configuration')
hostnames = {h for h, in cursor if h}
if hostnames and hostname not in hostnames:
return False
except ProgrammingError:
pass
2018-05-22 23:42:35 +02:00
return True
2009-02-04 15:29:28 +01:00
def nextid(self, connection, table):
cursor = connection.cursor()
2021-04-12 23:07:55 +02:00
cursor.execute("SELECT NEXTVAL(%s)", (table + '_id_seq',))
return cursor.fetchone()[0]
2009-07-07 19:32:31 +02:00
def setnextid(self, connection, table, value):
cursor = connection.cursor()
2021-04-12 23:07:55 +02:00
cursor.execute("SELECT SETVAL(%s, %s)", (table + '_id_seq', value))
2009-07-07 19:32:31 +02:00
def currid(self, connection, table):
cursor = connection.cursor()
2021-04-12 23:07:55 +02:00
cursor.execute(SQL("SELECT last_value FROM {}").format(
Identifier(table + '_id_seq')))
return cursor.fetchone()[0]
2009-07-07 19:32:31 +02:00
def lock(self, connection, table):
cursor = connection.cursor()
2021-04-12 23:07:55 +02:00
cursor.execute(SQL('LOCK {} IN EXCLUSIVE MODE NOWAIT').format(
Identifier(table)))
2009-07-07 19:32:31 +02:00
2018-07-27 09:32:28 +02:00
def lock_id(self, id, timeout=None):
if not timeout:
return TryAdvisoryLock(id)
else:
return AdvisoryLock(id)
2018-03-19 16:57:49 +01:00
def has_constraint(self, constraint):
return True
2015-07-28 08:04:29 +02:00
def has_multirow_insert(self):
return True
def get_table_schema(self, connection, table_name):
cursor = connection.cursor()
for schema in self.search_path:
cursor.execute('SELECT 1 '
'FROM information_schema.tables '
'WHERE table_name = %s AND table_schema = %s',
(table_name, schema))
if cursor.rowcount:
return schema
2015-09-04 11:16:09 +02:00
@property
def current_user(self):
if self._current_user is None:
connection = self.get_connection()
2016-03-25 10:46:12 +01:00
try:
cursor = connection.cursor()
cursor.execute('SELECT current_user')
self._current_user = cursor.fetchone()[0]
finally:
self.put_connection(connection)
2015-09-04 11:16:09 +02:00
return self._current_user
@property
def search_path(self):
if self._search_path is None:
connection = self.get_connection()
2016-03-25 10:46:12 +01:00
try:
cursor = connection.cursor()
cursor.execute('SHOW search_path')
path, = cursor.fetchone()
special_values = {
'user': self.current_user,
}
self._search_path = [
unescape_quote(replace_special_values(
p.strip(), **special_values))
for p in path.split(',')]
finally:
self.put_connection(connection)
2015-09-04 11:16:09 +02:00
return self._search_path
2016-08-25 15:38:07 +02:00
def has_returning(self):
if self._has_returning is None:
connection = self.get_connection()
try:
# RETURNING clause is available since PostgreSQL 8.2
self._has_returning = self.get_version(connection) >= (8, 2)
finally:
self.put_connection(connection)
return self._has_returning
2016-08-25 15:38:07 +02:00
2017-01-25 23:21:57 +01:00
def has_select_for(self):
return True
def get_select_for_skip_locked(self):
if self._has_select_for_skip_locked is None:
connection = self.get_connection()
try:
# SKIP LOCKED clause is available since PostgreSQL 9.5
self._has_select_for_skip_locked = (
self.get_version(connection) >= (9, 5))
finally:
self.put_connection(connection)
if self._has_select_for_skip_locked:
return ForSkipLocked
else:
return For
2017-03-22 10:49:26 +01:00
def has_window_functions(self):
return True
@classmethod
def has_sequence(cls):
return True
def has_proc(self, name, property='oid'):
if (name in self._has_proc[self.name]
and property in self._has_proc[self.name][name]):
return self._has_proc[self.name][name][property]
connection = self.get_connection()
2021-03-21 16:21:05 +01:00
result = False
try:
cursor = connection.cursor()
cursor.execute(
SQL('SELECT {} FROM pg_proc WHERE proname=%s').format(
Identifier(property)), (name,))
result = cursor.fetchone()
if result:
result, = result
finally:
self.put_connection(connection)
self._has_proc[self.name][name][property] = result
2021-03-21 16:21:05 +01:00
return result
def has_unaccent(self):
return self.has_proc(Unaccent._function)
def has_unaccent_indexable(self):
return self.has_proc(Unaccent._function, 'provolatile') == 'i'
2021-03-21 16:21:05 +01:00
def has_similarity(self):
return self.has_proc(Similarity._function)
def similarity(self, column, value):
return Similarity(column, value)
def has_search_full_text(self):
return True
def _search_full_text_language(self, language):
languages = self._search_full_text_languages[self.name]
if language not in languages:
lang = Table('ir_lang')
connection = self.get_connection()
try:
cursor = connection.cursor()
cursor.execute(*lang.select(
Coalesce(lang.pg_text_search, 'simple'),
where=lang.code == language,
limit=1))
config_name, = cursor.fetchone()
finally:
self.put_connection(connection)
languages[language] = config_name
else:
config_name = languages[language]
return config_name
def format_full_text(self, *documents, language=None):
size = max(len(documents) // 4, 1)
if len(documents) > 1:
weights = chain(
['A'] * size, ['B'] * size, ['C'] * size, repeat('D'))
else:
weights = [None]
expression = None
if language:
config_name = self._search_full_text_language(language)
else:
config_name = None
for document, weight in zip(documents, weights):
if not document:
continue
if config_name:
ts_vector = ToTsvector(config_name, document)
else:
ts_vector = ToTsvector('simple', document)
if weight:
ts_vector = Setweight(ts_vector, weight)
if expression is None:
expression = ts_vector
else:
expression = Concat(expression, ts_vector)
return expression
def format_full_text_query(self, query, language=None):
connection = self.get_connection()
try:
version = self.get_version(connection)
finally:
self.put_connection(connection)
if not isinstance(query, TsQuery):
if version >= (11, 0):
ToTsQuery = WebsearchToTsQuery
else:
ToTsQuery = PlainToTsQuery
if language:
config_name = self._search_full_text_language(language)
else:
config_name = 'simple'
query = ToTsQuery(config_name, query)
2021-03-21 16:21:05 +01:00
return query
def search_full_text(self, document, query):
return Match(document, query)
def rank_full_text(self, document, query, normalize=None):
# TODO: weights and cover density
norm_int = 0
if normalize:
values = {
'document log': 1,
'document': 2,
'mean': 4,
'word': 8,
'word log': 16,
'rank': 32,
}
for norm in normalize:
norm_int |= values.get(norm, 0)
return TsRank(document, query, norm_int)
def sql_type(self, type_):
if type_ in self.TYPES_MAPPING:
return self.TYPES_MAPPING[type_]
if type_.startswith('VARCHAR'):
return SQLType('VARCHAR', type_)
return SQLType(type_, type_)
def sql_format(self, type_, value):
if type_ == 'BLOB':
if value is not None:
return Binary(value)
return value
def unaccent(self, value):
if self.has_unaccent():
return Unaccent(value)
return value
def sequence_exist(self, connection, name):
cursor = connection.cursor()
for schema in self.search_path:
cursor.execute('SELECT 1 '
'FROM information_schema.sequences '
'WHERE sequence_name = %s AND sequence_schema = %s',
(name, schema))
if cursor.rowcount:
return True
return False
def sequence_create(
self, connection, name, number_increment=1, start_value=1):
cursor = connection.cursor()
cursor.execute(
2021-04-12 23:07:55 +02:00
SQL("CREATE SEQUENCE {} INCREMENT BY %s START WITH %s").format(
Identifier(name)),
(number_increment, start_value))
def sequence_update(
self, connection, name, number_increment=1, start_value=1):
cursor = connection.cursor()
cursor.execute(
2021-04-12 23:07:55 +02:00
SQL("ALTER SEQUENCE {} INCREMENT BY %s RESTART WITH %s").format(
Identifier(name)),
(number_increment, start_value))
def sequence_rename(self, connection, old_name, new_name):
cursor = connection.cursor()
if (self.sequence_exist(connection, old_name)
and not self.sequence_exist(connection, new_name)):
2021-04-12 23:07:55 +02:00
cursor.execute(
SQL("ALTER TABLE {} RENAME TO {}").format(
2021-04-12 23:07:55 +02:00
Identifier(old_name),
Identifier(new_name)))
def sequence_delete(self, connection, name):
cursor = connection.cursor()
2021-04-12 23:07:55 +02:00
cursor.execute(SQL("DROP SEQUENCE {}").format(
Identifier(name)))
def sequence_next_number(self, connection, name):
cursor = connection.cursor()
version = self.get_version(connection)
if version >= (10, 0):
cursor.execute(
'SELECT increment_by '
'FROM pg_sequences '
2021-04-12 23:07:55 +02:00
'WHERE sequencename=%s',
(name,))
increment, = cursor.fetchone()
cursor.execute(
2021-04-12 23:07:55 +02:00
SQL(
'SELECT CASE WHEN NOT is_called THEN last_value '
'ELSE last_value + %s '
'END '
'FROM {}').format(Identifier(name)),
(increment,))
else:
cursor.execute(
2021-04-12 23:07:55 +02:00
SQL(
'SELECT CASE WHEN NOT is_called THEN last_value '
'ELSE last_value + increment_by '
'END '
'FROM {}').format(Identifier(name)))
return cursor.fetchone()[0]
2018-07-27 09:32:28 +02:00
def has_channel(self):
return True
def has_extension(self, extension_name):
if extension_name in self._extensions[self.name]:
return self._extensions[self.name][extension_name]
connection = self.get_connection()
result = False
try:
cursor = connection.cursor()
cursor.execute(
"SELECT 1 FROM pg_extension WHERE extname=%s",
(extension_name,))
result = bool(cursor.rowcount)
finally:
self.put_connection(connection)
self._extensions[self.name][extension_name] = result
return result
2019-01-18 11:32:47 +01:00
def json_get(self, column, key=None):
column = Cast(column, 'jsonb')
if key:
column = JSONBExtractPath(column, key)
return column
def json_key_exists(self, column, key):
return JSONKeyExists(Cast(column, 'jsonb'), key)
def json_any_keys_exist(self, column, keys):
return JSONAnyKeyExist(Cast(column, 'jsonb'), keys)
def json_all_keys_exist(self, column, keys):
return JSONAllKeyExist(Cast(column, 'jsonb'), keys)
def json_contains(self, column, json):
return JSONContains(Cast(column, 'jsonb'), Cast(json, 'jsonb'))
2018-07-27 09:32:28 +02:00
2009-02-04 15:29:28 +01:00
register_type(UNICODE)
if PYDATE:
register_type(PYDATE)
if PYDATETIME:
register_type(PYDATETIME)
if PYTIME:
register_type(PYTIME)
2015-02-09 22:58:56 +01:00
if PYINTERVAL:
register_type(PYINTERVAL)
register_adapter(float, lambda value: AsIs(repr(value)))
register_adapter(Decimal, lambda value: AsIs(str(value)))
def convert_json(value):
2019-12-06 17:33:47 +01:00
from trytond.protocols.jsonrpc import JSONDecoder
return json.loads(value, object_hook=JSONDecoder())
2020-03-01 12:33:54 +01:00
register_default_json(loads=convert_json)
register_default_jsonb(loads=convert_json)
2019-02-05 19:09:53 +01:00
if is_gevent_monkey_patched():
from psycopg2.extensions import set_wait_callback
from psycopg2.extras import wait_select
set_wait_callback(wait_select)