(core) Nice summary table IDs

Summary:
Changes auto-generated summary table IDs from e.g. `GristSummary_6_Table1` to `Table1_summary_A_B` (meaning `Table1` grouped by `A` and `B`). This makes it easier to write formulas involving summary tables, make API requests, understand logs, etc.

Because these don't encode the source table ID as reliably as before, `decode_summary_table_name` now uses the summary table schema info, not just the summary table ID. Specifically, it looks at the type of the `group` column, which is `RefList:<source table id>`.

Renaming a source table renames the summary table as before, and now renaming a groupby column renames the summary table as well.

Conflicting table names are resolved in the usual way by adding a number at the end, e.g. `Table1_summary_A_B2`. These summary tables are not automatically renamed when the disambiguation is no longer needed.

A new migration renames all summary tables to the new scheme, and updates formulas using summary tables with a simple regex.

Test Plan:
Updated many tests to use the new style of name.

Added new Python tests to for resolving conflicts when renaming source tables and groupby columns.

Added a test for the migration, including renames in formulas.

Reviewers: georgegevoian

Reviewed By: georgegevoian

Differential Revision: https://phab.getgrist.com/D3508
pull/221/head
Alex Hall 2 years ago
parent f1df6c0a46
commit b8486dcdba

@ -4,7 +4,7 @@ import { GristObjCode } from "app/plugin/GristData";
// tslint:disable:object-literal-key-quotes
export const SCHEMA_VERSION = 30;
export const SCHEMA_VERSION = 31;
export const schema = {

@ -33,9 +33,10 @@ class GristDocAPIImpl implements GristDocAPI {
public async getDocName() { return this._activeDoc.docName; }
public async listTables(): Promise<string[]> {
const table = this._activeDoc.docData!.getMetaTable('_grist_Tables');
return table.getColValues('tableId')
.filter(id => !id.startsWith("GristSummary_")).sort();
return this._activeDoc.docData!.getMetaTable('_grist_Tables')
.getRecords()
.filter(r => !r.summarySourceTable)
.map(r => r.tableId);
}
public async fetchTable(tableId: string): Promise<TableColValues> {

@ -6,7 +6,7 @@ export const GRIST_DOC_SQL = `
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS "_grist_DocInfo" (id INTEGER PRIMARY KEY, "docId" TEXT DEFAULT '', "peers" TEXT DEFAULT '', "basketId" TEXT DEFAULT '', "schemaVersion" INTEGER DEFAULT 0, "timezone" TEXT DEFAULT '', "documentSettings" TEXT DEFAULT '');
INSERT INTO _grist_DocInfo VALUES(1,'','','',30,'','');
INSERT INTO _grist_DocInfo VALUES(1,'','','',31,'','');
CREATE TABLE IF NOT EXISTS "_grist_Tables" (id INTEGER PRIMARY KEY, "tableId" TEXT DEFAULT '', "primaryViewId" INTEGER DEFAULT 0, "summarySourceTable" INTEGER DEFAULT 0, "onDemand" BOOLEAN DEFAULT 0, "rawViewSectionRef" INTEGER DEFAULT 0);
CREATE TABLE IF NOT EXISTS "_grist_Tables_column" (id INTEGER PRIMARY KEY, "parentId" INTEGER DEFAULT 0, "parentPos" NUMERIC DEFAULT 1e999, "colId" TEXT DEFAULT '', "type" TEXT DEFAULT '', "widgetOptions" TEXT DEFAULT '', "isFormula" BOOLEAN DEFAULT 0, "formula" TEXT DEFAULT '', "label" TEXT DEFAULT '', "untieColIdFromLabel" BOOLEAN DEFAULT 0, "summarySourceCol" INTEGER DEFAULT 0, "displayCol" INTEGER DEFAULT 0, "visibleCol" INTEGER DEFAULT 0, "rules" TEXT DEFAULT NULL, "recalcWhen" INTEGER DEFAULT 0, "recalcDeps" TEXT DEFAULT NULL);
CREATE TABLE IF NOT EXISTS "_grist_Imports" (id INTEGER PRIMARY KEY, "tableRef" INTEGER DEFAULT 0, "origFileName" TEXT DEFAULT '', "parseFormula" TEXT DEFAULT '', "delimiter" TEXT DEFAULT '', "doublequote" BOOLEAN DEFAULT 0, "escapechar" TEXT DEFAULT '', "quotechar" TEXT DEFAULT '', "skipinitialspace" BOOLEAN DEFAULT 0, "encoding" TEXT DEFAULT '', "hasHeaders" BOOLEAN DEFAULT 0);
@ -42,7 +42,7 @@ export const GRIST_DOC_WITH_TABLE1_SQL = `
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS "_grist_DocInfo" (id INTEGER PRIMARY KEY, "docId" TEXT DEFAULT '', "peers" TEXT DEFAULT '', "basketId" TEXT DEFAULT '', "schemaVersion" INTEGER DEFAULT 0, "timezone" TEXT DEFAULT '', "documentSettings" TEXT DEFAULT '');
INSERT INTO _grist_DocInfo VALUES(1,'','','',30,'','');
INSERT INTO _grist_DocInfo VALUES(1,'','','',31,'','');
CREATE TABLE IF NOT EXISTS "_grist_Tables" (id INTEGER PRIMARY KEY, "tableId" TEXT DEFAULT '', "primaryViewId" INTEGER DEFAULT 0, "summarySourceTable" INTEGER DEFAULT 0, "onDemand" BOOLEAN DEFAULT 0, "rawViewSectionRef" INTEGER DEFAULT 0);
INSERT INTO _grist_Tables VALUES(1,'Table1',1,0,0,2);
CREATE TABLE IF NOT EXISTS "_grist_Tables_column" (id INTEGER PRIMARY KEY, "parentId" INTEGER DEFAULT 0, "parentPos" NUMERIC DEFAULT 1e999, "colId" TEXT DEFAULT '', "type" TEXT DEFAULT '', "widgetOptions" TEXT DEFAULT '', "isFormula" BOOLEAN DEFAULT 0, "formula" TEXT DEFAULT '', "label" TEXT DEFAULT '', "untieColIdFromLabel" BOOLEAN DEFAULT 0, "summarySourceCol" INTEGER DEFAULT 0, "displayCol" INTEGER DEFAULT 0, "visibleCol" INTEGER DEFAULT 0, "rules" TEXT DEFAULT NULL, "recalcWhen" INTEGER DEFAULT 0, "recalcDeps" TEXT DEFAULT NULL);

@ -436,7 +436,7 @@ class Engine(object):
m = match_counter.MatchCounter(sample)
# Iterates through each valid column in the document, counting matches.
for c in search_cols:
if (not gencode._is_special_table(c.tableId) and
if (not (gencode._is_special_table(c.tableId) or c.parentId.summarySourceTable) and
column.is_visible_column(c.colId) and
not c.type.startswith('Ref')):
table = self.tables[c.tableId]
@ -1265,7 +1265,7 @@ class Engine(object):
return sum(
table._num_rows()
for table_id, table in six.iteritems(self.tables)
if useractions.is_user_table(table_id)
if useractions.is_user_table(table_id) and not table._summary_source_table
)
def apply_user_actions(self, user_actions, user=None):

@ -128,7 +128,7 @@ class GenCode(object):
If filter_for_user is True, includes only user-visible columns.
"""
table_id = table_info.tableId
source_table_id = summary.decode_summary_table_name(table_id)
source_table_id = summary.decode_summary_table_name(table_info)
# Sort columns by "isFormula" to output all data columns before all formula columns.
columns = sorted(six.itervalues(table_info.columns), key=lambda c: c.isFormula)
@ -156,7 +156,7 @@ class GenCode(object):
# Collect summary tables to group them by source table.
summary_tables = {}
for table_info in six.itervalues(schema):
source_table_id = summary.decode_summary_table_name(table_info.tableId)
source_table_id = summary.decode_summary_table_name(table_info)
if source_table_id:
summary_tables.setdefault(source_table_id, []).append(table_info)
@ -167,7 +167,10 @@ class GenCode(object):
for table_info in six.itervalues(schema):
fullparts.append("\n\n")
fullparts.append(self._make_table_model(table_info, summary_tables.get(table_info.tableId)))
if not _is_special_table(table_info.tableId):
if not (
_is_special_table(table_info.tableId) or
summary.decode_summary_table_name(table_info)
):
userparts.append("\n\n")
userparts.append(self._make_table_model(table_info, summary_tables.get(table_info.tableId),
filter_for_user=True))
@ -193,7 +196,7 @@ class GenCode(object):
def _is_special_table(table_id):
return table_id.startswith("_grist_") or bool(summary.decode_summary_table_name(table_id))
return table_id.startswith("_grist_")
def exec_module_text(module_text):

@ -1,5 +1,6 @@
import json
import re
from collections import defaultdict
import six
from six.moves import xrange
@ -378,7 +379,8 @@ def migration7(tdset):
groupby_colrefs = [int(x) for x in m.group(2).strip("_").split("_")]
# Prepare a new-style name for the summary table. Be sure not to conflict with existing tables
# or with each other (i.e. don't rename multiple tables to the same name).
new_name = summary.encode_summary_table_name(source_table_name)
groupby_col_ids = [columns_map_by_ref[c].colId for c in groupby_colrefs]
new_name = summary.encode_summary_table_name(source_table_name, groupby_col_ids)
new_name = identifiers.pick_table_ident(new_name, avoid=table_name_set)
table_name_set.add(new_name)
log.warn("Upgrading summary table %s for %s(%s) to %s" % (
@ -1014,3 +1016,60 @@ def migration30(tdset):
new_view_section_id += 1
return tdset.apply_doc_actions(doc_actions)
@migration(schema_version=31)
def migration31(tdset):
columns = list(actions.transpose_bulk_action(tdset.all_tables['_grist_Tables_column']))
tables = list(actions.transpose_bulk_action(tdset.all_tables['_grist_Tables']))
tables_by_ref = {t.id: t for t in tables}
columns_by_table_ref = defaultdict(list)
for col in columns:
columns_by_table_ref[col.parentId].append(col)
table_name_set = {t.tableId for t in tables}
table_renames = [] # List of (table, new_name) pairs
for t in six.itervalues(tables_by_ref):
if not t.summarySourceTable:
continue
source_table = tables_by_ref[t.summarySourceTable]
# Prepare a new-style name for the summary table. Be sure not to conflict with existing tables
# or with each other (i.e. don't rename multiple tables to the same name).
groupby_col_ids = [c.colId for c in columns_by_table_ref[t.id] if c.summarySourceCol]
new_name = summary.encode_summary_table_name(source_table.tableId, groupby_col_ids)
if new_name == t.tableId:
continue
new_name = identifiers.pick_table_ident(new_name, avoid=table_name_set)
table_name_set.add(new_name)
log.warn("Upgrading summary table %s for %s(%s) to %s" % (
t.tableId, source_table.tableId, groupby_col_ids, new_name))
# Schedule a rename of the summary table.
table_renames.append((t, new_name))
doc_actions = [
actions.RenameTable(t.tableId, new)
for (t, new) in table_renames
]
if table_renames:
doc_actions.append(
actions.BulkUpdateRecord(
'_grist_Tables', [t.id for t, new in table_renames],
{'tableId': [new for t, new in table_renames]}
)
)
# Update formulas in all columns containing old-style names like 'GristSummary_'
for col in columns:
if 'GristSummary_' not in col.formula:
continue
formula = col.formula
for table, new_name in table_renames:
# Use regex to only match whole words
formula = re.sub(r'\b%s\b' % table.tableId, new_name, formula)
doc_actions.append(actions.UpdateRecord('_grist_Tables_column', col.id, {'formula': formula}))
return tdset.apply_doc_actions(doc_actions)

@ -15,7 +15,7 @@ import six
import actions
SCHEMA_VERSION = 30
SCHEMA_VERSION = 31
def make_column(col_id, col_type, formula='', isFormula=False):
return {

@ -1,6 +1,5 @@
from collections import namedtuple
import json
import re
import six
@ -79,38 +78,34 @@ def _copy_widget_options(options):
return options
return json.dumps({k: v for k, v in options.items() if k != "rulesOptions"})
# To generate code, we need to know for each summary table, what its source table is. It would be
# easy if we had access to metadata records, but (at least for now) we generate all code based on
# schema only. So we encode the source table name inside of the summary table name.
#
# The encoding includes the length of the source table name, to avoid the possibility of ambiguity
# between the second summary table for "Foo", and the first summary table for "Foo2".
#
# Note that it means we need to rename summary tables when the source table is renamed.
def encode_summary_table_name(source_table_name):
def encode_summary_table_name(source_table_id, groupby_col_ids):
"""
Create a summary table name that reliably encodes the source table name. It can be decoded even
if a suffix is added to the returned name.
Create a summary table name based on the source table ID and the groupby column IDs.
"""
return "GristSummary_%d_%s" % (len(source_table_name), source_table_name)
result = source_table_id + '_summary'
if groupby_col_ids:
result += '_' + '_'.join(sorted(groupby_col_ids))
return result
_summary_re = re.compile(r'GristSummary_(\d+)_')
def decode_summary_table_name(summary_table_name):
def decode_summary_table_name(summary_table_info):
"""
Extract the name of the source table from the summary table name.
Extract the name of the source table from the summary table schema info.
"""
m = _summary_re.match(summary_table_name)
if m:
start = m.end(0)
length = int(m.group(1))
source_name = summary_table_name[start : start + length]
if len(source_name) == length:
return source_name
# To generate code, we need to know for each summary table, what its source table is. It would be
# easy if we had access to metadata records, but (at least for now) we generate all code based on
# schema only. So we use the type of special 'group' column in the summary table.
group_col = summary_table_info.columns.get('group')
if (
group_col
and 'getSummarySourceGroup' in group_col.formula
and group_col.type.startswith('RefList:')
):
return group_col.type[8:]
return None
def _group_colinfo(source_table):
"""Returns ColInfo() for the 'group' column that must be present in every summary table."""
return _make_col_info(colId='group', type='RefList:%s' % source_table.tableId,
@ -202,8 +197,9 @@ class SummaryActions(object):
summary_table = next((t for t in source_table.summaryTables if t.summaryKey == key), None)
created = False
if not summary_table:
groupby_col_ids = [c.colId for c in groupby_colinfo]
result = self.useractions.doAddTable(
encode_summary_table_name(source_table.tableId),
encode_summary_table_name(source_table.tableId, groupby_col_ids),
[_get_colinfo_dict(ci, with_id=True) for ci in groupby_colinfo + formula_colinfo],
summarySourceTableRef=source_table.id,
raw_section=True)

@ -208,7 +208,7 @@ class TestColumnActions(test_engine.EngineTestCase):
Column(16, "B", "Text", False, "", 0),
Column(17, "C", "Any", True, "", 0),
]),
Table(3, "GristSummary_7_Address", 0, 1, columns=[
Table(3, "Address_summary_state", 0, 1, columns=[
Column(18, "state", "Text", False, "", summarySourceCol=12),
Column(19, "group", "RefList:Address", True, summarySourceCol=0,
formula="table.getSummarySourceGroup(rec)"),
@ -249,7 +249,7 @@ class TestColumnActions(test_engine.EngineTestCase):
[ 2, "b", "e", None, 2.0],
[ 3, "c", "f", None, 3.0],
])
self.assertTableData("GristSummary_7_Address", cols="subset", data=[
self.assertTableData("Address_summary_state", cols="subset", data=[
[ "id", "state", "count", "amount" ],
[ 1, "NY", 7, 1.+2+6+7+8+10+11 ],
[ 2, "WA", 1, 3. ],
@ -297,7 +297,7 @@ class TestColumnActions(test_engine.EngineTestCase):
Column(15, "A", "Text", False, "", 0),
Column(17, "C", "Any", True, "", 0),
]),
Table(3, "GristSummary_7_Address", 0, 1, columns=[
Table(3, "Address_summary_state", 0, 1, columns=[
Column(18, "state", "Text", False, "", summarySourceCol=12),
Column(19, "group", "RefList:Address", True, summarySourceCol=0,
formula="table.getSummarySourceGroup(rec)"),
@ -356,7 +356,7 @@ class TestColumnActions(test_engine.EngineTestCase):
Column(17, "C", "Any", True, "", 0),
]),
# Note that the summary table here switches to a new one, without the deleted group-by.
Table(4, "GristSummary_7_Address2", 0, 1, columns=[
Table(4, "Address_summary", 0, 1, columns=[
Column(23, "count", "Int", True, summarySourceCol=0, formula="len($group)"),
Column(24, "amount", "Numeric", True, summarySourceCol=0, formula="SUM($group.amount)"),
Column(22, "group", "RefList:Address", True, summarySourceCol=0,
@ -406,7 +406,7 @@ class TestColumnActions(test_engine.EngineTestCase):
[ 2, "b", None, 2.0],
[ 3, "c", None, 3.0],
])
self.assertTableData("GristSummary_7_Address2", cols="subset", data=[
self.assertTableData("Address_summary", cols="subset", data=[
[ "id", "count", "amount" ],
[ 1, 7+1+1+2, 1.+2+6+7+8+10+11+3+4+5+9 ],
])

@ -60,7 +60,7 @@ class TestDerived(test_engine.EngineTestCase):
self.apply_user_action(["CreateViewSection", 2, 0, 'record', [10], None])
# Check the results.
self.assertPartialData("GristSummary_6_Orders", ["id", "year", "count", "amount", "group" ], [
self.assertPartialData("Orders_summary_year", ["id", "year", "count", "amount", "group" ], [
[1, 2012, 1, 15, [1]],
[2, 2013, 2, 30, [2,3]],
[3, 2014, 3, 86, [4,5,6]],
@ -75,9 +75,9 @@ class TestDerived(test_engine.EngineTestCase):
self.assertPartialOutActions(out_actions, {
"stored": [
actions.BulkUpdateRecord("Orders", [1,2], {'amount': [14, 14]}),
actions.BulkUpdateRecord("GristSummary_6_Orders", [1,2], {'amount': [14, 29]})
actions.BulkUpdateRecord("Orders_summary_year", [1,2], {'amount': [14, 29]})
],
"calls": {"GristSummary_6_Orders": {"amount": 2}}
"calls": {"Orders_summary_year": {"amount": 2}}
})
# Changing a record from one product to another should cause the two affected lines to change.
@ -85,16 +85,16 @@ class TestDerived(test_engine.EngineTestCase):
self.assertPartialOutActions(out_actions, {
"stored": [
actions.UpdateRecord("Orders", 10, {"year": 2012}),
actions.BulkUpdateRecord("GristSummary_6_Orders", [1,4], {"amount": [31.0, 89.0]}),
actions.BulkUpdateRecord("GristSummary_6_Orders", [1,4], {"count": [2,3]}),
actions.BulkUpdateRecord("GristSummary_6_Orders", [1,4], {"group": [[1,10], [7,8,9]]}),
actions.BulkUpdateRecord("Orders_summary_year", [1,4], {"amount": [31.0, 89.0]}),
actions.BulkUpdateRecord("Orders_summary_year", [1,4], {"count": [2,3]}),
actions.BulkUpdateRecord("Orders_summary_year", [1,4], {"group": [[1,10], [7,8,9]]}),
],
"calls": {"GristSummary_6_Orders": {"group": 2, "amount": 2, "count": 2},
"Orders": {"#lookup##summary#GristSummary_6_Orders": 1,
"#summary#GristSummary_6_Orders": 1}}
"calls": {"Orders_summary_year": {"group": 2, "amount": 2, "count": 2},
"Orders": {"#lookup##summary#Orders_summary_year": 1,
"#summary#Orders_summary_year": 1}}
})
self.assertPartialData("GristSummary_6_Orders", ["id", "year", "count", "amount", "group" ], [
self.assertPartialData("Orders_summary_year", ["id", "year", "count", "amount", "group" ], [
[1, 2012, 2, 31.0, [1,10]],
[2, 2013, 2, 29.0, [2,3]],
[3, 2014, 3, 86.0, [4,5,6]],
@ -106,20 +106,20 @@ class TestDerived(test_engine.EngineTestCase):
self.assertPartialOutActions(out_actions, {
"stored": [
actions.UpdateRecord("Orders", 10, {"year": 1999}),
actions.AddRecord("GristSummary_6_Orders", 5, {'year': 1999}),
actions.BulkUpdateRecord("GristSummary_6_Orders", [1,5], {"amount": [14.0, 17.0]}),
actions.BulkUpdateRecord("GristSummary_6_Orders", [1,5], {"count": [1,1]}),
actions.BulkUpdateRecord("GristSummary_6_Orders", [1,5], {"group": [[1], [10]]}),
actions.AddRecord("Orders_summary_year", 5, {'year': 1999}),
actions.BulkUpdateRecord("Orders_summary_year", [1,5], {"amount": [14.0, 17.0]}),
actions.BulkUpdateRecord("Orders_summary_year", [1,5], {"count": [1,1]}),
actions.BulkUpdateRecord("Orders_summary_year", [1,5], {"group": [[1], [10]]}),
],
"calls": {
"GristSummary_6_Orders": {
"Orders_summary_year": {
'#lookup#year': 1, "group": 2, "amount": 2, "count": 2, "#lookup#": 1
},
"Orders": {"#lookup##summary#GristSummary_6_Orders": 1,
"#summary#GristSummary_6_Orders": 1}}
"Orders": {"#lookup##summary#Orders_summary_year": 1,
"#summary#Orders_summary_year": 1}}
})
self.assertPartialData("GristSummary_6_Orders", ["id", "year", "count", "amount", "group" ], [
self.assertPartialData("Orders_summary_year", ["id", "year", "count", "amount", "group" ], [
[1, 2012, 1, 14.0, [1]],
[2, 2013, 2, 29.0, [2,3]],
[3, 2014, 3, 86.0, [4,5,6]],
@ -135,7 +135,7 @@ class TestDerived(test_engine.EngineTestCase):
self.load_sample(self.sample)
self.apply_user_action(["CreateViewSection", 2, 0, 'record', [10, 12], None])
self.assertPartialData("GristSummary_6_Orders", [
self.assertPartialData("Orders_summary_product_year", [
"id", "year", "product", "count", "amount", "group"
], [
[1, 2012, "A", 1, 15.0, [1]],
@ -156,23 +156,23 @@ class TestDerived(test_engine.EngineTestCase):
self.assertPartialOutActions(out_actions, {
"stored": [
actions.BulkUpdateRecord("Orders", [2, 6, 7], {"product": ["B", "B", "C"]}),
actions.AddRecord("GristSummary_6_Orders", 7, {'year': 2013, 'product': 'B'}),
actions.AddRecord("GristSummary_6_Orders", 8, {'year': 2015, 'product': 'C'}),
actions.RemoveRecord("GristSummary_6_Orders", 4),
actions.BulkUpdateRecord("GristSummary_6_Orders", [2,3,5,7,8], {
actions.AddRecord("Orders_summary_product_year", 7, {'year': 2013, 'product': 'B'}),
actions.AddRecord("Orders_summary_product_year", 8, {'year': 2015, 'product': 'C'}),
actions.RemoveRecord("Orders_summary_product_year", 4),
actions.BulkUpdateRecord("Orders_summary_product_year", [2,3,5,7,8], {
"amount": [15.0, 86.0, 17.0, 15.0, 17.0]
}),
actions.BulkUpdateRecord("GristSummary_6_Orders", [2,3,5,7,8], {
actions.BulkUpdateRecord("Orders_summary_product_year", [2,3,5,7,8], {
"count": [1, 3, 1, 1, 1]
}),
actions.BulkUpdateRecord("GristSummary_6_Orders", [2,3,5,7,8], {
actions.BulkUpdateRecord("Orders_summary_product_year", [2,3,5,7,8], {
"group": [[3], [4,5,6], [10], [2], [7]]
}),
],
})
# Verify the results.
self.assertPartialData("GristSummary_6_Orders", [
self.assertPartialData("Orders_summary_product_year", [
"id", "year", "product", "count", "amount", "group"
], [
[1, 2012, "A", 1, 15.0, [1]],
@ -194,10 +194,10 @@ class TestDerived(test_engine.EngineTestCase):
# Create a summary on the Customers table. Adding orders involves a lookup for each customer.
self.apply_user_action(["CreateViewSection", 1, 0, 'record', [3], None])
self.add_column("GristSummary_9_Customers", "totalAmount",
self.add_column("Customers_summary_state", "totalAmount",
formula="sum(sum(Orders.lookupRecords(customer=c).amount) for c in $group)")
self.assertPartialData("GristSummary_9_Customers", ["id", "state", "count", "totalAmount"], [
self.assertPartialData("Customers_summary_state", ["id", "state", "count", "totalAmount"], [
[1, "NY", 2, 103.0 ],
[2, "CT", 2, 134.0 ],
[3, "NJ", 1, 0.0 ],
@ -219,14 +219,14 @@ class TestDerived(test_engine.EngineTestCase):
self.assertPartialOutActions(out_actions, {
"stored": [
actions.UpdateRecord("Orders", 9, {"amount": 37}),
actions.UpdateRecord("GristSummary_9_Customers", 2, {"totalAmount": 135.0}),
actions.UpdateRecord("Customers_summary_state", 2, {"totalAmount": 135.0}),
]
})
# In either case, changing a customer's state should trigger recomputation too.
# We are changing a NY customer with $51 in orders to MA.
self.update_record('Customers', 2, state="MA")
self.assertPartialData("GristSummary_9_Customers", ["id", "state", "count", "totalAmount"], [
self.assertPartialData("Customers_summary_state", ["id", "state", "count", "totalAmount"], [
[1, "NY", 1, 52.0 ],
[2, "CT", 2, 135.0 ],
[3, "NJ", 1, 0.0 ],
@ -246,7 +246,7 @@ class TestDerived(test_engine.EngineTestCase):
# actions.AddRecord("Summary4", 4, {"state": "NJ"}),
# actions.UpdateRecord("Summary4", 4, {"manualSort": 4.0})]
# })
self.assertPartialData("GristSummary_9_Customers", ["id", "state", "count", "totalAmount"], [
self.assertPartialData("Customers_summary_state", ["id", "state", "count", "totalAmount"], [
[1, "NY", 1, 35.0 ],
[2, "CT", 2, 135.0 ],
[3, "NJ", 1, 17.0 ],
@ -264,7 +264,7 @@ class TestDerived(test_engine.EngineTestCase):
# Create a summary table summarizing count and total of orders by year.
self.apply_user_action(["CreateViewSection", 2, 0, 'record', [10], None])
self.assertPartialData("GristSummary_6_Orders", ["id", "year", "count", "amount", "group" ], [
self.assertPartialData("Orders_summary_year", ["id", "year", "count", "amount", "group" ], [
[1, 2012, 1, 15.0, [1]],
[2, 2013, 2, 30.0, [2,3]],
[3, 2014, 3, 86.0, [4,5,6]],
@ -273,7 +273,7 @@ class TestDerived(test_engine.EngineTestCase):
# Update a record so that a new line appears in the summary table.
out_actions_update = self.update_record("Orders", 1, year=2007)
self.assertPartialData("GristSummary_6_Orders", ["id", "year", "count", "amount", "group" ], [
self.assertPartialData("Orders_summary_year", ["id", "year", "count", "amount", "group" ], [
[2, 2013, 2, 30.0, [2,3]],
[3, 2014, 3, 86.0, [4,5,6]],
[4, 2015, 4, 106.0, [7,8,9,10]],
@ -282,7 +282,7 @@ class TestDerived(test_engine.EngineTestCase):
# Undo and ensure that the new line is gone from the summary table.
out_actions_undo = self.apply_undo_actions(out_actions_update.undo)
self.assertPartialData("GristSummary_6_Orders", ["id", "year", "count", "amount", "group" ], [
self.assertPartialData("Orders_summary_year", ["id", "year", "count", "amount", "group" ], [
[1, 2012, 1, 15.0, [1]],
[2, 2013, 2, 30.0, [2,3]],
[3, 2014, 3, 86.0, [4,5,6]],
@ -290,18 +290,18 @@ class TestDerived(test_engine.EngineTestCase):
])
self.assertPartialOutActions(out_actions_undo, {
"stored": [
actions.AddRecord("GristSummary_6_Orders", 1, {
actions.AddRecord("Orders_summary_year", 1, {
"amount": 15.0, "count": 1, "group": [1], "year": 2012
}),
actions.RemoveRecord("GristSummary_6_Orders", 5),
actions.RemoveRecord("Orders_summary_year", 5),
actions.UpdateRecord("Orders", 1, {"year": 2012}),
],
"calls": {
"GristSummary_6_Orders": {
"Orders_summary_year": {
"#lookup#": 1, "#lookup#year": 1, "group": 1, "amount": 1, "count": 1
},
"Orders": {
"#lookup##summary#GristSummary_6_Orders": 1, "#summary#GristSummary_6_Orders": 1,
"#lookup##summary#Orders_summary_year": 1, "#summary#Orders_summary_year": 1,
},
},
})

@ -66,25 +66,10 @@ class TestSummary(test_engine.EngineTestCase):
#----------------------------------------------------------------------
def test_encode_summary_table_name(self):
self.assertEqual(summary.encode_summary_table_name("Foo"), "GristSummary_3_Foo")
self.assertEqual(summary.encode_summary_table_name("Foo2"), "GristSummary_4_Foo2")
self.assertEqual(summary.decode_summary_table_name("GristSummary_3_Foo"), "Foo")
self.assertEqual(summary.decode_summary_table_name("GristSummary_4_Foo2"), "Foo2")
self.assertEqual(summary.decode_summary_table_name("GristSummary_3_Foo2"), "Foo")
self.assertEqual(summary.decode_summary_table_name("GristSummary_4_Foo2_2"), "Foo2")
# Test that underscore in the name is OK.
self.assertEqual(summary.decode_summary_table_name("GristSummary_5_Foo_234"), "Foo_2")
self.assertEqual(summary.decode_summary_table_name("GristSummary_4_Foo_234"), "Foo_")
self.assertEqual(summary.decode_summary_table_name("GristSummary_6__Foo_234"), "_Foo_2")
# Test that we return None for invalid values.
self.assertEqual(summary.decode_summary_table_name("Foo2"), None)
self.assertEqual(summary.decode_summary_table_name("GristSummary_3Foo"), None)
self.assertEqual(summary.decode_summary_table_name("GristSummary_4_Foo"), None)
self.assertEqual(summary.decode_summary_table_name("GristSummary_3X_Foo"), None)
self.assertEqual(summary.decode_summary_table_name("_5_Foo_234"), None)
self.assertEqual(summary.decode_summary_table_name("_GristSummary_3_Foo"), None)
self.assertEqual(summary.decode_summary_table_name("gristsummary_3_Foo"), None)
self.assertEqual(summary.decode_summary_table_name("GristSummary3_Foo"), None)
self.assertEqual(summary.encode_summary_table_name("Foo", []), "Foo_summary")
self.assertEqual(summary.encode_summary_table_name("Foo", ["A"]), "Foo_summary_A")
self.assertEqual(summary.encode_summary_table_name("Foo", ["A", "B"]), "Foo_summary_A_B")
self.assertEqual(summary.encode_summary_table_name("Foo", ["B", "A"]), "Foo_summary_A_B")
#----------------------------------------------------------------------
@ -116,7 +101,7 @@ class TestSummary(test_engine.EngineTestCase):
# Verify that a new table gets created, and a new view, with a section for that table,
# and some auto-generated summary fields.
summary_table1 = Table(2, "GristSummary_7_Address", primaryViewId=0, summarySourceTable=1,
summary_table1 = Table(2, "Address_summary", primaryViewId=0, summarySourceTable=1,
columns=[
Column(14, "group", "RefList:Address", isFormula=True, summarySourceCol=0,
formula="table.getSummarySourceGroup(rec)"),
@ -135,7 +120,7 @@ class TestSummary(test_engine.EngineTestCase):
self.assertViews([basic_view, summary_view1])
# Verify the summarized data.
self.assertTableData('GristSummary_7_Address', cols="subset", data=[
self.assertTableData('Address_summary', cols="subset", data=[
[ "id", "count", "amount"],
[ 1, 11, 66.0 ],
])
@ -145,7 +130,7 @@ class TestSummary(test_engine.EngineTestCase):
# Verify that a new table gets created again, a new view, and a section for that table.
# Note that we also check that summarySourceTable and summarySourceCol fields are correct.
summary_table2 = Table(3, "GristSummary_7_Address2", primaryViewId=0, summarySourceTable=1,
summary_table2 = Table(3, "Address_summary_state", primaryViewId=0, summarySourceTable=1,
columns=[
Column(17, "state", "Text", isFormula=False, formula="", summarySourceCol=12),
Column(18, "group", "RefList:Address", isFormula=True, summarySourceCol=0,
@ -173,7 +158,7 @@ class TestSummary(test_engine.EngineTestCase):
])
# Verify the summarized data.
self.assertTableData('GristSummary_7_Address2', cols="subset", data=[
self.assertTableData('Address_summary_state', cols="subset", data=[
[ "id", "state", "count", "amount" ],
[ 1, "NY", 7, 1.+2+6+7+8+10+11 ],
[ 2, "WA", 1, 3. ],
@ -185,7 +170,7 @@ class TestSummary(test_engine.EngineTestCase):
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None])
# Verify the new table and views.
summary_table3 = Table(4, "GristSummary_7_Address3", primaryViewId=0, summarySourceTable=1,
summary_table3 = Table(4, "Address_summary_city_state", primaryViewId=0, summarySourceTable=1,
columns=[
Column(21, "city", "Text", isFormula=False, formula="", summarySourceCol=11),
Column(22, "state", "Text", isFormula=False, formula="", summarySourceCol=12),
@ -208,7 +193,7 @@ class TestSummary(test_engine.EngineTestCase):
self.assertViews([basic_view, summary_view1, summary_view2, summary_view3])
# Verify the summarized data.
self.assertTableData('GristSummary_7_Address3', cols="subset", data=[
self.assertTableData('Address_summary_city_state', cols="subset", data=[
[ "id", "city", "state", "count", "amount" ],
[ 1, "New York", "NY" , 3, 1.+6+11 ],
[ 2, "Albany", "NY" , 1, 2. ],
@ -269,7 +254,7 @@ class Address:
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None])
# Verify the new table and views.
summary_table = Table(2, "GristSummary_7_Address", primaryViewId=0, summarySourceTable=1,
summary_table = Table(2, "Address_summary_city_state", primaryViewId=0, summarySourceTable=1,
columns=[
Column(14, "city", "Text", isFormula=False, formula="", summarySourceCol=11),
Column(15, "state", "Text", isFormula=False, formula="", summarySourceCol=12),
@ -316,7 +301,7 @@ class Address:
self.assertViews([summary_view, summary_view2, summary_view3])
# Verify the summarized data.
self.assertTableData('GristSummary_7_Address', cols="subset", data=[
self.assertTableData('Address_summary_city_state', cols="subset", data=[
[ "id", "city", "state", "count", "amount" ],
[ 1, "New York", "NY" , 3, 1.+6+11 ],
[ 2, "Albany", "NY" , 1, 2. ],
@ -342,12 +327,12 @@ class Address:
self.assertTables([
self.starting_table,
Table(2, "GristSummary_7_Address", 0, 1, columns=[
Table(2, "Address_summary", 0, 1, columns=[
Column(14, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(15, "count", "Int", True, "len($group)", 0),
Column(16, "amount", "Numeric", True, "SUM($group.amount)", 0),
]),
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
Table(3, "Address_summary_state", 0, 1, columns=[
Column(17, "state", "Text", False, "", 12),
Column(18, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(19, "count", "Int", True, "len($group)", 0),
@ -370,8 +355,8 @@ class Address:
self.assertTableData("_grist_Tables", cols="subset", data=[
['id', 'tableId', 'summarySourceTable'],
[ 1, 'Address', 0],
[ 2, 'GristSummary_7_Address', 1],
[ 3, 'GristSummary_7_Address2', 1],
[ 2, 'Address_summary', 1],
[ 3, 'Address_summary_state', 1],
[ 4, 'Address2', 0],
])
@ -382,12 +367,12 @@ class Address:
# Make sure this creates new section rather than reuses similar ones for the wrong table.
self.assertTables([
self.starting_table,
Table(2, "GristSummary_7_Address", 0, 1, columns=[
Table(2, "Address_summary", 0, 1, columns=[
Column(14, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(15, "count", "Int", True, "len($group)", 0),
Column(16, "amount", "Numeric", True, "SUM($group.amount)", 0),
]),
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
Table(3, "Address_summary_state", 0, 1, columns=[
Column(17, "state", "Text", False, "", 12),
Column(18, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(19, "count", "Int", True, "len($group)", 0),
@ -399,12 +384,12 @@ class Address:
Column(23, "state", "Text", False, "", 0),
Column(24, "amount", "Numeric", False, "", 0),
]),
Table(5, "GristSummary_8_Address2", 0, 4, columns=[
Table(5, "Address2_summary", 0, 4, columns=[
Column(25, "group", "RefList:Address2", True, "table.getSummarySourceGroup(rec)", 0),
Column(26, "count", "Int", True, "len($group)", 0),
Column(27, "amount", "Numeric", True, "SUM($group.amount)", 0),
]),
Table(6, "GristSummary_8_Address2_2", 0, 4, columns=[
Table(6, "Address2_summary_state", 0, 4, columns=[
Column(28, "state", "Text", False, "", 23),
Column(29, "group", "RefList:Address2", True, "table.getSummarySourceGroup(rec)", 0),
Column(30, "count", "Int", True, "len($group)", 0),
@ -424,7 +409,7 @@ class Address:
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None])
# Verify that the summary table respects all updates to the source table.
self._do_test_updates("Address", "GristSummary_7_Address")
self._do_test_updates("Address", "Address_summary_city_state")
def _do_test_updates(self, source_tbl_name, summary_tbl_name):
# This is the main part of test_summary_updates(). It's moved to its own method so that
@ -541,7 +526,7 @@ class Address:
# Check what tables we have now.
self.assertPartialData("_grist_Tables", ["id", "tableId", "summarySourceTable"], [
[1, "Address", 0],
[2, "GristSummary_7_Address", 1],
[2, "Address_summary_city_state", 1],
])
# Rename the table: this is what we are really testing in this test case.
@ -549,11 +534,11 @@ class Address:
self.assertPartialData("_grist_Tables", ["id", "tableId", "summarySourceTable"], [
[1, "Location", 0],
[2, "GristSummary_8_Location", 1],
[2, "Location_summary_city_state", 1],
])
# Verify that the bigger summary table respects all updates to the renamed source table.
self._do_test_updates("Location", "GristSummary_8_Location")
self._do_test_updates("Location", "Location_summary_city_state")
#----------------------------------------------------------------------
@ -565,11 +550,11 @@ class Address:
self.apply_user_action(["CreateViewSection", 1, 0, "record", [], None])
self.assertPartialData("_grist_Tables", ["id", "tableId", "summarySourceTable"], [
[1, "Address", 0],
[2, "GristSummary_7_Address", 1],
[3, "GristSummary_7_Address2", 1],
[2, "Address_summary_city_state", 1],
[3, "Address_summary", 1],
])
# Verify the data in the simple totals-only summary table.
self.assertTableData('GristSummary_7_Address2', cols="subset", data=[
self.assertTableData('Address_summary', cols="subset", data=[
[ "id", "count", "amount"],
[ 1, 11, 66.0 ],
])
@ -578,21 +563,21 @@ class Address:
self.apply_user_action(["RenameTable", "Address", "Addresses"])
self.assertPartialData("_grist_Tables", ["id", "tableId", "summarySourceTable"], [
[1, "Addresses", 0],
[2, "GristSummary_9_Addresses", 1],
[3, "GristSummary_9_Addresses2", 1],
[2, "Addresses_summary_city_state", 1],
[3, "Addresses_summary", 1],
])
self.assertTableData('GristSummary_9_Addresses2', cols="subset", data=[
self.assertTableData('Addresses_summary', cols="subset", data=[
[ "id", "count", "amount"],
[ 1, 11, 66.0 ],
])
# Remove one of the tables so that we can use _do_test_updates to verify updates still work.
self.apply_user_action(["RemoveTable", "GristSummary_9_Addresses2"])
self.apply_user_action(["RemoveTable", "Addresses_summary"])
self.assertPartialData("_grist_Tables", ["id", "tableId", "summarySourceTable"], [
[1, "Addresses", 0],
[2, "GristSummary_9_Addresses", 1],
[2, "Addresses_summary_city_state", 1],
])
self._do_test_updates("Addresses", "GristSummary_9_Addresses")
self._do_test_updates("Addresses", "Addresses_summary_city_state")
#----------------------------------------------------------------------
@ -610,14 +595,14 @@ class Address:
# These are the tables and columns we automatically get.
self.assertTables([
self.starting_table,
Table(2, "GristSummary_7_Address", 0, 1, columns=[
Table(2, "Address_summary_city_state", 0, 1, columns=[
Column(14, "city", "Text", False, "", 11),
Column(15, "state", "Text", False, "", 12),
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(17, "count", "Int", True, "len($group)", 0),
Column(18, "amount", "Numeric", True, "SUM($group.amount)", 0),
]),
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
Table(3, "Address_summary", 0, 1, columns=[
Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(20, "count", "Int", True, "len($group)", 0),
Column(21, "amount", "Numeric", True, "SUM($group.amount)", 0),
@ -626,7 +611,7 @@ class Address:
# Now change a formula using one of the summary tables. It should trigger an equivalent
# change in the other.
self.apply_user_action(["ModifyColumn", "GristSummary_7_Address", "amount",
self.apply_user_action(["ModifyColumn", "Address_summary_city_state", "amount",
{"formula": "10*sum($group.amount)"}])
self.assertTableData('_grist_Tables_column', rows="subset", cols="subset", data=[
['id', 'colId', 'type', 'formula', 'widgetOptions', 'label'],
@ -635,7 +620,7 @@ class Address:
])
# Change a formula and a few other fields in the other table, and verify a change to both.
self.apply_user_action(["ModifyColumn", "GristSummary_7_Address2", "amount",
self.apply_user_action(["ModifyColumn", "Address_summary", "amount",
{"formula": "100*sum($group.amount)",
"type": "Text",
"widgetOptions": "hello",
@ -649,7 +634,7 @@ class Address:
])
# Check the values in the summary tables: they should reflect the new formula.
self.assertTableData('GristSummary_7_Address', cols="subset", data=[
self.assertTableData('Address_summary_city_state', cols="subset", data=[
[ "id", "city", "state", "count", "amount" ],
[ 1, "New York", "NY" , 3, str(100*(1+6+11))],
[ 2, "Albany", "NY" , 1, "200" ],
@ -661,7 +646,7 @@ class Address:
[ 8, "Boston", "MA" , 1, "900" ],
[ 9, "Yonkers", "NY" , 1, "1000" ],
])
self.assertTableData('GristSummary_7_Address2', cols="subset", data=[
self.assertTableData('Address_summary', cols="subset", data=[
[ "id", "count", "amount"],
[ 1, 11, "6600"],
])
@ -670,19 +655,19 @@ class Address:
self.apply_user_action(["CreateViewSection", 1, 0, "record", [12], None])
self.assertTables([
self.starting_table,
Table(2, "GristSummary_7_Address", 0, 1, columns=[
Table(2, "Address_summary_city_state", 0, 1, columns=[
Column(14, "city", "Text", False, "", 11),
Column(15, "state", "Text", False, "", 12),
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(17, "count", "Int", True, "len($group)", 0),
Column(18, "amount", "Text", True, "100*sum($group.amount)", 0),
]),
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
Table(3, "Address_summary", 0, 1, columns=[
Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(20, "count", "Int", True, "len($group)", 0),
Column(21, "amount", "Text", True, "100*sum($group.amount)", 0),
]),
Table(4, "GristSummary_7_Address3", 0, 1, columns=[
Table(4, "Address_summary_state", 0, 1, columns=[
Column(22, "state", "Text", False, "", 12),
Column(23, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(24, "count", "Int", True, "len($group)", 0),
@ -697,7 +682,7 @@ class Address:
])
# Verify the summarized data.
self.assertTableData('GristSummary_7_Address3', cols="subset", data=[
self.assertTableData('Address_summary_state', cols="subset", data=[
[ "id", "state", "count", "amount" ],
[ 1, "NY", 7, str(int(100*(1.+2+6+7+8+10+11))) ],
[ 2, "WA", 1, "300" ],
@ -722,7 +707,7 @@ class Address:
Column(3, "B", "Numeric", False, "", 0),
Column(4, "C", "Any", True, "", 0),
]),
Table(2, "GristSummary_6_Table1", summarySourceTable=1, primaryViewId=0, columns=[
Table(2, "Table1_summary_A", summarySourceTable=1, primaryViewId=0, columns=[
Column(5, "A", "Numeric", False, "", 2),
Column(6, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0),
Column(7, "count", "Int", True, "len($group)", 0),
@ -735,7 +720,7 @@ class Address:
[ 2, 2.0, 20, 2.0, None ],
[ 3, 3.0, 10, 3.0, None ],
])
self.assertTableData('GristSummary_6_Table1', data=[
self.assertTableData('Table1_summary_A', data=[
[ "id", "A", "group", "count", "B" ],
[ 1, 10, [1,3], 2, 4 ],
[ 2, 20, [2], 1, 2 ],
@ -753,7 +738,7 @@ class Address:
Column(3, "B", "Numeric", False, "", 0),
Column(4, "C", "Any", True, "", 0),
]),
Table(2, "GristSummary_6_Table1", summarySourceTable=1, primaryViewId=0, columns=[
Table(2, "Table1_summary_A", summarySourceTable=1, primaryViewId=0, columns=[
Column(5, "A", "Text", False, "", 2),
Column(6, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0),
Column(7, "count", "Int", True, "len($group)", 0),
@ -766,7 +751,7 @@ class Address:
[ 2, 2.0, "20", 2.0, None ],
[ 3, 3.0, "10", 3.0, None ],
])
self.assertTableData('GristSummary_6_Table1', data=[
self.assertTableData('Table1_summary_A', data=[
[ "id", "A", "group", "count", "B" ],
[ 1, "10", [1,3], 2, 4 ],
[ 2, "20", [2], 1, 2 ],
@ -791,7 +776,7 @@ class Address:
Column(3, "B", "Numeric", False, "", 0),
Column(4, "C", "Numeric", False, "", 0),
]),
Table(2, "GristSummary_6_Table1", summarySourceTable=1, primaryViewId=0, columns=[
Table(2, "Table1_summary_A_B", summarySourceTable=1, primaryViewId=0, columns=[
Column(5, "A", "Text", False, "", 2),
Column(6, "B", "Numeric", False, "", 3),
Column(7, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0),
@ -805,7 +790,7 @@ class Address:
[ 2, 2.0, 'b', 1.0, 5 ],
[ 3, 3.0, 'c', 2.0, 6 ],
])
self.assertTableData('GristSummary_6_Table1', data=[
self.assertTableData('Table1_summary_A_B', data=[
[ "id", "A", "B", "group", "count", "C" ],
[ 1, 'a', 1.0, [1], 1, 4 ],
[ 2, 'b', 1.0, [2], 1, 5 ],
@ -822,7 +807,7 @@ class Address:
Column(3, "B", "Numeric", False, "", 0),
Column(4, "C", "Numeric", False, "", 0),
]),
Table(3, "GristSummary_6_Table1_2", summarySourceTable=1, primaryViewId=0, columns=[
Table(3, "Table1_summary_B", summarySourceTable=1, primaryViewId=0, columns=[
Column(10, "B", "Numeric", False, "", 3),
Column(12, "count", "Int", True, "len($group)", 0),
Column(13, "C", "Numeric", True, "SUM($group.C)", 0),
@ -835,7 +820,7 @@ class Address:
[ 2, 2.0, 1.0, 5 ],
[ 3, 3.0, 2.0, 6 ],
])
self.assertTableData('GristSummary_6_Table1_2', data=[
self.assertTableData('Table1_summary_B', data=[
[ "id", "B", "group", "count", "C" ],
[ 1, 1.0, [1,2], 2, 9 ],
[ 2, 2.0, [3], 1, 6 ],

@ -4,9 +4,9 @@ files: test_summary.py and test_summary2.py.
"""
import actions
import logger
import objtypes
import test_engine
import test_summary
import testutil
from test_engine import Table, Column, View, Section, Field
@ -32,15 +32,15 @@ class TestSummary2(test_engine.EngineTestCase):
# Check that we cannot add a non-formula column.
with self.assertRaisesRegex(ValueError, r'non-formula column'):
self.apply_user_action(["AddColumn", "GristSummary_7_Address", "average",
self.apply_user_action(["AddColumn", "Address_summary_city_state", "average",
{"type": "Text", "isFormula": False}])
# Add two formula columns: one for 'state' (an existing column name, and a group-by column in
# some tables), and one for 'average' (a new column name).
self.apply_user_action(["AddVisibleColumn", "GristSummary_7_Address2", "state",
self.apply_user_action(["AddVisibleColumn", "Address_summary", "state",
{"formula": "':'.join(sorted(set($group.state)))"}])
self.apply_user_action(["AddVisibleColumn", "GristSummary_7_Address", "average",
self.apply_user_action(["AddVisibleColumn", "Address_summary_city_state", "average",
{"formula": "$amount / $count"}])
# Add two more summary tables: by 'city', and by 'state', and see what columns they get.
@ -52,7 +52,7 @@ class TestSummary2(test_engine.EngineTestCase):
# Check the table and columns for all the summary tables.
self.assertTables([
self.starting_table,
Table(2, "GristSummary_7_Address", 0, 1, columns=[
Table(2, "Address_summary_city_state", 0, 1, columns=[
Column(14, "city", "Text", False, "", 11),
Column(15, "state", "Text", False, "", 12),
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
@ -60,13 +60,13 @@ class TestSummary2(test_engine.EngineTestCase):
Column(18, "amount", "Numeric", True, "SUM($group.amount)", 0),
Column(23, "average", "Any", True, "$amount / $count", 0),
]),
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
Table(3, "Address_summary", 0, 1, columns=[
Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(20, "count", "Int", True, "len($group)", 0),
Column(21, "amount", "Numeric", True, "SUM($group.amount)", 0),
Column(22, "state", "Any", True, "':'.join(sorted(set($group.state)))", 0),
]),
Table(4, "GristSummary_7_Address3", 0, 1, columns=[
Table(4, "Address_summary_city", 0, 1, columns=[
Column(24, "city", "Text", False, "", 11),
Column(25, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(26, "count", "Int", True, "len($group)", 0),
@ -74,7 +74,7 @@ class TestSummary2(test_engine.EngineTestCase):
Column(28, "amount", "Numeric", True, "SUM($group.amount)", 0),
]),
# Note that since 'state' is used as a group-by column here, we skip the 'state' formula.
Table(5, "GristSummary_7_Address4", 0, 1, columns=[
Table(5, "Address_summary_state", 0, 1, columns=[
Column(29, "state", "Text", False, "", 12),
Column(30, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(31, "count", "Int", True, "len($group)", 0),
@ -106,7 +106,7 @@ class TestSummary2(test_engine.EngineTestCase):
# Check that the data is as we expect.
self.assertTableData('GristSummary_7_Address', cols="all", data=[
self.assertTableData('Address_summary_city_state', cols="all", data=[
[ "id", "city", "state", "group", "count", "amount", "average" ],
[ 1, "New York", "NY" , [21,26,31],3, 1.+6+11 , (1.+6+11)/3 ],
[ 2, "Albany", "NY" , [22], 1, 2. , 2. ],
@ -118,11 +118,11 @@ class TestSummary2(test_engine.EngineTestCase):
[ 8, "Boston", "MA" , [29], 1, 9. , 9. ],
[ 9, "Yonkers", "NY" , [30], 1, 10. , 10. ],
])
self.assertTableData('GristSummary_7_Address2', cols="all", data=[
self.assertTableData('Address_summary', cols="all", data=[
[ "id", "count", "amount", "state" , "group" ],
[ 1, 11, 66.0 , "IL:MA:NY:WA" , [21,22,23,24,25,26,27,28,29,30,31]],
])
self.assertTableData('GristSummary_7_Address3', cols="subset", data=[
self.assertTableData('Address_summary_city', cols="subset", data=[
[ "id", "city", "count", "amount", "state" ],
[ 1, "New York", 3, 1.+6+11 , "NY" ],
[ 2, "Albany", 1, 2. , "NY" ],
@ -133,7 +133,7 @@ class TestSummary2(test_engine.EngineTestCase):
[ 7, "Boston", 1, 9. , "MA" ],
[ 8, "Yonkers", 1, 10. , "NY" ],
])
self.assertTableData('GristSummary_7_Address4', cols="subset", data=[
self.assertTableData('Address_summary_state', cols="subset", data=[
[ "id", "state", "count", "amount" ],
[ 1, "NY", 7, 1.+2+6+7+8+10+11 ],
[ 2, "WA", 1, 3. ],
@ -146,16 +146,16 @@ class TestSummary2(test_engine.EngineTestCase):
self.assertPartialOutActions(out_actions, {
"stored": [
actions.UpdateRecord("Address", 28, {'state': 'MA'}),
actions.RemoveRecord("GristSummary_7_Address", 7),
actions.UpdateRecord("GristSummary_7_Address", 5, {'amount': 5.0 + 8.0}),
actions.UpdateRecord("GristSummary_7_Address", 5, {'average': 6.5}),
actions.UpdateRecord("GristSummary_7_Address", 5, {'count': 2}),
actions.UpdateRecord("GristSummary_7_Address", 5, {'group': [25, 28]}),
actions.UpdateRecord("GristSummary_7_Address3", 5, {'state': "MA"}),
actions.BulkUpdateRecord("GristSummary_7_Address4", [1,4],
actions.RemoveRecord("Address_summary_city_state", 7),
actions.UpdateRecord("Address_summary_city", 5, {'state': "MA"}),
actions.UpdateRecord("Address_summary_city_state", 5, {'amount': 5.0 + 8.0}),
actions.UpdateRecord("Address_summary_city_state", 5, {'average': 6.5}),
actions.UpdateRecord("Address_summary_city_state", 5, {'count': 2}),
actions.UpdateRecord("Address_summary_city_state", 5, {'group': [25, 28]}),
actions.BulkUpdateRecord("Address_summary_state", [1,4],
{'amount': [1.+2+6+7+10+11, 5.+8+9]}),
actions.BulkUpdateRecord("GristSummary_7_Address4", [1,4], {'count': [6, 3]}),
actions.BulkUpdateRecord("GristSummary_7_Address4", [1,4],
actions.BulkUpdateRecord("Address_summary_state", [1,4], {'count': [6, 3]}),
actions.BulkUpdateRecord("Address_summary_state", [1,4],
{'group': [[21,22,26,27,30,31], [25,28,29]]}),
]
})
@ -175,11 +175,11 @@ class TestSummary2(test_engine.EngineTestCase):
# Check that we cannot rename a summary group-by column. (Perhaps it's better to raise an
# exception, but currently we translate the invalid request to a no-op.)
with self.assertRaisesRegex(ValueError, r'Cannot modify .* group-by'):
self.apply_user_action(["RenameColumn", "GristSummary_7_Address", "state", "s"])
self.apply_user_action(["RenameColumn", "Address_summary_city_state", "state", "s"])
# Verify all data. We'll repeat this after renamings to make sure there are no errors.
self.assertTableData("Address", self.starting_table_data)
self.assertTableData('GristSummary_7_Address', cols="all", data=[
self.assertTableData('Address_summary_city_state', cols="all", data=[
[ "id", "city", "state", "group", "count", "amount" ],
[ 1, "New York", "NY" , [21,26,31],3, 1.+6+11 ],
[ 2, "Albany", "NY" , [22], 1, 2. ],
@ -191,25 +191,25 @@ class TestSummary2(test_engine.EngineTestCase):
[ 8, "Boston", "MA" , [29], 1, 9. ],
[ 9, "Yonkers", "NY" , [30], 1, 10. ],
])
self.assertTableData('GristSummary_7_Address2', cols="all", data=[
self.assertTableData('Address_summary', cols="all", data=[
[ "id", "count", "amount", "group" ],
[ 1, 11, 66.0 , [21,22,23,24,25,26,27,28,29,30,31]],
])
# This should work fine, and should affect sister tables.
self.apply_user_action(["RenameColumn", "GristSummary_7_Address", "count", "xcount"])
self.apply_user_action(["RenameColumn", "Address_summary_city_state", "count", "xcount"])
# These are the tables and columns we automatically get.
self.assertTables([
self.starting_table,
Table(2, "GristSummary_7_Address", 0, 1, columns=[
Table(2, "Address_summary_city_state", 0, 1, columns=[
Column(14, "city", "Text", False, "", 11),
Column(15, "state", "Text", False, "", 12),
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(17, "xcount", "Int", True, "len($group)", 0),
Column(18, "amount", "Numeric", True, "SUM($group.amount)", 0),
]),
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
Table(3, "Address_summary", 0, 1, columns=[
Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(20, "xcount", "Int", True, "len($group)", 0),
Column(21, "amount", "Numeric", True, "SUM($group.amount)", 0),
@ -226,14 +226,14 @@ class TestSummary2(test_engine.EngineTestCase):
Column(12, "xstate", "Text", False, "", 0),
Column(13, "xamount", "Numeric", False, "", 0),
]),
Table(2, "GristSummary_7_Address", 0, 1, columns=[
Table(2, "Address_summary_city_xstate", 0, 1, columns=[
Column(14, "city", "Text", False, "", 11),
Column(15, "xstate", "Text", False, "", 12),
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(17, "xcount", "Int", True, "len($group)", 0),
Column(18, "xamount", "Numeric", True, "SUM($group.xamount)", 0),
]),
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
Table(3, "Address_summary", 0, 1, columns=[
Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(20, "xcount", "Int", True, "len($group)", 0),
Column(21, "xamount", "Numeric", True, "SUM($group.xamount)", 0),
@ -247,7 +247,7 @@ class TestSummary2(test_engine.EngineTestCase):
address_table_data = replace_col_names(
self.starting_table_data, state='xstate', amount='xamount')
self.assertTableData("Address", address_table_data)
self.assertTableData('GristSummary_7_Address', cols="all", data=[
self.assertTableData('Address_summary_city_xstate', cols="all", data=[
[ "id", "city", "xstate", "group", "xcount", "xamount" ],
[ 1, "New York", "NY" , [21,26,31],3, 1.+6+11 ],
[ 2, "Albany", "NY" , [22], 1, 2. ],
@ -259,14 +259,14 @@ class TestSummary2(test_engine.EngineTestCase):
[ 8, "Boston", "MA" , [29], 1, 9. ],
[ 9, "Yonkers", "NY" , [30], 1, 10. ],
])
self.assertTableData('GristSummary_7_Address2', cols="all", data=[
self.assertTableData('Address_summary', cols="all", data=[
[ "id", "xcount", "xamount", "group" ],
[ 1, 11, 66.0 , [21,22,23,24,25,26,27,28,29,30,31]],
])
# Add a conflicting name to a summary table and see how renames behave.
self.apply_user_action(["AddColumn", "GristSummary_7_Address", "foo",
self.apply_user_action(["AddColumn", "Address_summary_city_xstate", "foo",
{"formula": "$xamount * 100"}])
self.apply_user_action(["RenameColumn", "Address", "xstate", "foo"])
self.apply_user_action(["RenameColumn", "Address", "xamount", "foo"])
@ -278,7 +278,7 @@ class TestSummary2(test_engine.EngineTestCase):
Column(12, "foo2", "Text", False, "", 0),
Column(13, "foo3", "Numeric", False, "", 0),
]),
Table(2, "GristSummary_7_Address", 0, 1, columns=[
Table(2, "Address_summary_city_foo2", 0, 1, columns=[
Column(14, "city", "Text", False, "", 11),
Column(15, "foo2", "Text", False, "", 12),
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
@ -286,7 +286,7 @@ class TestSummary2(test_engine.EngineTestCase):
Column(18, "foo3", "Numeric", True, "SUM($group.foo3)", 0),
Column(22, "foo", "Any", True, "$foo3 * 100", 0),
]),
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
Table(3, "Address_summary", 0, 1, columns=[
Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(20, "xcount", "Int", True, "len($group)", 0),
Column(21, "foo3", "Numeric", True, "SUM($group.foo3)", 0),
@ -297,7 +297,7 @@ class TestSummary2(test_engine.EngineTestCase):
address_table_data = replace_col_names(
address_table_data, xstate='foo2', xamount='foo3')
self.assertTableData("Address", address_table_data)
self.assertTableData('GristSummary_7_Address', cols="all", data=[
self.assertTableData('Address_summary_city_foo2', cols="all", data=[
[ "id", "city", "foo2" , "group", "xcount", "foo3", "foo" ],
[ 1, "New York", "NY" , [21,26,31],3, 1.+6+11, 100*(1.+6+11) ],
[ 2, "Albany", "NY" , [22], 1, 2. , 100*(2.) ],
@ -309,7 +309,7 @@ class TestSummary2(test_engine.EngineTestCase):
[ 8, "Boston", "MA" , [29], 1, 9. , 100*(9.) ],
[ 9, "Yonkers", "NY" , [30], 1, 10. , 100*(10.) ],
])
self.assertTableData('GristSummary_7_Address2', cols="all", data=[
self.assertTableData('Address_summary', cols="all", data=[
[ "id", "xcount", "foo3" , "group" ],
[ 1, 11, 66.0 , [21,22,23,24,25,26,27,28,29,30,31]],
])
@ -328,6 +328,145 @@ class TestSummary2(test_engine.EngineTestCase):
[21, 'foo3', True, 'WidgetOptions2'],
], rows=lambda r: r.colId in ('foo2', 'foo3'))
def test_summary_col_rename_conflict(self):
sample = testutil.parse_test_sample({
"SCHEMA": [
[1, "Table1", [
[11, "A", "Text", False, "", "A", ""],
[12, "B", "Text", False, "", "B", ""],
]],
[2, "Table1_summary_A_B", [
[13, "A", "Text", False, "", "A", ""],
]],
],
"DATA": {}
})
self.load_sample(sample)
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11, 12], None])
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11], None])
table1 = Table(
1, "Table1", primaryViewId=0, summarySourceTable=0, columns=[
Column(11, "A", "Text", False, "", 0),
Column(12, "B", "Text", False, "", 0),
],
)
# Normal table whose name conflicts with the automatically-generated summary table name below
fake_summary = Table(
2, "Table1_summary_A_B", primaryViewId=0, summarySourceTable=0, columns=[
Column(13, "A", "Text", False, "", 0),
],
)
# Auto-generated name has to have a '2' to disambiguate from the normal table.
summary_by_a_and_b = Table(
3, "Table1_summary_A_B2", primaryViewId=0, summarySourceTable=1, columns=[
Column(14, "A", "Text", False, "", 11),
Column(15, "B", "Text", False, "", 12),
Column(16, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0),
Column(17, "count", "Int", True, "len($group)", 0),
],
)
# nothing special here yet
summary_by_a = Table(
4, "Table1_summary_A", primaryViewId=0, summarySourceTable=1, columns=[
Column(18, "A", "Text", False, "", 11),
Column(19, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0),
Column(20, "count", "Int", True, "len($group)", 0),
],
)
tables = [table1, fake_summary, summary_by_a_and_b, summary_by_a]
self.assertTables(tables)
# Add some formulas using summary table names that are about to change
self.add_column("Table1", "summary_ref1",
type="RefList:Table1_summary_A_B2",
formula="Table1_summary_A_B2.lookupRecords(A=1)",
isFormula=True)
self.add_column("Table1", "summary_ref2",
type="Ref:Table1_summary_A",
formula="Table1_summary_A.lookupOne(A=23)",
isFormula=True)
# I got the weirdest heisenbug ever when renaming straight from A to A_B.
# The order of renaming is not deterministic so it may end up with
# 'Table1_summary_A_B3', but asserting that name made it come out as
# 'Table1_summary_A_B2' instead. Seems that file contents play a role in
# order in sets/dictionaries?
self.apply_user_action(["RenameColumn", "Table1", "A", "A2"])
self.apply_user_action(["RenameColumn", "Table1", "A2", "A_B"])
# Summary tables are automatically renamed to match the new column names.
summary_by_a_and_b = summary_by_a_and_b._replace(tableId="Table1_summary_A_B_B")
summary_by_a = summary_by_a._replace(tableId="Table1_summary_A_B2")
table1.columns[0] = table1.columns[0]._replace(colId="A_B")
summary_by_a_and_b.columns[0] = summary_by_a_and_b.columns[0]._replace(colId="A_B")
summary_by_a.columns[0] = summary_by_a.columns[0]._replace(colId="A_B")
table1.columns.extend([
Column(21, "summary_ref1", "RefList:Table1_summary_A_B_B", True,
"Table1_summary_A_B_B.lookupRecords(A_B=1)", 0),
Column(22, "summary_ref2", "Ref:Table1_summary_A_B2", True,
"Table1_summary_A_B2.lookupOne(A_B=23)", 0),
])
tables = [table1, fake_summary, summary_by_a_and_b, summary_by_a]
self.assertTables(tables)
def test_source_table_rename_conflict(self):
sample = testutil.parse_test_sample({
"SCHEMA": [
[1, "Table1", [
[11, "A", "Text", False, "", "A", ""],
]],
[2, "Table2_summary", [
[13, "A", "Text", False, "", "A", ""],
]],
],
"DATA": {}
})
self.load_sample(sample)
self.apply_user_action(["CreateViewSection", 1, 0, "record", [], None])
table1 = Table(
1, "Table1", primaryViewId=0, summarySourceTable=0, columns=[
Column(11, "A", "Text", False, "", 0),
],
)
fake_summary = Table(
2, "Table2_summary", primaryViewId=0, summarySourceTable=0, columns=[
Column(13, "A", "Text", False, "", 0),
],
)
summary = Table(
3, "Table1_summary", primaryViewId=0, summarySourceTable=1, columns=[
Column(14, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0),
Column(15, "count", "Int", True, "len($group)", 0),
],
)
tables = [table1, fake_summary, summary]
self.assertTables(tables)
self.apply_user_action(["RenameTable", "Table1", "Table2"])
table1 = table1._replace(tableId="Table2")
# Summary table is automatically renamed to match the new table name.
# Needs a '2' to disambiguate from the fake_summary table.
summary = summary._replace(tableId="Table2_summary2")
summary.columns[0] = summary.columns[0]._replace(type="RefList:Table2")
tables = [table1, fake_summary, summary]
self.assertTables(tables)
#----------------------------------------------------------------------
def test_restrictions(self):
@ -342,7 +481,7 @@ class TestSummary2(test_engine.EngineTestCase):
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None])
self.apply_user_action(["CreateViewSection", 1, 0, "record", [], None])
self.assertTableData('GristSummary_7_Address', cols="all", data=[
self.assertTableData('Address_summary_city_state', cols="all", data=[
[ "id", "city", "state", "group", "count", "amount" ],
[ 1, "New York", "NY" , [21,26,31],3, 1.+6+11 ],
[ 2, "Albany", "NY" , [22], 1, 2. ],
@ -357,48 +496,49 @@ class TestSummary2(test_engine.EngineTestCase):
# (1) no adding/removing/renaming non-formula columns.
with self.assertRaisesRegex(ValueError, r'non-formula column'):
self.apply_user_action(["AddColumn", "GristSummary_7_Address", "foo",
self.apply_user_action(["AddColumn", "Address_summary_city_state", "foo",
{"type": "Numeric", "isFormula": False}])
with self.assertRaisesRegex(ValueError, r'group-by column'):
self.apply_user_action(["RemoveColumn", "GristSummary_7_Address", "state"])
self.apply_user_action(["RemoveColumn", "Address_summary_city_state", "state"])
with self.assertRaisesRegex(ValueError, r'Cannot modify .* group-by'):
self.apply_user_action(["RenameColumn", "GristSummary_7_Address", "state", "st"])
self.apply_user_action(["RenameColumn", "Address_summary_city_state", "state", "st"])
# (2) no converting between formula/non-formula
with self.assertRaisesRegex(ValueError, r'Cannot change .* formula and data'):
self.apply_user_action(["ModifyColumn", "GristSummary_7_Address", "amount",
self.apply_user_action(["ModifyColumn", "Address_summary_city_state", "amount",
{"isFormula": False}])
with self.assertRaisesRegex(ValueError, r'Cannot change .* formula and data'):
self.apply_user_action(["ModifyColumn", "GristSummary_7_Address", "state",
self.apply_user_action(["ModifyColumn", "Address_summary_city_state", "state",
{"isFormula": True}])
# (3) no editing values in non-formula columns
with self.assertRaisesRegex(ValueError, r'Cannot enter data .* group-by'):
self.apply_user_action(["UpdateRecord", "GristSummary_7_Address", 6, {"state": "ny"}])
self.apply_user_action(["UpdateRecord", "Address_summary_city_state", 6, {"state": "ny"}])
# (4) no removing rows (this is questionable b/c empty rows might be OK to remove)
with self.assertRaisesRegex(ValueError, r'Cannot remove record .* summary'):
self.apply_user_action(["RemoveRecord", "GristSummary_7_Address", 6])
self.apply_user_action(["RemoveRecord", "Address_summary_city_state", 6])
# (5) no renaming summary tables.
with self.assertRaisesRegex(ValueError, r'cannot rename .* summary'):
self.apply_user_action(["RenameTable", "GristSummary_7_Address", "GristSummary_hello"])
self.apply_user_action(["RenameTable", "Address_summary_city_state", "Address_summary_X"])
# Check that we can add an empty column, then set a formula for it.
self.apply_user_action(["AddColumn", "GristSummary_7_Address", "foo", {}])
self.apply_user_action(["ModifyColumn", "GristSummary_7_Address", "foo", {"formula": "1+1"}])
self.apply_user_action(["AddColumn", "Address_summary_city_state", "foo", {}])
self.apply_user_action(["ModifyColumn", "Address_summary_city_state", "foo",
{"formula": "1+1"}])
with self.assertRaisesRegex(ValueError, "Can't save .* to formula"):
self.apply_user_action(["UpdateRecord", "GristSummary_7_Address", 1, {"foo": "hello"}])
self.apply_user_action(["UpdateRecord", "Address_summary_city_state", 1, {"foo": "hello"}])
# But we cannot add an empty column, then add a value to it.
self.apply_user_action(["AddColumn", "GristSummary_7_Address", "foo2", {}])
self.apply_user_action(["AddColumn", "Address_summary_city_state", "foo2", {}])
with self.assertRaisesRegex(ValueError, r'Cannot change .* between formula and data'):
self.apply_user_action(["UpdateRecord", "GristSummary_7_Address", 1, {"foo2": "hello"}])
self.apply_user_action(["UpdateRecord", "Address_summary_city_state", 1, {"foo2": "hello"}])
self.assertTableData('GristSummary_7_Address', cols="all", data=[
self.assertTableData('Address_summary_city_state', cols="all", data=[
[ "id", "city", "state", "group", "count", "amount", "foo", "foo2" ],
[ 1, "New York", "NY" , [21,26,31],3, 1.+6+11 , 2 , None ],
[ 2, "Albany", "NY" , [22], 1, 2. , 2 , None ],
@ -426,7 +566,7 @@ class TestSummary2(test_engine.EngineTestCase):
# We should have a single summary table, and a single section referring to it.
self.assertTables([
self.starting_table,
Table(2, "GristSummary_7_Address", 0, 1, columns=[
Table(2, "Address_summary_city_state", 0, 1, columns=[
Column(14, "city", "Text", False, "", 11),
Column(15, "state", "Text", False, "", 12),
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
@ -442,7 +582,7 @@ class TestSummary2(test_engine.EngineTestCase):
Field(8, colRef=18),
])
])])
self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address'])
self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_city_state'])
# Verify more fields of some of the new column objects.
self.assertTableData('_grist_Tables_column', rows="subset", cols="subset", data=[
@ -457,7 +597,7 @@ class TestSummary2(test_engine.EngineTestCase):
self.assertTables([
self.starting_table,
# Note that Table #2 is gone at this point, since it's unused.
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
Table(3, "Address_summary_state", 0, 1, columns=[
Column(19, "state", "Text", False, "", 12),
Column(20, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(21, "count", "Int", True, "len($group)", 0),
@ -471,14 +611,14 @@ class TestSummary2(test_engine.EngineTestCase):
Field(8, colRef=22),
])
])])
self.assertTableData('GristSummary_7_Address2', cols="subset", data=[
self.assertTableData('Address_summary_state', cols="subset", data=[
[ "id", "state", "count", "amount" ],
[ 1, "NY", 7, 1.+2+6+7+8+10+11 ],
[ 2, "WA", 1, 3. ],
[ 3, "IL", 1, 4. ],
[ 4, "MA", 2, 5.+9 ],
])
self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address2'])
self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_state'])
# Verify more fields of some of the new column objects.
self.assertTableData('_grist_Tables_column', rows="subset", cols="subset", data=[
@ -492,7 +632,7 @@ class TestSummary2(test_engine.EngineTestCase):
self.assertTables([
self.starting_table,
# Note that Table #3 is gone at this point, since it's unused.
Table(4, "GristSummary_7_Address", 0, 1, columns=[
Table(4, "Address_summary_city", 0, 1, columns=[
Column(23, "city", "Text", False, "", 11),
Column(24, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(25, "count", "Int", True, "len($group)", 0),
@ -506,7 +646,7 @@ class TestSummary2(test_engine.EngineTestCase):
Field(8, colRef=26),
])
])])
self.assertTableData('GristSummary_7_Address', cols="subset", data=[
self.assertTableData('Address_summary_city', cols="subset", data=[
[ "id", "city", "count", "amount" ],
[ 1, "New York", 3, 1.+6+11 ],
[ 2, "Albany", 1, 2. ],
@ -517,7 +657,7 @@ class TestSummary2(test_engine.EngineTestCase):
[ 7, "Boston", 1, 9. ],
[ 8, "Yonkers", 1, 10. ],
])
self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address'])
self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_city'])
# Verify more fields of some of the new column objects.
self.assertTableData('_grist_Tables_column', rows="subset", cols="subset", data=[
@ -531,7 +671,7 @@ class TestSummary2(test_engine.EngineTestCase):
self.assertTables([
self.starting_table,
# Note that Table #4 is gone at this point, since it's unused.
Table(5, "GristSummary_7_Address2", 0, 1, columns=[
Table(5, "Address_summary", 0, 1, columns=[
Column(27, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(28, "count", "Int", True, "len($group)", 0),
Column(29, "amount", "Numeric", True, "SUM($group.amount)", 0),
@ -543,18 +683,18 @@ class TestSummary2(test_engine.EngineTestCase):
Field(8, colRef=29),
])
])])
self.assertTableData('GristSummary_7_Address2', cols="subset", data=[
self.assertTableData('Address_summary', cols="subset", data=[
[ "id", "count", "amount"],
[ 1, 11, 66.0 ],
])
self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address2'])
self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary'])
# Back to full circle, but with group-by columns differently arranged.
self.apply_user_action(["UpdateSummaryViewSection", 2, [12,11]])
self.assertTables([
self.starting_table,
# Note that Table #5 is gone at this point, since it's unused.
Table(6, "GristSummary_7_Address", 0, 1, columns=[
Table(6, "Address_summary_city_state", 0, 1, columns=[
Column(30, "state", "Text", False, "", 12),
Column(31, "city", "Text", False, "", 11),
Column(32, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
@ -570,7 +710,7 @@ class TestSummary2(test_engine.EngineTestCase):
Field(8, colRef=34),
])
])])
self.assertTableData('GristSummary_7_Address', cols="subset", data=[
self.assertTableData('Address_summary_city_state', cols="subset", data=[
[ "id", "city", "state", "count", "amount" ],
[ 1, "New York", "NY" , 3, 1.+6+11 ],
[ 2, "Albany", "NY" , 1, 2. ],
@ -582,13 +722,13 @@ class TestSummary2(test_engine.EngineTestCase):
[ 8, "Boston", "MA" , 1, 9. ],
[ 9, "Yonkers", "NY" , 1, 10. ],
])
self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address'])
self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_city_state'])
# Now add a different view section with the same group-by columns.
self.apply_user_action(["CreateViewSection", 1, 1, "record", [11,12], None])
self.assertTables([
self.starting_table,
Table(6, "GristSummary_7_Address", 0, 1, columns=[
Table(6, "Address_summary_city_state", 0, 1, columns=[
Column(30, "state", "Text", False, "", 12),
Column(31, "city", "Text", False, "", 11),
Column(32, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
@ -610,20 +750,20 @@ class TestSummary2(test_engine.EngineTestCase):
Field(27, colRef=34),
])
])])
self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address'])
self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_city_state'])
# Change one view section, and ensure there are now two summary tables.
self.apply_user_action(["UpdateSummaryViewSection", 7, []])
self.assertTables([
self.starting_table,
Table(6, "GristSummary_7_Address", 0, 1, columns=[
Table(6, "Address_summary_city_state", 0, 1, columns=[
Column(30, "state", "Text", False, "", 12),
Column(31, "city", "Text", False, "", 11),
Column(32, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(33, "count", "Int", True, "len($group)", 0),
Column(34, "amount", "Numeric", True, "SUM($group.amount)", 0),
]),
Table(7, "GristSummary_7_Address2", 0, 1, columns=[
Table(7, "Address_summary", 0, 1, columns=[
Column(35, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(36, "count", "Int", True, "len($group)", 0),
Column(37, "amount", "Numeric", True, "SUM($group.amount)", 0),
@ -641,15 +781,15 @@ class TestSummary2(test_engine.EngineTestCase):
Field(27, colRef=37),
])
])])
self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address',
'#summary#GristSummary_7_Address2'])
self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_city_state',
'#summary#Address_summary'])
# Delete one view section, and see that the summary table is gone.
self.apply_user_action(["RemoveViewSection", 7])
self.assertTables([
self.starting_table,
# Note that Table #7 is gone at this point, since it's now unused.
Table(6, "GristSummary_7_Address", 0, 1, columns=[
Table(6, "Address_summary_city_state", 0, 1, columns=[
Column(30, "state", "Text", False, "", 12),
Column(31, "city", "Text", False, "", 11),
Column(32, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
@ -665,14 +805,14 @@ class TestSummary2(test_engine.EngineTestCase):
Field(8, colRef=34),
])
])])
self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address'])
self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_city_state'])
# Change the section to add and then remove the "amount" to the group-by column; check that
# column "amount" was correctly restored
self.apply_user_action(["UpdateSummaryViewSection", 2, [11, 12, 13]])
self.assertTables([
self.starting_table,
Table(7, "GristSummary_7_Address2", 0, 1, columns=[
Table(7, "Address_summary_amount_city_state", 0, 1, columns=[
Column(35, "city", "Text", False, "", 11),
Column(36, "state", "Text", False, "", 12),
Column(37, "amount", "Numeric", False, "", 13),
@ -691,7 +831,7 @@ class TestSummary2(test_engine.EngineTestCase):
self.apply_user_action(["UpdateSummaryViewSection", 2, [11,12]])
self.assertTables([
self.starting_table,
Table(8, "GristSummary_7_Address", 0, 1, columns=[
Table(8, "Address_summary_city_state", 0, 1, columns=[
Column(40, "city", "Text", False, "", 11),
Column(41, "state", "Text", False, "", 12),
Column(42, "amount", "Numeric", True, "SUM($group.amount)", 0),
@ -715,7 +855,7 @@ class TestSummary2(test_engine.EngineTestCase):
self.apply_user_action(["UpdateSummaryViewSection", 2, [11]])
self.assertTables([
self.starting_table,
Table(9, "GristSummary_7_Address2", 0, 1, columns=[
Table(9, "Address_summary_city", 0, 1, columns=[
Column(45, "city", "Text", False, "", 11),
Column(46, "amount", "Numeric", True, "SUM($group.amount)", 0),
Column(48, "count", "Int", True, "len($group)", 0),
@ -741,13 +881,13 @@ class TestSummary2(test_engine.EngineTestCase):
self.load_sample(self.sample)
self.apply_user_action(["CreateViewSection", 1, 0, "record", [12], None])
self.apply_user_action(["AddVisibleColumn", "GristSummary_7_Address", "city",
self.apply_user_action(["AddVisibleColumn", "Address_summary_state", "city",
{"formula": "$state.lower()"}])
# We should have a single summary table, and a single section referring to it.
self.assertTables([
self.starting_table,
Table(2, "GristSummary_7_Address", 0, 1, columns=[
Table(2, "Address_summary_state", 0, 1, columns=[
Column(14, "state", "Text", False, "", 12),
Column(15, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(16, "count", "Int", True, "len($group)", 0),
@ -763,7 +903,7 @@ class TestSummary2(test_engine.EngineTestCase):
Field(8, colRef=18),
])
])])
self.assertTableData('GristSummary_7_Address', cols="subset", data=[
self.assertTableData('Address_summary_state', cols="subset", data=[
[ "id", "state", "count", "amount" , "city"],
[ 1, "NY", 7, 1.+2+6+7+8+10+11 , "ny" ],
[ 2, "WA", 1, 3. , "wa" ],
@ -775,7 +915,7 @@ class TestSummary2(test_engine.EngineTestCase):
self.apply_user_action(["UpdateSummaryViewSection", 2, [11,12]])
self.assertTables([
self.starting_table,
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
Table(3, "Address_summary_city_state", 0, 1, columns=[
Column(19, "city", "Text", False, "", 11),
Column(20, "state", "Text", False, "", 12),
Column(21, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
@ -812,19 +952,19 @@ class TestSummary2(test_engine.EngineTestCase):
# We should have a single summary table, and a single section referring to it.
self.assertTables([
self.starting_table,
Table(2, "GristSummary_7_Address", 0, 1, columns=[
Table(2, "Address_summary_city_state", 0, 1, columns=[
Column(14, "city", "Text", False, "", 11),
Column(15, "state", "Text", False, "", 12),
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(17, "count", "Int", True, "len($group)", 0),
Column(18, "amount", "Numeric", True, "SUM($group.amount)", 0),
]),
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
Table(3, "Address_summary", 0, 1, columns=[
Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(20, "count", "Int", True, "len($group)", 0),
Column(21, "amount", "Numeric", True, "SUM($group.amount)", 0),
]),
Table(4, "GristSummary_7_Address3", 0, 1, columns=[
Table(4, "Address_summary_state", 0, 1, columns=[
Column(22, "state", "Text", False, "", 12),
Column(23, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(24, "count", "Int", True, "len($group)", 0),
@ -862,7 +1002,7 @@ class TestSummary2(test_engine.EngineTestCase):
# Verify that unused summary tables are also gone, but the one used remains.
self.assertTables([
self.starting_table,
Table(2, "GristSummary_7_Address", 0, 1, columns=[
Table(2, "Address_summary_city_state", 0, 1, columns=[
Column(14, "city", "Text", False, "", 11),
Column(15, "state", "Text", False, "", 12),
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
@ -893,7 +1033,7 @@ class TestSummary2(test_engine.EngineTestCase):
# We should have a single summary table, and a single (non-raw) section referring to it.
self.assertTables([
self.starting_table,
Table(2, "GristSummary_7_Address", 0, 1, columns=[
Table(2, "Address_summary_city_state", 0, 1, columns=[
Column(14, "city", "Text", False, "", 11),
Column(15, "state", "Text", False, "", 12),
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
@ -912,7 +1052,7 @@ class TestSummary2(test_engine.EngineTestCase):
self.assertTables([
self.starting_table,
# Note that Table #2 is gone at this point, since it's unused.
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
Table(3, "Address_summary_state", 0, 1, columns=[
Column(19, "state", "Text", False, "", 12),
Column(20, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(21, "count", "Int", True, "len($group)", 0),
@ -935,13 +1075,13 @@ class TestSummary2(test_engine.EngineTestCase):
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None])
self.apply_user_action(["CreateViewSection", 1, 0, "record", [], None])
# Add a formula column
self.apply_user_action(["AddVisibleColumn", "GristSummary_7_Address", "average",
self.apply_user_action(["AddVisibleColumn", "Address_summary_city_state", "average",
{"formula": "$amount / $count"}])
# Check the table and columns for all the summary tables.
self.assertTables([
self.starting_table,
Table(2, "GristSummary_7_Address", 0, 1, columns=[
Table(2, "Address_summary_city_state", 0, 1, columns=[
Column(14, "city", "Text", False, "", 11),
Column(15, "state", "Text", False, "", 12),
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
@ -949,7 +1089,7 @@ class TestSummary2(test_engine.EngineTestCase):
Column(18, "amount", "Numeric", True, "SUM($group.amount)", 0),
Column(22, "average", "Any", True, "$amount / $count", 0),
]),
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
Table(3, "Address_summary", 0, 1, columns=[
Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(20, "count", "Int", True, "len($group)", 0),
Column(21, "amount", "Numeric", True, "SUM($group.amount)", 0),
@ -986,7 +1126,7 @@ class TestSummary2(test_engine.EngineTestCase):
# Check the table and columns for all the summary tables.
self.assertTables([
self.starting_table,
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
Table(3, "Address_summary", 0, 1, columns=[
Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
Column(20, "count", "Int", True, "len($group)", 0),
Column(21, "amount", "Numeric", True, "SUM($group.amount)", 0),
@ -1039,7 +1179,7 @@ class TestSummary2(test_engine.EngineTestCase):
[ 8, 8.0, "Boston", "MA" , [29], 1, 9. , 9. ],
[ 9, 9.0, "Yonkers", "NY" , [30], 1, 10. , 10. ],
])
self.assertTableData('GristSummary_7_Address2', cols="all", data=[
self.assertTableData('Address_summary', cols="all", data=[
[ "id", "count", "amount", "group" ],
[ 1, 11, 66.0 , [21,22,23,24,25,26,27,28,29,30,31]],
])
@ -1069,7 +1209,7 @@ class TestSummary2(test_engine.EngineTestCase):
Column(24, "group", "RefList:Address", True,
"Address.lookupRecords(city=$city, state=$state)", 0),
]),
Table(4, "GristSummary_6_Table1", primaryViewId=0, summarySourceTable=3, columns=[
Table(4, "Table1_summary_state", primaryViewId=0, summarySourceTable=3, columns=[
Column(25, "state", "Text", False, "", 21),
Column(26, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0),
Column(27, "count", "Int", True, "SUM($group.count)", 0),
@ -1090,7 +1230,7 @@ class TestSummary2(test_engine.EngineTestCase):
[ 8, 8.0, "Boston", "MA" , [29], 1, 9. ],
[ 9, 9.0, "Yonkers", "NY" , [30], 1, 10. ],
])
self.assertTableData('GristSummary_6_Table1', cols="all", data=[
self.assertTableData('Table1_summary_state', cols="all", data=[
[ "id", "state", "group", "count", "amount" ],
[ 1, "NY", [1,2,6,7,9], 7, 1.+6+11+2+7+8+10 ],
[ 2, "WA", [3], 1, 3. ],

@ -46,7 +46,7 @@ class TestSummaryChoiceList(EngineTestCase):
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11], None])
summary_table1 = Table(
2, "GristSummary_6_Source", primaryViewId=0, summarySourceTable=1,
2, "Source_summary_choices1", primaryViewId=0, summarySourceTable=1,
columns=[
Column(13, "choices1", "Choice", isFormula=False, formula="", summarySourceCol=11),
Column(14, "group", "RefList:Source", isFormula=True, summarySourceCol=0,
@ -60,7 +60,7 @@ class TestSummaryChoiceList(EngineTestCase):
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11, 12], None])
summary_table2 = Table(
3, "GristSummary_6_Source2", primaryViewId=0, summarySourceTable=1,
3, "Source_summary_choices1_choices2", primaryViewId=0, summarySourceTable=1,
columns=[
Column(16, "choices1", "Choice", isFormula=False, formula="", summarySourceCol=11),
Column(17, "choices2", "Choice", isFormula=False, formula="", summarySourceCol=12),
@ -75,7 +75,7 @@ class TestSummaryChoiceList(EngineTestCase):
self.apply_user_action(["CreateViewSection", 1, 0, "record", [10], None])
summary_table3 = Table(
4, "GristSummary_6_Source3", primaryViewId=0, summarySourceTable=1,
4, "Source_summary_other", primaryViewId=0, summarySourceTable=1,
columns=[
Column(20, "other", "Text", isFormula=False, formula="", summarySourceCol=10),
Column(21, "group", "RefList:Source", isFormula=True, summarySourceCol=0,
@ -89,7 +89,7 @@ class TestSummaryChoiceList(EngineTestCase):
self.apply_user_action(["CreateViewSection", 1, 0, "record", [10, 11], None])
summary_table4 = Table(
5, "GristSummary_6_Source4", primaryViewId=0, summarySourceTable=1,
5, "Source_summary_choices1_other", primaryViewId=0, summarySourceTable=1,
columns=[
Column(23, "other", "Text", isFormula=False, formula="", summarySourceCol=10),
Column(24, "choices1", "Choice", isFormula=False, formula="", summarySourceCol=11),
@ -105,13 +105,13 @@ class TestSummaryChoiceList(EngineTestCase):
)
# Verify the summarized data.
self.assertTableData('GristSummary_6_Source', data=[
self.assertTableData('Source_summary_choices1', data=[
["id", "choices1", "group", "count"],
[1, "a", [21], 1],
[2, "b", [21], 1],
])
self.assertTableData('GristSummary_6_Source2', data=[
self.assertTableData('Source_summary_choices1_choices2', data=[
["id", "choices1", "choices2", "group", "count"],
[1, "a", "c", [21], 1],
[2, "a", "d", [21], 1],
@ -119,12 +119,12 @@ class TestSummaryChoiceList(EngineTestCase):
[4, "b", "d", [21], 1],
])
self.assertTableData('GristSummary_6_Source3', data=[
self.assertTableData('Source_summary_other', data=[
["id", "other", "group", "count"],
[1, "foo", [21], 1],
])
self.assertTableData('GristSummary_6_Source4', data=[
self.assertTableData('Source_summary_choices1_other', data=[
["id", "other", "choices1", "group", "count"],
[1, "foo", "a", [21], 1],
[2, "foo", "b", [21], 1],
@ -132,28 +132,30 @@ class TestSummaryChoiceList(EngineTestCase):
# Verify the optimisation works for the table without choicelists
self.assertIs(self.engine.tables["Source"]._summary_simple, None)
self.assertIs(self.engine.tables["GristSummary_6_Source"]._summary_simple, False)
self.assertIs(self.engine.tables["GristSummary_6_Source2"]._summary_simple, False)
self.assertIs(self.engine.tables["Source_summary_choices1"]._summary_simple, False)
self.assertIs(self.engine.tables["Source_summary_choices1_choices2"]._summary_simple, False)
# simple summary and lookup
self.assertIs(self.engine.tables["GristSummary_6_Source3"]._summary_simple, True)
self.assertIs(self.engine.tables["GristSummary_6_Source4"]._summary_simple, False)
self.assertIs(self.engine.tables["Source_summary_other"]._summary_simple, True)
self.assertIs(self.engine.tables["Source_summary_choices1_other"]._summary_simple, False)
self.assertEqual(
{k: type(v) for k, v in self.engine.tables["Source"]._special_cols.items()},
{
'#summary#GristSummary_6_Source': column.ReferenceListColumn,
"#lookup#_Contains(value='#summary#GristSummary_6_Source', match_empty=no_match_empty)":
'#summary#Source_summary_choices1': column.ReferenceListColumn,
"#lookup#_Contains(value='#summary#Source_summary_choices1', match_empty=no_match_empty)":
lookup.ContainsLookupMapColumn,
'#summary#GristSummary_6_Source2': column.ReferenceListColumn,
"#lookup#_Contains(value='#summary#GristSummary_6_Source2', match_empty=no_match_empty)":
'#summary#Source_summary_choices1_choices2': column.ReferenceListColumn,
"#lookup#_Contains(value='#summary#Source_summary_choices1_choices2', "
"match_empty=no_match_empty)":
lookup.ContainsLookupMapColumn,
# simple summary and lookup
'#summary#GristSummary_6_Source3': column.ReferenceColumn,
'#lookup##summary#GristSummary_6_Source3': lookup.SimpleLookupMapColumn,
'#summary#Source_summary_other': column.ReferenceColumn,
'#lookup##summary#Source_summary_other': lookup.SimpleLookupMapColumn,
'#summary#GristSummary_6_Source4': column.ReferenceListColumn,
"#lookup#_Contains(value='#summary#GristSummary_6_Source4', match_empty=no_match_empty)":
'#summary#Source_summary_choices1_other': column.ReferenceListColumn,
"#lookup#_Contains(value='#summary#Source_summary_choices1_other', "
"match_empty=no_match_empty)":
lookup.ContainsLookupMapColumn,
"#lookup#": lookup.SimpleLookupMapColumn,
@ -169,12 +171,12 @@ class TestSummaryChoiceList(EngineTestCase):
])
# Verify that the summary table rows containing 'b' are removed
self.assertTableData('GristSummary_6_Source', data=[
self.assertTableData('Source_summary_choices1', data=[
["id", "choices1", "group", "count"],
[1, "a", [21], 1],
])
self.assertTableData('GristSummary_6_Source2', data=[
self.assertTableData('Source_summary_choices1_choices2', data=[
["id", "choices1", "choices2", "group", "count"],
[1, "a", "c", [21], 1],
[2, "a", "d", [21], 1],
@ -184,13 +186,13 @@ class TestSummaryChoiceList(EngineTestCase):
self.update_record("Source", 21, choices2=["L", "c", "d", "e"])
# First summary table unaffected
self.assertTableData('GristSummary_6_Source', data=[
self.assertTableData('Source_summary_choices1', data=[
["id", "choices1", "group", "count"],
[1, "a", [21], 1],
])
# New row added for 'e'
self.assertTableData('GristSummary_6_Source2', data=[
self.assertTableData('Source_summary_choices1_choices2', data=[
["id", "choices1", "choices2", "group", "count"],
[1, "a", "c", [21], 1],
[2, "a", "d", [21], 1],
@ -205,12 +207,12 @@ class TestSummaryChoiceList(EngineTestCase):
[21, None, ["c", "d", "e"], "foo"],
])
self.assertTableData('GristSummary_6_Source', data=[
self.assertTableData('Source_summary_choices1', data=[
["id", "choices1", "group", "count"],
[2, "", [21], 1],
])
self.assertTableData('GristSummary_6_Source2', data=[
self.assertTableData('Source_summary_choices1_choices2', data=[
["id", "choices1", "choices2", "group", "count"],
[4, "", "c", [21], 1],
[5, "", "d", [21], 1],
@ -221,11 +223,11 @@ class TestSummaryChoiceList(EngineTestCase):
self.remove_record("Source", 21)
# All summary rows are now empty and thus removed
self.assertTableData('GristSummary_6_Source', data=[
self.assertTableData('Source_summary_choices1', data=[
["id", "choices1", "group", "count"],
])
self.assertTableData('GristSummary_6_Source2', data=[
self.assertTableData('Source_summary_choices1_choices2', data=[
["id", "choices1", "choices2", "group", "count"],
])
@ -263,7 +265,7 @@ class TestSummaryChoiceList(EngineTestCase):
])
# Summary tables now have an even distribution of combinations
self.assertTableData('GristSummary_6_Source', data=[
self.assertTableData('Source_summary_choices1', data=[
["id", "choices1", "group", "count"],
[1, "a", [101, 103, 104, 106, 107, 109], 6],
[2, "b", [102, 103, 105, 106, 108, 109], 6],
@ -279,7 +281,7 @@ class TestSummaryChoiceList(EngineTestCase):
[5, "", "", [110], 1],
]
self.assertTableData('GristSummary_6_Source2', data=summary_data)
self.assertTableData('Source_summary_choices1_choices2', data=summary_data)
# Verify that "DetachSummaryViewSection" useraction works correctly.
self.apply_user_action(["DetachSummaryViewSection", 4])
@ -336,7 +338,7 @@ class TestSummaryChoiceList(EngineTestCase):
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11], None])
summary_table = Table(
2, "GristSummary_6_Source", primaryViewId=0, summarySourceTable=1,
2, "Source_summary_choices1", primaryViewId=0, summarySourceTable=1,
columns=[
Column(12, "choices1", "Choice", isFormula=False, formula="", summarySourceCol=11),
Column(13, "group", "RefList:Source", isFormula=True, summarySourceCol=0,
@ -353,7 +355,7 @@ class TestSummaryChoiceList(EngineTestCase):
]
self.assertTables([starting_table, summary_table])
self.assertTableData('GristSummary_6_Source', data=data)
self.assertTableData('Source_summary_choices1', data=data)
# Change the column from Choice to ChoiceList
self.apply_user_action(["UpdateRecord", "_grist_Tables_column", 11, {"type": "ChoiceList"}])
@ -365,7 +367,7 @@ class TestSummaryChoiceList(EngineTestCase):
starting_table.columns[1] = starting_table.columns[1]._replace(type="ChoiceList")
self.assertTables([starting_table, summary_table])
self.assertTableData('GristSummary_6_Source', data=data)
self.assertTableData('Source_summary_choices1', data=data)
def test_rename_choices(self):
self.load_sample(self.sample)
@ -374,7 +376,7 @@ class TestSummaryChoiceList(EngineTestCase):
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11, 12], None])
summary_table = Table(
2, "GristSummary_6_Source", primaryViewId=0, summarySourceTable=1,
2, "Source_summary_choices1_choices2", primaryViewId=0, summarySourceTable=1,
columns=[
Column(13, "choices1", "Choice", isFormula=False, formula="", summarySourceCol=11),
Column(14, "choices2", "Choice", isFormula=False, formula="", summarySourceCol=12),
@ -397,17 +399,17 @@ class TestSummaryChoiceList(EngineTestCase):
self.assertPartialOutActions(out_actions, {'stored': [
['UpdateRecord', 'Source', 21, {'choices1': ['L', u'aa', u'bb']}],
['BulkAddRecord',
'GristSummary_6_Source',
'Source_summary_choices1_choices2',
[5, 6, 7, 8],
{'choices1': [u'aa', u'aa', u'bb', u'bb'],
'choices2': [u'c', u'd', u'c', u'd']}],
['BulkRemoveRecord', 'GristSummary_6_Source', [1, 2, 3, 4]],
['BulkRemoveRecord', 'Source_summary_choices1_choices2', [1, 2, 3, 4]],
['BulkUpdateRecord',
'GristSummary_6_Source',
'Source_summary_choices1_choices2',
[5, 6, 7, 8],
{'count': [1, 1, 1, 1]}],
['BulkUpdateRecord',
'GristSummary_6_Source',
'Source_summary_choices1_choices2',
[5, 6, 7, 8],
{'group': [['L', 21],
['L', 21],
@ -423,7 +425,7 @@ class TestSummaryChoiceList(EngineTestCase):
# Final summary table is very similar to before, but with two empty chunks of 4 rows
# left over from each rename
self.assertTableData('GristSummary_6_Source', data=[
self.assertTableData('Source_summary_choices1_choices2', data=[
["id", "choices1", "choices2", "group", "count"],
[9, "aa", "cc", [21], 1],
[10, "aa", "dd", [21], 1],

@ -31,7 +31,7 @@ class TestSummaryUndo(test_engine.EngineTestCase):
self.load_sample(self.sample)
# Create a summary section, grouped by the "State" column.
self.apply_user_action(["CreateViewSection", 1, 0, "record", [1], None])
self.assertTableData('GristSummary_6_Person', cols="subset", data=[
self.assertTableData('Person_summary_state', cols="subset", data=[
[ "id", "state", "count"],
[ 1, "NY", 2],
[ 2, "IL", 2],
@ -39,7 +39,7 @@ class TestSummaryUndo(test_engine.EngineTestCase):
])
out_actions = self.update_record('Person', 4, state='ME')
self.assertTableData('GristSummary_6_Person', cols="subset", data=[
self.assertTableData('Person_summary_state', cols="subset", data=[
[ "id", "state", "count"],
[ 1, "NY", 1],
[ 2, "IL", 2],
@ -47,7 +47,7 @@ class TestSummaryUndo(test_engine.EngineTestCase):
])
self.apply_undo_actions(out_actions.undo[0:1])
self.assertTableData('GristSummary_6_Person', cols="subset", data=[
self.assertTableData('Person_summary_state', cols="subset", data=[
[ "id", "state", "count"],
[ 1, "NY", 2],
[ 2, "IL", 2],

@ -71,7 +71,7 @@ class TestTableActions(test_engine.EngineTestCase):
Column(7, "address", "Ref:Address", False, "", 0),
Column(8, "city", "Any", True, "$address.city", 0),
]),
Table(3, "GristSummary_7_Address", 0, 1, columns=[
Table(3, "Address_summary_state", 0, 1, columns=[
Column(9, "state", "Text", False, "", summarySourceCol=3),
Column(10, "group", "RefList:Address", True, summarySourceCol=0,
formula="table.getSummarySourceGroup(rec)"),
@ -116,7 +116,7 @@ class TestTableActions(test_engine.EngineTestCase):
# Verify the data we've loaded.
self.assertTableData('Address', cols="subset", data=self.address_table_data)
self.assertTableData('People', cols="subset", data=self.people_table_data)
self.assertTableData("GristSummary_7_Address", cols="subset", data=[
self.assertTableData("Address_summary_state", cols="subset", data=[
[ "id", "state", "count", "amount" ],
[ 1, "NY", 7, 1.+2+6+7+8+10+11 ],
[ 2, "WA", 1, 3. ],
@ -142,7 +142,7 @@ class TestTableActions(test_engine.EngineTestCase):
["id", "tableId"],
[1, "Location"],
[2, "Persons"],
[3, "GristSummary_8_Location"],
[3, "Location_summary_state"],
])
# Check that reference columns to renamed tables get their type modified.
@ -161,14 +161,14 @@ class TestTableActions(test_engine.EngineTestCase):
self.assertPartialOutActions(out_actions, {
"stored": [
["ModifyColumn", "Persons", "address", {"type": "Int"}],
["ModifyColumn", "GristSummary_8_Location", "group", {"type": "Int"}],
["ModifyColumn", "Location_summary_state", "group", {"type": "Int"}],
["RenameTable", "Location", "A2"],
["RenameTable", "GristSummary_8_Location", "GristSummary_2_A2"],
["RenameTable", "Location_summary_state", "A2_summary_state"],
["RenameTable", "Persons", "A3"],
["BulkUpdateRecord", "_grist_Tables", [1, 3, 2],
{"tableId": ["A2", "GristSummary_2_A2", "A3"]}],
{"tableId": ["A2", "A2_summary_state", "A3"]}],
["ModifyColumn", "A3", "address", {"type": "Ref:A2"}],
["ModifyColumn", "GristSummary_2_A2", "group", {"type": "RefList:A2"}],
["ModifyColumn", "A2_summary_state", "group", {"type": "RefList:A2"}],
["BulkUpdateRecord", "_grist_Tables_column", [7, 10], {"type": ["Ref:A2", "RefList:A2"]}],
]
})
@ -178,7 +178,7 @@ class TestTableActions(test_engine.EngineTestCase):
["id", "tableId"],
[1, "A2"],
[2, "A3"],
[3, "GristSummary_2_A2"],
[3, "A2_summary_state"],
[4, "A"],
])
@ -192,7 +192,7 @@ class TestTableActions(test_engine.EngineTestCase):
# Verify the data we've loaded.
self.assertTableData('A2', cols="subset", data=self.address_table_data)
self.assertTableData('A3', cols="subset", data=self.people_table_data)
self.assertTableData("GristSummary_2_A2", cols="subset", data=[
self.assertTableData("A2_summary_state", cols="subset", data=[
[ "id", "state", "count", "amount" ],
[ 1, "NY", 7, 1.+2+6+7+8+10+11 ],
[ 2, "WA", 1, 3. ],
@ -226,15 +226,15 @@ class TestTableActions(test_engine.EngineTestCase):
self.assertPartialOutActions(out_actions, {
"stored": [
["ModifyColumn", "People", "address", {"type": "Int"}],
["ModifyColumn", "GristSummary_7_Address", "group", {"type": "Int"}],
["ModifyColumn", "GristSummary_6_People", "address", {"type": "Int"}],
["ModifyColumn", "Address_summary_state", "group", {"type": "Int"}],
["ModifyColumn", "People_summary_address", "address", {"type": "Int"}],
["RenameTable", "Address", "Location"],
["RenameTable", "GristSummary_7_Address", "GristSummary_8_Location"],
["RenameTable", "Address_summary_state", "Location_summary_state"],
["BulkUpdateRecord", "_grist_Tables", [1, 3],
{"tableId": ["Location", "GristSummary_8_Location"]}],
{"tableId": ["Location", "Location_summary_state"]}],
["ModifyColumn", "People", "address", {"type": "Ref:Location"}],
["ModifyColumn", "GristSummary_8_Location", "group", {"type": "RefList:Location"}],
["ModifyColumn", "GristSummary_6_People", "address", {"type": "Ref:Location"}],
["ModifyColumn", "Location_summary_state", "group", {"type": "RefList:Location"}],
["ModifyColumn", "People_summary_address", "address", {"type": "Ref:Location"}],
["BulkUpdateRecord", "_grist_Tables_column", [7, 10, 13],
{"type": ["Ref:Location", "RefList:Location", "Ref:Location"]}],
]

@ -156,7 +156,7 @@ class TestUserActions(test_engine.EngineTestCase):
# Create another section for the same view, this time summarized.
self.apply_user_action(["CreateViewSection", 1, 1, "record", [21], None])
summary_table = Table(2, "GristSummary_7_Address", 0, summarySourceTable=1, columns=[
summary_table = Table(2, "Address_summary_city", 0, summarySourceTable=1, columns=[
Column(22, "city", "Text", isFormula=False, formula="", summarySourceCol=21),
Column(23, "group", "RefList:Address", isFormula=True,
formula="table.getSummarySourceGroup(rec)", summarySourceCol=0),
@ -248,7 +248,7 @@ class TestUserActions(test_engine.EngineTestCase):
Column(33, "C", "Any", isFormula=True, formula="", summarySourceCol=0),
])
# A summary of it.
summary_table = Table(5, "GristSummary_6_Table3", 0, summarySourceTable=4, columns=[
summary_table = Table(5, "Table3_summary", 0, summarySourceTable=4, columns=[
Column(34, "group", "RefList:Table3", isFormula=True,
formula="table.getSummarySourceGroup(rec)", summarySourceCol=0),
Column(35, "count", "Int", isFormula=True, formula="len($group)", summarySourceCol=0),
@ -287,7 +287,7 @@ class TestUserActions(test_engine.EngineTestCase):
Column(3, "state", "Text", False, "", 0),
Column(4, "size", "Numeric", False, "", 0),
]),
Table(2, "GristSummary_7_Schools", 0, 1, columns=[
Table(2, "Schools_summary_state", 0, 1, columns=[
Column(5, "state", "Text", False, "", 3),
Column(6, "group", "RefList:Schools", True, "table.getSummarySourceGroup(rec)", 0),
Column(7, "count", "Int", True, "len($group)", 0),
@ -398,7 +398,7 @@ class TestUserActions(test_engine.EngineTestCase):
self.assertTableData('_grist_Tables', cols="subset", data=[
[ 'id', 'tableId', 'primaryViewId' ],
[ 1, 'Schools', 1],
[ 2, 'GristSummary_7_Schools', 0],
[ 2, 'Schools_summary_state', 0],
[ 3, 'Table1', 0],
])
self.assertTableData('_grist_Views', cols="subset", data=[
@ -416,7 +416,7 @@ class TestUserActions(test_engine.EngineTestCase):
self.assertTableData('_grist_Tables', cols="subset", data=[
[ 'id', 'tableId', 'primaryViewId' ],
[ 1, 'Schools', 1],
[ 2, 'GristSummary_7_Schools', 0],
[ 2, 'Schools_summary_state', 0],
[ 3, 'Table1', 0],
])
self.assertTableData('_grist_Views', cols="subset", data=[
@ -434,7 +434,7 @@ class TestUserActions(test_engine.EngineTestCase):
self.assertTableData('_grist_Tables', cols="subset", data=[
['id', 'tableId'],
[1, 'Bars', 1],
[2, 'GristSummary_4_Bars', 0],
[2, 'Bars_summary_state', 0],
[3, 'Table1', 0],
])
self.assertTableData('_grist_Views', cols="subset", data=[
@ -452,7 +452,7 @@ class TestUserActions(test_engine.EngineTestCase):
self.assertTableData('_grist_Tables', cols="subset", data=[
['id', 'tableId'],
[1, 'A', 1],
[2, 'GristSummary_1_A', 0],
[2, 'A_summary_state', 0],
[3, 'Table1', 0],
])
self.assertTableData('_grist_Views', cols="subset", data=[
@ -468,7 +468,7 @@ class TestUserActions(test_engine.EngineTestCase):
self.assertTableData('_grist_Tables', cols="subset", data=[
['id', 'tableId', 'primaryViewId', 'rawViewSectionRef'],
[1, 'Z', 1, 2],
[2, 'GristSummary_1_Z', 0, 4],
[2, 'Z_summary_state', 0, 4],
[3, 'Table1', 0, 7],
])
self.assertTableData('_grist_Views', cols="subset", data=[
@ -485,7 +485,7 @@ class TestUserActions(test_engine.EngineTestCase):
self.assertTableData('_grist_Tables', cols="subset", data=[
['id', 'tableId', 'primaryViewId', 'rawViewSectionRef'],
[1, 'Z', 1, 2],
[2, 'GristSummary_1_Z', 0, 4],
[2, 'Z_summary_state', 0, 4],
[3, 'Table1', 0, 7],
[4, 'Stations', 4, 10],
])
@ -513,7 +513,7 @@ class TestUserActions(test_engine.EngineTestCase):
self.assertTableData('_grist_Tables', cols="subset", data=[
['id', 'tableId'],
[1, 'Schools'],
[2, 'GristSummary_7_Schools'],
[2, 'Schools_summary_state'],
[3, 'Table1'],
[4, 'Stations'],
])
@ -618,7 +618,7 @@ class TestUserActions(test_engine.EngineTestCase):
self.assertTableData('_grist_Tables', cols="subset", data=[
[ 'id', 'tableId', 'primaryViewId' ],
[ 1, 'Schools', 1],
[ 2, 'GristSummary_7_Schools', 0],
[ 2, 'Schools_summary_state', 0],
[ 3, 'C', 0],
])

@ -561,7 +561,7 @@ class UserActions(object):
update_pairs.append((rec, values))
if has_diff_value(values, 'tableId', rec.tableId):
# Disallow renaming of summary tables.
if rec.summarySourceTable:
if rec.summarySourceTable and self._indirection_level == DIRECT_ACTION:
raise ValueError("RenameTable: cannot rename a summary table")
# Find a non-conflicting name, except that we don't need to avoid the old name.
@ -572,7 +572,8 @@ class UserActions(object):
if new_table_id != rec.tableId:
# If there are summary tables based on this table, rename them to appropriate names.
for st in rec.summaryTables:
st_table_id = summary.encode_summary_table_name(new_table_id)
groupby_col_ids = [c.colId for c in st.columns if c.summarySourceCol]
st_table_id = summary.encode_summary_table_name(new_table_id, groupby_col_ids)
st_table_id = identifiers.pick_table_ident(st_table_id, avoid=avoid_tableid_set)
avoid_tableid_set.add(st_table_id)
update_pairs.append((st, {'tableId': st_table_id}))
@ -697,6 +698,7 @@ class UserActions(object):
make_acl_updates = acl.prepare_acl_col_renames(self._docmodel, self, renames)
rename_summary_tables = set()
for c, values in update_pairs:
# Trigger ModifyColumn and RenameColumn as necessary
schema_colinfo = select_keys(values, _modify_col_schema_props)
@ -704,6 +706,8 @@ class UserActions(object):
self.doModifyColumn(c.parentId.tableId, c.colId, schema_colinfo)
if has_diff_value(values, 'colId', c.colId):
self._do_doc_action(actions.RenameColumn(c.parentId.tableId, c.colId, values['colId']))
if c.summarySourceCol:
rename_summary_tables.add(c.parentId)
# If we change a column's type, we should ALSO unset each affected field's displayCol.
type_changed = [c for c, values in update_pairs if has_diff_value(values, 'type', c.type)]
@ -718,6 +722,12 @@ class UserActions(object):
make_acl_updates()
for table in rename_summary_tables:
groupby_col_ids = [c.colId for c in table.columns if c.summarySourceCol]
new_table_id = summary.encode_summary_table_name(table.summarySourceTable.tableId,
groupby_col_ids)
with self.indirect_actions():
self.RenameTable(table.tableId, new_table_id)
@override_action('BulkUpdateRecord', '_grist_Views_section')
def _updateViewSections(self, table_id, row_ids, col_values):

Binary file not shown.
Loading…
Cancel
Save