Skip to content

Commit

Permalink
Execute Go migrations during Translate() if available
Browse files Browse the repository at this point in the history
  • Loading branch information
sam boyer committed Jul 23, 2023
1 parent eea4ec7 commit db0ad40
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 120 deletions.
204 changes: 98 additions & 106 deletions gomig_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package thema

import (
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -22,6 +23,12 @@ schemas: [{
simple: {
init: "some string"
}
TOwithOptional: {
init: "some string"
}
TOwithoutOptional: {
init: "some string"
}
}
},
{
Expand All @@ -30,15 +37,6 @@ schemas: [{
init: string
optional?: int32
}
examples: {
withoutOptional: {
init: "some string"
}
withOptional: {
init: "some string"
optional: 32
}
}
},
{
version: [0, 2]
Expand All @@ -47,17 +45,6 @@ schemas: [{
optional?: int32
withDefault?: *"foo" | "bar"
}
examples: {
withoutOptional: {
init: "some string"
withDefault: "foo"
}
withOptional: {
init: "some string"
optional: 32
withDefault: "bar"
}
}
},
{
version: [0, 3]
Expand All @@ -66,17 +53,6 @@ schemas: [{
optional?: int32
withDefault?: *"foo" | "bar" | "baz"
}
examples: {
withoutOptional: {
init: "some string"
withDefault: "baz"
}
withOptional: {
init: "some string"
optional: 32
withDefault: "baz"
}
}
},
{
version: [1, 0]
Expand All @@ -85,17 +61,6 @@ schemas: [{
optional?: int32
withDefault: "foo" | *"bar" | "baz"
}
examples: {
withoutOptional: {
renamed: "some string"
withDefault: "foo"
}
withOptional: {
renamed: "some string"
optional: 32
withDefault: "bar"
}
}
},
{
version: [1, 1]
Expand All @@ -104,17 +69,6 @@ schemas: [{
optional?: int32
withDefault: "foo" | *"bar" | "baz" | "bing"
}
examples: {
withoutOptional: {
renamed: "some string"
withDefault: "bing"
}
withOptional: {
renamed: "some string"
optional: 32
withDefault: "bing"
}
}
},
{
version: [2, 0]
Expand All @@ -126,6 +80,12 @@ schemas: [{
withDefault: "foo" | *"bar" | "baz" | "bing"
}
examples: {
TOsimple: {
toObj: {
init: "some string"
}
withDefault: "bar"
}
withoutOptional: {
toObj: {
init: "some string"
Expand Down Expand Up @@ -258,7 +218,7 @@ schemas: [{
if v, has := m["optional"]; has {
tom["optional"] = v
}
if v, has := m["withDefault"]; has {
if v, has := m["withDefault"]; has && v != "bing" {
tom["withDefault"] = v
} else {
tom["withDefault"] = "bar"
Expand Down Expand Up @@ -315,60 +275,92 @@ schemas: [{
ctx := cuecontext.New()
rt := NewRuntime(ctx)
linval := rt.Context().CompileString(multivlin)
_, err := BindLineage(linval, rt, ImperativeLenses(correctLenses...))
lin, err := BindLineage(linval, rt, ImperativeLenses(correctLenses...))
require.NoError(t, err)

_, err = BindLineage(linval, rt, ImperativeLenses(correctLenses[1:]...))
assert.Error(t, err, "expected error when missing a reverse Go migration")
_, err = BindLineage(linval, rt, ImperativeLenses(correctLenses[:1]...))
assert.Error(t, err, "expected error when missing a forward Go migration")

_, err = BindLineage(linval, rt, ImperativeLenses(append(correctLenses, ImperativeLens{
To: SV(2, 0),
From: SV(2, 1),
Mapper: func(inst *Instance, to Schema) (*Instance, error) { return nil, nil },
})...))
assert.Error(t, err, "expected error when adding Go migration pointing to nonexistent version")

_, err = BindLineage(linval, rt, ImperativeLenses(append(correctLenses, ImperativeLens{
To: SV(2, 1),
From: SV(2, 0),
Mapper: func(inst *Instance, to Schema) (*Instance, error) { return nil, nil },
})...))
assert.Error(t, err, "expected error when adding Go migration pointing to nonexistent version")

_, err = BindLineage(linval, rt, ImperativeLenses(append(correctLenses, ImperativeLens{
To: SV(2, 0),
From: SV(1, 1),
Mapper: func(inst *Instance, to Schema) (*Instance, error) { return nil, nil },
})...))
assert.Error(t, err, "expected error when adding duplicate Go migration")

_, err = BindLineage(linval, rt, ImperativeLenses(append(correctLenses, ImperativeLens{
Mapper: func(inst *Instance, to Schema) (*Instance, error) { return nil, nil },
})...))
assert.Error(t, err, "expected error when providing a Go migration with same to and from")

_, err = BindLineage(linval, rt, ImperativeLenses(append(correctLenses, ImperativeLens{
To: SV(2, 0),
From: SV(1, 0),
Mapper: func(inst *Instance, to Schema) (*Instance, error) { return nil, nil },
})...))
assert.Error(t, err, "expected error when providing Go migration with wrong successor")

_, err = BindLineage(linval, rt, ImperativeLenses(append(correctLenses, ImperativeLens{
To: SV(1, 0),
From: SV(2, 0),
Mapper: func(inst *Instance, to Schema) (*Instance, error) { return nil, nil },
})...))
assert.Error(t, err, "expected error when providing Go migration with wrong predecessor")

_, err = BindLineage(linval, rt, ImperativeLenses(append(correctLenses, ImperativeLens{
To: SV(1, 1),
From: SV(1, 0),
Mapper: func(inst *Instance, to Schema) (*Instance, error) { return nil, nil },
})...))
assert.Error(t, err, "expected error when providing Go migration for minor version upgrade")
transtest := func(t *testing.T, start, end Schema) {
for name, ex := range start.Examples() {
if strings.HasPrefix(name, "TO") {
continue
}
tex, tname := ex, name
t.Run(tname, func(t *testing.T) {
t.Log(start.Version(), end.Version())
tinst, lacunas, err := tex.Translate(end.Version())
require.NoError(t, err)
assert.Nil(t, lacunas, "pure go migrations cannot emit lacunas")

b, err := tinst.Underlying().MarshalJSON()
require.NoError(t, err)

eb, err := end.Examples()["TO"+tname].Underlying().MarshalJSON()
require.NoError(t, err)
assert.Equal(t, b, eb)
})
}
}

t.Run("forward", func(t *testing.T) {
transtest(t, lin.First(), lin.Latest())
})

t.Run("reverse", func(t *testing.T) {
transtest(t, lin.Latest(), lin.First())
})

t.Run("bind-invalid", func(t *testing.T) {
_, err = BindLineage(linval, rt, ImperativeLenses(correctLenses[1:]...))
assert.Error(t, err, "expected error when missing a reverse Go migration")
_, err = BindLineage(linval, rt, ImperativeLenses(correctLenses[:1]...))
assert.Error(t, err, "expected error when missing a forward Go migration")

_, err = BindLineage(linval, rt, ImperativeLenses(append(correctLenses, ImperativeLens{
To: SV(2, 0),
From: SV(2, 1),
Mapper: func(inst *Instance, to Schema) (*Instance, error) { return nil, nil },
})...))
assert.Error(t, err, "expected error when adding Go migration pointing to nonexistent version")

_, err = BindLineage(linval, rt, ImperativeLenses(append(correctLenses, ImperativeLens{
To: SV(2, 1),
From: SV(2, 0),
Mapper: func(inst *Instance, to Schema) (*Instance, error) { return nil, nil },
})...))
assert.Error(t, err, "expected error when adding Go migration pointing to nonexistent version")

_, err = BindLineage(linval, rt, ImperativeLenses(append(correctLenses, ImperativeLens{
To: SV(2, 0),
From: SV(1, 1),
Mapper: func(inst *Instance, to Schema) (*Instance, error) { return nil, nil },
})...))
assert.Error(t, err, "expected error when adding duplicate Go migration")

_, err = BindLineage(linval, rt, ImperativeLenses(append(correctLenses, ImperativeLens{
Mapper: func(inst *Instance, to Schema) (*Instance, error) { return nil, nil },
})...))
assert.Error(t, err, "expected error when providing a Go migration with same to and from")

_, err = BindLineage(linval, rt, ImperativeLenses(append(correctLenses, ImperativeLens{
To: SV(2, 0),
From: SV(1, 0),
Mapper: func(inst *Instance, to Schema) (*Instance, error) { return nil, nil },
})...))
assert.Error(t, err, "expected error when providing Go migration with wrong successor")

_, err = BindLineage(linval, rt, ImperativeLenses(append(correctLenses, ImperativeLens{
To: SV(1, 0),
From: SV(2, 0),
Mapper: func(inst *Instance, to Schema) (*Instance, error) { return nil, nil },
})...))
assert.Error(t, err, "expected error when providing Go migration with wrong predecessor")

_, err = BindLineage(linval, rt, ImperativeLenses(append(correctLenses, ImperativeLens{
To: SV(1, 1),
From: SV(1, 0),
Mapper: func(inst *Instance, to Schema) (*Instance, error) { return nil, nil },
})...))
assert.Error(t, err, "expected error when providing Go migration for minor version upgrade")
})
}

func tomap(inst *Instance) map[string]any {
Expand Down
54 changes: 40 additions & 14 deletions instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ func (i *Instance) Dehydrate() *Instance {
// schema.
func (i *Instance) AsSuccessor() (*Instance, TranslationLacunas, error) {
i.check()
// If it's a minor version upgrade, we can safely shortcut and just create
// a new instance
nsch := i.Schema().Successor()
if nsch.Version()[0] == i.Schema().Version()[0] {
ni := new(Instance)
*ni = *i
ni.sch = nsch
return ni, nil, nil
}
return i.Translate(i.sch.Successor().Version())
}

Expand Down Expand Up @@ -256,23 +265,40 @@ func (i *Instance) translateGo(to SyntacticVersion) (*Instance, TranslationLacun
ti := new(Instance)
*ti = *i
for sch.Version() != to {
rti, err := lensmap[lid(sch.Version(), ti.Schema().Version())].Mapper(ti, sch)
if err != nil {
return nil, nil, err
}
// Ensure the returned instance exists and the caller returned an instance of the expected schema version
if rti == nil {
return nil, nil, fmt.Errorf("lens returned a nil instance")
}
if rti.Schema().Version() != sch.Version() {
return nil, nil, fmt.Errorf("lens returned an instance of the wrong schema version: expected %v, got %v", sch.Version(), ti.Schema().Version())
}
*ti = *rti
var nsch Schema
if to.Less(from) {
sch = sch.Predecessor()
nsch = sch.Predecessor()
} else {
sch = sch.Successor()
nsch = sch.Successor()
}

var rti *Instance
var err error
if to.Less(from) || sch.Version()[0] != nsch.Version()[0] {
// Going backward, or crossing major version - need explicit lens
mlid := lid(nsch.Version(), sch.Version())
rti, err = lensmap[mlid].Mapper(ti, nsch)
if err != nil {
return nil, nil, fmt.Errorf("error executing %s migration: %w", mlid, err)
}
// Ensure that
// - the returned instance exists
// - the caller returned an instance of the expected schema version
if rti == nil {
return nil, nil, fmt.Errorf("lens returned a nil instance")
}
if rti.Schema().Version() != nsch.Version() {
return nil, nil, fmt.Errorf("lens returned an instance of the wrong schema version: expected %v, got %v", nsch.Version(), rti.Schema().Version())
}
} else {
// going up a minor version - neither errors nor lacunas are possible
rti, _, err = ti.AsSuccessor()
if err != nil {
panic(fmt.Sprintf("unreachable - error on minor version upgrade: %s", err))
}
}
*ti = *rti
sch = nsch
}

return ti, nil, nil
Expand Down

0 comments on commit db0ad40

Please sign in to comment.