(core) Be more careful with avoiding actions which don't change encoded values

Summary:
- For comparing for equality of encoding, do better at approximating what's equal in JSON.
- Fix loading of "RaisedException" values so that they can match an equivalent
  exception raised when the formula is re-evaluated.

Test Plan: Added another column to the test that verifies the Calculate action.

Reviewers: paulfitz

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D2682
This commit is contained in:
Dmitry S 2020-12-10 09:09:36 -05:00
parent 4f263fc7ec
commit b3a57e3b5c
3 changed files with 53 additions and 53 deletions

View File

@ -11,7 +11,6 @@ If an object cannot be encoded or decoded, an "UnmarshallableValue" is returned
of the form ['U', repr(obj)].
"""
# pylint: disable=too-many-return-statements
import exceptions
import traceback
from datetime import date, datetime
from math import isnan
@ -131,11 +130,19 @@ def strict_equal(a, b):
return False
def equal_encoding(a, b):
# pylint: disable=unidiomatic-typecheck
if isinstance(a, (str, unicode, bool, long, int)) or a is None:
# Compare NaNs as equal.
if isinstance(a, float) and isinstance(b, float):
return a == b or (isnan(a) and isnan(b))
# Compare bools as equal only to bools (these are distinguishable from numbers in JSON, and we
# take care to distinguish them in DB too).
if isinstance(a, bool) or isinstance(b, bool):
# pylint: disable=unidiomatic-typecheck
return type(a) == type(b) and a == b
if isinstance(a, float):
return type(a) == type(b) and (a == b or (isnan(a) and isnan(b)))
# Note for simple types, encode_object is trivial, and will result in a non-type-specific
# comparison (e.g. 1 and 1.0 will compare equal, as would "a" and u"a"). This is to capture
# equivalence of values in their JSON representations.
return encode_object(a) == encode_object(b)
def encode_object(value):
@ -230,11 +237,15 @@ class RaisedException(object):
widely-used wrapper. To encode_args, it simply returns the entire encoded stored error, e.g.
RaisedException(ValueError("foo")) is encoded as ["E", "ValueError", "foo"].
"""
def __init__(self, error, include_details=False):
def __init__(self, error, include_details=False, encoded_error=None):
self.error = error
self.details = traceback.format_exc() if include_details else None
self._encoded_error = encoded_error or self._encode_error()
def encode_args(self):
return self._encoded_error
def _encode_error(self):
# TODO: We should probably return all args, to communicate the error details to the browser
# and to DB (for when we store formula results). There are two concerns: one is that it's
# potentially quite verbose; the other is that it's makes the tests more annoying (again b/c
@ -247,17 +258,8 @@ class RaisedException(object):
@classmethod
def decode_args(cls, *args):
# Decoding of a RaisedException is currently only used in tests.
name = args[0]
exc_type = getattr(exceptions, name)
assert isinstance(exc_type, type) and issubclass(exc_type, BaseException)
return cls(exc_type(*args[1:]))
def __eq__(self, other):
return isinstance(other, type(self)) and self.encode_args() == other.encode_args()
def __ne__(self, other):
return not self.__eq__(other)
# Decoding of a RaisedException is only enough to re-encode it.
return cls(None, encoded_error=list(args))
class RecordList(list):

View File

