예제 #1
0
  def test_migrations(self):
    tdset = table_data_set.TableDataSet()
    tdset.apply_doc_actions(schema_version0())
    migration_actions = migrations.create_migrations(tdset.all_tables)
    tdset.apply_doc_actions(migration_actions)

    # Compare schema derived from migrations to the current schema.
    migrated_schema = tdset.get_schema()
    current_schema = {a.table_id: {c['id']: c for c in a.columns}
                      for a in schema.schema_create_actions()}
    # pylint: disable=too-many-nested-blocks
    if migrated_schema != current_schema:
      # Figure out the version of new migration to suggest, and whether to update SCHEMA_VERSION.
      new_version = max(schema.SCHEMA_VERSION, migrations.get_last_migration_version() + 1)

      # Figure out the missing actions.
      doc_actions = []
      for table_id in sorted(six.viewkeys(current_schema) | six.viewkeys(migrated_schema)):
        if table_id not in migrated_schema:
          doc_actions.append(actions.AddTable(table_id, current_schema[table_id].values()))
        elif table_id not in current_schema:
          doc_actions.append(actions.RemoveTable(table_id))
        else:
          current_cols = current_schema[table_id]
          migrated_cols = migrated_schema[table_id]
          for col_id in sorted(six.viewkeys(current_cols) | six.viewkeys(migrated_cols)):
            if col_id not in migrated_cols:
              doc_actions.append(actions.AddColumn(table_id, col_id, current_cols[col_id]))
            elif col_id not in current_cols:
              doc_actions.append(actions.RemoveColumn(table_id, col_id))
            else:
              current_info = current_cols[col_id]
              migrated_info = migrated_cols[col_id]
              delta = {k: v for k, v in six.iteritems(current_info) if v != migrated_info.get(k)}
              if delta:
                doc_actions.append(actions.ModifyColumn(table_id, col_id, delta))

      suggested_migration = (
        "----------------------------------------------------------------------\n" +
        "*** migrations.py ***\n" +
        "----------------------------------------------------------------------\n" +
        "@migration(schema_version=%s)\n" % new_version +
        "def migration%s(tdset):\n" % new_version +
        "  return tdset.apply_doc_actions([\n" +
        "".join(stringify(a) + ",\n" for a in doc_actions) +
        "  ])\n"
      )

      if new_version != schema.SCHEMA_VERSION:
        suggested_schema_update = (
          "----------------------------------------------------------------------\n" +
          "*** schema.py ***\n" +
          "----------------------------------------------------------------------\n" +
          "SCHEMA_VERSION = %s\n" % new_version
        )
      else:
        suggested_schema_update = ""

      self.fail("Migrations are incomplete. Suggested migration to add:\n" +
                suggested_schema_update + suggested_migration)
예제 #2
0
 def alist():
     return [
         actions.BulkUpdateRecord("Table1", [1, 2, 3],
                                  {'Foo': [10, 20, 30]}),
         actions.BulkUpdateRecord("Table2", [1, 2, 3], {
             'Foo': [10, 20, 30],
             'Bar': ['a', 'b', 'c']
         }),
         actions.UpdateRecord("Table1", 17, {'Foo': 10}),
         actions.UpdateRecord("Table2", 18, {
             'Foo': 10,
             'Bar': 'a'
         }),
         actions.AddRecord("Table1", 17, {'Foo': 10}),
         actions.BulkAddRecord("Table2", 18, {
             'Foo': 10,
             'Bar': 'a'
         }),
         actions.ReplaceTableData("Table2", 18, {
             'Foo': 10,
             'Bar': 'a'
         }),
         actions.RemoveRecord("Table1", 17),
         actions.BulkRemoveRecord("Table2", [17, 18]),
         actions.AddColumn("Table1", "Foo", {"type": "Text"}),
         actions.RenameColumn("Table1", "Foo", "Bar"),
         actions.ModifyColumn("Table1", "Foo", {"type": "Text"}),
         actions.RemoveColumn("Table1", "Foo"),
         actions.AddTable("THello", [{
             "id": "Foo"
         }, {
             "id": "Bar"
         }]),
         actions.RemoveTable("THello"),
         actions.RenameTable("THello", "TWorld"),
     ]
