Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add lens migration engine to defra #1564

Merged
merged 60 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
87e755b
Correctly explain orderby
AndrewSisley Jun 14, 2023
96ef489
Track schema version history
AndrewSisley Jun 5, 2023
8e72361
Make decode public, and move it to core
AndrewSisley Jun 7, 2023
54feb84
Explicitly define integer typing
AndrewSisley Jun 14, 2023
bd252c9
Use new enumerable stuff
AndrewSisley Jun 6, 2023
46310f0
Add schema migration key
AndrewSisley Jun 2, 2023
702a417
TODO/DISCUSS - Some mock tests disabled
AndrewSisley Jun 26, 2023
afd2bb2
Add lense test module
AndrewSisley Jun 26, 2023
0c4b1fc
Add schema migration system to Defra
AndrewSisley Jun 13, 2023
12b90ae
PR FIXUP - Document test prerequisites
AndrewSisley Jun 29, 2023
26818cf
PR FIXUP - Remove history cache todo
AndrewSisley Jun 29, 2023
2a352cf
PR FIXUP - Explicitly install rust wasm build target
AndrewSisley Jun 30, 2023
383296e
PR FIXUP - Correctly use lensPipe
AndrewSisley Jun 30, 2023
fddfadb
Test with reduced lensPoolSize
AndrewSisley Jun 30, 2023
577c23b
PR FIXUP - Rename fetcher reciever var
AndrewSisley Jun 30, 2023
6fd6e02
PR FIXUP - Improve update vs delete dag comment
AndrewSisley Jun 30, 2023
31de1be
PR FIXUP - Remove cached []byte schemaVersionID
AndrewSisley Jun 30, 2023
05fcb7e
PR FIXUP - Rename DecodeFieldValue
AndrewSisley Jul 4, 2023
7fc4a74
PR FIXUP - Fix typo
AndrewSisley Jul 4, 2023
a69c039
PR FIXUP - Expand history comment
AndrewSisley Jul 4, 2023
5cda64b
PR FIXUP - Remove isMigratingUp var
AndrewSisley Jul 4, 2023
3dcca1d
PR FIXUP - Add mutation of existing fields tests
AndrewSisley Jul 4, 2023
ad79cd3
PR FIXUP - Add tests for removing property
AndrewSisley Jul 4, 2023
30c7fcf
PR FIXUP - With restart tests
AndrewSisley Jul 4, 2023
4992edb
PR FIXUP - Correctly cache migration in datastore on query
AndrewSisley Jul 4, 2023
6fb86d8
PR FIXUP - Add deep equal todo
AndrewSisley Jul 4, 2023
22be184
PR FIXUP - Rename history stuff
AndrewSisley Jul 4, 2023
490e724
PR FIXUP - Document breaking change
AndrewSisley Jul 4, 2023
c554737
PR FIXUP - Fix transaction leak
AndrewSisley Jul 4, 2023
fc3ce29
PR FIXUP - Add tests documenting transaction issue
AndrewSisley Jul 4, 2023
8d9c8be
PR FIXUP - Add txn-commit test
AndrewSisley Jul 4, 2023
d503cd0
PR FIXUP - Make lens.new private
AndrewSisley Jul 5, 2023
112c628
PR FIXUP - Doc LensDoc
AndrewSisley Jul 5, 2023
871f764
PR FIXUP - Document Lens
AndrewSisley Jul 5, 2023
058f91e
Update mocks
AndrewSisley Jul 5, 2023
6fac4e7
PR FIXUP - Add doumentation to internal pipe access points
AndrewSisley Jul 6, 2023
013cb03
PR FIXUP - Expand getSchemaHistory docs
AndrewSisley Jul 6, 2023
6965a78
PR FIXUP - Move DefaultPoolSize to const
AndrewSisley Jul 6, 2023
7003496
PR FIXUP - Tweak borrow docs
AndrewSisley Jul 6, 2023
5c2ef8f
PR FIXUP - Remove nil check
AndrewSisley Jul 6, 2023
2b1e169
PR FIXUP - Move gitignore lines
AndrewSisley Jul 6, 2023
4214319
PR FIXUP - Change copyright header year
AndrewSisley Jul 6, 2023
077eceb
PR FIXUP - Exclude lens tests from make test
AndrewSisley Jul 6, 2023
74ab7c6
PR FIXUP - Rename ReloadLenses
AndrewSisley Jul 7, 2023
1e406e4
PR FIXUP - Fix test doc typo
AndrewSisley Jul 7, 2023
9fe9e8d
PR FIXUP - Replace mock with real error from real fetcher
AndrewSisley Jul 7, 2023
44208bb
PR FIXUP - Fix mock fetcher close test
AndrewSisley Jul 7, 2023
897540f
PR FIXUP - Log test case name
AndrewSisley Jul 7, 2023
c7d8f5b
PR FIXUP - Fix non-unique update mock test
AndrewSisley Jul 7, 2023
1aac0d9
PR FIXUP - Remove misleading save call
AndrewSisley Jul 7, 2023
f0f55ff
PR FIXUP - Fix typo
AndrewSisley Jul 10, 2023
cd55fb7
PR FIXUP - Fix typo
AndrewSisley Jul 11, 2023
548dbb6
PR FIXUP - Add MIT License to test lens modules
AndrewSisley Jul 11, 2023
5156255
PR FIXUP - Always update ds doc version if migrated
AndrewSisley Jul 11, 2023
18057e3
PR FIXUP - Add missing mocks to make file
AndrewSisley Jul 11, 2023
4341268
PR FIXUP - Fetch all fields if migrations registered
AndrewSisley Jul 11, 2023
0557fe6
PR FIXUP - Remove lens prefix for registery props
AndrewSisley Jul 11, 2023
bf95056
PR FIXUP - Rename warehouse stuff
AndrewSisley Jul 12, 2023
6fb6415
REBASE FIXUP
AndrewSisley Jul 12, 2023
09f6825
PR FIXUP - Wrap close errors
AndrewSisley Jul 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ coverage.txt
tests/bench/*.log
tests/bench/*.svg

tests/lenses/rust_wasm32_set_default/Cargo.lock
tests/lenses/rust_wasm32_set_default/target
tests/lenses/rust_wasm32_set_default/pkg
tests/lenses/rust_wasm32_remove/Cargo.lock
tests/lenses/rust_wasm32_remove/target
tests/lenses/rust_wasm32_remove/pkg
tests/lenses/rust_wasm32_copy/Cargo.lock
tests/lenses/rust_wasm32_copy/target
tests/lenses/rust_wasm32_copy/pkg

# Ignore OS X metadata files.
.history
**.DS_Store
Expand Down
7 changes: 7 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ Run the following commands for testing:
- `make bench` to run the benchmark suite. To compare a branch's results with the `develop` branch results, execute the suite on both branches, output the results to files, and compare them with a tool like `benchstat` (e.g., `benchstat develop.txt current.txt`). To install `benchstat`, use `make deps:bench`.
- `make test:changes` to run a test suite detecting breaking changes. Accompany breaking changes with documentation in `docs/data_format_changes/` for the test to pass.

### Test prerequisites

The following tools are required in order to build and run the tests within this repository:

- [Go](https://go.dev/doc/install)
- Cargo/rustc, typically installed via [rustup](https://www.rust-lang.org/tools/install)

## Documentation
The overall project documentation can be found at [docs.source.network](https://docs.source.network), and its source at [github.com/sourcenetwork/docs.source.network](https://github.com/sourcenetwork/docs.source.network).

Expand Down
36 changes: 29 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ endif

TEST_FLAGS=-race -shuffle=on -timeout 70s

LENS_TEST_DIRECTORY=tests/integration/schema/migrations
DEFAULT_TEST_DIRECTORIES=$$(go list ./... | grep -v $(LENS_TEST_DIRECTORY))

default:
@go run $(BUILD_FLAGS) cmd/defradb/main.go

Expand Down Expand Up @@ -73,9 +76,15 @@ deps\:lint:
deps\:test:
go install gotest.tools/gotestsum@latest

.PHONY: deps\:lens
deps\:lens:
rustup target add wasm32-unknown-unknown
@$(MAKE) -C ./tests/lenses build

.PHONY: deps\:coverage
deps\:coverage:
go install github.com/ory/go-acc@latest
@$(MAKE) deps:lens
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: Can you remove this from here and add this to the test\:coverage: rule. On line 232. This is for the dependencies of the coverage-generating tool.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should this only be for the dependencies of the coverage-generating tool, and not the make test:coverage command that consumes it? (Also, there is nothing in the file that suggested to me that such a distinction existed)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need this dep as a dep when you are generating coverage not as a dependency to a coverage tool dependency target.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deps\:coverage is just there for people who want to build half the dependencies for executing the make test:coverage action? I am not sure I understand you.


.PHONY: deps\:bench
deps\:bench:
Expand Down Expand Up @@ -114,6 +123,8 @@ mock:
mockery --dir ./datastore --output ./datastore/mocks --name RootStore --with-expecter
mockery --dir ./datastore --output ./datastore/mocks --name Txn --with-expecter
mockery --dir ./datastore --output ./datastore/mocks --name DAGStore --with-expecter
mockery --dir ./db/fetcher --output ./db/fetcher/mocks --name Fetcher --with-expecter
mockery --dir ./db/fetcher --output ./db/fetcher/mocks --name EncodedDocument --with-expecter

.PHONY: dev\:start
dev\:start:
Expand Down Expand Up @@ -162,32 +173,37 @@ endif

.PHONY: test
test:
gotestsum --format pkgname -- ./... $(TEST_FLAGS)
gotestsum --format pkgname -- $(DEFAULT_TEST_DIRECTORIES) $(TEST_FLAGS)

# Only build the tests (don't execute them).
.PHONY: test\:build
test\:build:
gotestsum --format pkgname -- ./... $(TEST_FLAGS) -run=nope
gotestsum --format pkgname -- $(DEFAULT_TEST_DIRECTORIES) $(TEST_FLAGS) -run=nope

.PHONY: test\:ci
test\:ci:
DEFRA_BADGER_MEMORY=true DEFRA_BADGER_FILE=true $(MAKE) test:names
DEFRA_BADGER_MEMORY=true DEFRA_BADGER_FILE=true $(MAKE) test:all

.PHONY: test\:go
test\:go:
go test ./... $(TEST_FLAGS)
go test $(DEFAULT_TEST_DIRECTORIES) $(TEST_FLAGS)

.PHONY: test\:names
test\:names:
gotestsum --format testname -- ./... $(TEST_FLAGS)
gotestsum --format testname -- $(DEFAULT_TEST_DIRECTORIES) $(TEST_FLAGS)

.PHONY: test\:all
test\:all:
@$(MAKE) test:names
@$(MAKE) test:lens

.PHONY: test\:verbose
test\:verbose:
gotestsum --format standard-verbose -- ./... $(TEST_FLAGS)
gotestsum --format standard-verbose -- $(DEFAULT_TEST_DIRECTORIES) $(TEST_FLAGS)

.PHONY: test\:watch
test\:watch:
gotestsum --watch -- ./...
gotestsum --watch -- $(DEFAULT_TEST_DIRECTORIES)

.PHONY: test\:clean
test\:clean:
Expand All @@ -205,6 +221,11 @@ test\:bench-short:
test\:scripts:
@$(MAKE) -C ./tools/scripts/ test

.PHONY: test\:lens
test\:lens:
@$(MAKE) deps:lens
gotestsum --format testname -- ./$(LENS_TEST_DIRECTORY)/... $(TEST_FLAGS)

# Using go-acc to ensure integration tests are included.
# Usage: `make test:coverage` or `make test:coverage path="{pathToPackage}"`
# Example: `make test:coverage path="./api/..."`
Expand All @@ -231,6 +252,7 @@ test\:coverage-html:

.PHONY: test\:changes
test\:changes:
@$(MAKE) deps:lens
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: This rule assumes deps are there. If you see .github/workflows/detect-change.yml:

Should instead add make deps:test-all according to above suggestion and then just change in detect-change.yml to:

      - name: Build dependencies
        run: |
          make deps:modules
          make deps:test-all

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But that would hide the dependency from the make file. With the current setup make test:changes is independent and complete - including in documenting its own dependencies. Why would I want to hide that/force people to find and look through a yaml file?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would build lens deps every time you run test:changes. The Build dependencies already has deps it needs before calling this test:changes, that is because it runs it for the first time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would I want to have to make two calls to run the change detector? I just want one call, and I want it to run the code in front of me, not a stale binary from ages back. These make actions are very handy outside of the CI as well as within it.

env DEFRA_DETECT_DATABASE_CHANGES=true gotestsum -- ./... -shuffle=on -p 1

.PHONY: validate\:codecov
Expand Down
19 changes: 19 additions & 0 deletions client/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,25 @@ type Store interface {
// [FieldKindStringToEnumMapping].
PatchSchema(context.Context, string) error

// SetMigration sets the migration for the given source-destination schema version IDs. Is equivilent to
// calling `LensRegistry().SetMigration(ctx, cfg)`.
//
// There may only be one migration per schema version id. If another migration was registered it will be
// overwritten by this migration.
//
// Neither of the schema version IDs specified in the configuration need to exist at the time of calling.
// This is to allow the migration of documents of schema versions unknown to the local node recieved by the
// P2P system.
//
// Migrations will only run if there is a complete path from the document schema version to the latest local
// schema version.
SetMigration(context.Context, LensConfig) error

// LensRegistry returns the LensRegistry in use by this database instance.
//
// It exposes several useful thread-safe migration related functions.
LensRegistry() LensRegistry

// GetCollectionByName attempts to retrieve a collection matching the given name.
//
// If no matching collection is found an error will be returned.
Expand Down
16 changes: 11 additions & 5 deletions client/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,17 @@ import (
// @body: A document interface can be implemented by both a TypedDocument and a
// UnTypedDocument, which use a schema and schemaless approach respectively.
type Document struct {
key DocKey
fields map[string]Field
values map[Field]Value
head cid.Cid
mu sync.RWMutex
key DocKey
// SchemaVersionID holds the id of the schema version that this document is
// currently at.
//
// Migrating the document will update this value to the output version of the
// migration.
SchemaVersionID string
fields map[string]Field
values map[Field]Value
head cid.Cid
mu sync.RWMutex
// marks if document has unsaved changes
isDirty bool
}
Expand Down
83 changes: 83 additions & 0 deletions client/lens.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2023 Democratized Data Foundation
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package client

import (
"context"

"github.com/lens-vm/lens/host-go/config/model"
"github.com/sourcenetwork/immutable/enumerable"

"github.com/sourcenetwork/defradb/datastore"
)

// LensConfig represents the configuration of a Lens migration in Defra.
type LensConfig struct {
// SourceSchemaVersionID is the ID of the schema version from which to migrate
// from.
//
// The source and destination versions must be next to each other in the history.
SourceSchemaVersionID string

// DestinationSchemaVersionID is the ID of the schema version from which to migrate
// to.
//
// The source and destination versions must be next to each other in the history.
DestinationSchemaVersionID string

// The configuration of the Lens module.
//
// For now, the wasm module must remain at the location specified as long as the
// migration is active.
model.Lens
}

// LensRegistry exposes several useful thread-safe migration related functions which may
// be used to manage migrations.
type LensRegistry interface {
// SetMigration sets the migration for the given source-destination schema version IDs. Is equivilent to
// calling `Store.SetMigration(ctx, cfg)`.
//
// There may only be one migration per schema version id. If another migration was registered it will be
// overwritten by this migration.
//
// Neither of the schema version IDs specified in the configuration need to exist at the time of calling.
// This is to allow the migration of documents of schema versions unknown to the local node recieved by the
// P2P system.
//
// Migrations will only run if there is a complete path from the document schema version to the latest local
// schema version.
SetMigration(context.Context, datastore.Txn, LensConfig) error

// ReloadLenses clears any cached migrations, loads their configurations from the database and re-initializes
// them. It is run on database start if the database already existed.
ReloadLenses(ctx context.Context, txn datastore.Txn) error

// MigrateUp returns an enumerable that feeds the given source through the Lens migration for the given
// schema version id if one is found, if there is no matching migration the given source will be returned.
MigrateUp(enumerable.Enumerable[map[string]any], string) (enumerable.Enumerable[map[string]any], error)

// MigrateDown returns an enumerable that feeds the given source through the Lens migration for the schema
// version that precedes the given schema version id in reverse, if one is found, if there is no matching
// migration the given source will be returned.
//
// This downgrades any documents in the source enumerable if/when enumerated.
MigrateDown(enumerable.Enumerable[map[string]any], string) (enumerable.Enumerable[map[string]any], error)

// Config returns a slice of the configurations of the currently loaded migrations.
//
// Modifying the slice does not affect the loaded configurations.
Config() []LensConfig

// HasMigration returns true if there is a migration registered for the given schema version id, otherwise
// will return false.
HasMigration(string) bool
}
86 changes: 86 additions & 0 deletions client/mocks/DB.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 18 additions & 4 deletions core/crdt/composite.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,27 @@ func (c CompositeDAG) Merge(ctx context.Context, delta core.Delta, id string) er
return c.deleteWithPrefix(ctx, c.key.WithValueFlag().WithFieldId(""))
}

// ensure object marker exists
exists, err := c.store.Has(ctx, c.key.ToPrimaryDataStoreKey().ToDS())
// We cannot rely on the dagDelta.Status here as it may have been deleted locally, this is not
// reflected in `dagDelta.Status` if sourced via P2P. Updates synced via P2P should not undelete
// the local reperesentation of the document.
versionKey := c.key.WithValueFlag().WithFieldId(core.DATASTORE_DOC_VERSION_FIELD_ID)
objectMarker, err := c.store.Get(ctx, c.key.ToPrimaryDataStoreKey().ToDS())
hasObjectMarker := !errors.Is(err, ds.ErrNotFound)
if err != nil && hasObjectMarker {
return err
}

if bytes.Equal(objectMarker, []byte{base.DeletedObjectMarker}) {
versionKey = versionKey.WithDeletedFlag()
}

err = c.store.Put(ctx, versionKey.ToDS(), []byte(c.schemaVersionKey.SchemaVersionId))
if err != nil {
return err
}
if !exists {
// write object marker

if !hasObjectMarker {
// ensure object marker exists
return c.store.Put(ctx, c.key.ToPrimaryDataStoreKey().ToDS(), []byte{base.ObjectMarker})
}

Expand Down
Loading