@ -209,13 +209,13 @@ class TestTypes(test_engine.EngineTestCase):
self.assertPartialOutActions(out_actions, {
"stored": [
["ModifyColumn", "Types", "text", {"type": "Numeric"}],
["BulkUpdateRecord", "Types", [13, 14, 15, 17, 18, 19],
{"text": [0.0, 1.0, 1509556595.0, 0.0, 1.0, None]}],
["BulkUpdateRecord", "Types", [13, 14, 19],
{"text": [0.0, 1.0, None]}],
["UpdateRecord", "_grist_Tables_column", 21, {"type": "Numeric"}],
],
"undo": [
["BulkUpdateRecord", "Types", [13, 14, 15, 17, 18, 19],
{"text": [False, True, 1509556595, 0, 1, ""]}],
["BulkUpdateRecord", "Types", [13, 14, 19],
{"text": [False, True, ""]}],
["ModifyColumn", "Types", "text", {"type": "Text"}],
["UpdateRecord", "_grist_Tables_column", 21, {"type": "Text"}],
]
@ -233,13 +233,13 @@ class TestTypes(test_engine.EngineTestCase):
self.assertPartialOutActions(out_actions, {
"stored": [
["ModifyColumn", "Types", "int", {"type": "Numeric"}],
["BulkUpdateRecord", "Types", [13, 14, 15, 17, 18, 19],
{"int": [0.0, 1.0, 1509556595.0, 0.0, 1.0, None]}],
["BulkUpdateRecord", "Types", [13, 14, 19],
{"int": [0.0, 1.0, None]}],
["UpdateRecord", "_grist_Tables_column", 23, {"type": "Numeric"}],
],
"undo": [
["BulkUpdateRecord", "Types", [13, 14, 15, 17, 18, 19],
{"int": [False, True, 1509556595, 0, 1, ""]}],
["BulkUpdateRecord", "Types", [13, 14, 19],
{"int": [False, True, ""]}],
["ModifyColumn", "Types", "int", {"type": "Int"}],
["UpdateRecord", "_grist_Tables_column", 23, {"type": "Int"}],
]
@ -250,13 +250,13 @@ class TestTypes(test_engine.EngineTestCase):
self.assertPartialOutActions(out_actions, {
"stored": [
["ModifyColumn", "Types", "bool", {"type": "Numeric"}],
["BulkUpdateRecord", "Types", [13, 14, 15, 17, 18, 19],
{"bool": [0.0, 1.0, 1509556595.0, 0.0, 1.0, None]}],
["BulkUpdateRecord", "Types", [13, 14, 17, 18, 19],
{"bool": [0.0, 1.0, 0.0, 1.0, None]}],
["UpdateRecord", "_grist_Tables_column", 24, {"type": "Numeric"}],
],
"undo": [
["BulkUpdateRecord", "Types", [13, 14, 15, 17, 18, 19],
{"bool": [False, True, 1509556595, False, True, ""]}],
["BulkUpdateRecord", "Types", [13, 14, 17, 18, 19],
{"bool": [False, True, False, True, ""]}],
["ModifyColumn", "Types", "bool", {"type": "Bool"}],
["UpdateRecord", "_grist_Tables_column", 24, {"type": "Bool"}],
]
@ -324,14 +324,14 @@ class TestTypes(test_engine.EngineTestCase):
self.assertPartialOutActions(out_actions, {
"stored": [
["ModifyColumn", "Types", "numeric", {"type": "Int"}],
["BulkUpdateRecord", "Types", [13, 14, 15, 16, 17, 18, 19],
{"numeric": [0, 1, 1509556595, 8, 0, 1, None]}],
["BulkUpdateRecord", "Types", [13, 14, 16, 19],
{"numeric": [0, 1, 8, None]}],
["UpdateRecord", "_grist_Tables_column", 22, {"type": "Int"}],
["UpdateRecord", "Formulas", 1, {"division": 0}],
],
"undo": [
["BulkUpdateRecord", "Types", [13, 14, 15, 16, 17, 18, 19],
{"numeric": [False, True, 1509556595.0, 8.153, 0.0, 1.0, ""]}],
["BulkUpdateRecord", "Types", [13, 14, 16, 19],
{"numeric": [False, True, 8.153, ""]}],
["ModifyColumn", "Types", "numeric", {"type": "Numeric"}],
["UpdateRecord", "_grist_Tables_column", 22, {"type": "Numeric"}],
["UpdateRecord", "Formulas", 1, {"division": 0.5}],
@ -367,13 +367,13 @@ class TestTypes(test_engine.EngineTestCase):
self.assertPartialOutActions(out_actions, {
"stored": [
["ModifyColumn", "Types", "date", {"type": "Int"}],
["BulkUpdateRecord", "Types", [13, 14, 15, 16, 17, 18, 19],
{"date": [0, 1, 1509556595, 8, 0, 1, None]}],
["BulkUpdateRecord", "Types", [13, 14, 16, 19],
{"date": [0, 1, 8, None]}],
["UpdateRecord", "_grist_Tables_column", 25, {"type": "Int"}]
],
"undo": [
["BulkUpdateRecord", "Types", [13, 14, 15, 16, 17, 18, 19],
{"date": [False, True, 1509556595.0, 8.153, 0.0, 1.0, ""]}],
["BulkUpdateRecord", "Types", [13, 14, 16, 19],
{"date": [False, True, 8.153, ""]}],
["ModifyColumn", "Types", "date", {"type": "Date"}],
["UpdateRecord", "_grist_Tables_column", 25, {"type": "Date"}]
]
@ -509,13 +509,13 @@ class TestTypes(test_engine.EngineTestCase):
self.assertPartialOutActions(out_actions, {
"stored": [
["ModifyColumn", "Types", "text", {"type": "Date"}],
["BulkUpdateRecord", "Types", [13, 14, 15, 17, 18, 19],
{"text": [0.0, 1.0, 1509556595.0, 0.0, 1.0, None]}],
["BulkUpdateRecord", "Types", [13, 14, 19],
{"text": [0.0, 1.0, None]}],
["UpdateRecord", "_grist_Tables_column", 21, {"type": "Date"}],
],
"undo": [
["BulkUpdateRecord", "Types", [13, 14, 15, 17, 18, 19],
{"text": [False, True, 1509556595, 0, 1, ""]}],
["BulkUpdateRecord", "Types", [13, 14, 19],
{"text": [False, True, ""]}],
["ModifyColumn", "Types", "text", {"type": "Text"}],
["UpdateRecord", "_grist_Tables_column", 21, {"type": "Text"}],
]
@ -545,13 +545,13 @@ class TestTypes(test_engine.EngineTestCase):
self.assertPartialOutActions(out_actions, {
"stored": [
["ModifyColumn", "Types", "int", {"type": "Date"}],
["BulkUpdateRecord", "Types", [13, 14, 15, 17, 18, 19],
{"int": [0.0, 1.0, 1509556595.0, 0.0, 1.0, None]}],
["BulkUpdateRecord", "Types", [13, 14, 19],
{"int": [0.0, 1.0, None]}],
["UpdateRecord", "_grist_Tables_column", 23, {"type": "Date"}],
],
"undo": [
["BulkUpdateRecord", "Types", [13, 14, 15, 17, 18, 19],
{"int": [False, True, 1509556595, 0, 1, ""]}],
["BulkUpdateRecord", "Types", [13, 14, 19],
{"int": [False, True, ""]}],
["ModifyColumn", "Types", "int", {"type": "Int"}],
["UpdateRecord", "_grist_Tables_column", 23, {"type": "Int"}],
]
@ -562,13 +562,13 @@ class TestTypes(test_engine.EngineTestCase):
self.assertPartialOutActions(out_actions, {
"stored": [
["ModifyColumn", "Types", "bool", {"type": "Date"}],
["BulkUpdateRecord", "Types", [13, 14, 15, 17, 18, 19],
{"bool": [0.0, 1.0, 1509556595.0, 0.0, 1.0, None]}],
["BulkUpdateRecord", "Types", [13, 14, 17, 18, 19],
{"bool": [0.0, 1.0, 0.0, 1.0, None]}],
["UpdateRecord", "_grist_Tables_column", 24, {"type": "Date"}]
],
"undo": [
["BulkUpdateRecord", "Types", [13, 14, 15, 17, 18, 19],
{"bool": [False, True, 1509556595, False, True, ""]}],
["BulkUpdateRecord", "Types", [13, 14, 17, 18, 19],
{"bool": [False, True, False, True, ""]}],
["ModifyColumn", "Types", "bool", {"type": "Bool"}],
["UpdateRecord", "_grist_Tables_column", 24, {"type": "Bool"}]
]

View File

@ -262,8 +262,8 @@
["ModifyColumn", "Schools", "numStudents", {"type": "Numeric"}],
// Record 13 is not updated since it can't be properly converted.
["BulkUpdateRecord", "Schools", [1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15],
{"numStudents": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1e+27, 1000000.0]}],
// Other records aren't updated because the converted value is equivalent for JSON and DB.
["UpdateRecord", "Schools", 14, {"numStudents": 1e+27}],
["UpdateRecord", "_grist_Tables_column", 30, {"type": "Numeric"}],
["AddRecord", "Schools", 16, {"numStudents": "@+Infinity"}],
["BulkUpdateRecord", "Schools", [14, 15, 16], {"name": ["14$\\$$'\\'", "15$\\$$'\\'", "16$\\$$'\\'"]}]
@ -1445,7 +1445,6 @@
"addr = $university.address\naddr.stateName if addr.country == 'US' else addr.region"]
}],
["ModifyColumn", "Address", "stateName", {"type": "Numeric"}],
["UpdateRecord", "Address", 2, {"stateName": 73.0}],
["UpdateRecord", "_grist_Tables_column", 27, {"type": "Numeric"}]
],
"undo": [
@ -1458,7 +1457,6 @@
"formula": ["",
"addr = $university.address\naddr.state if addr.country == 'US' else addr.region"]
}],
["UpdateRecord", "Address", 2, {"stateName": 73}],
["ModifyColumn", "Address", "stateName", {"type": "Int"}],
["UpdateRecord", "_grist_Tables_column", 27, {"type": "Int"}]
]