Manage DatabaseDataError in ModelSQL

issue11314
review399111003
This commit is contained in:
Cédric Krier
2022-04-04 00:35:38 +02:00
parent 115d185c55
commit 1c55eee094
8 changed files with 72 additions and 21 deletions
+1
View File
@@ -1,3 +1,4 @@
* Manage DatabaseDataError in ModelSQL
* Remove support for pysqlite2
* Do not import empty One2Many data
* Deduplicate entries in domain inversion's simplify
+3 -2
View File
@@ -11,8 +11,8 @@ except ImportError:
from trytond.config import config
__all__ = [
'name', 'Database', 'DatabaseIntegrityError',
'DatabaseOperationalError', 'TableHandler']
'name', 'Database', 'TableHandler',
'DatabaseIntegrityError', 'DatabaseDataError', 'DatabaseOperationalError']
name = urllib.parse.urlparse(config.get('database', 'uri', default='')).scheme
@@ -34,5 +34,6 @@ except ImportError:
Database = _module.Database
DatabaseIntegrityError = _module.DatabaseIntegrityError
DatabaseDataError = _module.DatabaseDataError
DatabaseOperationalError = _module.DatabaseOperationalError
TableHandler = _module.TableHandler
+4 -2
View File
@@ -2,8 +2,10 @@
# this repository contains the full copyright notices and license terms.
from .database import (
Database, DatabaseIntegrityError, DatabaseOperationalError)
Database, DatabaseDataError, DatabaseIntegrityError,
DatabaseOperationalError)
from .table import TableHandler
__all__ = [
Database, DatabaseIntegrityError, DatabaseOperationalError, TableHandler]
Database, TableHandler,
DatabaseIntegrityError, DatabaseDataError, DatabaseOperationalError]
+4 -1
View File
@@ -27,6 +27,7 @@ try:
from psycopg2.extensions import PYDATE, PYDATETIME, PYINTERVAL, PYTIME
except ImportError:
PYDATE, PYDATETIME, PYTIME, PYINTERVAL = None, None, None, None
from psycopg2 import DataError as DatabaseDataError
from psycopg2 import IntegrityError as DatabaseIntegrityError
from psycopg2 import OperationalError as DatabaseOperationalError
from psycopg2 import ProgrammingError
@@ -40,7 +41,9 @@ from trytond.backend.database import DatabaseInterface, SQLType
from trytond.config import config, parse_uri
from trytond.tools.gevent import is_gevent_monkey_patched
__all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError']
__all__ = [
'Database',
'DatabaseIntegrityError', 'DatabaseDataError', 'DatabaseOperationalError']
logger = logging.getLogger(__name__)
+4 -2
View File
@@ -2,8 +2,10 @@
# this repository contains the full copyright notices and license terms.
from .database import (
Database, DatabaseIntegrityError, DatabaseOperationalError)
Database, DatabaseDataError, DatabaseIntegrityError,
DatabaseOperationalError)
from .table import TableHandler
__all__ = [
Database, DatabaseIntegrityError, DatabaseOperationalError, TableHandler]
Database, TableHandler,
DatabaseIntegrityError, DatabaseDataError, DatabaseOperationalError]
+8 -1
View File
@@ -11,6 +11,7 @@ import time
import urllib.parse
import warnings
from decimal import Decimal
from sqlite3 import DatabaseError
from sqlite3 import IntegrityError as DatabaseIntegrityError
from sqlite3 import OperationalError as DatabaseOperationalError
from weakref import WeakKeyDictionary
@@ -26,12 +27,18 @@ from trytond.backend.database import DatabaseInterface, SQLType
from trytond.config import config, parse_uri
from trytond.transaction import Transaction
__all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError']
__all__ = [
'Database',
'DatabaseIntegrityError', 'DatabaseDataError', 'DatabaseOperationalError']
logger = logging.getLogger(__name__)
_default_name = config.get('database', 'default_name', default=':memory:')
class DatabaseDataError(DatabaseError):
pass
class SQLiteExtract(Function):
__slots__ = ()
_function = 'EXTRACT'
+46 -9
View File
@@ -27,8 +27,8 @@ from trytond.transaction import Transaction, record_cache_size
from . import fields
from .descriptors import dualmethod
from .modelstorage import (
AccessError, ModelStorage, RequiredValidationError, ValidationError,
is_leaf)
AccessError, ModelStorage, RequiredValidationError, SizeValidationError,
ValidationError, is_leaf)
from .modelview import ModelView
@@ -393,6 +393,29 @@ class ModelSQL(ModelStorage):
gettext('ir.msg_foreign_model_missing',
**error_args))
@classmethod
def __raise_data_error(
cls, exception, values, field_names=None, transaction=None):
if field_names is None:
field_names = list(cls._fields.keys())
if transaction is None:
transaction = Transaction()
for field_name in field_names:
if field_name not in cls._fields:
continue
field = cls._fields[field_name]
# Check field size
if (hasattr(field, 'size')
and isinstance(field.size, int)
and field.sql_type()):
size = len(values.get(field_name) or '')
if size > field.size:
error_args = cls.__names__(field_name)
error_args['size'] = size
error_args['max_size'] = field.size
raise SizeValidationError(
gettext('ir.msg_size_validation_record', **error_args))
@classmethod
def history_revisions(cls, ids):
pool = Pool()
@@ -640,12 +663,18 @@ class ModelSQL(ModelStorage):
[insert_values]))
id_new = transaction.database.lastid(cursor)
new_ids.append(id_new)
except backend.DatabaseIntegrityError as exception:
except (
backend.DatabaseIntegrityError,
backend.DatabaseDataError) as exception:
transaction = Transaction()
with Transaction().new_transaction(), \
Transaction().set_context(_check_access=False):
cls.__raise_integrity_error(
exception, values, transaction=transaction)
if isinstance(exception, backend.DatabaseIntegrityError):
cls.__raise_integrity_error(
exception, values, transaction=transaction)
elif isinstance(exception, backend.DatabaseDataError):
cls.__raise_data_error(
exception, values, transaction=transaction)
raise
transaction.create_records[cls.__name__].update(new_ids)
@@ -1049,13 +1078,21 @@ class ModelSQL(ModelStorage):
try:
cursor.execute(*table.update(columns, update_values,
where=red_sql))
except backend.DatabaseIntegrityError as exception:
except (
backend.DatabaseIntegrityError,
backend.DatabaseDataError) as exception:
transaction = Transaction()
with Transaction().new_transaction(), \
Transaction().set_context(_check_access=False):
cls.__raise_integrity_error(
exception, values, list(values.keys()),
transaction=transaction)
if isinstance(
exception, backend.DatabaseIntegrityError):
cls.__raise_integrity_error(
exception, values, list(values.keys()),
transaction=transaction)
elif isinstance(exception, backend.DatabaseDataError):
cls.__raise_data_error(
exception, values, list(values.keys()),
transaction=transaction)
raise
for fname, value in values.items():
+2 -4
View File
@@ -416,8 +416,7 @@ class FieldCharTestCase(unittest.TestCase, CommonTestCaseMixin):
"Test create char with invalid size"
Char = Pool().get('test.char_size')
# XXX: should be UserError but postgresql raises DataError
with self.assertRaises(Exception):
with self.assertRaises(SizeValidationError):
Char.create([{
'char': "foobar",
}])
@@ -492,8 +491,7 @@ class FieldCharTestCase(unittest.TestCase, CommonTestCaseMixin):
'char': "Test",
}])
# XXX: should be UserError but postgresql raises DataError
with self.assertRaises(Exception):
with self.assertRaises(SizeValidationError):
Char.write([char], {
'char': 'foobar',
})