Browse Source

Mapping rewrite. Concurrect mapping

Joachim M. Giæver 4 years ago
parent
commit
c9abecf2f9
9 changed files with 714 additions and 134 deletions
  1. 35 0
      README.md
  2. 20 0
      column.go
  3. 71 11
      field.go
  4. 4 4
      mappable.go
  5. 222 33
      mapper.go
  6. 0 15
      relaltions.go
  7. 45 0
      relation.go
  8. 131 0
      strings.go
  9. 186 71
      table.go

+ 35 - 0
README.md

@@ -1,2 +1,37 @@
 # orm
 
+This is a work in progress project. Not meant to be used, as only the mapping part is _«ready»_. Check back once up in your lifetime and maybe you'll find something that works.
+
+The only usefull information you'll get from this now is just an output representation of the relation, e.g
+
+```
+User (`users`):
+ - Fields
+	* id:    `id`, uint64
+	* email: `email`, string
+ - HasMany
+	UserProperty AS `properties` ON `properties`.`user_id` WITH `id`
+	Plan AS `plans` ON `plans`.`user_id` WITH `id`
+
+UserProperty (`user_properties`):
+ - Fields
+	* id:      `id`, uint64
+	* userId:  `user_id`, uint64
+	* name:    `name`, string
+	* address: `address`, string
+	* lat:     `lat`, float64
+	* lng:     `lng`, float64
+ - BelongsTo
+	User AS `user` ON `user`.`id` WITH `user_id`
+ - HasOne
+	Plan AS `plan` ON `plan`.`user_property_id` WITH `id`
+
+Plan (`plans`):
+ - Fields
+	* id:             `id`, uint64
+	* userId:         `user_id`, uint64
+	* userPropertyId: `user_property_id`, uint64
+ - BelongsTo
+	User AS `user` ON `user`.`id` WITH `user_id`
+	UserProperty AS `userProperty` ON `userProperty`.`id` WITH `user_property_id`
+```

+ 20 - 0
column.go

@@ -0,0 +1,20 @@
+package orm
+
+// column is a column in a table
+type column struct {
+	field
+	ref string
+}
+
+// GetName returns the name. If relation is given, it
+// will return `tbl`.`col` retpr
+func (c column) GetName(r ...*relation) string {
+	if len(r) > 0 {
+		return "`" + r[0].f.getFieldName() + "`.`" + c.ref + "`"
+	}
+
+	return "`" + c.ref + "`"
+}
+
+// columns is the collection of all columns in a table
+type columns []column

+ 71 - 11
field.go

@@ -5,22 +5,82 @@ import (
 	"strings"
 )
 
+// fieldType is the type we expect
+type fieldType reflect.Kind
+
+// Relation or Column defines type of mapping
+const (
+	Relation fieldType = (fieldType)(reflect.Struct)
+	Column
+)
+
+// field holds necessary data on specific field
 type field struct {
-	name string
-	ref  string
-	typ  reflect.Kind
+	sf reflect.StructField
+	t  reflect.Type
+	v  reflect.Value
+}
+
+// getFieldName returns the field name within the struct,
+// e.g struct { fieldName fieldType }{}
+func (f *field) getFieldName() string {
+	return f.sf.Name
+}
+
+// getFieldType returns the field type within the struct,
+// e.g struct { fieldName fieldType }{}
+func (f *field) getFieldType() string {
+	return f.t.Name()
+}
+
+// getType returns the type; Relation or Column
+func (f *field) getType() fieldType {
+	return (fieldType)(f.t.Kind())
 }
 
