Commit 6eb4e36c authored by Amos Wenger's avatar Amos Wenger

Fix automigration for squashed fields

parent 47c31489
Pipeline #11080 passed with stage
in 30 seconds
......@@ -11,9 +11,19 @@ import (
"github.com/pkg/errors"
)
type AutoMigrateStats struct {
NumCreated int64
NumMigrated int64
NumCurrent int64
}
func (c *Context) AutoMigrate(conn *sqlite.Conn) error {
return c.AutoMigrateEx(conn, &AutoMigrateStats{})
}
func (c *Context) AutoMigrateEx(conn *sqlite.Conn, stats *AutoMigrateStats) error {
for _, m := range c.ScopeMap.byDBName {
err := c.syncTable(conn, m.GetModelStruct())
err := c.syncTable(conn, stats, m.GetModelStruct())
if err != nil {
return err
}
......@@ -21,13 +31,14 @@ func (c *Context) AutoMigrate(conn *sqlite.Conn) error {
return nil
}
func (c *Context) syncTable(conn *sqlite.Conn, ms *ModelStruct) (err error) {
func (c *Context) syncTable(conn *sqlite.Conn, stats *AutoMigrateStats, ms *ModelStruct) (err error) {
tableName := ms.TableName
pti, err := c.PragmaTableInfo(conn, tableName)
if err != nil {
return err
}
if len(pti) == 0 {
stats.NumCreated++
return c.createTable(conn, ms)
}
......@@ -47,23 +58,38 @@ func (c *Context) syncTable(conn *sqlite.Conn, ms *ModelStruct) (err error) {
numOldCols := len(oldColumns)
numNewCols := 0
isMissingCols := false
for _, sf := range ms.StructFields {
if !sf.IsNormal {
continue
}
numNewCols++
if _, ok := oldColumns[sf.DBName]; !ok {
isMissingCols = true
break
{
var processField func(sf *StructField)
processField = func(sf *StructField) {
if sf.IsSquashed {
for _, nsf := range sf.SquashedFields {
processField(nsf)
}
}
if !sf.IsNormal {
return
}
numNewCols++
if _, ok := oldColumns[sf.DBName]; !ok {
isMissingCols = true
return
}
}
for _, sf := range ms.StructFields {
processField(sf)
}
}
if !isMissingCols && numOldCols == numNewCols {
// all done
stats.NumCurrent++
return nil
}
stats.NumMigrated++
tempName := fmt.Sprintf("__hades_migrate__%s__", tableName)
err = c.ExecRaw(conn, fmt.Sprintf("CREATE TABLE %s AS SELECT * FROM %s", tempName, tableName), nil)
if err != nil {
......@@ -81,14 +107,26 @@ func (c *Context) syncTable(conn *sqlite.Conn, ms *ModelStruct) (err error) {
}
var columns []string
for _, sf := range ms.StructFields {
if sf.Relationship != nil {
continue
{
var processField func(sf *StructField)
processField = func(sf *StructField) {
if sf.IsSquashed {
for _, nsf := range sf.SquashedFields {
processField(nsf)
}
}
if !sf.IsNormal {
return
}
if _, ok := oldColumns[sf.DBName]; ok {
columns = append(columns, EscapeIdentifier(sf.DBName))
}
}
if _, ok := oldColumns[sf.DBName]; !ok {
continue
for _, sf := range ms.StructFields {
processField(sf)
}
columns = append(columns, EscapeIdentifier(sf.DBName))
}
var columnList = strings.Join(columns, ",")
......
......@@ -6,6 +6,8 @@ import (
"testing"
"time"
"github.com/itchio/hades/sqliteutil2"
"crawshaw.io/sqlite"
"github.com/go-xorm/builder"
"github.com/itchio/hades"
......@@ -273,3 +275,114 @@ func ordie(err error) {
panic(fmt.Sprintf("%+v", err))
}
}
func Test_AutoMigratePreservesData(t *testing.T) {
dbpool, err := sqlite.Open("file:memory:?mode=memory", 0, 10)
ordie(err)
defer dbpool.Close()
conn := dbpool.Get(context.Background().Done())
defer dbpool.Put(conn)
defer sqliteutil2.Exec(conn, "DROP TABLE androids", nil)
{
type AndroidTraits struct {
Funny string
Wise string
}
type Android struct {
ID int64
Title string
Traits AndroidTraits `hades:"squash"`
}
models := []interface{}{&Android{}}
c, err := hades.NewContext(makeConsumer(t), models...)
ordie(err)
c.Log = true
{
var migrateStats hades.AutoMigrateStats
ordie(c.AutoMigrateEx(conn, &migrateStats))
assert.EqualValues(t, 1, migrateStats.NumCreated)
assert.EqualValues(t, 0, migrateStats.NumMigrated)
assert.EqualValues(t, 0, migrateStats.NumCurrent)
}
refAndroid := &Android{
ID: 123,
Title: "tomo",
Traits: AndroidTraits{
Funny: "funny",
Wise: "wise",
},
}
ordie(c.Save(conn, refAndroid))
var a Android
ok, err := c.SelectOne(conn, &a, builder.NewCond())
ordie(err)
assert.True(t, ok)
assert.EqualValues(t, refAndroid, &a)
}
{
type AndroidTraits struct {
Funny string
Loyal string
Wise string
}
type Android struct {
ID int64
Title string
Traits AndroidTraits `hades:"squash"`
}
models := []interface{}{&Android{}}
c, err := hades.NewContext(makeConsumer(t), models...)
ordie(err)
c.Log = true
{
var migrateStats hades.AutoMigrateStats
ordie(c.AutoMigrateEx(conn, &migrateStats))
assert.EqualValues(t, 0, migrateStats.NumCreated)
assert.EqualValues(t, 1, migrateStats.NumMigrated)
assert.EqualValues(t, 0, migrateStats.NumCurrent)
}
refAndroid := &Android{
ID: 123,
Title: "tomo",
Traits: AndroidTraits{
Funny: "funny",
Wise: "wise",
},
}
var a Android
ok, err := c.SelectOne(conn, &a, builder.NewCond())
ordie(err)
assert.True(t, ok)
assert.EqualValues(t, refAndroid, &a)
// migrate once more
{
var migrateStats hades.AutoMigrateStats
ordie(c.AutoMigrateEx(conn, &migrateStats))
assert.EqualValues(t, 0, migrateStats.NumCreated)
assert.EqualValues(t, 0, migrateStats.NumMigrated)
assert.EqualValues(t, 1, migrateStats.NumCurrent)
}
ok, err = c.SelectOne(conn, &a, builder.NewCond())
ordie(err)
assert.True(t, ok)
assert.EqualValues(t, refAndroid, &a)
}
}
......@@ -166,6 +166,7 @@ func Test_SquashedFull(t *testing.T) {
wtest.Must(t, c.Save(conn, fu, hades.Assoc("Games")))
u := &FakeUser{}
found, err := c.SelectOne(conn, u, builder.NewCond())
wtest.Must(t, err)
assert.True(t, found)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment