/
schema.go
181 lines (155 loc) · 3.81 KB
/
schema.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// Copyright 2014, Hǎiliàng Wáng. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package schema
/*
Field, Record classes are used to describe a struct value binded to a schema.
*/
import (
"fmt"
"reflect"
"regexp"
"strings"
"time"
)
// Field represents a field in a record.
type Field struct {
Name string
Value interface{}
IsKey bool
}
// NewField creates a Field given name, value and whether it is a primary key or not.
func NewField(name string, value interface{}, isKey bool) *Field {
v := reflect.ValueOf(value)
if v.Kind() == reflect.Ptr {
if v.IsNil() {
value = nil
} else {
value = v.Elem().Interface()
}
}
return &Field{
Name: camelToSnake(name),
Value: value,
IsKey: isKey}
}
// FieldSelector is a function that returns a boolean value given a Field, for
// selecting one or multiple fields.
type FieldSelector func(*Field) bool
// Key is a FieldSelector for selecting a primary key.
func Key(f *Field) bool {
return f.IsKey
}
// NonKey is a FieldSelector for selecting a non-key field.
func NonKey(f *Field) bool {
return !f.IsKey
}
// DbType is a FieldSelector for selecting a Field with a value supported by
// the database driver.
func DbType(f *Field) bool {
switch f.Value.(type) {
case int, *int, string, *string, bool, *bool,
int8, int16, int32, int64,
*int8, *int16, *int32, *int64,
uint8, uint16, uint32, uint64,
*uint8, *uint16, *uint32, *uint64,
float32, float64,
*float32, *float64,
time.Time, *time.Time:
return true
}
return false
}
// NonNil is a FieldSelector for selecting a non-nil value.
func NonNil(f *Field) bool {
return f.Value != nil
}
// Fields is the slice of Field.
type Fields []*Field
// Filter method filters the Fields according to some FieldSelectors and returns
// the result.
func (fs Fields) Filter(conds ...FieldSelector) (fields Fields) {
nextField:
for _, field := range fs {
for _, cond := range conds {
if !cond(field) {
continue nextField
}
}
fields = append(fields, field)
}
return
}
// Values returns a slice of field values.
func (fs Fields) Values() []interface{} {
values := make([]interface{}, len(fs))
for i, f := range fs {
values[i] = f.Value
}
return values
}
// Record represents a table record including the table name.
type Record struct {
Name string // Name of the table
Fields Fields // Fields in the table
}
type sqlTag struct {
Pk bool
}
func parseSqlTag(tag string) *sqlTag {
sqlTag := &sqlTag{}
specs := strings.Split(tag, ",")
for _, spec := range specs {
if spec == "pk" {
sqlTag.Pk = true
}
}
return sqlTag
}
// NewRecord creates a Record from an object.
func NewRecord(s interface{}) *Record {
v := reflect.ValueOf(s)
if v.Kind() == reflect.Ptr {
if v.IsNil() {
return nil
}
v = v.Elem()
}
if v.Kind() != reflect.Struct {
panic(fmt.Errorf(
"record must be initialized from a struct. %v, %v", v.Kind(), v))
}
t := v.Type()
name := camelToSnake(t.Name())
// extract key value pairs
fields := make(Fields, t.NumField())
for i := 0; i < t.NumField(); i++ {
sqlTag := parseSqlTag(t.Field(i).Tag.Get("sql"))
fields[i] = NewField(t.Field(i).Name, v.Field(i).Interface(), sqlTag.Pk)
}
if len(fields) > 0 &&
fields[0].Name == "id" {
fields[0].IsKey = true
}
return &Record{
Name: name,
Fields: fields,
}
}
// SetKey sets the primary keys of a record.
func (r *Record) SetKey(names ...string) *Record {
for _, name := range names {
name = camelToSnake(name)
for i := range r.Fields {
if r.Fields[i].Name == name {
r.Fields[i].IsKey = true
}
}
}
return r
}
var lowerUpper = regexp.MustCompile(`([\P{Lu}])([\p{Lu}])`)
// convert name from camel case to snake case
func camelToSnake(s string) string {
return strings.ToLower(lowerUpper.ReplaceAllString(s, `${1}_${2}`))
}