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

Commit d60b52a

Browse files
committed
[WIP] Schema versioning
1 parent 0253ac6 commit d60b52a

File tree

5 files changed

+255
-48
lines changed

5 files changed

+255
-48
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 {
@@ -67,6 +70,7 @@ var newRoles = []*Role{
6770
}
6871

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

pkg/e2db/model.go

Lines changed: 184 additions & 29 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,24 +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-
87-
func NewModelDef(t reflect.Type) *ModelDef {
88-
if t.Kind() == reflect.Ptr {
89-
t = t.Elem()
90-
}
91-
if t.NumField() == 0 {
92-
panic("must have at least 1 struct field")
93-
}
94-
m := &ModelDef{
95-
Name: t.Name(),
96-
Fields: make(map[string]*FieldDef),
97-
}
123+
func newFieldDefs(t reflect.Type) []*FieldDef {
124+
fields := make([]*FieldDef, 0)
98125
for i := 0; i < t.NumField(); i++ {
99126
ft := t.Field(i)
127+
if unicode.IsLower([]rune(ft.Name)[0]) {
128+
continue
129+
}
100130
tags := make([]*Tag, 0)
101131
if tagValue, ok := ft.Tag.Lookup("e2db"); ok {
102132
for _, t := range strings.Split(tagValue, ",") {
@@ -108,26 +138,100 @@ func NewModelDef(t reflect.Type) *ModelDef {
108138
}
109139
}
110140
}
111-
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{
112145
Name: ft.Name,
146+
Kind: ft.Type.Kind(),
147+
Type: ft.Type.String(),
113148
Tags: tags,
114149
}
150+
if ft.Type.Kind() == reflect.Struct {
151+
fd.Fields = newFieldDefs(ft.Type)
152+
}
153+
fields = append(fields, fd)
115154
}
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()
173+
}
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)
116193
return m
117194
}
118195

196+
func (m *ModelDef) getPrimaryKey() *FieldDef {
197+
for _, f := range m.Fields {
198+
if f.isPrimaryKey() {
199+
return f
200+
}
201+
}
202+
return nil
203+
}
204+
205+
func (m *ModelDef) hasPrimaryKey() bool {
206+
return m.getPrimaryKey() != nil
207+
}
208+
209+
func (m *ModelDef) getFieldByName(name string) (*FieldDef, bool) {
210+
for _, f := range m.Fields {
211+
if f.Name == name {
212+
return f, true
213+
}
214+
}
215+
return nil, false
216+
}
217+
218+
func (m *ModelDef) String() string {
219+
return m.t.String()
220+
}
221+
119222
type Field struct {
120223
*FieldDef
121-
value reflect.Value
224+
225+
v reflect.Value
122226
}
123227

124228
func (f *Field) isZero() bool {
125-
return f.value.IsValid() && reflect.DeepEqual(f.value.Interface(), reflect.Zero(f.value.Type()).Interface())
229+
return f.v.IsValid() && reflect.DeepEqual(f.v.Interface(), reflect.Zero(f.v.Type()).Interface())
126230
}
127231

128232
type ModelItem struct {
129233
*ModelDef
130-
Fields map[string]*Field
234+
Fields []*Field
131235
}
132236

133237
func NewModelItem(v reflect.Value) *ModelItem {
@@ -137,13 +241,13 @@ func NewModelItem(v reflect.Value) *ModelItem {
137241
}
138242
m := &ModelItem{
139243
ModelDef: NewModelDef(v.Type()),
140-
Fields: make(map[string]*Field),
244+
Fields: make([]*Field, 0),
141245
}
142-
for name, f := range m.ModelDef.Fields {
143-
m.Fields[name] = &Field{
246+
for _, f := range m.ModelDef.Fields {
247+
m.Fields = append(m.Fields, &Field{
144248
FieldDef: f,
145-
value: v.FieldByName(name),
146-
}
249+
v: v.FieldByName(f.Name),
250+
})
147251
}
148252
return m
149253
}
@@ -156,3 +260,54 @@ func (m *ModelItem) getPrimaryKey() (*Field, error) {
156260
}
157261
return nil, ErrNoPrimaryKey
158262
}
263+
264+
func schemaCheckSumFieldDef(f *FieldDef) string {
265+
var sb strings.Builder
266+
sb.WriteString(f.String())
267+
for _, f := range f.Fields {
268+
switch f.Kind {
269+
case reflect.Struct:
270+
sb.WriteString(schemaCheckSumFieldDef(f))
271+
default:
272+
sb.WriteString(f.String())
273+
}
274+
}
275+
return sb.String()
276+
}
277+
278+
func SchemaCheckSum(m *ModelDef) string {
279+
var b bytes.Buffer
280+
for _, f := range m.Fields {
281+
b.WriteString(schemaCheckSumFieldDef(f))
282+
}
283+
h := sha1.Sum(b.Bytes())
284+
name := hex.EncodeToString(h[:])
285+
if len(name) > 5 {
286+
name = name[:5]
287+
}
288+
return strings.ToLower(name)
289+
}
290+
291+
func printFieldDef(f *FieldDef) {
292+
fmt.Println(f)
293+
for _, f := range f.Fields {
294+
switch f.Kind {
295+
case reflect.Struct:
296+
printFieldDef(f)
297+
default:
298+
fmt.Println(f)
299+
}
300+
}
301+
}
302+
303+
func PrintModelDef(m *ModelDef) {
304+
fmt.Println(m)
305+
for _, f := range m.Fields {
306+
switch f.Kind {
307+
case reflect.Struct:
308+
printFieldDef(f)
309+
default:
310+
fmt.Println(f)
311+
}
312+
}
313+
}

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+
}

pkg/e2db/query.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ func (q *query) Count(fieldName string, data interface{}) (int64, error) {
219219
if err := q.t.tableMustExist(); err != nil {
220220
return 0, err
221221
}
222-
f, ok := q.t.meta.Fields[fieldName]
222+
f, ok := q.t.meta.getFieldByName(fieldName)
223223
if !ok {
224224
return 0, errors.Wrap(ErrInvalidField, fieldName)
225225
}
@@ -246,7 +246,7 @@ func (q *query) Find(fieldName string, data interface{}, to interface{}) error {
246246
if err := q.t.validateSchema(v.Type()); err != nil {
247247
return err
248248
}
249-
f, ok := q.t.meta.Fields[fieldName]
249+
f, ok := q.t.meta.getFieldByName(fieldName)
250250
if !ok {
251251
return errors.Wrap(ErrInvalidField, fieldName)
252252
}
@@ -257,15 +257,15 @@ func (q *query) Find(fieldName string, data interface{}, to interface{}) error {
257257
if v.Type().Kind() == reflect.Slice {
258258
return q.findManyByIndex(key.Indexes(q.t.meta.Name, f.Name, k), v)
259259
}
260-
switch f.Type() {
260+
switch f.indexType() {
261261
case PrimaryKey:
262262
return q.findOneByPrimaryKey(key.ID(q.t.meta.Name, k), v)
263263
case UniqueIndex:
264264
return q.findOneByUniqueIndex(key.Indexes(q.t.meta.Name, f.Name, k), v)
265265
case SecondaryIndex:
266266
return q.findOneBySecondaryIndex(key.Indexes(q.t.meta.Name, f.Name, k), v)
267267
default:
268-
return errors.Errorf("field %#v has invalid index type: %#v", fieldName, f.Type())
268+
return errors.Errorf("field %#v has invalid index type: %#v", fieldName, f.indexType())
269269
}
270270
}
271271

0 commit comments

Comments
 (0)