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 ") // 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 ") //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) }