|
@@ -1,111 +1,226 @@
|
|
|
package orm
|
|
|
|
|
|
import (
|
|
|
- "fmt"
|
|
|
- "math/rand"
|
|
|
"reflect"
|
|
|
- "strings"
|
|
|
"sync"
|
|
|
- "time"
|
|
|
+
|
|
|
+ "github.com/go-openapi/inflect"
|
|
|
)
|
|
|
|
|
|
-type tbl struct {
|
|
|
- rt reflect.Type
|
|
|
- rv reflect.Value
|
|
|
- lock *sync.RWMutex
|
|
|
- name string
|
|
|
- tFn MapperFn
|
|
|
- fFn MapperFn
|
|
|
- f fields
|
|
|
- rel relations
|
|
|
- mapped bool
|
|
|
+type table struct {
|
|
|
+ rt reflect.Type
|
|
|
+ rv reflect.Value
|
|
|
+ l *sync.RWMutex
|
|
|
+ tFn, cFn MapperFn
|
|
|
+ cols columns
|
|
|
+ rels relations
|
|
|
+ mapped bool
|
|
|
}
|
|
|
|
|
|
-func (t *tbl) GetName() string {
|
|
|
- t.ReadLock()
|
|
|
- defer t.Unlock()
|
|
|
- return t.getName()
|
|
|
+// getPrimaryKey tries to find primary key
|
|
|
+func (t *table) getPrimaryKey() *column {
|
|
|
+ var pkey *column
|
|
|
+ if c := t.hasTaggedColumn("primary"); c != nil {
|
|
|
+ pkey = c
|
|
|
+ } else if c := t.hasColumn("Id"); c != nil {
|
|
|
+ pkey = c
|
|
|
+ }
|
|
|
+ return pkey
|
|
|
}
|
|
|
|
|
|
-func (t *tbl) getName() string {
|
|
|
- return t.getType().Name()
|
|
|
+// hasTaggetColumn checks for a collumn tagget as
|
|
|
+func (t *table) hasTaggedColumn(ct string) *column {
|
|
|
+ for _, col := range t.cols {
|
|
|
+ if col.hasTag(ct) {
|
|
|
+ return &col
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
-func (t *tbl) GetTableName() string {
|
|
|
- t.ReadLock()
|
|
|
- defer t.Unlock()
|
|
|
- return t.getTable()
|
|
|
+// hasColumn checks for a column by name
|
|
|
+func (t *table) hasColumn(c string) *column {
|
|
|
+ for _, col := range t.cols {
|
|
|
+ if col.ref == t.cFn(c) {
|
|
|
+ return &col
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
-func (t *tbl) getTable() string {
|
|
|
- if len(t.name) == 0 {
|
|
|
- t.name = t.tFn(t.getType().Name())
|
|
|
+// addField from struct; both «relations» and «columns»
|
|
|
+// Communicates over a channel, to do relation mapping
|
|
|
+func (t *table) addField(f field, cbCh chan<- mapperCallback) {
|
|
|
+
|
|
|
+ if f.hasTags("omit", "-") {
|
|
|
+ return // Skip "omitted" and "dashed" fields
|
|
|
}
|
|
|
- return t.name
|
|
|
-}
|
|
|
|
|
|
-func (t *tbl) String() string {
|
|
|
- return fmt.Sprintf("%s (`%s`):%s",
|
|
|
- t.getName(), t.getTable(),
|
|
|
- func() string {
|
|
|
- s := strings.Split(t.f.String(), ",")
|
|
|
- if len(s) <= 1 {
|
|
|
- return " " + t.f.String()
|
|
|
+ switch f.getType() {
|
|
|
+ case Relation:
|
|
|
+
|
|
|
+ // Make callback to itself, relations should be mapped after columns
|
|
|
+ cbCh <- mapperCallback{
|
|
|
+ from: t.getStructName(),
|
|
|
+ to: t.getStructName(),
|
|
|
+
|
|
|
+ fn: func(self *table) {
|
|
|
+ // Get Primary Key
|
|
|
+ pkey := t.getPrimaryKey()
|
|
|
+
|
|
|
+ // Or at least try to
|
|
|
+ if pkey == nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // Predict related table name
|
|
|
+ rtbl := f.getFieldType()
|
|
|
+
|
|
|
+ // And check for tag
|
|
|
+ if tag, ok := f.getTag("table"); ok {
|
|
|
+ rtbl = tag
|
|
|
+ }
|
|
|
+
|
|
|
+ // Predict column name
|
|
|
+ cn := f.getFieldName() + "Id"
|
|
|
+
|
|
|
+ // And check for tag
|
|
|
+ if tag, ok := f.getTag("fkey"); ok {
|
|
|
+ cn = tag
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if it contains reference itself
|
|
|
+ if c := self.hasColumn(cn); c != nil {
|
|
|
+ // Make a call to load related table into scope;
|
|
|
+ // we need its addr and must be loaded from mapper
|
|
|
+ cbCh <- mapperCallback{
|
|
|
+ from: self.getStructName(),
|
|
|
+ to: rtbl,
|
|
|
+ fn: func(tbl *table) {
|
|
|
+ key := tbl.getPrimaryKey()
|
|
|
+ self.rels.addRelation(belongsTo, relation{tbl, f, *key, *c})
|
|
|
+ },
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+
|
|
|
+ // Or predict column name in related table
|
|
|
+ cn = self.getStructName() + "Id"
|
|
|
+
|
|
|
+ // Check for reference tag
|
|
|
+ if tag, ok := f.getTag("ref"); ok {
|
|
|
+ cn = tag
|
|
|
+ }
|
|
|
+
|
|
|
+ // Make a callback to the related table to check for relationg
|
|
|
+ cbCh <- mapperCallback{
|
|
|
+ from: self.getStructName(),
|
|
|
+ to: rtbl,
|
|
|
+ fn: func(tbl *table) {
|
|
|
+
|
|
|
+ // Check for relation on column mane
|
|
|
+ if c := tbl.hasColumn(cn); c != nil {
|
|
|
+
|
|
|
+ // Predict the relations is «hasOne»
|
|
|
+ has := hasOne
|
|
|
+
|
|
|
+ // Try to predict (or simply guess) with pluralization, if «hasMany»
|
|
|
+ if inflect.Pluralize(f.getFieldName()) == f.getFieldName() {
|
|
|
+ has = hasMany
|
|
|
+ }
|
|
|
+
|
|
|
+ // Override with tagging if specified
|
|
|
+ if tag, ok := f.getTag("has"); ok {
|
|
|
+ switch tag {
|
|
|
+ case "many":
|
|
|
+ has = hasMany
|
|
|
+ case "one":
|
|
|
+ has = hasOne
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ self.rels.addRelation(has, relation{tbl, f, *c, *pkey})
|
|
|
+ }
|
|
|
+ },
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ default: // Add column
|
|
|
+ switch f.getKind() {
|
|
|
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
|
+ fallthrough // Support all Uint types
|
|
|
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
|
+ fallthrough // Support all Int types
|
|
|
+ case reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
|
|
|
+ fallthrough // Support all Float and Complex types
|
|
|
+ case reflect.String, reflect.Bool:
|
|
|
+ // Support string and boolean
|
|
|
+
|
|
|
+ // Map column name
|
|
|
+ dbf := t.cFn(f.getFieldName())
|
|
|
+
|
|
|
+ // override with tagging
|
|
|
+ if tag, ok := f.getTag("field"); ok {
|
|
|
+ dbf = t.cFn(tag)
|
|
|
}
|
|
|
|
|
|
- return "\n\t" + strings.Join(s, ",\n\t")
|
|
|
- }(),
|
|
|
- )
|
|
|
-}
|
|
|
-
|
|
|
-func (t *tbl) add(r reflect.StructField) {
|
|
|
- switch r.Type.Kind() {
|
|
|
- case reflect.Ptr:
|
|
|
- fmt.Println("Ptr", r)
|
|
|
- case reflect.Map:
|
|
|
- fmt.Println("Map", r)
|
|
|
- case reflect.Struct:
|
|
|
- fmt.Println("Struct", r)
|
|
|
- default:
|
|
|
- t.f = append(t.f, field{r.Name, t.fFn(r.Name), r.Type.Kind()})
|
|
|
+ t.cols = append(t.cols, column{
|
|
|
+ f, dbf,
|
|
|
+ })
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func (t *tbl) getType() reflect.Type {
|
|
|
+// getType returns the reflect.Type of the «table»
|
|
|
+func (t *table) getType() reflect.Type {
|
|
|
return t.rt
|
|
|
}
|
|
|
|
|
|
-func (t *tbl) getValue() reflect.Value {
|
|
|
+// getValue returns the reflect.Value of the «table»
|
|
|
+func (t *table) getValue() reflect.Value {
|
|
|
return t.rv
|
|
|
}
|
|
|
|
|
|
-func (t *tbl) isMapped() bool {
|
|
|
+// isMapped returns true when columns is mapped
|
|
|
+// Not relations! They will be mapped in separate routines
|
|
|
+func (t *table) isMapped() bool {
|
|
|
return t.mapped
|
|
|
}
|
|
|
|
|
|
-func (t *tbl) ReadLock() *tbl {
|
|
|
- t.lock.RLock()
|
|
|
+// Lock read lock
|
|
|
+func (t *table) Lock() *table {
|
|
|
+ t.l.RLock()
|
|
|
return t
|
|
|
}
|
|
|
|
|
|
-func (t *tbl) WriteLock() *tbl {
|
|
|
- if t.mapped {
|
|
|
- return nil
|
|
|
- }
|
|
|
- t.lock.Lock()
|
|
|
+// Unlock read lock
|
|
|
+func (t *table) Unlock() {
|
|
|
+ t.l.RUnlock()
|
|
|
+}
|
|
|
+
|
|
|
+// lock write lock
|
|
|
+func (t *table) lock() *table {
|
|
|
+ t.l.Lock()
|
|
|
return t
|
|
|
}
|
|
|
|
|
|
-func (t *tbl) Unlock() {
|
|
|
- if !t.mapped {
|
|
|
- t.mapped = true
|
|
|
- t.lock.Unlock()
|
|
|
- return
|
|
|
- }
|
|
|
- t.lock.RUnlock()
|
|
|
+// unlock write lock
|
|
|
+func (t *table) unlock() {
|
|
|
+ t.l.Unlock()
|
|
|
}
|
|
|
|
|
|
-func (t *tbl) randomWork(wg *sync.WaitGroup) *tbl {
|
|
|
- time.Sleep((time.Duration)(rand.Intn(100)*rand.Intn(100)) * time.Millisecond)
|
|
|
- return t
|
|
|
+// getStructName returns the name of the struct
|
|
|
+func (t *table) getStructName() string {
|
|
|
+ return t.getType().Name()
|
|
|
+}
|
|
|
+
|
|
|
+// getName returns the mapped table name
|
|
|
+// as identified in the database
|
|
|
+func (t *table) getName() string {
|
|
|
+ return t.tFn(t.getType().Name())
|
|
|
}
|
|
|
+
|
|
|
+// tables is simply a collection of tables
|
|
|
+type tables map[string]*table
|