Use singleton for TableHandler
issue11301 review366861002
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
* Use singleton for TableHandler
|
||||
|
||||
Version 6.4.0 - 2022-05-02
|
||||
* Bug fixes (see mercurial logs for details)
|
||||
* Use unittest discover
|
||||
|
||||
@@ -17,13 +17,12 @@ VARCHAR_SIZE_RE = re.compile(r'VARCHAR\(([0-9]+)\)')
|
||||
class TableHandler(TableHandlerInterface):
|
||||
namedatalen = 64
|
||||
|
||||
def __init__(self, model, module_name=None, history=False):
|
||||
super(TableHandler, self).__init__(model,
|
||||
module_name=module_name, history=history)
|
||||
self._columns = {}
|
||||
self._constraints = []
|
||||
self._fk_deltypes = {}
|
||||
self._indexes = []
|
||||
def _init(self, model, history=False):
|
||||
super()._init(model, history=history)
|
||||
self.__columns = None
|
||||
self.__constraints = None
|
||||
self.__fk_deltypes = None
|
||||
self.__indexes = None
|
||||
|
||||
transaction = Transaction()
|
||||
cursor = transaction.connection.cursor()
|
||||
@@ -50,7 +49,6 @@ class TableHandler(TableHandlerInterface):
|
||||
Identifier(self.table_name)),
|
||||
(model.__doc__,))
|
||||
|
||||
self._update_definitions(columns=True)
|
||||
if 'id' not in self._columns:
|
||||
if not self.history:
|
||||
cursor.execute(
|
||||
@@ -77,6 +75,7 @@ class TableHandler(TableHandlerInterface):
|
||||
cursor.execute(
|
||||
SQL('ALTER TABLE {} ADD PRIMARY KEY(__id)')
|
||||
.format(Identifier(self.table_name)))
|
||||
self._update_definitions(columns=True)
|
||||
else:
|
||||
default = "nextval('%s'::regclass)" % self.sequence_name
|
||||
if self.history:
|
||||
@@ -86,13 +85,14 @@ class TableHandler(TableHandlerInterface):
|
||||
"ALTER __id SET DEFAULT nextval(%s::regclass)")
|
||||
.format(Identifier(self.table_name)),
|
||||
(self.sequence_name,))
|
||||
self._update_definitions(columns=True)
|
||||
if self._columns['id']['default'] != default:
|
||||
cursor.execute(
|
||||
SQL("ALTER TABLE {} "
|
||||
"ALTER id SET DEFAULT nextval(%s::regclass)")
|
||||
.format(Identifier(self.table_name)),
|
||||
(self.sequence_name,))
|
||||
self._update_definitions()
|
||||
self._update_definitions(columns=True)
|
||||
|
||||
@staticmethod
|
||||
def table_exist(table_name):
|
||||
@@ -140,13 +140,11 @@ class TableHandler(TableHandlerInterface):
|
||||
'Unable to rename column %s on table %s to %s.',
|
||||
old_name, self.table_name, new_name)
|
||||
|
||||
def _update_definitions(self,
|
||||
columns=None, constraints=None, indexes=None):
|
||||
if columns is None and constraints is None and indexes is None:
|
||||
columns = constraints = indexes = True
|
||||
cursor = Transaction().connection.cursor()
|
||||
if columns:
|
||||
self._columns = {}
|
||||
@property
|
||||
def _columns(self):
|
||||
if self.__columns is None:
|
||||
cursor = Transaction().connection.cursor()
|
||||
self.__columns = {}
|
||||
# Fetch columns definitions from the table
|
||||
cursor.execute('SELECT '
|
||||
'column_name, udt_name, is_nullable, '
|
||||
@@ -156,20 +154,24 @@ class TableHandler(TableHandlerInterface):
|
||||
'WHERE table_name = %s AND table_schema = %s',
|
||||
(self.table_name, self.table_schema))
|
||||
for column, typname, nullable, size, default in cursor:
|
||||
self._columns[column] = {
|
||||
self.__columns[column] = {
|
||||
'typname': typname,
|
||||
'notnull': True if nullable == 'NO' else False,
|
||||
'size': size,
|
||||
'default': default,
|
||||
}
|
||||
return self.__columns
|
||||
|
||||
if constraints:
|
||||
@property
|
||||
def _constraints(self):
|
||||
if self.__constraints is None:
|
||||
cursor = Transaction().connection.cursor()
|
||||
# fetch constraints for the table
|
||||
cursor.execute('SELECT constraint_name '
|
||||
'FROM information_schema.table_constraints '
|
||||
'WHERE table_name = %s AND table_schema = %s',
|
||||
(self.table_name, self.table_schema))
|
||||
self._constraints = [c for c, in cursor]
|
||||
self.__constraints = [c for c, in cursor]
|
||||
|
||||
# add nonstandard exclude constraint
|
||||
cursor.execute('SELECT c.conname '
|
||||
@@ -183,8 +185,13 @@ class TableHandler(TableHandlerInterface):
|
||||
"AND r.relkind IN ('r', 'p') "
|
||||
'AND r.relname = %s AND nr.nspname = %s',
|
||||
(self.table_name, self.table_schema))
|
||||
self._constraints.extend((c for c, in cursor))
|
||||
self.__constraints.extend((c for c, in cursor))
|
||||
return self.__constraints
|
||||
|
||||
@property
|
||||
def _fk_deltypes(self):
|
||||
if self.__fk_deltypes is None:
|
||||
cursor = Transaction().connection.cursor()
|
||||
cursor.execute('SELECT k.column_name, r.delete_rule '
|
||||
'FROM information_schema.key_column_usage AS k '
|
||||
'JOIN information_schema.referential_constraints AS r '
|
||||
@@ -192,9 +199,13 @@ class TableHandler(TableHandlerInterface):
|
||||
'AND r.constraint_name = k.constraint_name '
|
||||
'WHERE k.table_name = %s AND k.table_schema = %s',
|
||||
(self.table_name, self.table_schema))
|
||||
self._fk_deltypes = dict(cursor)
|
||||
self.__fk_deltypes = dict(cursor)
|
||||
return self.__fk_deltypes
|
||||
|
||||
if indexes:
|
||||
@property
|
||||
def _indexes(self):
|
||||
if self.__indexes is None:
|
||||
cursor = Transaction().connection.cursor()
|
||||
# Fetch indexes defined for the table
|
||||
cursor.execute("SELECT cl2.relname "
|
||||
"FROM pg_index ind "
|
||||
@@ -203,17 +214,20 @@ class TableHandler(TableHandlerInterface):
|
||||
"JOIN pg_class cl2 on (cl2.oid = ind.indexrelid) "
|
||||
"WHERE cl.relname = %s AND n.nspname = %s",
|
||||
(self.table_name, self.table_schema))
|
||||
self._indexes = [l[0] for l in cursor]
|
||||
self.__indexes = [l[0] for l in cursor]
|
||||
return self.__indexes
|
||||
|
||||
@property
|
||||
def _field2module(self):
|
||||
cursor = Transaction().connection.cursor()
|
||||
cursor.execute('SELECT f.name, f.module '
|
||||
'FROM ir_model_field f '
|
||||
'JOIN ir_model m on (f.model=m.id) '
|
||||
'WHERE m.model = %s',
|
||||
(self.object_name,))
|
||||
return dict(cursor)
|
||||
def _update_definitions(self,
|
||||
columns=None, constraints=None, indexes=None):
|
||||
if columns is None and constraints is None and indexes is None:
|
||||
columns = constraints = indexes = True
|
||||
if columns:
|
||||
self.__columns = None
|
||||
if constraints:
|
||||
self.__constraints = None
|
||||
self.__fk_deltypes = None
|
||||
if indexes:
|
||||
self.__indexes = None
|
||||
|
||||
def alter_size(self, column_name, column_type):
|
||||
cursor = Transaction().connection.cursor()
|
||||
@@ -427,11 +441,6 @@ class TableHandler(TableHandlerInterface):
|
||||
params)
|
||||
self._update_definitions(indexes=True)
|
||||
elif action == 'remove':
|
||||
if len(columns) == 1 and isinstance(columns[0], str):
|
||||
if self._field2module.get(columns[0],
|
||||
self.module_name) != self.module_name:
|
||||
return
|
||||
|
||||
if index_name in self._indexes:
|
||||
cursor.execute(SQL('DROP INDEX {}').format(
|
||||
Identifier(index_name)))
|
||||
@@ -469,9 +478,6 @@ class TableHandler(TableHandlerInterface):
|
||||
elif action == 'remove':
|
||||
if not self._columns[column_name]['notnull']:
|
||||
return
|
||||
if (self._field2module.get(column_name, self.module_name)
|
||||
!= self.module_name):
|
||||
return
|
||||
cursor.execute(
|
||||
SQL('ALTER TABLE {} ALTER COLUMN {} DROP NOT NULL')
|
||||
.format(
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import logging
|
||||
import re
|
||||
import warnings
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
from trytond.backend.table import TableHandlerInterface
|
||||
from trytond.transaction import Transaction
|
||||
@@ -21,13 +22,12 @@ def _escape_identifier(name):
|
||||
|
||||
|
||||
class TableHandler(TableHandlerInterface):
|
||||
def __init__(self, model, module_name=None, history=False):
|
||||
super(TableHandler, self).__init__(model,
|
||||
module_name=module_name, history=history)
|
||||
self._columns = {}
|
||||
self._constraints = []
|
||||
self._fk_deltypes = {}
|
||||
self._indexes = []
|
||||
__handlers = WeakKeyDictionary()
|
||||
|
||||
def _init(self, model, history=False):
|
||||
super()._init(model, history=history)
|
||||
self.__columns = None
|
||||
self.__indexes = None
|
||||
self._model = model
|
||||
|
||||
cursor = Transaction().connection.cursor()
|
||||
@@ -143,14 +143,12 @@ class TableHandler(TableHandlerInterface):
|
||||
'Unable to rename column %s on table %s to %s.',
|
||||
old_name, self.table_name, new_name)
|
||||
|
||||
def _update_definitions(self, columns=None, indexes=None):
|
||||
if columns is None and indexes is None:
|
||||
columns = indexes = True
|
||||
cursor = Transaction().connection.cursor()
|
||||
# Fetch columns definitions from the table
|
||||
if columns:
|
||||
@property
|
||||
def _columns(self):
|
||||
if self.__columns is None:
|
||||
cursor = Transaction().connection.cursor()
|
||||
cursor.execute('PRAGMA table_info("' + self.table_name + '")')
|
||||
self._columns = {}
|
||||
self.__columns = {}
|
||||
for _, column, type_, notnull, hasdef, _ in cursor:
|
||||
column = re.sub(r'^\"|\"$', '', column)
|
||||
match = re.match(r'(\w+)(\((.*?)\))?', type_)
|
||||
@@ -160,30 +158,32 @@ class TableHandler(TableHandlerInterface):
|
||||
else:
|
||||
typname = type_.upper()
|
||||
size = None
|
||||
self._columns[column] = {
|
||||
self.__columns[column] = {
|
||||
'notnull': notnull,
|
||||
'hasdef': hasdef,
|
||||
'size': size,
|
||||
'typname': typname,
|
||||
}
|
||||
return self.__columns
|
||||
|
||||
# Fetch indexes defined for the table
|
||||
if indexes:
|
||||
@property
|
||||
def _indexes(self):
|
||||
if self.__indexes is None:
|
||||
cursor = Transaction().connection.cursor()
|
||||
try:
|
||||
cursor.execute('PRAGMA index_list("' + self.table_name + '")')
|
||||
except IndexError: # There is sometimes IndexError
|
||||
cursor.execute('PRAGMA index_list("' + self.table_name + '")')
|
||||
self._indexes = [l[1] for l in cursor]
|
||||
self.__indexes = [l[1] for l in cursor]
|
||||
return self.__indexes
|
||||
|
||||
@property
|
||||
def _field2module(self):
|
||||
cursor = Transaction().connection.cursor()
|
||||
cursor.execute('SELECT f.name, f.module '
|
||||
'FROM ir_model_field f '
|
||||
'JOIN ir_model m on (f.model=m.id) '
|
||||
'WHERE m.model = ?',
|
||||
(self.object_name,))
|
||||
return dict(cursor)
|
||||
def _update_definitions(self, columns=None, indexes=None):
|
||||
if columns is None and indexes is None:
|
||||
columns = indexes = True
|
||||
if columns:
|
||||
self.__columns = None
|
||||
if indexes:
|
||||
self.__indexes = None
|
||||
|
||||
def alter_size(self, column_name, column_type):
|
||||
self._recreate_table({column_name: {'size': column_type}})
|
||||
@@ -324,11 +324,6 @@ class TableHandler(TableHandlerInterface):
|
||||
params)
|
||||
self._update_definitions(indexes=True)
|
||||
elif action == 'remove':
|
||||
if len(columns) == 1 and isinstance(columns[0], str):
|
||||
if self._field2module.get(columns[0],
|
||||
self.module_name) != self.module_name:
|
||||
return
|
||||
|
||||
if index_name in self._indexes:
|
||||
cursor.execute('DROP INDEX %s'
|
||||
% (_escape_identifier(index_name),))
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
import hashlib
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
|
||||
class TableHandlerInterface(object):
|
||||
@@ -8,8 +11,18 @@ class TableHandlerInterface(object):
|
||||
Define generic interface to handle database table
|
||||
'''
|
||||
namedatalen = None
|
||||
__handlers = WeakKeyDictionary()
|
||||
|
||||
def __init__(self, model, module_name=None, history=False):
|
||||
def __new__(cls, model, history=False):
|
||||
transaction = Transaction()
|
||||
handlers = cls.__handlers.setdefault(transaction, {})
|
||||
key = (model.__name__, history)
|
||||
if key not in handlers:
|
||||
instance = handlers[key] = super().__new__(cls)
|
||||
instance._init(model, history=history)
|
||||
return handlers[key]
|
||||
|
||||
def _init(self, model, history=False):
|
||||
'''
|
||||
:param model: the Model linked to the table
|
||||
:param module_name: the module name
|
||||
@@ -25,7 +38,6 @@ class TableHandlerInterface(object):
|
||||
self.sequence_name = self.table_name + '___id_seq'
|
||||
else:
|
||||
self.sequence_name = self.table_name + '_id_seq'
|
||||
self.module_name = module_name
|
||||
self.history = history
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -211,7 +211,7 @@ class ModelSQL(ModelStorage):
|
||||
|
||||
@classmethod
|
||||
def __table_handler__(cls, module_name=None, history=False):
|
||||
return backend.TableHandler(cls, module_name, history=history)
|
||||
return backend.TableHandler(cls, history=history)
|
||||
|
||||
@classmethod
|
||||
def __register__(cls, module_name):
|
||||
|
||||
Reference in New Issue
Block a user