-func (f field) String() string {
-	return f.name + " (" + f.ref + "|" + f.typ.String() + ")"
+// getKind returns the actual reflect.Kind
+func (f *field) getKind() reflect.Kind {
+	return f.t.Kind()
 }
 
-type fields []field
+// getTag returns tags on this field, can be
+// db:"siglevalue" (bool type?) or db:"key:value"
+func (f *field) getTag(key string) (string, bool) {
+	if tag := f.sf.Tag.Get(Prefix); len(tag) != 0 {
+		tags := strings.Split(tag, ";")
+
+		for _, tag := range tags {
+			kv := strings.Split(tag, ":")
+			kv[0] = strings.Trim(kv[0], " ")
+
+			if len(kv) == 1 && kv[0] == key {
+				return kv[0], true
+			}
+
+			if len(kv) == 2 && kv[0] == key {
+				kv[1] = strings.Trim(kv[1], " ")
+				return kv[1], true
+			}
+		}
+	}
+	return "", false
+}
+
+// hasTags checks if it has all keys
+func (f *field) hasTags(keys ...string) bool {
+	match := 0
+	for _, key := range keys {
+		if f.hasTag(key) {
+			match += 1
+		}
+	}
+	return len(keys) == match
+}
 
-func (fs fields) String() string {
-	s := []string{}
-	for _, f := range fs {
-		s = append(s, f.String())
+// hasTags checks if it has key
+func (f *field) hasTag(key string) bool {
+	if _, ok := f.getTag(key); ok {
+		return true
 	}
-	return strings.Join(s, ",")
+	return false
 }

+ 4 - 4
mappable.go

@@ -8,10 +8,10 @@ import (
 )
 
 type MappableInterface interface {
-	SetDb(*conn.DB) MappableInterface
+	SetDb(db *conn.DB) MappableInterface
 	GetDb() *conn.DB
 	GetTableMapperFn() MapperFn
-	GetFieldMapperFn() MapperFn
+	GetColumnMapperFn() MapperFn
 }
 
 type MapperFn func(string) string
@@ -29,7 +29,7 @@ func (m *Mappable) GetDb() *conn.DB {
 	return m.db
 }
 
-func (m Mappable) GetTableMapperFn() MapperFn {
+func (m *Mappable) GetTableMapperFn() MapperFn {
 	return func(t string) string {
 		s := []byte{}
 		for i := 0; i < len(t); i++ {
@@ -49,7 +49,7 @@ func (m Mappable) GetTableMapperFn() MapperFn {
 	}
 }
 
-func (m Mappable) GetFieldMapperFn() MapperFn {
+func (m *Mappable) GetColumnMapperFn() MapperFn {
 	return func(f string) string {
 		s := []byte{}
 		for i := 0; i < len(f); i++ {

+ 222 - 33
mapper.go

@@ -3,85 +3,274 @@ package orm
 import (
 	"fmt"
 	"reflect"
-	"strings"
 	"sync"
 )
 
-type tblmap map[string]*tbl
-
-func (t tblmap) String() string {
-	s := make([]string, 0)
-	for _, tbl := range t {
-		s = append(s, tbl.String())
-	}
-
-	return strings.Join(s, "\n")
-}
+// Prefix used for tagging
+var Prefix = "db"
 
 type mapper struct {
 	lock *sync.Mutex
 	wg   *sync.WaitGroup
-	tbls tblmap
+	tbls tables
+	cbs  *mapperCallbacks
+	init bool
 }
 
+// Global mapper context
 var ctx *mapper = &mapper{
 	lock: &sync.Mutex{},
 	wg:   &sync.WaitGroup{},
-	tbls: make(tblmap),
+	tbls: make(tables),
+	cbs: &mapperCallbacks{
+		ch:   make(chan mapperCallback),
+		list: make([]mapperCallback, 0),
+		cond: sync.NewCond(&sync.Mutex{}),
+	},
+	init: false,
 }
 
+// Map a table (struct) that implements MappableInterface
+// Note! Should be mapped in init-function from the that struct
 func Map(tbl MappableInterface) *mapper {
+
+	// Add to working group
 	ctx.wg.Add(1)
+
+	ctx.lock.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.getTbl(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.lock.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()
-		t.Unlock()
 	}()
+
 	return ctx
 }
 
+// WaitInit should be called in main() to wait for the mapping
+// to complete before start using the tables
 func WaitInit() {
-	fmt.Println("Waiting in main")
 	ctx.wg.Wait()
+	// Debug print
 	fmt.Println(ctx.tbls)
 }
 
-func (m *mapper) addTbl(t MappableInterface) *tbl {
+// hasTable checks for table
+func (m *mapper) hasTbl(n string) bool {
+	m.lock.Lock()
+	defer m.lock.Unlock()
+
+	_, ok := m.tbls[n]
+
+	return ok
+}
+
+// getTbl should only be called controller; Must lock after
+func (m *mapper) getTbl(n string) *table {
+	m.lock.Lock()
+	defer m.lock.Unlock()
+
+	if t, ok := m.tbls[n]; ok {
+		return t
+	}
+
+	return nil
+}
+
+// addTbl creates a new or returns an existing table; will write lock!
+func (m *mapper) addTbl(t MappableInterface) *table {
 	rt := reflect.TypeOf(t).Elem()
 
 	m.lock.Lock()
 	defer m.lock.Unlock()
 
 	if t, ok := m.tbls[rt.Name()]; ok {
-		return t.ReadLock()
+		return t.lock()
 	}
 
-	m.tbls[rt.Name()] = &tbl{
-		rt:     rt,
-		rv:     reflect.ValueOf(t).Elem(),
-		lock:   &sync.RWMutex{},
-		tFn:    t.GetTableMapperFn(),
-		fFn:    t.GetFieldMapperFn(),
+	m.tbls[rt.Name()] = &table{
+		rt:   rt,
+		rv:   reflect.ValueOf(t).Elem(),
+		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()].WriteLock()
+	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 m.isPossibleRelation(csf.Type.Elem()) {
+			return field{
+				sf: csf,
+				t:  csf.Type.Elem(),
+				v:  cv,
+			}
+		}
+	case reflect.Struct:
+		if m.isPossibleRelation(csf.Type) {
+			return field{
+				sf: csf,
+				t:  csf.Type,
+				v:  cv,
+			}
+		}
+	}
+	return field{
+		sf: csf,
+		t:  csf.Type,
+		v:  cv,
+	}
 }
 
-func (m *mapper) mapTbl(t *tbl) {
+// mapTbl will simply loop throug every field in the table
+func (m *mapper) mapTbl(t *table) {
 	for n := 0; n < t.getType().NumField(); n++ {
-		switch t.getType().Kind() {
-		case reflect.Ptr:
-		case reflect.Struct:
-			if f := t.getType().Field(n); !f.Anonymous {
-				t.add(t.getType().Field(n))
-			} else if t.getType().Field(n).Anonymous && t.getValue().Field(n).CanInterface() {
-				t.add(t.getType().Field(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)
+				}
 			}
-		default:
 		}
 	}
+	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)
 }

+ 0 - 15
relaltions.go

@@ -1,15 +0,0 @@
-package orm
-
-type relType int8
-
-const (
-	hasOne    relType = iota << 1
-	hasMany   relType = iota << 1
-	belongsTo relType = iota << 1
-)
-
-type rel struct {
-	*tbl
-}
-
-type relations map[relType]*rel

+ 45 - 0
relation.go

@@ -0,0 +1,45 @@
+package orm
+
+import (
+	"sync"
+)
+
+// relType describes relation type
+type relType int8
+
+// Of these three
+const (
+	hasOne    relType = iota << 1
+	hasMany   relType = iota << 1
+	belongsTo relType = iota << 1
+)
+
+// relation describes how tables are related
+type relation struct {
+	*table
+	f   field
+	on  column
+	key column
+}
+
+// relations holds all relation on types
+type relations struct {
+	rmap map[relType][]relation
+	m    *sync.Mutex
+}
+
+// addRelation to collection. Requires fields to have equal type
+func (r relations) addRelation(rt relType, rel relation) {
+	r.m.Lock()
+	defer r.m.Unlock()
+
+	if _, ok := r.rmap[rt]; !ok {
+		r.rmap[rt] = make([]relation, 0)
+	}
+
+	if rel.on.getKind() != rel.key.getKind() {
+		return
+	}
+
+	r.rmap[rt] = append(r.rmap[rt], rel)
+}

+ 131 - 0
strings.go

@@ -0,0 +1,131 @@
+package orm
+
+import (
+	"fmt"
+	"strings"
+)
+
+/**
+ * Just a collectiong on strings (output) methods
+ * for many of the types in the package
+ *
+ * Moved here for better readability in source code.
+ **/
+
+func (f fieldType) String() string {
+	switch f {
+	case Relation:
+		return "Relation"
+	}
+	return "Column"
+}
+
+func (t tables) String() string {
+	s := make([]string, 0)
+	for _, tbl := range t {
+		s = append(s, tbl.String())
+	}
+
+	return strings.Join(s, "\n\n")
+}
+
+func (t *table) String() string {
+	t.Lock()
+	defer t.Unlock()
+	return fmt.Sprintf("%s (`%s`):%s%s",
+		t.getStructName(), t.getName(),
+		func() string { // Print columns
+			s := []string{}
+			cols := strings.Split(t.cols.String(), "\n")
+			if len(cols) > 0 {
+				s = append(s, "\n - Fields")
+			}
+			max := 0
+			for _, col := range cols {
+				col := strings.Split(col, ":")
+				if len(col[0]) > max {
+					max = len(col[0])
+				}
+
+				s = append(s, strings.Join(col, ":"))
+			}
+			for i, col := range s {
+				col := strings.Split(col, ":")
+				if len(col) != 2 || len(col[0]) == max {
+					continue
+				}
+				s[i] = col[0] + ":" + strings.Repeat(" ", max-len(col[0])) + col[1]
+			}
+			return strings.Join(s, "\n\t")
+		}(),
+		func() string {
+			if len(t.rels.rmap) == 0 {
+				return ""
+			}
+
+			s := []string{}
+			rels := strings.Split(t.rels.String(), "\n")
+
+			for _, rel := range rels {
+				xrel := strings.Split(rel, ":")
+				s = append(s, " - "+xrel[0])
+				for _, rel := range strings.Split(xrel[1], ",") {
+					s = append(s, "\t"+strings.Trim(rel, " "))
+				}
+			}
+
+			return "\n" + strings.Join(s, "\n")
+		}(),
+	)
+}
+
+func (c column) String() string {
+	return c.getFieldName() + ": " + c.GetName() + ", " + c.getKind().String()
+}
+
+func (cs columns) String() string {
+	s := []string{}
+	for _, c := range cs {
+		s = append(s, c.String())
+	}
+	return "* " + strings.Join(s, "\n* ")
+}
+
+func (t relType) String() string {
+	switch t {
+	case hasOne:
+		return "HasOne"
+	case hasMany:
+		return "HasMany"
+	case belongsTo:
+		return "BelongsTo"
+	}
+	return "Not mapped"
+}
+
+func (r *relation) String() string {
+	return r.f.getFieldType() + " AS `" + r.f.getFieldName() + "` ON " + r.on.GetName(r) + " WITH " + r.key.GetName()
+}
+
+func (r relations) String() string {
+	r.m.Lock()
+	defer r.m.Unlock()
+
+	s := []string{}
+	for t, rels := range r.rmap {
+
+		if len(rels) == 0 {
+			continue
+		}
+
+		ss := []string{}
+
+		for _, rel := range rels {
+			ss = append(ss, rel.String())
+		}
+
+		s = append(s, t.String()+":"+strings.Join(ss, ", "))
+	}
+
+	return strings.Join(s, "\n")
+}

+ 186 - 71
table.go

@@ -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