123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- package orm
- import (
- "fmt"
- "reflect"
- "sync"
- )
- // Prefix used for tagging
- var Prefix = "db"
- type mapper struct {
- cond *sync.Cond
- wg *sync.WaitGroup
- tbls tables
- cbs *mapperCallbacks
- init bool
- }
- // Global mapper context
- var ctx *mapper = &mapper{
- cond: sync.NewCond(&sync.Mutex{}),
- wg: &sync.WaitGroup{},
- tbls: make(tables),
- cbs: &mapperCallbacks{
- ch: make(chan mapperCallback),
- list: make([]mapperCallback, 0),
- cond: sync.NewCond(&sync.Mutex{}),
- },
- init: false,
- }
- func Mapper() *mapper {
- return ctx
- }
- // Map a table (struct) that implements MappableInterface
- // Note! Should be mapped in init-function from the that struct
- func Map(tbl MappableInterface) *mapper {
- fmt.Println("Begin <mapping>")
- // Add to working group
- ctx.wg.Add(1)
- ctx.cond.L.Lock()
- // Kick off some routines on first call
- if !ctx.init {
- ctx.init = true
- // Go routine to receive mapping-calls between mapping routines
- go func() {
- for {
- // Receive callback from tables during mapping
- select {
- case q := <-ctx.cbs.ch:
- // Add to callback queue until table is mapped
- ctx.wg.Add(1)
- ctx.cbs.lock()
- ctx.cbs.add(q)
- ctx.cbs.unlock()
- ctx.cbs.cond.Broadcast()
- }
- }
- }()
- // Routine to communicate between other mapping routines
- go func() {
- for {
- // Loop on condition as long as empty
- ctx.cbs.lock()
- for ctx.cbs.length() == 0 {
- ctx.cbs.cond.Wait()
- }
- // Loop through all new callbacks
- for i, l := 0, ctx.cbs.length(); i < l; i++ {
- cb := ctx.cbs.get(l - i - 1)
- // Ensure callback is ran when columns are mapped
- if t := ctx.getTblByName(cb.to); t != nil && t.isMapped() {
- // Remove callback from slice
- ctx.cbs.remove(l - i - 1)
- // Kick off the callback; and lock table
- go func(t *table) {
- t.lock()
- defer t.unlock()
- defer ctx.wg.Done()
- cb.fn(t)
- }(t)
- }
- }
- ctx.cbs.unlock()
- }
- }()
- }
- ctx.cond.L.Unlock()
- // Start mapping of table
- go func() {
- t := ctx.addTbl(tbl)
- // Mapping should only occur once
- if !t.isMapped() {
- ctx.mapTbl(t)
- ctx.cbs.cond.Broadcast()
- }
- // Unclock write to allow reading
- t.unlock()
- ctx.wg.Done()
- }()
- return ctx
- }
- // WaitInit should be called in main() to wait for the mapping
- // to complete before start using the tables
- func WaitInit() {
- ctx.wg.Wait()
- // Debug print
- fmt.Println("Done <mapping>")
- //fmt.Println(ctx.tbls)
- }
- // hasTable checks for table
- func (m *mapper) hasTbl(n string) bool {
- m.cond.L.Lock()
- defer m.cond.L.Unlock()
- _, ok := m.tbls[n]
- return ok
- }
- // getTbl should only be called controller; Must lock after
- func (m *mapper) getTblByName(n string) *table {
- m.cond.L.Lock()
- defer m.cond.L.Unlock()
- if t, ok := m.tbls[n]; ok {
- return t
- }
- return nil
- }
- func (m *mapper) getTbl(t MappableInterface) *table {
- m.cond.L.Lock()
- defer m.cond.L.Unlock()
- for tbl, ok := m.tbls[reflect.TypeOf(t).Elem().Name()]; ; {
- if tbl == nil || !ok || !tbl.isMapped() {
- m.cond.Wait()
- }
- return tbl
- }
- }
- // addTbl creates a new or returns an existing table; will write lock!
- func (m *mapper) addTbl(t MappableInterface) *table {
- m.cond.L.Lock()
- defer m.cond.L.Unlock()
- rt := reflect.TypeOf(t).Elem()
- if t, ok := m.tbls[rt.Name()]; ok {
- return t.lock()
- }
- m.tbls[rt.Name()] = &table{
- rt: rt,
- rv: reflect.ValueOf(t),
- l: &sync.RWMutex{},
- tFn: t.GetTableMapperFn(),
- cFn: t.GetColumnMapperFn(),
- cols: make(columns, 0),
- rels: relations{
- m: &sync.Mutex{},
- rmap: make(map[relType][]relation),
- },
- mapped: false,
- }
- return m.tbls[rt.Name()].lock()
- }
- // isPossibleRelation determine if fields in table implements MappableInterface
- // then its possible a relation
- func (m *mapper) isPossibleRelation(t reflect.Type) bool {
- return reflect.New(t).Type().Implements(
- reflect.TypeOf((*MappableInterface)(nil)).Elem(),
- )
- }
- // mapField will prepare any field in table struct for mapping
- func (m *mapper) mapField(t *table, csf reflect.StructField, cv reflect.Value) field {
- switch csf.Type.Kind() {
- case reflect.Ptr:
- fallthrough
- case reflect.Slice:
- fallthrough
- case reflect.Map:
- if x := csf.Type.Elem(); x.Kind() == reflect.Ptr {
- if m.isPossibleRelation(x.Elem()) {
- return field{
- sf: csf,
- t: x.Elem(),
- v: cv,
- ft: Relation,
- }
- }
- }
- if m.isPossibleRelation(csf.Type.Elem()) {
- return field{
- sf: csf,
- t: csf.Type.Elem(),
- v: cv,
- ft: Relation,
- }
- }
- case reflect.Struct:
- if m.isPossibleRelation(csf.Type) {
- return field{
- sf: csf,
- t: csf.Type,
- v: cv,
- ft: Relation,
- }
- }
- }
- return field{
- sf: csf,
- t: csf.Type,
- v: cv,
- ft: Column,
- }
- }
- // mapTbl will simply loop throug every field in the table
- func (m *mapper) mapTbl(t *table) {
- for n := 0; n < t.getType().NumField(); n++ {
- if sf := t.getType().Field(n); !sf.Anonymous {
- t.addField(m.mapField(t, sf, t.getValue().Field(n)), m.cbs.ch)
- } else if t.getType().Field(n).Anonymous && t.getValue().Field(n).CanInterface() {
- cv := t.getValue().Field(n)
- csf := reflect.TypeOf(cv.Interface())
- for i := 0; i < csf.NumField(); i++ {
- if !csf.Field(i).Anonymous {
- t.addField(m.mapField(t, csf.Field(i), cv.Field(i)), m.cbs.ch)
- }
- }
- }
- }
- t.mapped = true
- }
- // mappcerCallbackFn is used to communicate between mapping routines
- type mapperCallbackFn func(tbl *table)
- // mappcerCallback is used to communicate between mapping routines
- type mapperCallback struct {
- from, to string
- fn mapperCallbackFn
- }
- // mappcerCallbacks holds the main channel from main thread
- // and a queue for all callbacks
- type mapperCallbacks struct {
- ch chan mapperCallback
- list []mapperCallback
- cond *sync.Cond
- }
- /**
- * Below is just helper methods to make code cleaner
- */
- func (m *mapperCallbacks) lock() *mapperCallbacks {
- m.cond.L.Lock()
- return m
- }
- func (m *mapperCallbacks) unlock() {
- m.cond.L.Unlock()
- }
- func (m *mapperCallbacks) add(mf mapperCallback) {
- m.list = append(m.list, mf)
- }
- func (m *mapperCallbacks) get(i int) mapperCallback {
- return m.list[i]
- }
- func (m *mapperCallbacks) remove(i int) {
- m.list = append(m.list[:i], m.list[i+1:]...)
- }
- func (m *mapperCallbacks) length() int {
- return len(m.list)
- }
|