Merge pull request #11: fix deterministic template table index ordering
This commit was merged in pull request #11.
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
package reflectutil
|
package reflectutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -134,7 +136,7 @@ func MapKeys(i interface{}) []interface{} {
|
|||||||
return []interface{}{}
|
return []interface{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
keys := v.MapKeys()
|
keys := sortedMapKeys(v)
|
||||||
result := make([]interface{}, len(keys))
|
result := make([]interface{}, len(keys))
|
||||||
for i, key := range keys {
|
for i, key := range keys {
|
||||||
result[i] = key.Interface()
|
result[i] = key.Interface()
|
||||||
@@ -155,14 +157,39 @@ func MapValues(i interface{}) []interface{} {
|
|||||||
return []interface{}{}
|
return []interface{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]interface{}, 0, v.Len())
|
keys := sortedMapKeys(v)
|
||||||
iter := v.MapRange()
|
result := make([]interface{}, 0, len(keys))
|
||||||
for iter.Next() {
|
for _, key := range keys {
|
||||||
result = append(result, iter.Value().Interface())
|
result = append(result, v.MapIndex(key).Interface())
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sortedMapKeys(v reflect.Value) []reflect.Value {
|
||||||
|
keys := v.MapKeys()
|
||||||
|
sort.SliceStable(keys, func(i, j int) bool {
|
||||||
|
return mapKeyLess(keys[i], keys[j])
|
||||||
|
})
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapKeyLess(a, b reflect.Value) bool {
|
||||||
|
switch a.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
return a.String() < b.String()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return a.Int() < b.Int()
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return a.Uint() < b.Uint()
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return a.Float() < b.Float()
|
||||||
|
case reflect.Bool:
|
||||||
|
return !a.Bool() && b.Bool()
|
||||||
|
default:
|
||||||
|
return fmt.Sprint(a.Interface()) < fmt.Sprint(b.Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MapGet safely gets a value from a map by key
|
// MapGet safely gets a value from a map by key
|
||||||
// Returns nil if key doesn't exist or not a map
|
// Returns nil if key doesn't exist or not a map
|
||||||
func MapGet(m interface{}, key interface{}) interface{} {
|
func MapGet(m interface{}, key interface{}) interface{} {
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
||||||
|
"git.warky.dev/wdevs/relspecgo/pkg/writers"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWriterTableIndexValuesDeterministic(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
templatePath := filepath.Join(dir, "indexes.tmpl")
|
||||||
|
outputDir := filepath.Join(dir, "out")
|
||||||
|
outputPath := filepath.Join(outputDir, "accounts.txt")
|
||||||
|
templateBody := "{{range values .Table.Indexes}}{{.Name}}:{{join .Columns \",\"}}\n{{end}}"
|
||||||
|
|
||||||
|
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||||
|
t.Fatalf("create output dir: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(templatePath, []byte(templateBody), 0644); err != nil {
|
||||||
|
t.Fatalf("write template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db := databaseWithMultipleIndexes()
|
||||||
|
var first []byte
|
||||||
|
const runs = 100
|
||||||
|
for i := 0; i < runs; i++ {
|
||||||
|
writer, err := NewWriter(&writers.WriterOptions{
|
||||||
|
OutputPath: outputDir,
|
||||||
|
Metadata: map[string]interface{}{
|
||||||
|
"template_path": templatePath,
|
||||||
|
"mode": string(TableMode),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("new writer: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := writer.WriteDatabase(db); err != nil {
|
||||||
|
t.Fatalf("write database run %d: %v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := os.ReadFile(outputPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read output run %d: %v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
first = got
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if string(got) != string(first) {
|
||||||
|
t.Fatalf("run %d output differed from first run\nfirst:\n%s\nrun %d:\n%s", i, first, i, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
want := strings.Join([]string{
|
||||||
|
"idx_accounts_email:email",
|
||||||
|
"idx_accounts_last_login:last_login",
|
||||||
|
"idx_accounts_name:name",
|
||||||
|
"idx_accounts_status:status",
|
||||||
|
"idx_accounts_tenant:tenant_id",
|
||||||
|
"",
|
||||||
|
}, "\n")
|
||||||
|
if string(first) != want {
|
||||||
|
t.Fatalf("unexpected index order\nwant:\n%s\ngot:\n%s", want, first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func databaseWithMultipleIndexes() *models.Database {
|
||||||
|
db := models.InitDatabase("test")
|
||||||
|
schema := models.InitSchema("public")
|
||||||
|
table := models.InitTable("accounts", "public")
|
||||||
|
|
||||||
|
table.Indexes["idx_accounts_status"] = &models.Index{Name: "idx_accounts_status", Table: table.Name, Schema: schema.Name, Columns: []string{"status"}}
|
||||||
|
table.Indexes["idx_accounts_email"] = &models.Index{Name: "idx_accounts_email", Table: table.Name, Schema: schema.Name, Columns: []string{"email"}}
|
||||||
|
table.Indexes["idx_accounts_tenant"] = &models.Index{Name: "idx_accounts_tenant", Table: table.Name, Schema: schema.Name, Columns: []string{"tenant_id"}}
|
||||||
|
table.Indexes["idx_accounts_name"] = &models.Index{Name: "idx_accounts_name", Table: table.Name, Schema: schema.Name, Columns: []string{"name"}}
|
||||||
|
table.Indexes["idx_accounts_last_login"] = &models.Index{Name: "idx_accounts_last_login", Table: table.Name, Schema: schema.Name, Columns: []string{"last_login"}}
|
||||||
|
|
||||||
|
schema.Tables = append(schema.Tables, table)
|
||||||
|
db.Schemas = append(db.Schemas, schema)
|
||||||
|
return db
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user