Skip to content
This repository was archived by the owner on Nov 20, 2021. It is now read-only.

Commit 038671c

Browse files
committed
[WIP] Schema versioning
1 parent e75d9d8 commit 038671c

File tree

5 files changed

+260
-56
lines changed

5 files changed

+260
-56
lines changed

pkg/e2db/db_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ import (
1515

1616
var db *DB
1717

18-
func init() {
18+
func initDB() {
19+
if db != nil {
20+
return
21+
}
1922
log.SetLevel(zapcore.DebugLevel)
2023

2124
if err := os.RemoveAll("testdata"); err != nil {
@@ -68,6 +71,7 @@ var newRoles = []*Role{
6871
}
6972

7073
func resetTable(t *testing.T) {
74+
initDB()
7175
roles := db.Table(&Role{})
7276
if err := roles.Drop(); err != nil && errors.Cause(err) != ErrTableNotFound {
7377
t.Fatal(err)

pkg/e2db/model.go

Lines changed: 184 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
package e2db
22

33
import (
4+
"bytes"
5+
"crypto/sha1"
6+
"encoding/hex"
7+
"fmt"
48
"reflect"
9+
"sort"
510
"strings"
11+
"unicode"
612

713
"github.com/criticalstack/e2d/pkg/e2db/key"
814
"github.com/pkg/errors"
@@ -16,15 +22,50 @@ type Tag struct {
1622
Name, Value string
1723
}
1824

25+
func (t *Tag) String() string {
26+
if t.Value == "" {
27+
return t.Name
28+
}
29+
return fmt.Sprintf("%s=%s", t.Name, t.Value)
30+
}
31+
1932
type FieldDef struct {
20-
Name string
21-
Tags []*Tag
33+
Name string
34+
Kind reflect.Kind
35+
Type string
36+
Tags []*Tag
37+
Fields []*FieldDef
38+
}
39+
40+
func (f *FieldDef) String() string {
41+
var tags string
42+
if len(f.Tags) > 0 {
43+
tt := make([]string, 0)
44+
for _, t := range f.Tags {
45+
tt = append(tt, t.String())
46+
}
47+
tags = fmt.Sprintf(" `%s`", strings.Join(tt, ","))
48+
}
49+
t := f.Kind.String()
50+
if f.Kind == reflect.Struct {
51+
t = f.Type
52+
}
53+
return fmt.Sprintf("%s %s%s", f.Name, t, tags)
2254
}
2355

2456
func (f *FieldDef) isIndex() bool {
2557
return f.isPrimaryKey() || f.hasTag("index", "unique")
2658
}
2759

60+
func (f *FieldDef) getTag(name string) *Tag {
61+
for _, t := range f.Tags {
62+
if t.Name == name {
63+
return t
64+
}
65+
}
66+
return nil
67+
}
68+
2869
func (f *FieldDef) hasTag(tags ...string) bool {
2970
for _, t := range f.Tags {
3071
for _, tag := range tags {
@@ -55,7 +96,7 @@ const (
5596
UniqueIndex
5697
)
5798

58-
func (f *FieldDef) Type() IndexType {
99+
func (f *FieldDef) indexType() IndexType {
59100
switch {
60101
case f.hasTag("increment", "id"):
61102
return PrimaryKey
@@ -69,7 +110,7 @@ func (f *FieldDef) Type() IndexType {
69110
}
70111

71112
func (f *FieldDef) indexKey(tableName string, value string) (string, error) {
72-
switch f.Type() {
113+
switch f.indexType() {
73114
case PrimaryKey:
74115
return key.ID(tableName, value), nil
75116
case SecondaryIndex, UniqueIndex:
@@ -79,27 +120,13 @@ func (f *FieldDef) indexKey(tableName string, value string) (string, error) {
79120
}
80121
}
81122

82-
type ModelDef struct {
83-
Name string
84-
Fields map[string]*FieldDef
85-
86-
t reflect.Type
87-
}
88-
89-
func NewModelDef(t reflect.Type) *ModelDef {
90-
if t.Kind() == reflect.Ptr {
91-
t = t.Elem()
92-
}
93-
if t.NumField() == 0 {
94-
panic("must have at least 1 struct field")
95-
}
96-
m := &ModelDef{
97-
Name: t.Name(),
98-
Fields: make(map[string]*FieldDef),
99-
t: t,
100-
}
123+
func newFieldDefs(t reflect.Type) []*FieldDef {
124+
fields := make([]*FieldDef, 0)
101125
for i := 0; i < t.NumField(); i++ {
102126
ft := t.Field(i)
127+
if unicode.IsLower([]rune(ft.Name)[0]) {
128+
continue
129+
}
103130
tags := make([]*Tag, 0)
104131
if tagValue, ok := ft.Tag.Lookup("e2db"); ok {
105132
for _, t := range strings.Split(tagValue, ",") {
@@ -111,11 +138,58 @@ func NewModelDef(t reflect.Type) *ModelDef {
111138
}
112139
}
113140
}
114-
m.Fields[ft.Name] = &FieldDef{
141+
sort.Slice(tags, func(i, j int) bool {
142+
return tags[i].Name < tags[j].Name
143+
})
144+
fd := &FieldDef{
115145
Name: ft.Name,
146+
Kind: ft.Type.Kind(),
147+
Type: ft.Type.String(),
116148
Tags: tags,
117149
}
150+
if ft.Type.Kind() == reflect.Struct {
151+
fd.Fields = newFieldDefs(ft.Type)
152+
}
153+
fields = append(fields, fd)
154+
}
155+
sort.Slice(fields, func(i, j int) bool {
156+
return fields[i].Name < fields[j].Name
157+
})
158+
return fields
159+
}
160+
161+
type ModelDef struct {
162+
Name string
163+
Fields []*FieldDef
164+
CheckSum string
165+
Version string
166+
167+
t reflect.Type
168+
}
169+
170+
func NewModelDef(t reflect.Type) *ModelDef {
171+
if t.Kind() == reflect.Ptr {
172+
t = t.Elem()
118173
}
174+
if t.NumField() == 0 {
175+
panic("must have at least 1 struct field")
176+
}
177+
m := &ModelDef{
178+
Name: t.Name(),
179+
Fields: newFieldDefs(t),
180+
t: t,
181+
}
182+
if !m.hasPrimaryKey() {
183+
panic("must specify a primary key")
184+
}
185+
pk := m.getPrimaryKey()
186+
vt := pk.getTag("v")
187+
if vt == nil {
188+
vt = &Tag{Name: "v", Value: "0"}
189+
pk.Tags = append(pk.Tags, vt)
190+
}
191+
m.Version = vt.Value
192+
m.CheckSum = SchemaCheckSum(m)
119193
return m
120194
}
121195

@@ -127,18 +201,45 @@ func (m *ModelDef) New() *reflect.Value {
127201
return &v
128202
}
129203

204+
func (m *ModelDef) getPrimaryKey() *FieldDef {
205+
for _, f := range m.Fields {
206+
if f.isPrimaryKey() {
207+
return f
208+
}
209+
}
210+
return nil
211+
}
212+
213+
func (m *ModelDef) hasPrimaryKey() bool {
214+
return m.getPrimaryKey() != nil
215+
}
216+
217+
func (m *ModelDef) getFieldByName(name string) (*FieldDef, bool) {
218+
for _, f := range m.Fields {
219+
if f.Name == name {
220+
return f, true
221+
}
222+
}
223+
return nil, false
224+
}
225+
226+
func (m *ModelDef) String() string {
227+
return m.t.String()
228+
}
229+
130230
type Field struct {
131231
*FieldDef
132-
value reflect.Value
232+
233+
v reflect.Value
133234
}
134235

135236
func (f *Field) isZero() bool {
136-
return f.value.IsValid() && reflect.DeepEqual(f.value.Interface(), reflect.Zero(f.value.Type()).Interface())
237+
return f.v.IsValid() && reflect.DeepEqual(f.v.Interface(), reflect.Zero(f.v.Type()).Interface())
137238
}
138239

139240
type ModelItem struct {
140241
*ModelDef
141-
Fields map[string]*Field
242+
Fields []*Field
142243
}
143244

144245
func NewModelItem(v reflect.Value) *ModelItem {
@@ -148,13 +249,13 @@ func NewModelItem(v reflect.Value) *ModelItem {
148249
}
149250
m := &ModelItem{
150251
ModelDef: NewModelDef(v.Type()),
151-
Fields: make(map[string]*Field),
252+
Fields: make([]*Field, 0),
152253
}
153-
for name, f := range m.ModelDef.Fields {
154-
m.Fields[name] = &Field{
254+
for _, f := range m.ModelDef.Fields {
255+
m.Fields = append(m.Fields, &Field{
155256
FieldDef: f,
156-
value: v.FieldByName(name),
157-
}
257+
v: v.FieldByName(f.Name),
258+
})
158259
}
159260
return m
160261
}
@@ -167,3 +268,54 @@ func (m *ModelItem) getPrimaryKey() (*Field, error) {
167268
}
168269
return nil, ErrNoPrimaryKey
169270
}
271+
272+
func schemaCheckSumFieldDef(f *FieldDef) string {
273+
var sb strings.Builder
274+
sb.WriteString(f.String())
275+
for _, f := range f.Fields {
276+
switch f.Kind {
277+
case reflect.Struct:
278+
sb.WriteString(schemaCheckSumFieldDef(f))
279+
default:
280+
sb.WriteString(f.String())
281+
}
282+
}
283+
return sb.String()
284+
}
285+
286+
func SchemaCheckSum(m *ModelDef) string {
287+
var b bytes.Buffer
288+
for _, f := range m.Fields {
289+
b.WriteString(schemaCheckSumFieldDef(f))
290+
}
291+
h := sha1.Sum(b.Bytes())
292+
name := hex.EncodeToString(h[:])
293+
if len(name) > 5 {
294+
name = name[:5]
295+
}
296+
return strings.ToLower(name)
297+
}
298+
299+
func printFieldDef(f *FieldDef) {
300+
fmt.Println(f)
301+
for _, f := range f.Fields {
302+
switch f.Kind {
303+
case reflect.Struct:
304+
printFieldDef(f)
305+
default:
306+
fmt.Println(f)
307+
}
308+
}
309+
}
310+
311+
func PrintModelDef(m *ModelDef) {
312+
fmt.Println(m)
313+
for _, f := range m.Fields {
314+
switch f.Kind {
315+
case reflect.Struct:
316+
printFieldDef(f)
317+
default:
318+
fmt.Println(f)
319+
}
320+
}
321+
}

pkg/e2db/model_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package e2db_test
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
"testing"
7+
"time"
8+
9+
"github.com/criticalstack/e2d/pkg/e2db"
10+
)
11+
12+
type ModelEnum int
13+
14+
const (
15+
Invalid ModelEnum = iota
16+
EnumVal1
17+
EnumVal2
18+
)
19+
20+
type NestedStruct struct {
21+
Name string
22+
Count int
23+
}
24+
25+
type Model1 struct {
26+
Name string `e2db:"unique,required"`
27+
ID int `e2db:"id"`
28+
CreatedAt time.Time
29+
Stats NestedStruct
30+
Enum ModelEnum
31+
}
32+
33+
type Model2 struct {
34+
ID int `e2db:"id"`
35+
Name string `e2db:"unique,required"`
36+
CreatedAt time.Time
37+
Stats NestedStruct
38+
Enum ModelEnum
39+
}
40+
41+
func TestSchemaCheckSumArbitraryOrder(t *testing.T) {
42+
m := e2db.NewModelDef(reflect.TypeOf(&Model1{}))
43+
fmt.Println(m.String())
44+
fmt.Println(m.CheckSum)
45+
m = e2db.NewModelDef(reflect.TypeOf(&Model2{}))
46+
fmt.Println(m.String())
47+
fmt.Println(m.CheckSum)
48+
}

0 commit comments

Comments
 (0)