mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Log number of rows in user tables in data engine
Summary: Adds a method Table._num_rows using an empty lookup map column. Adds a method Engine.count_rows which adds them all up. Returns the count after applying user actions to be logged by ActiveDoc. Test Plan: Added a unit test in Python. Tested log message manually. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D3275
This commit is contained in:
parent
f1002c0e67
commit
437d30bd9f
@ -61,6 +61,7 @@ export interface SandboxActionBundle {
|
|||||||
calc: Array<EnvContent<DocAction>>;
|
calc: Array<EnvContent<DocAction>>;
|
||||||
undo: Array<EnvContent<DocAction>>; // Inverse actions for all 'stored' actions.
|
undo: Array<EnvContent<DocAction>>; // Inverse actions for all 'stored' actions.
|
||||||
retValues: any[]; // Contains retValue for each of userActions.
|
retValues: any[]; // Contains retValue for each of userActions.
|
||||||
|
rowCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Local action that's been applied. It now has an actionNum, and includes doc actions packaged
|
// Local action that's been applied. It now has an actionNum, and includes doc actions packaged
|
||||||
|
@ -1216,6 +1216,10 @@ export class ActiveDoc extends EventEmitter {
|
|||||||
}
|
}
|
||||||
const user = docSession ? await this._granularAccess.getCachedUser(docSession) : undefined;
|
const user = docSession ? await this._granularAccess.getCachedUser(docSession) : undefined;
|
||||||
sandboxActionBundle = await this._rawPyCall('apply_user_actions', normalActions, user?.toJSON());
|
sandboxActionBundle = await this._rawPyCall('apply_user_actions', normalActions, user?.toJSON());
|
||||||
|
log.rawInfo('Sandbox row count', {
|
||||||
|
...this.getLogMeta(docSession),
|
||||||
|
rowCount: sandboxActionBundle.rowCount
|
||||||
|
});
|
||||||
await this._reportDataEngineMemory();
|
await this._reportDataEngineMemory();
|
||||||
} else {
|
} else {
|
||||||
// Create default SandboxActionBundle to use if the data engine is not called.
|
// Create default SandboxActionBundle to use if the data engine is not called.
|
||||||
@ -1736,7 +1740,8 @@ function createEmptySandboxActionBundle(): SandboxActionBundle {
|
|||||||
direct: [],
|
direct: [],
|
||||||
calc: [],
|
calc: [],
|
||||||
undo: [],
|
undo: [],
|
||||||
retValues: []
|
retValues: [],
|
||||||
|
rowCount: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1175,6 +1175,13 @@ class Engine(object):
|
|||||||
"""
|
"""
|
||||||
self._unused_lookups.add(lookup_map_column)
|
self._unused_lookups.add(lookup_map_column)
|
||||||
|
|
||||||
|
def count_rows(self):
|
||||||
|
return sum(
|
||||||
|
table._num_rows()
|
||||||
|
for table_id, table in six.iteritems(self.tables)
|
||||||
|
if useractions.is_user_table(table_id)
|
||||||
|
)
|
||||||
|
|
||||||
def apply_user_actions(self, user_actions, user=None):
|
def apply_user_actions(self, user_actions, user=None):
|
||||||
"""
|
"""
|
||||||
Applies the list of user_actions. Returns an ActionGroup.
|
Applies the list of user_actions. Returns an ActionGroup.
|
||||||
|
@ -111,6 +111,13 @@ class BaseLookupMapColumn(column.BaseColumn):
|
|||||||
if not self._lookup_relations:
|
if not self._lookup_relations:
|
||||||
self._engine.mark_lookupmap_for_cleanup(self)
|
self._engine.mark_lookupmap_for_cleanup(self)
|
||||||
|
|
||||||
|
def _do_fast_empty_lookup(self):
|
||||||
|
"""
|
||||||
|
Simplified version of do_lookup for a lookup column with no key columns
|
||||||
|
to make Table._num_rows as fast as possible.
|
||||||
|
"""
|
||||||
|
return self._row_key_map.lookup_right((), default=())
|
||||||
|
|
||||||
def do_lookup(self, key):
|
def do_lookup(self, key):
|
||||||
"""
|
"""
|
||||||
Looks up key in the lookup map and returns a tuple with two elements: the set of matching
|
Looks up key in the lookup map and returns a tuple with two elements: the set of matching
|
||||||
|
@ -64,7 +64,10 @@ def run(sandbox):
|
|||||||
@export
|
@export
|
||||||
def apply_user_actions(action_reprs, user=None):
|
def apply_user_actions(action_reprs, user=None):
|
||||||
action_group = eng.apply_user_actions([useractions.from_repr(u) for u in action_reprs], user)
|
action_group = eng.apply_user_actions([useractions.from_repr(u) for u in action_reprs], user)
|
||||||
return eng.acl_split(action_group).to_json_obj()
|
return dict(
|
||||||
|
rowCount=eng.count_rows(),
|
||||||
|
**eng.acl_split(action_group).to_json_obj()
|
||||||
|
)
|
||||||
|
|
||||||
@export
|
@export
|
||||||
def fetch_table(table_id, formulas=True, query=None):
|
def fetch_table(table_id, formulas=True, query=None):
|
||||||
|
@ -225,6 +225,11 @@ class Table(object):
|
|||||||
# which are 'flattened' so source records may appear in multiple groups
|
# which are 'flattened' so source records may appear in multiple groups
|
||||||
self._summary_simple = None
|
self._summary_simple = None
|
||||||
|
|
||||||
|
# For use in _num_rows. The attribute isn't strictly needed,
|
||||||
|
# but it makes _num_rows slightly faster, and only creating the lookup map when _num_rows
|
||||||
|
# is called seems to be too late, at least for unit tests.
|
||||||
|
self._empty_lookup_column = self._get_lookup_map(())
|
||||||
|
|
||||||
# Add Record and RecordSet subclasses which fill in this table as the first argument
|
# Add Record and RecordSet subclasses which fill in this table as the first argument
|
||||||
class Record(records.Record):
|
class Record(records.Record):
|
||||||
def __init__(inner_self, *args, **kwargs): # pylint: disable=no-self-argument
|
def __init__(inner_self, *args, **kwargs): # pylint: disable=no-self-argument
|
||||||
@ -237,6 +242,12 @@ class Table(object):
|
|||||||
self.Record = Record
|
self.Record = Record
|
||||||
self.RecordSet = RecordSet
|
self.RecordSet = RecordSet
|
||||||
|
|
||||||
|
def _num_rows(self):
|
||||||
|
"""
|
||||||
|
Similar to `len(self.lookup_records())` but faster and doesn't create dependencies.
|
||||||
|
"""
|
||||||
|
return len(self._empty_lookup_column._do_fast_empty_lookup())
|
||||||
|
|
||||||
def _rebuild_model(self, user_table):
|
def _rebuild_model(self, user_table):
|
||||||
"""
|
"""
|
||||||
Sets class-wide properties from a new Model class for the table (inner class within the table
|
Sets class-wide properties from a new Model class for the table (inner class within the table
|
||||||
|
@ -112,7 +112,9 @@ class TestDerived(test_engine.EngineTestCase):
|
|||||||
actions.BulkUpdateRecord("GristSummary_6_Orders", [1,5], {"group": [[1], [10]]}),
|
actions.BulkUpdateRecord("GristSummary_6_Orders", [1,5], {"group": [[1], [10]]}),
|
||||||
],
|
],
|
||||||
"calls": {
|
"calls": {
|
||||||
"GristSummary_6_Orders": {'#lookup#year': 1, "group": 2, "amount": 2, "count": 2},
|
"GristSummary_6_Orders": {
|
||||||
|
'#lookup#year': 1, "group": 2, "amount": 2, "count": 2, "#lookup#": 1
|
||||||
|
},
|
||||||
"Orders": {"#lookup##summary#GristSummary_6_Orders": 1,
|
"Orders": {"#lookup##summary#GristSummary_6_Orders": 1,
|
||||||
"#summary#GristSummary_6_Orders": 1}}
|
"#summary#GristSummary_6_Orders": 1}}
|
||||||
})
|
})
|
||||||
|
@ -399,7 +399,9 @@ return ",".join(str(r.id) for r in Students.lookupRecords(firstName=fn, lastName
|
|||||||
self.assertPartialOutActions(out_actions, {
|
self.assertPartialOutActions(out_actions, {
|
||||||
"calls": {
|
"calls": {
|
||||||
# No calculations of anything Schools because nothing depends on the incomplete value.
|
# No calculations of anything Schools because nothing depends on the incomplete value.
|
||||||
"Students": {"#lookup#firstName:lastName": 1, "schoolIds": 1, "schoolCities": 1}
|
"Students": {
|
||||||
|
"#lookup#firstName:lastName": 1, "schoolIds": 1, "schoolCities": 1, "#lookup#": 1
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"retValues": [7],
|
"retValues": [7],
|
||||||
})
|
})
|
||||||
@ -408,7 +410,9 @@ return ",".join(str(r.id) for r in Students.lookupRecords(firstName=fn, lastName
|
|||||||
out_actions = self.add_record("Students", firstName="Chuck", lastName="Norris")
|
out_actions = self.add_record("Students", firstName="Chuck", lastName="Norris")
|
||||||
self.assertPartialOutActions(out_actions, {
|
self.assertPartialOutActions(out_actions, {
|
||||||
"calls": {
|
"calls": {
|
||||||
"Students": {"#lookup#firstName:lastName": 1, "schoolIds": 1, "schoolCities": 1},
|
"Students": {
|
||||||
|
"#lookup#firstName:lastName": 1, "schoolIds": 1, "schoolCities": 1, "#lookup#": 1
|
||||||
|
},
|
||||||
"Schools": {"bestStudentId": 1}
|
"Schools": {"bestStudentId": 1}
|
||||||
},
|
},
|
||||||
"retValues": [8],
|
"retValues": [8],
|
||||||
|
@ -155,6 +155,8 @@ class TestSummaryChoiceList(EngineTestCase):
|
|||||||
'#summary#GristSummary_6_Source4': column.ReferenceListColumn,
|
'#summary#GristSummary_6_Source4': column.ReferenceListColumn,
|
||||||
"#lookup#_Contains(value='#summary#GristSummary_6_Source4')":
|
"#lookup#_Contains(value='#summary#GristSummary_6_Source4')":
|
||||||
lookup.ContainsLookupMapColumn,
|
lookup.ContainsLookupMapColumn,
|
||||||
|
|
||||||
|
"#lookup#": lookup.SimpleLookupMapColumn,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -145,7 +145,10 @@ class TestTriggerFormulas(test_engine.EngineTestCase):
|
|||||||
|
|
||||||
self.assertEqual(self.call_counts, {})
|
self.assertEqual(self.call_counts, {})
|
||||||
self.load_sample(sample)
|
self.load_sample(sample)
|
||||||
self.assertEqual(self.call_counts, {'Creatures': {'OceanName': 3}})
|
self.assertEqual(self.call_counts, {
|
||||||
|
'Creatures': {'#lookup#': 3, 'OceanName': 3},
|
||||||
|
'Oceans': {'#lookup#': 4},
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def test_recalc_undo(self):
|
def test_recalc_undo(self):
|
||||||
@ -247,7 +250,7 @@ class TestTriggerFormulas(test_engine.EngineTestCase):
|
|||||||
[2, "Manatee", 2, "Poseidon", "", "Poseidon", "Poseidon", "Atlantic" ],
|
[2, "Manatee", 2, "Poseidon", "", "Poseidon", "Poseidon", "Atlantic" ],
|
||||||
])
|
])
|
||||||
self.assertEqual(out_actions.calls,
|
self.assertEqual(out_actions.calls,
|
||||||
{"Creatures": {"BossDef": 1, "BossUpd": 1, "BossAll": 1, "OceanName": 1}})
|
{"Creatures": {"BossDef": 1, "BossUpd": 1, "BossAll": 1, "OceanName": 1, "#lookup#": 1}})
|
||||||
|
|
||||||
|
|
||||||
def test_recalc_trigger_off(self):
|
def test_recalc_trigger_off(self):
|
||||||
@ -272,7 +275,7 @@ class TestTriggerFormulas(test_engine.EngineTestCase):
|
|||||||
[2, "Manatee", 2, "Poseidon", "", "", "Poseidon", "Atlantic" ],
|
[2, "Manatee", 2, "Poseidon", "", "", "Poseidon", "Atlantic" ],
|
||||||
])
|
])
|
||||||
self.assertEqual(out_actions.calls,
|
self.assertEqual(out_actions.calls,
|
||||||
{"Creatures": {"BossDef": 1, "BossAll": 1, "OceanName": 1}})
|
{"Creatures": {"BossDef": 1, "BossAll": 1, "OceanName": 1, "#lookup#": 1}})
|
||||||
|
|
||||||
|
|
||||||
def test_renames(self):
|
def test_renames(self):
|
||||||
|
@ -1172,6 +1172,23 @@ class TestUserActions(test_engine.EngineTestCase):
|
|||||||
"999",
|
"999",
|
||||||
]}]]})
|
]}]]})
|
||||||
|
|
||||||
|
def test_num_rows(self):
|
||||||
|
self.load_sample(testutil.parse_test_sample({
|
||||||
|
"SCHEMA": [
|
||||||
|
[1, "Address", [
|
||||||
|
[21, "city", "Text", False, "", "", ""],
|
||||||
|
]],
|
||||||
|
],
|
||||||
|
"DATA": {
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
table = self.engine.tables["Address"]
|
||||||
|
for i in range(20):
|
||||||
|
self.add_record("Address", None)
|
||||||
|
self.assertEqual(i + 1, table._num_rows())
|
||||||
|
self.assertEqual(i + 1, self.engine.count_rows())
|
||||||
|
|
||||||
def test_raw_view_section_restrictions(self):
|
def test_raw_view_section_restrictions(self):
|
||||||
# load_sample handles loading basic metadata, but doesn't create any view sections
|
# load_sample handles loading basic metadata, but doesn't create any view sections
|
||||||
self.load_sample(self.sample)
|
self.load_sample(self.sample)
|
||||||
|
@ -105,7 +105,7 @@
|
|||||||
"retValue": [ 11 ]
|
"retValue": [ 11 ]
|
||||||
},
|
},
|
||||||
"CHECK_CALL_COUNTS": {
|
"CHECK_CALL_COUNTS": {
|
||||||
"Address" : {"region" : 1}
|
"Address" : {"region" : 1, "#lookup#": 1}
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
["APPLY", {
|
["APPLY", {
|
||||||
@ -200,7 +200,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"CHECK_CALL_COUNTS": {
|
"CHECK_CALL_COUNTS": {
|
||||||
"Schools": { "name": 1 },
|
"Schools": { "name": 1, "#lookup#": 2 },
|
||||||
"Students" : { "schoolShort" : 7 }
|
"Students" : { "schoolShort" : 7 }
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
@ -239,7 +239,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"CHECK_CALL_COUNTS" : {
|
"CHECK_CALL_COUNTS" : {
|
||||||
"Schools": { "name": 1 },
|
"Schools": { "name": 1, "#lookup#": 1 },
|
||||||
"Students" : { "schoolShort" : 7 }
|
"Students" : { "schoolShort" : 7 }
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
@ -357,7 +357,7 @@
|
|||||||
"retValue": [ [11, 12] ]
|
"retValue": [ [11, 12] ]
|
||||||
},
|
},
|
||||||
"CHECK_CALL_COUNTS" : {
|
"CHECK_CALL_COUNTS" : {
|
||||||
"Address" : { "country": 2, "region" : 2 }
|
"Address" : { "country": 2, "region" : 2, "#lookup#": 2 }
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
["CHECK_OUTPUT", {
|
["CHECK_OUTPUT", {
|
||||||
@ -1690,7 +1690,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"CHECK_CALL_COUNTS" : {
|
"CHECK_CALL_COUNTS" : {
|
||||||
"Address" : { "city": 1, "region" : 1 },
|
"Address" : { "city": 1, "region" : 1, "#lookup#": 1 },
|
||||||
"Students": { "fullName" : 1, "fullNameLen" : 1 }
|
"Students": { "fullName" : 1, "fullNameLen" : 1 }
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
@ -2563,7 +2563,8 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"CHECK_CALL_COUNTS" : {
|
"CHECK_CALL_COUNTS" : {
|
||||||
"People" : { "fullName" : 7, "schoolRegion" : 7, "schoolShort" : 7, "fullNameLen" : 7 }
|
"People" : { "fullName" : 7, "schoolRegion" : 7, "schoolShort" : 7, "fullNameLen" : 7, "#lookup#": 7 },
|
||||||
|
"School": {"#lookup#": 6}
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
||||||
@ -2677,7 +2678,8 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"CHECK_CALL_COUNTS" : {
|
"CHECK_CALL_COUNTS" : {
|
||||||
"People" : { "fullName" : 7, "schoolRegion" : 7, "schoolShort" : 7, "fullNameLen" : 7 }
|
"People" : { "fullName" : 7, "schoolRegion" : 7, "schoolShort" : 7, "fullNameLen" : 7, "#lookup#": 7 },
|
||||||
|
"School": {"#lookup#": 6}
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import six
|
|||||||
from six.moves import xrange
|
from six.moves import xrange
|
||||||
|
|
||||||
import acl
|
import acl
|
||||||
|
import gencode
|
||||||
from acl_formula import parse_acl_formula_json
|
from acl_formula import parse_acl_formula_json
|
||||||
import actions
|
import actions
|
||||||
import column
|
import column
|
||||||
@ -83,6 +84,10 @@ def is_hidden_table(table_id):
|
|||||||
return table_id.startswith('GristHidden_')
|
return table_id.startswith('GristHidden_')
|
||||||
|
|
||||||
|
|
||||||
|
def is_user_table(table_id):
|
||||||
|
return not (is_hidden_table(table_id) or gencode._is_special_table(table_id))
|
||||||
|
|
||||||
|
|
||||||
def useraction(method):
|
def useraction(method):
|
||||||
"""
|
"""
|
||||||
Decorator for a method, which creates an action class with the same name and arguments.
|
Decorator for a method, which creates an action class with the same name and arguments.
|
||||||
|
Loading…
Reference in New Issue
Block a user