gristlabs_grist-core/sandbox/grist/test_lookups.py
Paul Fitzpatrick b82eec714a (core) move data engine code to core
Summary:
this moves sandbox/grist to core, and adds a requirements.txt
file for reconstructing the content of sandbox/thirdparty.

Test Plan:
existing tests pass.
Tested core functionality manually.  Tested docker build manually.

Reviewers: dsagal

Reviewed By: dsagal

Differential Revision: https://phab.getgrist.com/D2563
2020-07-29 08:57:25 -04:00

647 lines
27 KiB
Python

import actions
import logger
import testsamples
import testutil
import test_engine
log = logger.Logger(__name__, logger.INFO)
def _bulk_update(table_name, col_names, row_data):
return actions.BulkUpdateRecord(
*testutil.table_data_from_rows(table_name, col_names, row_data))
class TestLookups(test_engine.EngineTestCase):
def test_verify_sample(self):
self.load_sample(testsamples.sample_students)
self.assertPartialData("Students", ["id", "schoolIds", "schoolCities" ], [
[1, "1:2", "New York:Colombia" ],
[2, "3:4", "New Haven:West Haven" ],
[3, "1:2", "New York:Colombia" ],
[4, "3:4", "New Haven:West Haven" ],
[5, "", ""],
[6, "3:4", "New Haven:West Haven" ]
])
#----------------------------------------
def test_lookup_dependencies(self, pre_loaded=False):
"""
Test changes to records accessed via lookup.
"""
if not pre_loaded:
self.load_sample(testsamples.sample_students)
out_actions = self.update_record("Address", 14, city="Bedford")
self.assertPartialOutActions(out_actions, {
"calc": [_bulk_update("Students", ["id", "schoolCities" ], [
[2, "New Haven:Bedford" ],
[4, "New Haven:Bedford" ],
[6, "New Haven:Bedford" ]]
)],
"calls": {"Students": {"schoolCities": 3}}
})
out_actions = self.update_record("Schools", 4, address=13)
self.assertPartialOutActions(out_actions, {
"calc": [_bulk_update("Students", ["id", "schoolCities" ], [
[2, "New Haven:New Haven" ],
[4, "New Haven:New Haven" ],
[6, "New Haven:New Haven" ]]
)],
"calls": {"Students": {"schoolCities": 3}}
})
out_actions = self.update_record("Address", 14, city="Hartford")
# No schoolCities need to be recalculatd here, since nothing depends on Address 14 any more.
self.assertPartialOutActions(out_actions, {
"calls": {}
})
# Confirm the final result.
self.assertPartialData("Students", ["id", "schoolIds", "schoolCities" ], [
[1, "1:2", "New York:Colombia" ],
[2, "3:4", "New Haven:New Haven" ],
[3, "1:2", "New York:Colombia" ],
[4, "3:4", "New Haven:New Haven" ],
[5, "", ""],
[6, "3:4", "New Haven:New Haven" ]
])
#----------------------------------------
def test_dependency_reset(self, pre_loaded=False):
"""
A somewhat tricky case. We know that Student 2 depends on Schools 3,4 and on Address 13,14.
If we change Student 2 to depend on nothing, then changing Address 13 should not cause it to
recompute.
"""
if not pre_loaded:
self.load_sample(testsamples.sample_students)
out_actions = self.update_record("Address", 13, city="AAA")
self.assertPartialOutActions(out_actions, {
"calls": {"Students": {"schoolCities": 3}} # Initially 3 students depend on Address 13.
})
out_actions = self.update_record("Students", 2, schoolName="Invalid")
out_actions = self.update_record("Address", 13, city="BBB")
# If the count below is 3, then the engine forgot to reset the dependencies of Students 2.
self.assertPartialOutActions(out_actions, {
"calls": {"Students": {"schoolCities": 2}} # Now only 2 Students depend on Address 13.
})
#----------------------------------------
def test_lookup_key_changes(self, pre_loaded=False):
"""
Test changes to lookup values in the target table. Note that student #3 does not depend on
any records, but depends on the value "Eureka", so gets updated when this value appears.
"""
if not pre_loaded:
self.load_sample(testsamples.sample_students)
out_actions = self.update_record("Schools", 2, name="Eureka")
self.assertPartialOutActions(out_actions, {
"calc": [
actions.BulkUpdateRecord("Students", [1,3,5], {
'schoolCities': ["New York", "New York", "Colombia"]
}),
actions.BulkUpdateRecord("Students", [1,3,5], {
'schoolIds': ["1", "1","2"]
}),
],
"calls": {"Students": { 'schoolCities': 3, 'schoolIds': 3 },
"Schools": {'#lookup#name': 1} },
})
# Test changes to lookup values in the table doing the lookup.
out_actions = self.update_records("Students", ["id", "schoolName"], [
[3, ""],
[5, "Yale"]
])
self.assertPartialOutActions(out_actions, {
"calc": [
actions.BulkUpdateRecord("Students", [3,5], {'schoolCities': ["", "New Haven:West Haven"]}),
actions.BulkUpdateRecord("Students", [3,5], {'schoolIds': ["", "3:4"]}),
],
"calls": { "Students": { 'schoolCities': 2, 'schoolIds': 2 } },
})
# Confirm the final result.
self.assertPartialData("Students", ["id", "schoolIds", "schoolCities" ], [
[1, "1", "New York" ],
[2, "3:4", "New Haven:West Haven" ],
[3, "", "" ],
[4, "3:4", "New Haven:West Haven" ],
[5, "3:4", "New Haven:West Haven" ],
[6, "3:4", "New Haven:West Haven" ]
])
#----------------------------------------
def test_lookup_formula_after_schema_change(self):
self.load_sample(testsamples.sample_students)
self.add_column("Schools", "state", type="Text")
# Make a change that causes recomputation of a lookup formula after a schema change.
# We should NOT get attribute errors in the values.
out_actions = self.update_record("Schools", 4, address=13)
self.assertPartialOutActions(out_actions, {
"calc": [_bulk_update("Students", ["id", "schoolCities" ], [
[2, "New Haven:New Haven" ],
[4, "New Haven:New Haven" ],
[6, "New Haven:New Haven" ]]
)],
"calls": { "Students": { 'schoolCities': 3 } }
})
#----------------------------------------
def test_lookup_formula_changes(self):
self.load_sample(testsamples.sample_students)
self.add_column("Schools", "state", type="Text")
self.update_records("Schools", ["id", "state"], [
[1, "NY"],
[2, "MO"],
[3, "CT"],
[4, "CT"]
])
# Verify that when we change a formula, we get appropriate changes.
out_actions = self.modify_column("Students", "schoolCities", formula=(
"','.join(Schools.lookupRecords(name=$schoolName).state)"))
self.assertPartialOutActions(out_actions, {
"calc": [_bulk_update("Students", ["id", "schoolCities" ], [
[1, "NY,MO" ],
[2, "CT,CT" ],
[3, "NY,MO" ],
[4, "CT,CT" ],
[6, "CT,CT" ]]
)],
# Note that it got computed 6 times (once for each record), but one value remained unchanged
# (because no schools matched).
"calls": { "Students": { 'schoolCities': 6 } }
})
# Check that we've created new dependencies, and removed old ones.
out_actions = self.update_record("Schools", 4, address=13)
self.assertPartialOutActions(out_actions, {
"calls": {}
})
out_actions = self.update_record("Schools", 4, state="MA")
self.assertPartialOutActions(out_actions, {
"calc": [_bulk_update("Students", ["id", "schoolCities" ], [
[2, "CT,MA" ],
[4, "CT,MA" ],
[6, "CT,MA" ]]
)],
"calls": { "Students": { 'schoolCities': 3 } }
})
# If we change to look up uppercase values, we shouldn't find anything.
out_actions = self.modify_column("Students", "schoolCities", formula=(
"','.join(Schools.lookupRecords(name=$schoolName.upper()).state)"))
self.assertPartialOutActions(out_actions, {
"calc": [actions.BulkUpdateRecord("Students", [1,2,3,4,6],
{'schoolCities': ["","","","",""]})],
"calls": { "Students": { 'schoolCities': 6 } }
})
# Changes to dependencies should cause appropriate recalculations.
out_actions = self.update_record("Schools", 4, state="KY", name="EUREKA")
self.assertPartialOutActions(out_actions, {
"calc": [
actions.UpdateRecord("Students", 5, {'schoolCities': "KY"}),
actions.BulkUpdateRecord("Students", [2,4,6], {'schoolIds': ["3","3","3"]}),
],
"calls": {"Students": { 'schoolCities': 1, 'schoolIds': 3 },
'Schools': {'#lookup#name': 1 } }
})
self.assertPartialData("Students", ["id", "schoolIds", "schoolCities" ], [
# schoolCities aren't found here because we changed formula to lookup uppercase names.
[1, "1:2", "" ],
[2, "3", "" ],
[3, "1:2", "" ],
[4, "3", "" ],
[5, "", "KY" ],
[6, "3", "" ]
])
def test_add_remove_lookup(self):
# Verify that when we add or remove a lookup formula, we get appropriate changes.
self.load_sample(testsamples.sample_students)
# Add another lookup formula.
out_actions = self.add_column("Schools", "lastNames", formula=(
"','.join(Students.lookupRecords(schoolName=$name).lastName)"))
self.assertPartialOutActions(out_actions, {
"calc": [_bulk_update("Schools", ["id", "lastNames"], [
[1, "Obama,Clinton"],
[2, "Obama,Clinton"],
[3, "Bush,Bush,Ford"],
[4, "Bush,Bush,Ford"]]
)],
"calls": {"Schools": {"lastNames": 4}, "Students": {"#lookup#schoolName": 6}},
})
# Make sure it responds to changes.
out_actions = self.update_record("Students", 5, schoolName="Columbia")
self.assertPartialOutActions(out_actions, {
"calc": [
_bulk_update("Schools", ["id", "lastNames"], [
[1, "Obama,Clinton,Reagan"],
[2, "Obama,Clinton,Reagan"]]
),
actions.UpdateRecord("Students", 5, {"schoolCities": "New York:Colombia"}),
actions.UpdateRecord("Students", 5, {"schoolIds": "1:2"}),
],
"calls": {"Students": {'schoolCities': 1, 'schoolIds': 1, '#lookup#schoolName': 1},
"Schools": { 'lastNames': 2 }},
})
# Modify the column: in the process, the LookupMapColumn on Students.schoolName becomes unused
# while the old formula column is removed, but used again when it's added. It should not have
# to be rebuilt (so there should be no calls to recalculate the LookupMapColumn.
out_actions = self.modify_column("Schools", "lastNames", formula=(
"','.join(Students.lookupRecords(schoolName=$name).firstName)"))
self.assertPartialOutActions(out_actions, {
"calc": [_bulk_update("Schools", ["id", "lastNames"], [
[1, "Barack,Bill,Ronald"],
[2, "Barack,Bill,Ronald"],
[3, "George W,George H,Gerald"],
[4, "George W,George H,Gerald"]]
)],
"calls": {"Schools": {"lastNames": 4}}
})
# Remove the new lookup formula.
out_actions = self.remove_column("Schools", "lastNames")
self.assertPartialOutActions(out_actions, {}) # No calc actions
# Make sure that changes still work without errors.
out_actions = self.update_record("Students", 5, schoolName="Eureka")
self.assertPartialOutActions(out_actions, {
"calc": [
actions.UpdateRecord("Students", 5, {"schoolCities": ""}),
actions.UpdateRecord("Students", 5, {"schoolIds": ""}),
],
# This should NOT have '#lookup#schoolName' recalculation because there are no longer any
# formulas which do such a lookup.
"calls": { "Students": {'schoolCities': 1, 'schoolIds': 1}}
})
def test_multi_column_lookups(self):
"""
Check that we can do lookups by multiple columns.
"""
self.load_sample(testsamples.sample_students)
# Add a lookup formula which looks up a student matching on both first and last names.
self.add_column("Schools", "bestStudent", type="Text")
self.update_record("Schools", 1, bestStudent="Bush,George W")
self.add_column("Schools", "bestStudentId", formula=("""
if not $bestStudent: return ""
ln, fn = $bestStudent.split(",")
return ",".join(str(r.id) for r in Students.lookupRecords(firstName=fn, lastName=ln))
"""))
# Check data so far: only one record is filled.
self.assertPartialData("Schools", ["id", "bestStudent", "bestStudentId" ], [
[1, "Bush,George W", "2" ],
[2, "", "" ],
[3, "", "" ],
[4, "", "" ],
])
# Fill a few more records and check that we find records we should, and don't find those we
# shouldn't.
out_actions = self.update_records("Schools", ["id", "bestStudent"], [
[2, "Clinton,Bill"],
[3, "Norris,Chuck"],
[4, "Bush,George H"],
])
self.assertPartialOutActions(out_actions, {
"calc": [actions.BulkUpdateRecord("Schools", [2, 4], {"bestStudentId": ["3", "4"]})],
"calls": {"Schools": {"bestStudentId": 3}}
})
self.assertPartialData("Schools", ["id", "bestStudent", "bestStudentId" ], [
[1, "Bush,George W", "2" ],
[2, "Clinton,Bill", "3" ],
[3, "Norris,Chuck", "" ],
[4, "Bush,George H", "4" ],
])
# Now add more records, first matching only some of the lookup fields.
out_actions = self.add_record("Students", firstName="Chuck", lastName="Morris")
self.assertPartialOutActions(out_actions, {
"calls": {
# No calculations of anything Schools because nothing depends on the incomplete value.
"Students": {"#lookup#firstName:lastName": 2, "schoolIds": 1, "schoolCities": 1}
},
"retValues": [7],
})
# If we add a matching record, then we get a calculation of a record in Schools
out_actions = self.add_record("Students", firstName="Chuck", lastName="Norris")
self.assertPartialOutActions(out_actions, {
"calls": {
"Students": {"#lookup#firstName:lastName": 2, "schoolIds": 1, "schoolCities": 1},
"Schools": {"bestStudentId": 1}
},
"retValues": [8],
})
# And the data should be correct.
self.assertPartialData("Schools", ["id", "bestStudent", "bestStudentId" ], [
[1, "Bush,George W", "2" ],
[2, "Clinton,Bill", "3" ],
[3, "Norris,Chuck", "8" ],
[4, "Bush,George H", "4" ],
])
def test_record_removal(self):
# Remove a record, make sure that lookup maps get updated.
self.load_sample(testsamples.sample_students)
out_actions = self.remove_record("Schools", 3)
self.assertPartialOutActions(out_actions, {
"calc": [
actions.BulkUpdateRecord("Students", [2,4,6], {
"schoolCities": ["West Haven","West Haven","West Haven"]}),
actions.BulkUpdateRecord("Students", [2,4,6], {
"schoolIds": ["4","4","4"]}),
],
"calls": {
"Students": {"schoolIds": 3, "schoolCities": 3},
# LookupMapColumn is also updated but via a different path (unset() vs method() call), so
# it's not included in the count of formula calls.
}
})
self.assertPartialData("Students", ["id", "schoolIds", "schoolCities" ], [
[1, "1:2", "New York:Colombia" ],
[2, "4", "West Haven" ],
[3, "1:2", "New York:Colombia" ],
[4, "4", "West Haven" ],
[5, "", ""],
[6, "4", "West Haven" ]
])
def test_empty_relation(self):
# Make sure that when a relation becomes empty, it doesn't get messed up.
self.load_sample(testsamples.sample_students)
# Clear out dependencies.
self.update_records("Students", ["id", "schoolName"],
[ [i, ""] for i in [1,2,3,4,5,6] ])
self.assertPartialData("Students", ["id", "schoolIds", "schoolCities" ],
[ [i, "", ""] for i in [1,2,3,4,5,6] ])
# Make a number of changeas, to ensure they reuse rather than re-create _LookupRelations.
self.update_record("Students", 2, schoolName="Yale")
self.update_record("Students", 2, schoolName="Columbia")
self.update_record("Students", 3, schoolName="Columbia")
self.assertPartialData("Students", ["id", "schoolIds", "schoolCities" ], [
[1, "", ""],
[2, "1:2", "New York:Colombia" ],
[3, "1:2", "New York:Colombia" ],
[4, "", ""],
[5, "", ""],
[6, "", ""],
])
# When we messed up the dependencies, this change didn't cause a corresponding update. Check
# that it now does.
self.remove_record("Schools", 2)
self.assertPartialData("Students", ["id", "schoolIds", "schoolCities" ], [
[1, "", ""],
[2, "1", "New York" ],
[3, "1", "New York" ],
[4, "", ""],
[5, "", ""],
[6, "", ""],
])
def test_lookups_of_computed_values(self):
"""
Make sure that lookups get updated when the value getting looked up is a formula result.
"""
self.load_sample(testsamples.sample_students)
# Add a column like Schools.name, but computed, and change schoolIds to use that one instead.
self.add_column("Schools", "cname", formula="$name")
self.modify_column("Students", "schoolIds", formula=
"':'.join(str(id) for id in Schools.lookupRecords(cname=$schoolName).id)")
self.assertPartialData("Students", ["id", "schoolIds" ], [
[1, "1:2" ],
[2, "3:4" ],
[3, "1:2" ],
[4, "3:4" ],
[5, "" ],
[6, "3:4" ],
])
# Check that a change to School.name, which triggers a change to School.cname, causes a change
# to the looked-up ids. The changes here should be the same as in test_lookup_key_changes
# test, even though schoolIds depends on name indirectly.
out_actions = self.update_record("Schools", 2, name="Eureka")
self.assertPartialOutActions(out_actions, {
"calc": [
actions.UpdateRecord("Schools", 2, {"cname": "Eureka"}),
actions.BulkUpdateRecord("Students", [1,3,5], {
'schoolCities': ["New York", "New York", "Colombia"]
}),
actions.BulkUpdateRecord("Students", [1,3,5], {
'schoolIds': ["1", "1","2"]
}),
],
"calls": {"Students": { 'schoolCities': 3, 'schoolIds': 3 },
"Schools": {'#lookup#name': 1, '#lookup#cname': 1, "cname": 1} },
})
def use_saved_lookup_results(self):
"""
This sets up data so that lookupRecord results are stored in a column and used in another. Key
tests that check lookup dependencies should work unchanged with this setup.
"""
self.load_sample(testsamples.sample_students)
# Split up Students.schoolCities into Students.schools and Students.schoolCities.
self.add_column("Students", "schools", formula="Schools.lookupRecords(name=$schoolName)",
type="RefList:Schools")
self.modify_column("Students", "schoolCities",
formula="':'.join(r.address.city for r in $schools)")
# The following tests check correctness of dependencies when lookupResults are stored in one
# column and used in another. They reuse existing test cases with modified data.
def test_lookup_dependencies_reflist(self):
self.use_saved_lookup_results()
self.test_lookup_dependencies(pre_loaded=True)
# Confirm the final result including the additional 'schools' column.
self.assertPartialData("Students", ["id", "schools", "schoolIds", "schoolCities" ], [
[1, [1,2], "1:2", "New York:Colombia" ],
[2, [3,4], "3:4", "New Haven:New Haven" ],
[3, [1,2], "1:2", "New York:Colombia" ],
[4, [3,4], "3:4", "New Haven:New Haven" ],
[5, [], "", ""],
[6, [3,4], "3:4", "New Haven:New Haven" ]
])
def test_dependency_reset_reflist(self):
self.use_saved_lookup_results()
self.test_dependency_reset(pre_loaded=True)
def test_lookup_key_changes_reflist(self):
# We can't run this test case unchanged since our new column changes too in this test.
self.use_saved_lookup_results()
out_actions = self.update_record("Schools", 2, name="Eureka")
self.assertPartialOutActions(out_actions, {
"calc": [
actions.BulkUpdateRecord('Students', [1,3,5], {'schools': [[1],[1],[2]]}),
actions.BulkUpdateRecord("Students", [1,3,5], {
'schoolCities': ["New York", "New York", "Colombia"]
}),
actions.BulkUpdateRecord("Students", [1,3,5], {
'schoolIds': ["1", "1","2"]
}),
],
"calls": {"Students": { 'schools': 3, 'schoolCities': 3, 'schoolIds': 3 },
"Schools": {'#lookup#name': 1} },
})
# Test changes to lookup values in the table doing the lookup.
out_actions = self.update_records("Students", ["id", "schoolName"], [
[3, ""],
[5, "Yale"]
])
self.assertPartialOutActions(out_actions, {
"calc": [
actions.BulkUpdateRecord("Students", [3,5], {'schools': [[], [3,4]]}),
actions.BulkUpdateRecord("Students", [3,5], {'schoolCities': ["", "New Haven:West Haven"]}),
actions.BulkUpdateRecord("Students", [3,5], {'schoolIds': ["", "3:4"]}),
],
"calls": { "Students": { 'schools': 2, 'schoolCities': 2, 'schoolIds': 2 } },
})
# Confirm the final result.
self.assertPartialData("Students", ["id", "schools", "schoolIds", "schoolCities" ], [
[1, [1], "1", "New York" ],
[2, [3,4], "3:4", "New Haven:West Haven" ],
[3, [], "", "" ],
[4, [3,4], "3:4", "New Haven:West Haven" ],
[5, [3,4], "3:4", "New Haven:West Haven" ],
[6, [3,4], "3:4", "New Haven:West Haven" ]
])
def test_dependencies_relations_bug(self):
# We had a serious bug with dependencies, for which this test verifies a fix. Imagine Table2
# has a formula a=Table1.lookupOne(A=$A), and b=$a.foo. When col A changes in Table1, columns
# a and b in Table2 get recomputed. Each recompute triggers reset_rows() which is there to
# clear lookup relations (it actually triggers reset_dependencies() which resets rows for the
# relation on each dependency edge).
#
# The first recompute (of a) triggers reset_rows() on the LookupRelation, then recomputes the
# lookup formula which re-populates the relation correctly. The second recompute (of b) also
# triggers reset_rows(). The bug was that it was triggering it in the same LookupRelation, but
# since it doesn't get followed with recomputing the lookup formula, the relation remains
# incomplete.
#
# It's important that a formula like "b=$a.foo" doesn't reuse the LookupRelation by itself on
# the edge between b and $a, but a composition of IdentityRelation and LookupRelation. The
# composition will correctly forward reset_rows() to only the first half of the relation.
# Set up two tables with a situation as described above. Here, the role of column Table2.a
# above is taken by "Students.schools=Schools.lookupRecords(name=$schoolName)".
self.use_saved_lookup_results()
# We intentionally try behavior with type Any formulas too, without converting to a reference
# type, in case that affects relations.
self.modify_column("Students", "schools", type="Any")
self.add_column("Students", "schoolsCount", formula="len($schools.name)")
self.add_column("Students", "oneSchool", formula="Schools.lookupOne(name=$schoolName)")
self.add_column("Students", "oneSchoolName", formula="$oneSchool.name")
# A helper for comparing Record objects below.
schools_table = self.engine.tables['Schools']
def SchoolsRec(row_id):
return schools_table.Record(schools_table, row_id, None)
# We'll play with schools "Columbia" and "Eureka", which are rows 1,3,5 in the Students table.
self.assertTableData("Students", cols="subset", rows="subset", data=[
["id", "schoolName", "schoolsCount", "oneSchool", "oneSchoolName"],
[1, "Columbia", 2, SchoolsRec(1), "Columbia"],
[3, "Columbia", 2, SchoolsRec(1), "Columbia"],
[5, "Eureka", 0, SchoolsRec(0), ""],
])
# Now change Schools.schoolName which should trigger recomputations.
self.update_record("Schools", 1, name="Eureka")
self.assertTableData("Students", cols="subset", rows="subset", data=[
["id", "schoolName", "schoolsCount", "oneSchool", "oneSchoolName"],
[1, "Columbia", 1, SchoolsRec(2), "Columbia"],
[3, "Columbia", 1, SchoolsRec(2), "Columbia"],
[5, "Eureka", 1, SchoolsRec(1), "Eureka"],
])
# The first change is expected to work. The important check is that the relations don't get
# corrupted afterwards. So we do a second change to see if that still updates.
self.update_record("Schools", 1, name="Columbia")
self.assertTableData("Students", cols="subset", rows="subset", data=[
["id", "schoolName", "schoolsCount", "oneSchool", "oneSchoolName"],
[1, "Columbia", 2, SchoolsRec(1), "Columbia"],
[3, "Columbia", 2, SchoolsRec(1), "Columbia"],
[5, "Eureka", 0, SchoolsRec(0), ""],
])
# One more time, for good measure.
self.update_record("Schools", 1, name="Eureka")
self.assertTableData("Students", cols="subset", rows="subset", data=[
["id", "schoolName", "schoolsCount", "oneSchool", "oneSchoolName"],
[1, "Columbia", 1, SchoolsRec(2), "Columbia"],
[3, "Columbia", 1, SchoolsRec(2), "Columbia"],
[5, "Eureka", 1, SchoolsRec(1), "Eureka"],
])
def test_vlookup(self):
self.load_sample(testsamples.sample_students)
self.add_column("Students", "school", formula="VLOOKUP(Schools, name=$schoolName)")
self.add_column("Students", "schoolCity",
formula="VLOOKUP(Schools, name=$schoolName).address.city")
# A helper for comparing Record objects below.
schools_table = self.engine.tables['Schools']
def SchoolsRec(row_id):
return schools_table.Record(schools_table, row_id, None)
# We'll play with schools "Columbia" and "Eureka", which are rows 1,3,5 in the Students table.
self.assertTableData("Students", cols="subset", rows="all", data=[
["id", "schoolName", "school", "schoolCity"],
[1, "Columbia", SchoolsRec(1), "New York" ],
[2, "Yale", SchoolsRec(3), "New Haven" ],
[3, "Columbia", SchoolsRec(1), "New York" ],
[4, "Yale", SchoolsRec(3), "New Haven" ],
[5, "Eureka", SchoolsRec(0), "" ],
[6, "Yale", SchoolsRec(3), "New Haven" ],
])
# Now change some values which should trigger recomputations.
self.update_record("Schools", 1, name="Eureka")
self.update_record("Students", 2, schoolName="Unknown")
self.assertTableData("Students", cols="subset", rows="all", data=[
["id", "schoolName", "school", "schoolCity"],
[1, "Columbia", SchoolsRec(2), "Colombia" ],
[2, "Unknown", SchoolsRec(0), "" ],
[3, "Columbia", SchoolsRec(2), "Colombia" ],
[4, "Yale", SchoolsRec(3), "New Haven" ],
[5, "Eureka", SchoolsRec(1), "New York" ],
[6, "Yale", SchoolsRec(3), "New Haven" ],
])