예제 #3
0
    def RemoveColumn(self, table_id, col_id):
        table = self._engine.tables[table_id]
        assert table.has_column(
            col_id), "Column %s not in table %s" % (col_id, table_id)

        # Generate (if needed) the undo action to restore the data.
        undo_action = None
        column = table.get_column(col_id)
        if not column.is_private():
            default = column.getdefault()
            # Add to undo a BulkUpdateRecord for non-default values in the column being removed.
            undo_values = [(r, column.raw_get(r)) for r in table.row_ids
                           if not strict_equal(column.raw_get(r), default)]

        # Remove the specified column from the schema object.
        colinfo = self._engine.schema[table_id].columns.pop(col_id)
        self._engine.rebuild_usercode()

        # Generate the undo action(s); if for a formula column, add them to the calc summary.
        if undo_values:
            if column.is_formula():
                changes = [(r, v, default) for (r, v) in undo_values]
                self._engine.out_actions.summary.add_changes(
                    table_id, col_id, changes)
            else:
                row_ids = [r for (r, v) in undo_values]
                values = [v for (r, v) in undo_values]
                undo_action = actions.BulkUpdateRecord(table_id, row_ids, {
                    col_id: values
                }).simplify()
                self._engine.out_actions.undo.append(undo_action)

        self._engine.out_actions.undo.append(
            actions.AddColumn(table_id, col_id,
                              schema.col_to_dict(colinfo, include_id=False)))
        self._engine.out_actions.summary.remove_column(table_id, col_id)
예제 #4
0
    def test_add_remove_lookup(self):
        # Verify that when we add or remove a lookup formula, we get appropriate changes.
        self.load_sample(testsamples.sample_students)

        # Add another lookup formula.
        out_actions = self.add_column(
            "Schools",
            "lastNames",
            formula=(
                "','.join(Students.lookupRecords(schoolName=$name).lastName)"))
        self.assertPartialOutActions(
            out_actions, {
                "stored": [
                    actions.AddColumn(
                        "Schools", "lastNames", {
                            "formula":
                            "','.join(Students.lookupRecords(schoolName=$name).lastName)",
                            "isFormula": True,
                            "type": "Any"
                        }),
                    actions.AddRecord(
                        "_grist_Tables_column", 22, {
                            "colId": "lastNames",
                            "formula":
                            "','.join(Students.lookupRecords(schoolName=$name).lastName)",
                            "isFormula": True,
                            "label": "lastNames",
                            "parentId": 2,
                            "parentPos": 6.0,
                            "type": "Any",
                            "widgetOptions": ""
                        }),
                    _bulk_update(
                        "Schools", ["id", "lastNames"],
                        [[1, "Obama,Clinton"], [2, "Obama,Clinton"],
                         [3, "Bush,Bush,Ford"], [4, "Bush,Bush,Ford"]]),
                ],
                "calls": {
                    "Schools": {
                        "lastNames": 4
                    },
                    "Students": {
                        "#lookup#schoolName": 6
                    }
                },
            })

        # Make sure it responds to changes.
        out_actions = self.update_record("Students", 5, schoolName="Columbia")
        self.assertPartialOutActions(
            out_actions, {
                "stored": [
                    actions.UpdateRecord("Students", 5,
                                         {"schoolName": "Columbia"}),
                    _bulk_update("Schools", ["id", "lastNames"],
                                 [[1, "Obama,Clinton,Reagan"],
                                  [2, "Obama,Clinton,Reagan"]]),
                    actions.UpdateRecord(
                        "Students", 5, {"schoolCities": "New York:Colombia"}),
                    actions.UpdateRecord("Students", 5, {"schoolIds": "1:2"}),
                ],
                "calls": {
                    "Students": {
                        'schoolCities': 1,
                        'schoolIds': 1,
                        '#lookup#schoolName': 1
                    },
                    "Schools": {
                        'lastNames': 2
                    }
                },
            })

        # Modify the column: in the process, the LookupMapColumn on Students.schoolName becomes unused
        # while the old formula column is removed, but used again when it's added. It should not have
        # to be rebuilt (so there should be no calls to recalculate the LookupMapColumn.
        out_actions = self.modify_column(
            "Schools",
            "lastNames",
            formula=(
                "','.join(Students.lookupRecords(schoolName=$name).firstName)"
            ))
        self.assertPartialOutActions(
            out_actions, {
                "stored": [
                    actions.ModifyColumn(
                        "Schools", "lastNames", {
                            "formula":
                            "','.join(Students.lookupRecords(schoolName=$name).firstName)"
                        }),
                    actions.UpdateRecord(
                        "_grist_Tables_column", 22, {
                            "formula":
                            "','.join(Students.lookupRecords(schoolName=$name).firstName)"
                        }),
                    _bulk_update(
                        "Schools", ["id", "lastNames"],
                        [[1, "Barack,Bill,Ronald"], [2, "Barack,Bill,Ronald"],
                         [3, "George W,George H,Gerald"],
                         [4, "George W,George H,Gerald"]])
                ],
                "calls": {
                    "Schools": {
                        "lastNames": 4
                    }
                }
            })

        # Remove the new lookup formula.
        out_actions = self.remove_column("Schools", "lastNames")
        self.assertPartialOutActions(out_actions, {})  # No calc actions

        # Make sure that changes still work without errors.
        out_actions = self.update_record("Students", 5, schoolName="Eureka")
        self.assertPartialOutActions(
            out_actions,
            {
                "stored": [
                    actions.UpdateRecord("Students", 5,
                                         {"schoolName": "Eureka"}),
                    actions.UpdateRecord("Students", 5, {"schoolCities": ""}),
                    actions.UpdateRecord("Students", 5, {"schoolIds": ""}),
                ],
                # This should NOT have '#lookup#schoolName' recalculation because there are no longer any
                # formulas which do such a lookup.
                "calls": {
                    "Students": {
                        'schoolCities': 1,
                        'schoolIds': 1
                    }
                }
            })
예제 #5
0
def add_column(table_id, col_id, col_type, *args, **kwargs):
    return actions.AddColumn(
        table_id, col_id, schema.make_column(col_id, col_type, *args,
                                             **kwargs))
예제 #6
0
    def test_prune_actions(self):
        # prune_actions is in-place, so we make a new list every time.
        def alist():
            return [
                actions.BulkUpdateRecord("Table1", [1, 2, 3],
                                         {'Foo': [10, 20, 30]}),
                actions.BulkUpdateRecord("Table2", [1, 2, 3], {
                    'Foo': [10, 20, 30],
                    'Bar': ['a', 'b', 'c']
                }),
                actions.UpdateRecord("Table1", 17, {'Foo': 10}),
                actions.UpdateRecord("Table2", 18, {
                    'Foo': 10,
                    'Bar': 'a'
                }),
                actions.AddRecord("Table1", 17, {'Foo': 10}),
                actions.BulkAddRecord("Table2", 18, {
                    'Foo': 10,
                    'Bar': 'a'
                }),
                actions.ReplaceTableData("Table2", 18, {
                    'Foo': 10,
                    'Bar': 'a'
                }),
                actions.RemoveRecord("Table1", 17),
                actions.BulkRemoveRecord("Table2", [17, 18]),
                actions.AddColumn("Table1", "Foo", {"type": "Text"}),
                actions.RenameColumn("Table1", "Foo", "Bar"),
                actions.ModifyColumn("Table1", "Foo", {"type": "Text"}),
                actions.RemoveColumn("Table1", "Foo"),
                actions.AddTable("THello", [{
                    "id": "Foo"
                }, {
                    "id": "Bar"
                }]),
                actions.RemoveTable("THello"),
                actions.RenameTable("THello", "TWorld"),
            ]

        def prune(table_id, col_id):
            a = alist()
            actions.prune_actions(a, table_id, col_id)
            return a

        self.assertEqual(
            prune('Table1', 'Foo'),
            [
                actions.BulkUpdateRecord("Table2", [1, 2, 3], {
                    'Foo': [10, 20, 30],
                    'Bar': ['a', 'b', 'c']
                }),
                actions.UpdateRecord("Table2", 18, {
                    'Foo': 10,
                    'Bar': 'a'
                }),
                actions.BulkAddRecord("Table2", 18, {
                    'Foo': 10,
                    'Bar': 'a'
                }),
                actions.ReplaceTableData("Table2", 18, {
                    'Foo': 10,
                    'Bar': 'a'
                }),
                actions.RemoveRecord("Table1", 17),
                actions.BulkRemoveRecord("Table2", [17, 18]),
                # It doesn't do anything with column renames; it can be addressed if needed.
                actions.RenameColumn("Table1", "Foo", "Bar"),
                # It doesn't do anything with AddTable, which is expected.
                actions.AddTable("THello", [{
                    "id": "Foo"
                }, {
                    "id": "Bar"
                }]),
                actions.RemoveTable("THello"),
                actions.RenameTable("THello", "TWorld"),
            ])

        self.assertEqual(prune('Table2', 'Foo'), [
            actions.BulkUpdateRecord("Table1", [1, 2, 3],
                                     {'Foo': [10, 20, 30]}),
            actions.BulkUpdateRecord("Table2", [1, 2, 3],
                                     {'Bar': ['a', 'b', 'c']}),
            actions.UpdateRecord("Table1", 17, {'Foo': 10}),
            actions.UpdateRecord("Table2", 18, {'Bar': 'a'}),
            actions.AddRecord("Table1", 17, {'Foo': 10}),
            actions.BulkAddRecord("Table2", 18, {'Bar': 'a'}),
            actions.ReplaceTableData("Table2", 18, {'Bar': 'a'}),
            actions.RemoveRecord("Table1", 17),
            actions.BulkRemoveRecord("Table2", [17, 18]),
            actions.AddColumn("Table1", "Foo", {"type": "Text"}),
            actions.RenameColumn("Table1", "Foo", "Bar"),
            actions.ModifyColumn("Table1", "Foo", {"type": "Text"}),
            actions.RemoveColumn("Table1", "Foo"),
            actions.AddTable("THello", [{
                "id": "Foo"
            }, {
                "id": "Bar"
            }]),
            actions.RemoveTable("THello"),
            actions.RenameTable("THello", "TWorld"),
        ])