ソースを参照

add some tests

xormplus 8 年 前
コミット
4473ab9228

+ 294 - 0
cache_lru.go

@@ -0,0 +1,294 @@
+// Copyright 2015 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+	"container/list"
+	"fmt"
+	"sync"
+	"time"
+
+	"github.com/xormplus/core"
+)
+
+// LRUCacher implments cache object facilities
+type LRUCacher struct {
+	idList   *list.List
+	sqlList  *list.List
+	idIndex  map[string]map[string]*list.Element
+	sqlIndex map[string]map[string]*list.Element
+	store    core.CacheStore
+	mutex    sync.Mutex
+	// maxSize    int
+	MaxElementSize int
+	Expired        time.Duration
+	GcInterval     time.Duration
+}
+
+// NewLRUCacher creates a cacher
+func NewLRUCacher(store core.CacheStore, maxElementSize int) *LRUCacher {
+	return NewLRUCacher2(store, 3600*time.Second, maxElementSize)
+}
+
+// NewLRUCacher2 creates a cache include different params
+func NewLRUCacher2(store core.CacheStore, expired time.Duration, maxElementSize int) *LRUCacher {
+	cacher := &LRUCacher{store: store, idList: list.New(),
+		sqlList: list.New(), Expired: expired,
+		GcInterval: core.CacheGcInterval, MaxElementSize: maxElementSize,
+		sqlIndex: make(map[string]map[string]*list.Element),
+		idIndex:  make(map[string]map[string]*list.Element),
+	}
+	cacher.RunGC()
+	return cacher
+}
+
+// RunGC run once every m.GcInterval
+func (m *LRUCacher) RunGC() {
+	time.AfterFunc(m.GcInterval, func() {
+		m.RunGC()
+		m.GC()
+	})
+}
+
+// GC check ids lit and sql list to remove all element expired
+func (m *LRUCacher) GC() {
+	//fmt.Println("begin gc ...")
+	//defer fmt.Println("end gc ...")
+	m.mutex.Lock()
+	defer m.mutex.Unlock()
+	var removedNum int
+	for e := m.idList.Front(); e != nil; {
+		if removedNum <= core.CacheGcMaxRemoved &&
+			time.Now().Sub(e.Value.(*idNode).lastVisit) > m.Expired {
+			removedNum++
+			next := e.Next()
+			//fmt.Println("removing ...", e.Value)
+			node := e.Value.(*idNode)
+			m.delBean(node.tbName, node.id)
+			e = next
+		} else {
+			//fmt.Printf("removing %d cache nodes ..., left %d\n", removedNum, m.idList.Len())
+			break
+		}
+	}
+
+	removedNum = 0
+	for e := m.sqlList.Front(); e != nil; {
+		if removedNum <= core.CacheGcMaxRemoved &&
+			time.Now().Sub(e.Value.(*sqlNode).lastVisit) > m.Expired {
+			removedNum++
+			next := e.Next()
+			//fmt.Println("removing ...", e.Value)
+			node := e.Value.(*sqlNode)
+			m.delIds(node.tbName, node.sql)
+			e = next
+		} else {
+			//fmt.Printf("removing %d cache nodes ..., left %d\n", removedNum, m.sqlList.Len())
+			break
+		}
+	}
+}
+
+// GetIds returns all bean's ids according to sql and parameter from cache
+func (m *LRUCacher) GetIds(tableName, sql string) interface{} {
+	m.mutex.Lock()
+	defer m.mutex.Unlock()
+	if _, ok := m.sqlIndex[tableName]; !ok {
+		m.sqlIndex[tableName] = make(map[string]*list.Element)
+	}
+	if v, err := m.store.Get(sql); err == nil {
+		if el, ok := m.sqlIndex[tableName][sql]; !ok {
+			el = m.sqlList.PushBack(newSQLNode(tableName, sql))
+			m.sqlIndex[tableName][sql] = el
+		} else {
+			lastTime := el.Value.(*sqlNode).lastVisit
+			// if expired, remove the node and return nil
+			if time.Now().Sub(lastTime) > m.Expired {
+				m.delIds(tableName, sql)
+				return nil
+			}
+			m.sqlList.MoveToBack(el)
+			el.Value.(*sqlNode).lastVisit = time.Now()
+		}
+		return v
+	}
+
+	m.delIds(tableName, sql)
+
+	return nil
+}
+
+// GetBean returns bean according tableName and id from cache
+func (m *LRUCacher) GetBean(tableName string, id string) interface{} {
+	m.mutex.Lock()
+	defer m.mutex.Unlock()
+	if _, ok := m.idIndex[tableName]; !ok {
+		m.idIndex[tableName] = make(map[string]*list.Element)
+	}
+	tid := genID(tableName, id)
+	if v, err := m.store.Get(tid); err == nil {
+		if el, ok := m.idIndex[tableName][id]; ok {
+			lastTime := el.Value.(*idNode).lastVisit
+			// if expired, remove the node and return nil
+			if time.Now().Sub(lastTime) > m.Expired {
+				m.delBean(tableName, id)
+				//m.clearIds(tableName)
+				return nil
+			}
+			m.idList.MoveToBack(el)
+			el.Value.(*idNode).lastVisit = time.Now()
+		} else {
+			el = m.idList.PushBack(newIDNode(tableName, id))
+			m.idIndex[tableName][id] = el
+		}
+		return v
+	}
+
+	// store bean is not exist, then remove memory's index
+	m.delBean(tableName, id)
+	//m.clearIds(tableName)
+	return nil
+}
+
+// clearIds clears all sql-ids mapping on table tableName from cache
+func (m *LRUCacher) clearIds(tableName string) {
+	if tis, ok := m.sqlIndex[tableName]; ok {
+		for sql, v := range tis {
+			m.sqlList.Remove(v)
+			m.store.Del(sql)
+		}
+	}
+	m.sqlIndex[tableName] = make(map[string]*list.Element)
+}
+
+// ClearIds clears all sql-ids mapping on table tableName from cache
+func (m *LRUCacher) ClearIds(tableName string) {
+	m.mutex.Lock()
+	defer m.mutex.Unlock()
+	m.clearIds(tableName)
+}
+
+func (m *LRUCacher) clearBeans(tableName string) {
+	if tis, ok := m.idIndex[tableName]; ok {
+		for id, v := range tis {
+			m.idList.Remove(v)
+			tid := genID(tableName, id)
+			m.store.Del(tid)
+		}
+	}
+	m.idIndex[tableName] = make(map[string]*list.Element)
+}
+
+// ClearBeans clears all beans in some table
+func (m *LRUCacher) ClearBeans(tableName string) {
+	m.mutex.Lock()
+	defer m.mutex.Unlock()
+	m.clearBeans(tableName)
+}
+
+// PutIds pus ids into table
+func (m *LRUCacher) PutIds(tableName, sql string, ids interface{}) {
+	m.mutex.Lock()
+	defer m.mutex.Unlock()
+	if _, ok := m.sqlIndex[tableName]; !ok {
+		m.sqlIndex[tableName] = make(map[string]*list.Element)
+	}
+	if el, ok := m.sqlIndex[tableName][sql]; !ok {
+		el = m.sqlList.PushBack(newSQLNode(tableName, sql))
+		m.sqlIndex[tableName][sql] = el
+	} else {
+		el.Value.(*sqlNode).lastVisit = time.Now()
+	}
+	m.store.Put(sql, ids)
+	if m.sqlList.Len() > m.MaxElementSize {
+		e := m.sqlList.Front()
+		node := e.Value.(*sqlNode)
+		m.delIds(node.tbName, node.sql)
+	}
+}
+
+// PutBean puts beans into table
+func (m *LRUCacher) PutBean(tableName string, id string, obj interface{}) {
+	m.mutex.Lock()
+	defer m.mutex.Unlock()
+	var el *list.Element
+	var ok bool
+
+	if el, ok = m.idIndex[tableName][id]; !ok {
+		el = m.idList.PushBack(newIDNode(tableName, id))
+		m.idIndex[tableName][id] = el
+	} else {
+		el.Value.(*idNode).lastVisit = time.Now()
+	}
+
+	m.store.Put(genID(tableName, id), obj)
+	if m.idList.Len() > m.MaxElementSize {
+		e := m.idList.Front()
+		node := e.Value.(*idNode)
+		m.delBean(node.tbName, node.id)
+	}
+}
+
+func (m *LRUCacher) delIds(tableName, sql string) {
+	if _, ok := m.sqlIndex[tableName]; ok {
+		if el, ok := m.sqlIndex[tableName][sql]; ok {
+			delete(m.sqlIndex[tableName], sql)
+			m.sqlList.Remove(el)
+		}
+	}
+	m.store.Del(sql)
+}
+
+// DelIds deletes ids
+func (m *LRUCacher) DelIds(tableName, sql string) {
+	m.mutex.Lock()
+	defer m.mutex.Unlock()
+	m.delIds(tableName, sql)
+}
+
+func (m *LRUCacher) delBean(tableName string, id string) {
+	tid := genID(tableName, id)
+	if el, ok := m.idIndex[tableName][id]; ok {
+		delete(m.idIndex[tableName], id)
+		m.idList.Remove(el)
+		m.clearIds(tableName)
+	}
+	m.store.Del(tid)
+}
+
+// DelBean deletes beans in some table
+func (m *LRUCacher) DelBean(tableName string, id string) {
+	m.mutex.Lock()
+	defer m.mutex.Unlock()
+	m.delBean(tableName, id)
+}
+
+type idNode struct {
+	tbName    string
+	id        string
+	lastVisit time.Time
+}
+
+type sqlNode struct {
+	tbName    string
+	sql       string
+	lastVisit time.Time
+}
+
+func genSQLKey(sql string, args interface{}) string {
+	return fmt.Sprintf("%v-%v", sql, args)
+}
+
+func genID(prefix string, id string) string {
+	return fmt.Sprintf("%v-%v", prefix, id)
+}
+
+func newIDNode(tbName string, id string) *idNode {
+	return &idNode{tbName, id, time.Now()}
+}
+
+func newSQLNode(tbName, sql string) *sqlNode {
+	return &sqlNode{tbName, sql, time.Now()}
+}

+ 51 - 0
cache_memory_store.go

@@ -0,0 +1,51 @@
+// Copyright 2015 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+	"sync"
+
+	"github.com/xormplus/core"
+)
+
+var _ core.CacheStore = NewMemoryStore()
+
+// MemoryStore represents in-memory store
+type MemoryStore struct {
+	store map[interface{}]interface{}
+	mutex sync.RWMutex
+}
+
+// NewMemoryStore creates a new store in memory
+func NewMemoryStore() *MemoryStore {
+	return &MemoryStore{store: make(map[interface{}]interface{})}
+}
+
+// Put puts object into store
+func (s *MemoryStore) Put(key string, value interface{}) error {
+	s.mutex.Lock()
+	defer s.mutex.Unlock()
+	s.store[key] = value
+	return nil
+}
+
+// Get gets object from store
+func (s *MemoryStore) Get(key string) (interface{}, error) {
+	s.mutex.RLock()
+	defer s.mutex.RUnlock()
+	if v, ok := s.store[key]; ok {
+		return v, nil
+	}
+
+	return nil, ErrNotExist
+}
+
+// Del deletes object
+func (s *MemoryStore) Del(key string) error {
+	s.mutex.Lock()
+	defer s.mutex.Unlock()
+	delete(s.store, key)
+	return nil
+}

+ 148 - 0
cache_test.go

@@ -0,0 +1,148 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCacheFind(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	type MailBox struct {
+		Id       int64
+		Username string
+		Password string
+	}
+
+	cacher := NewLRUCacher2(NewMemoryStore(), time.Hour, 10000)
+	testEngine.SetDefaultCacher(cacher)
+
+	assert.NoError(t, testEngine.Sync2(new(MailBox)))
+
+	var inserts = []*MailBox{
+		{
+			Username: "user1",
+			Password: "pass1",
+		},
+		{
+			Username: "user2",
+			Password: "pass2",
+		},
+	}
+	_, err := testEngine.Insert(inserts[0], inserts[1])
+	assert.NoError(t, err)
+
+	var boxes []MailBox
+	assert.NoError(t, testEngine.Find(&boxes))
+	assert.EqualValues(t, 2, len(boxes))
+	for i, box := range boxes {
+		assert.Equal(t, inserts[i].Id, box.Id)
+		assert.Equal(t, inserts[i].Username, box.Username)
+		assert.Equal(t, inserts[i].Password, box.Password)
+	}
+
+	boxes = make([]MailBox, 0, 2)
+	assert.NoError(t, testEngine.Find(&boxes))
+	assert.EqualValues(t, 2, len(boxes))
+	for i, box := range boxes {
+		assert.Equal(t, inserts[i].Id, box.Id)
+		assert.Equal(t, inserts[i].Username, box.Username)
+		assert.Equal(t, inserts[i].Password, box.Password)
+	}
+
+	testEngine.SetDefaultCacher(nil)
+}
+
+func TestCacheFind2(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	type MailBox2 struct {
+		Id       uint64
+		Username string
+		Password string
+	}
+
+	cacher := NewLRUCacher2(NewMemoryStore(), time.Hour, 10000)
+	testEngine.SetDefaultCacher(cacher)
+
+	assert.NoError(t, testEngine.Sync2(new(MailBox2)))
+
+	var inserts = []*MailBox2{
+		{
+			Username: "user1",
+			Password: "pass1",
+		},
+		{
+			Username: "user2",
+			Password: "pass2",
+		},
+	}
+	_, err := testEngine.Insert(inserts[0], inserts[1])
+	assert.NoError(t, err)
+
+	var boxes []MailBox2
+	assert.NoError(t, testEngine.Find(&boxes))
+	assert.EqualValues(t, 2, len(boxes))
+	for i, box := range boxes {
+		assert.Equal(t, inserts[i].Id, box.Id)
+		assert.Equal(t, inserts[i].Username, box.Username)
+		assert.Equal(t, inserts[i].Password, box.Password)
+	}
+
+	boxes = make([]MailBox2, 0, 2)
+	assert.NoError(t, testEngine.Find(&boxes))
+	assert.EqualValues(t, 2, len(boxes))
+	for i, box := range boxes {
+		assert.Equal(t, inserts[i].Id, box.Id)
+		assert.Equal(t, inserts[i].Username, box.Username)
+		assert.Equal(t, inserts[i].Password, box.Password)
+	}
+
+	testEngine.SetDefaultCacher(nil)
+}
+
+func TestCacheGet(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	type MailBox3 struct {
+		Id       uint64
+		Username string
+		Password string
+	}
+
+	cacher := NewLRUCacher2(NewMemoryStore(), time.Hour, 10000)
+	testEngine.SetDefaultCacher(cacher)
+
+	assert.NoError(t, testEngine.Sync2(new(MailBox3)))
+
+	var inserts = []*MailBox3{
+		{
+			Username: "user1",
+			Password: "pass1",
+		},
+	}
+	_, err := testEngine.Insert(inserts[0])
+	assert.NoError(t, err)
+
+	var box1 MailBox3
+	has, err := testEngine.Where("id = ?", inserts[0].Id).Get(&box1)
+	assert.NoError(t, err)
+	assert.True(t, has)
+	assert.EqualValues(t, "user1", box1.Username)
+	assert.EqualValues(t, "pass1", box1.Password)
+
+	var box2 MailBox3
+	has, err = testEngine.Where("id = ?", inserts[0].Id).Get(&box2)
+	assert.NoError(t, err)
+	assert.True(t, has)
+	assert.EqualValues(t, "user1", box2.Username)
+	assert.EqualValues(t, "pass1", box2.Password)
+
+	testEngine.SetDefaultCacher(nil)
+}

+ 21 - 0
helpler_time.go

@@ -0,0 +1,21 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import "time"
+
+const (
+	zeroTime0 = "0000-00-00 00:00:00"
+	zeroTime1 = "0001-01-01 00:00:00"
+)
+
+func formatTime(t time.Time) string {
+	return t.Format("2006-01-02 15:04:05")
+}
+
+func isTimeZero(t time.Time) bool {
+	return t.IsZero() || formatTime(t) == zeroTime0 ||
+		formatTime(t) == zeroTime1
+}

+ 966 - 0
processors_test.go

@@ -0,0 +1,966 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+	"errors"
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestBefore_Get(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	type BeforeTable struct {
+		Id   int64
+		Name string
+		Val  string `xorm:"-"`
+	}
+
+	assert.NoError(t, testEngine.Sync2(new(BeforeTable)))
+
+	cnt, err := testEngine.Insert(&BeforeTable{
+		Name: "test",
+	})
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+
+	var be BeforeTable
+	has, err := testEngine.Before(func(bean interface{}) {
+		bean.(*BeforeTable).Val = "val"
+	}).Get(&be)
+	assert.NoError(t, err)
+	assert.Equal(t, true, has)
+	assert.Equal(t, "val", be.Val)
+	assert.Equal(t, "test", be.Name)
+}
+
+func TestBefore_Find(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	type BeforeTable struct {
+		Id   int64
+		Name string
+		Val  string `xorm:"-"`
+	}
+
+	assert.NoError(t, testEngine.Sync2(new(BeforeTable)))
+
+	cnt, err := testEngine.Insert([]BeforeTable{
+		{Name: "test1"},
+		{Name: "test2"},
+	})
+	assert.NoError(t, err)
+	assert.EqualValues(t, 2, cnt)
+
+	var be []BeforeTable
+	err = testEngine.Before(func(bean interface{}) {
+		bean.(*BeforeTable).Val = "val"
+	}).Find(&be)
+	assert.NoError(t, err)
+	assert.Equal(t, 2, len(be))
+	assert.Equal(t, "val", be[0].Val)
+	assert.Equal(t, "test1", be[0].Name)
+	assert.Equal(t, "val", be[1].Val)
+	assert.Equal(t, "test2", be[1].Name)
+}
+
+type ProcessorsStruct struct {
+	Id int64
+
+	B4InsertFlag      int
+	AfterInsertedFlag int
+	B4UpdateFlag      int
+	AfterUpdatedFlag  int
+	B4DeleteFlag      int `xorm:"-"`
+	AfterDeletedFlag  int `xorm:"-"`
+	BeforeSetFlag     int `xorm:"-"`
+
+	B4InsertViaExt      int
+	AfterInsertedViaExt int
+	B4UpdateViaExt      int
+	AfterUpdatedViaExt  int
+	B4DeleteViaExt      int `xorm:"-"`
+	AfterDeletedViaExt  int `xorm:"-"`
+	AfterSetFlag        int `xorm:"-"`
+}
+
+func (p *ProcessorsStruct) BeforeInsert() {
+	p.B4InsertFlag = 1
+}
+
+func (p *ProcessorsStruct) BeforeUpdate() {
+	p.B4UpdateFlag = 1
+}
+
+func (p *ProcessorsStruct) BeforeDelete() {
+	p.B4DeleteFlag = 1
+}
+
+func (p *ProcessorsStruct) BeforeSet(col string, cell Cell) {
+	p.BeforeSetFlag = p.BeforeSetFlag + 1
+}
+
+func (p *ProcessorsStruct) AfterInsert() {
+	p.AfterInsertedFlag = 1
+}
+
+func (p *ProcessorsStruct) AfterUpdate() {
+	p.AfterUpdatedFlag = 1
+}
+
+func (p *ProcessorsStruct) AfterDelete() {
+	p.AfterDeletedFlag = 1
+}
+
+func (p *ProcessorsStruct) AfterSet(col string, cell Cell) {
+	p.AfterSetFlag = p.AfterSetFlag + 1
+}
+
+func TestProcessors(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	err := testEngine.DropTables(&ProcessorsStruct{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	p := &ProcessorsStruct{}
+
+	err = testEngine.CreateTables(&ProcessorsStruct{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	b4InsertFunc := func(bean interface{}) {
+		if v, ok := (bean).(*ProcessorsStruct); ok {
+			v.B4InsertViaExt = 1
+		} else {
+			t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?"))
+		}
+	}
+
+	afterInsertFunc := func(bean interface{}) {
+		if v, ok := (bean).(*ProcessorsStruct); ok {
+			v.AfterInsertedViaExt = 1
+		} else {
+			t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?"))
+		}
+	}
+
+	_, err = testEngine.Before(b4InsertFunc).After(afterInsertFunc).Insert(p)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p.B4InsertFlag == 0 {
+			t.Error(errors.New("B4InsertFlag not set"))
+		}
+		if p.AfterInsertedFlag == 0 {
+			t.Error(errors.New("B4InsertFlag not set"))
+		}
+		if p.B4InsertViaExt == 0 {
+			t.Error(errors.New("B4InsertFlag not set"))
+		}
+		if p.AfterInsertedViaExt == 0 {
+			t.Error(errors.New("AfterInsertedViaExt not set"))
+		}
+	}
+
+	p2 := &ProcessorsStruct{}
+	_, err = testEngine.Id(p.Id).Get(p2)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p2.B4InsertFlag == 0 {
+			t.Error(errors.New("B4InsertFlag not set"))
+		}
+		if p2.AfterInsertedFlag != 0 {
+			t.Error(errors.New("AfterInsertedFlag is set"))
+		}
+		if p2.B4InsertViaExt == 0 {
+			t.Error(errors.New("B4InsertViaExt not set"))
+		}
+		if p2.AfterInsertedViaExt != 0 {
+			t.Error(errors.New("AfterInsertedViaExt is set"))
+		}
+		if p2.BeforeSetFlag != 9 {
+			t.Error(fmt.Errorf("BeforeSetFlag is %d not 9", p2.BeforeSetFlag))
+		}
+		if p2.AfterSetFlag != 9 {
+			t.Error(fmt.Errorf("AfterSetFlag is %d not 9", p2.BeforeSetFlag))
+		}
+	}
+	// --
+
+	// test find processors
+	var p2Find []*ProcessorsStruct
+	err = testEngine.Find(&p2Find)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if len(p2Find) != 1 {
+			err = errors.New("Should get 1")
+			t.Error(err)
+		}
+		p21 := p2Find[0]
+		if p21.B4InsertFlag == 0 {
+			t.Error(errors.New("B4InsertFlag not set"))
+		}
+		if p21.AfterInsertedFlag != 0 {
+			t.Error(errors.New("AfterInsertedFlag is set"))
+		}
+		if p21.B4InsertViaExt == 0 {
+			t.Error(errors.New("B4InsertViaExt not set"))
+		}
+		if p21.AfterInsertedViaExt != 0 {
+			t.Error(errors.New("AfterInsertedViaExt is set"))
+		}
+		if p21.BeforeSetFlag != 9 {
+			t.Error(fmt.Errorf("BeforeSetFlag is %d not 9", p21.BeforeSetFlag))
+		}
+		if p21.AfterSetFlag != 9 {
+			t.Error(fmt.Errorf("AfterSetFlag is %d not 9", p21.BeforeSetFlag))
+		}
+	}
+	// --
+
+	// test find map processors
+	var p2FindMap = make(map[int64]*ProcessorsStruct)
+	err = testEngine.Find(&p2FindMap)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if len(p2FindMap) != 1 {
+			err = errors.New("Should get 1")
+			t.Error(err)
+		}
+		var p22 *ProcessorsStruct
+		for _, v := range p2FindMap {
+			p22 = v
+		}
+
+		if p22.B4InsertFlag == 0 {
+			t.Error(errors.New("B4InsertFlag not set"))
+		}
+		if p22.AfterInsertedFlag != 0 {
+			t.Error(errors.New("AfterInsertedFlag is set"))
+		}
+		if p22.B4InsertViaExt == 0 {
+			t.Error(errors.New("B4InsertViaExt not set"))
+		}
+		if p22.AfterInsertedViaExt != 0 {
+			t.Error(errors.New("AfterInsertedViaExt is set"))
+		}
+		if p22.BeforeSetFlag != 9 {
+			t.Error(fmt.Errorf("BeforeSetFlag is %d not 9", p22.BeforeSetFlag))
+		}
+		if p22.AfterSetFlag != 9 {
+			t.Error(fmt.Errorf("AfterSetFlag is %d not 9", p22.BeforeSetFlag))
+		}
+	}
+	// --
+
+	// test update processors
+	b4UpdateFunc := func(bean interface{}) {
+		if v, ok := (bean).(*ProcessorsStruct); ok {
+			v.B4UpdateViaExt = 1
+		} else {
+			t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?"))
+		}
+	}
+
+	afterUpdateFunc := func(bean interface{}) {
+		if v, ok := (bean).(*ProcessorsStruct); ok {
+			v.AfterUpdatedViaExt = 1
+		} else {
+			t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?"))
+		}
+	}
+
+	p = p2 // reset
+
+	_, err = testEngine.Before(b4UpdateFunc).After(afterUpdateFunc).Update(p)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p.B4UpdateFlag == 0 {
+			t.Error(errors.New("B4UpdateFlag not set"))
+		}
+		if p.AfterUpdatedFlag == 0 {
+			t.Error(errors.New("AfterUpdatedFlag not set"))
+		}
+		if p.B4UpdateViaExt == 0 {
+			t.Error(errors.New("B4UpdateViaExt not set"))
+		}
+		if p.AfterUpdatedViaExt == 0 {
+			t.Error(errors.New("AfterUpdatedViaExt not set"))
+		}
+	}
+
+	p2 = &ProcessorsStruct{}
+	_, err = testEngine.Id(p.Id).Get(p2)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p2.B4UpdateFlag == 0 {
+			t.Error(errors.New("B4UpdateFlag not set"))
+		}
+		if p2.AfterUpdatedFlag != 0 {
+			t.Error(errors.New("AfterUpdatedFlag is set: " + string(p.AfterUpdatedFlag)))
+		}
+		if p2.B4UpdateViaExt == 0 {
+			t.Error(errors.New("B4UpdateViaExt not set"))
+		}
+		if p2.AfterUpdatedViaExt != 0 {
+			t.Error(errors.New("AfterUpdatedViaExt is set: " + string(p.AfterUpdatedViaExt)))
+		}
+		if p2.BeforeSetFlag != 9 {
+			t.Error(fmt.Errorf("BeforeSetFlag is %d not 9", p2.BeforeSetFlag))
+		}
+		if p2.AfterSetFlag != 9 {
+			t.Error(fmt.Errorf("AfterSetFlag is %d not 9", p2.BeforeSetFlag))
+		}
+	}
+	// --
+
+	// test delete processors
+	b4DeleteFunc := func(bean interface{}) {
+		if v, ok := (bean).(*ProcessorsStruct); ok {
+			v.B4DeleteViaExt = 1
+		} else {
+			t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?"))
+		}
+	}
+
+	afterDeleteFunc := func(bean interface{}) {
+		if v, ok := (bean).(*ProcessorsStruct); ok {
+			v.AfterDeletedViaExt = 1
+		} else {
+			t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?"))
+		}
+	}
+
+	p = p2 // reset
+	_, err = testEngine.Before(b4DeleteFunc).After(afterDeleteFunc).Delete(p)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p.B4DeleteFlag == 0 {
+			t.Error(errors.New("B4DeleteFlag not set"))
+		}
+		if p.AfterDeletedFlag == 0 {
+			t.Error(errors.New("AfterDeletedFlag not set"))
+		}
+		if p.B4DeleteViaExt == 0 {
+			t.Error(errors.New("B4DeleteViaExt not set"))
+		}
+		if p.AfterDeletedViaExt == 0 {
+			t.Error(errors.New("AfterDeletedViaExt not set"))
+		}
+	}
+	// --
+
+	// test insert multi
+	pslice := make([]*ProcessorsStruct, 0)
+	pslice = append(pslice, &ProcessorsStruct{})
+	pslice = append(pslice, &ProcessorsStruct{})
+	cnt, err := testEngine.Before(b4InsertFunc).After(afterInsertFunc).Insert(&pslice)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if cnt != 2 {
+			t.Error(errors.New("incorrect insert count"))
+		}
+		for _, elem := range pslice {
+			if elem.B4InsertFlag == 0 {
+				t.Error(errors.New("B4InsertFlag not set"))
+			}
+			if elem.AfterInsertedFlag == 0 {
+				t.Error(errors.New("B4InsertFlag not set"))
+			}
+			if elem.B4InsertViaExt == 0 {
+				t.Error(errors.New("B4InsertFlag not set"))
+			}
+			if elem.AfterInsertedViaExt == 0 {
+				t.Error(errors.New("AfterInsertedViaExt not set"))
+			}
+		}
+	}
+
+	for _, elem := range pslice {
+		p = &ProcessorsStruct{}
+		_, err = testEngine.Id(elem.Id).Get(p)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		} else {
+			if p2.B4InsertFlag == 0 {
+				t.Error(errors.New("B4InsertFlag not set"))
+			}
+			if p2.AfterInsertedFlag != 0 {
+				t.Error(errors.New("AfterInsertedFlag is set"))
+			}
+			if p2.B4InsertViaExt == 0 {
+				t.Error(errors.New("B4InsertViaExt not set"))
+			}
+			if p2.AfterInsertedViaExt != 0 {
+				t.Error(errors.New("AfterInsertedViaExt is set"))
+			}
+			if p2.BeforeSetFlag != 9 {
+				t.Error(fmt.Errorf("BeforeSetFlag is %d not 9", p2.BeforeSetFlag))
+			}
+			if p2.AfterSetFlag != 9 {
+				t.Error(fmt.Errorf("AfterSetFlag is %d not 9", p2.BeforeSetFlag))
+			}
+		}
+	}
+	// --
+}
+
+func TestProcessorsTx(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	err := testEngine.DropTables(&ProcessorsStruct{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	err = testEngine.CreateTables(&ProcessorsStruct{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	// test insert processors with tx rollback
+	session := testEngine.NewSession()
+	err = session.Begin()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	p := &ProcessorsStruct{}
+	b4InsertFunc := func(bean interface{}) {
+		if v, ok := (bean).(*ProcessorsStruct); ok {
+			v.B4InsertViaExt = 1
+		} else {
+			t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?"))
+		}
+	}
+
+	afterInsertFunc := func(bean interface{}) {
+		if v, ok := (bean).(*ProcessorsStruct); ok {
+			v.AfterInsertedViaExt = 1
+		} else {
+			t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?"))
+		}
+	}
+	_, err = session.Before(b4InsertFunc).After(afterInsertFunc).Insert(p)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p.B4InsertFlag == 0 {
+			t.Error(errors.New("B4InsertFlag not set"))
+		}
+		if p.AfterInsertedFlag != 0 {
+			t.Error(errors.New("B4InsertFlag is set"))
+		}
+		if p.B4InsertViaExt == 0 {
+			t.Error(errors.New("B4InsertViaExt not set"))
+		}
+		if p.AfterInsertedViaExt != 0 {
+			t.Error(errors.New("AfterInsertedViaExt is set"))
+		}
+	}
+
+	err = session.Rollback()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p.B4InsertFlag == 0 {
+			t.Error(errors.New("B4InsertFlag not set"))
+		}
+		if p.AfterInsertedFlag != 0 {
+			t.Error(errors.New("B4InsertFlag is set"))
+		}
+		if p.B4InsertViaExt == 0 {
+			t.Error(errors.New("B4InsertViaExt not set"))
+		}
+		if p.AfterInsertedViaExt != 0 {
+			t.Error(errors.New("AfterInsertedViaExt is set"))
+		}
+	}
+	session.Close()
+	p2 := &ProcessorsStruct{}
+	_, err = testEngine.Id(p.Id).Get(p2)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p2.Id > 0 {
+			err = errors.New("tx got committed upon insert!?")
+			t.Error(err)
+			panic(err)
+		}
+	}
+	// --
+
+	// test insert processors with tx commit
+	session = testEngine.NewSession()
+	err = session.Begin()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	p = &ProcessorsStruct{}
+	_, err = session.Before(b4InsertFunc).After(afterInsertFunc).Insert(p)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p.B4InsertFlag == 0 {
+			t.Error(errors.New("B4InsertFlag not set"))
+		}
+		if p.AfterInsertedFlag != 0 {
+			t.Error(errors.New("AfterInsertedFlag is set"))
+		}
+		if p.B4InsertViaExt == 0 {
+			t.Error(errors.New("B4InsertViaExt not set"))
+		}
+		if p.AfterInsertedViaExt != 0 {
+			t.Error(errors.New("AfterInsertedViaExt is set"))
+		}
+	}
+
+	err = session.Commit()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p.B4InsertFlag == 0 {
+			t.Error(errors.New("B4InsertFlag not set"))
+		}
+		if p.AfterInsertedFlag == 0 {
+			t.Error(errors.New("AfterInsertedFlag not set"))
+		}
+		if p.B4InsertViaExt == 0 {
+			t.Error(errors.New("B4InsertViaExt not set"))
+		}
+		if p.AfterInsertedViaExt == 0 {
+			t.Error(errors.New("AfterInsertedViaExt not set"))
+		}
+	}
+	session.Close()
+	p2 = &ProcessorsStruct{}
+	_, err = testEngine.Id(p.Id).Get(p2)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p2.B4InsertFlag == 0 {
+			t.Error(errors.New("B4InsertFlag not set"))
+		}
+		if p2.AfterInsertedFlag != 0 {
+			t.Error(errors.New("AfterInsertedFlag is set"))
+		}
+		if p2.B4InsertViaExt == 0 {
+			t.Error(errors.New("B4InsertViaExt not set"))
+		}
+		if p2.AfterInsertedViaExt != 0 {
+			t.Error(errors.New("AfterInsertedViaExt is set"))
+		}
+	}
+	insertedId := p2.Id
+	// --
+
+	// test update processors with tx rollback
+	session = testEngine.NewSession()
+	err = session.Begin()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	b4UpdateFunc := func(bean interface{}) {
+		if v, ok := (bean).(*ProcessorsStruct); ok {
+			v.B4UpdateViaExt = 1
+		} else {
+			t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?"))
+		}
+	}
+
+	afterUpdateFunc := func(bean interface{}) {
+		if v, ok := (bean).(*ProcessorsStruct); ok {
+			v.AfterUpdatedViaExt = 1
+		} else {
+			t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?"))
+		}
+	}
+
+	p = p2 // reset
+
+	_, err = session.Id(insertedId).Before(b4UpdateFunc).After(afterUpdateFunc).Update(p)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p.B4UpdateFlag == 0 {
+			t.Error(errors.New("B4UpdateFlag not set"))
+		}
+		if p.AfterUpdatedFlag != 0 {
+			t.Error(errors.New("AfterUpdatedFlag is set"))
+		}
+		if p.B4UpdateViaExt == 0 {
+			t.Error(errors.New("B4UpdateViaExt not set"))
+		}
+		if p.AfterUpdatedViaExt != 0 {
+			t.Error(errors.New("AfterUpdatedViaExt is set"))
+		}
+	}
+	err = session.Rollback()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p.B4UpdateFlag == 0 {
+			t.Error(errors.New("B4UpdateFlag not set"))
+		}
+		if p.AfterUpdatedFlag != 0 {
+			t.Error(errors.New("AfterUpdatedFlag is set"))
+		}
+		if p.B4UpdateViaExt == 0 {
+			t.Error(errors.New("B4UpdateViaExt not set"))
+		}
+		if p.AfterUpdatedViaExt != 0 {
+			t.Error(errors.New("AfterUpdatedViaExt is set"))
+		}
+	}
+
+	session.Close()
+
+	p2 = &ProcessorsStruct{}
+	_, err = testEngine.Id(insertedId).Get(p2)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p2.B4UpdateFlag != 0 {
+			t.Error(errors.New("B4UpdateFlag is set"))
+		}
+		if p2.AfterUpdatedFlag != 0 {
+			t.Error(errors.New("AfterUpdatedFlag is set"))
+		}
+		if p2.B4UpdateViaExt != 0 {
+			t.Error(errors.New("B4UpdateViaExt not set"))
+		}
+		if p2.AfterUpdatedViaExt != 0 {
+			t.Error(errors.New("AfterUpdatedViaExt is set"))
+		}
+	}
+	// --
+
+	// test update processors with tx rollback
+	session = testEngine.NewSession()
+	err = session.Begin()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	p = &ProcessorsStruct{Id: insertedId}
+
+	_, err = session.Update(p)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p.B4UpdateFlag == 0 {
+			t.Error(errors.New("B4UpdateFlag not set"))
+		}
+		if p.AfterUpdatedFlag != 0 {
+			t.Error(errors.New("AfterUpdatedFlag is set"))
+		}
+	}
+	err = session.Commit()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p.B4UpdateFlag == 0 {
+			t.Error(errors.New("B4UpdateFlag not set"))
+		}
+		if p.AfterUpdatedFlag == 0 {
+			t.Error(errors.New("AfterUpdatedFlag not set"))
+		}
+		if p.AfterDeletedFlag != 0 {
+			t.Error(errors.New("AfterDeletedFlag set"))
+		}
+		if p.AfterInsertedFlag != 0 {
+			t.Error(errors.New("AfterInsertedFlag set"))
+		}
+	}
+
+	session.Close()
+
+	// test update processors with tx commit
+	session = testEngine.NewSession()
+	err = session.Begin()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	p = &ProcessorsStruct{}
+
+	_, err = session.Id(insertedId).Before(b4UpdateFunc).After(afterUpdateFunc).Update(p)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p.B4UpdateFlag == 0 {
+			t.Error(errors.New("B4UpdateFlag not set"))
+		}
+		if p.AfterUpdatedFlag != 0 {
+			t.Error(errors.New("AfterUpdatedFlag is set"))
+		}
+		if p.B4UpdateViaExt == 0 {
+			t.Error(errors.New("B4UpdateViaExt not set"))
+		}
+		if p.AfterUpdatedViaExt != 0 {
+			t.Error(errors.New("AfterUpdatedViaExt is set"))
+		}
+	}
+	err = session.Commit()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p.B4UpdateFlag == 0 {
+			t.Error(errors.New("B4UpdateFlag not set"))
+		}
+		if p.AfterUpdatedFlag == 0 {
+			t.Error(errors.New("AfterUpdatedFlag not set"))
+		}
+		if p.B4UpdateViaExt == 0 {
+			t.Error(errors.New("B4UpdateViaExt not set"))
+		}
+		if p.AfterUpdatedViaExt == 0 {
+			t.Error(errors.New("AfterUpdatedViaExt not set"))
+		}
+	}
+	session.Close()
+	p2 = &ProcessorsStruct{}
+	_, err = testEngine.Id(insertedId).Get(p2)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p.B4UpdateFlag == 0 {
+			t.Error(errors.New("B4UpdateFlag not set"))
+		}
+		if p.AfterUpdatedFlag == 0 {
+			t.Error(errors.New("AfterUpdatedFlag not set"))
+		}
+		if p.B4UpdateViaExt == 0 {
+			t.Error(errors.New("B4UpdateViaExt not set"))
+		}
+		if p.AfterUpdatedViaExt == 0 {
+			t.Error(errors.New("AfterUpdatedViaExt not set"))
+		}
+	}
+	// --
+
+	// test delete processors with tx rollback
+	session = testEngine.NewSession()
+	err = session.Begin()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	b4DeleteFunc := func(bean interface{}) {
+		if v, ok := (bean).(*ProcessorsStruct); ok {
+			v.B4DeleteViaExt = 1
+		} else {
+			t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?"))
+		}
+	}
+
+	afterDeleteFunc := func(bean interface{}) {
+		if v, ok := (bean).(*ProcessorsStruct); ok {
+			v.AfterDeletedViaExt = 1
+		} else {
+			t.Error(errors.New("cast to ProcessorsStruct failed, how can this be!?"))
+		}
+	}
+
+	p = &ProcessorsStruct{} // reset
+
+	_, err = session.Id(insertedId).Before(b4DeleteFunc).After(afterDeleteFunc).Delete(p)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p.B4DeleteFlag == 0 {
+			t.Error(errors.New("B4DeleteFlag not set"))
+		}
+		if p.AfterDeletedFlag != 0 {
+			t.Error(errors.New("AfterDeletedFlag is set"))
+		}
+		if p.B4DeleteViaExt == 0 {
+			t.Error(errors.New("B4DeleteViaExt not set"))
+		}
+		if p.AfterDeletedViaExt != 0 {
+			t.Error(errors.New("AfterDeletedViaExt is set"))
+		}
+	}
+	err = session.Rollback()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p.B4DeleteFlag == 0 {
+			t.Error(errors.New("B4DeleteFlag not set"))
+		}
+		if p.AfterDeletedFlag != 0 {
+			t.Error(errors.New("AfterDeletedFlag is set"))
+		}
+		if p.B4DeleteViaExt == 0 {
+			t.Error(errors.New("B4DeleteViaExt not set"))
+		}
+		if p.AfterDeletedViaExt != 0 {
+			t.Error(errors.New("AfterDeletedViaExt is set"))
+		}
+	}
+	session.Close()
+
+	p2 = &ProcessorsStruct{}
+	_, err = testEngine.Id(insertedId).Get(p2)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p2.B4DeleteFlag != 0 {
+			t.Error(errors.New("B4DeleteFlag is set"))
+		}
+		if p2.AfterDeletedFlag != 0 {
+			t.Error(errors.New("AfterDeletedFlag is set"))
+		}
+		if p2.B4DeleteViaExt != 0 {
+			t.Error(errors.New("B4DeleteViaExt is set"))
+		}
+		if p2.AfterDeletedViaExt != 0 {
+			t.Error(errors.New("AfterDeletedViaExt is set"))
+		}
+	}
+	// --
+
+	// test delete processors with tx commit
+	session = testEngine.NewSession()
+	err = session.Begin()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	p = &ProcessorsStruct{}
+
+	_, err = session.Id(insertedId).Before(b4DeleteFunc).After(afterDeleteFunc).Delete(p)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p.B4DeleteFlag == 0 {
+			t.Error(errors.New("B4DeleteFlag not set"))
+		}
+		if p.AfterDeletedFlag != 0 {
+			t.Error(errors.New("AfterDeletedFlag is set"))
+		}
+		if p.B4DeleteViaExt == 0 {
+			t.Error(errors.New("B4DeleteViaExt not set"))
+		}
+		if p.AfterDeletedViaExt != 0 {
+			t.Error(errors.New("AfterDeletedViaExt is set"))
+		}
+	}
+	err = session.Commit()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p.B4DeleteFlag == 0 {
+			t.Error(errors.New("B4DeleteFlag not set"))
+		}
+		if p.AfterDeletedFlag == 0 {
+			t.Error(errors.New("AfterDeletedFlag not set"))
+		}
+		if p.B4DeleteViaExt == 0 {
+			t.Error(errors.New("B4DeleteViaExt not set"))
+		}
+		if p.AfterDeletedViaExt == 0 {
+			t.Error(errors.New("AfterDeletedViaExt not set"))
+		}
+	}
+	session.Close()
+
+	// test delete processors with tx commit
+	session = testEngine.NewSession()
+	err = session.Begin()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	p = &ProcessorsStruct{Id: insertedId}
+	fmt.Println("delete")
+	_, err = session.Delete(p)
+
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p.B4DeleteFlag == 0 {
+			t.Error(errors.New("B4DeleteFlag not set"))
+		}
+		if p.AfterDeletedFlag != 0 {
+			t.Error(errors.New("AfterDeletedFlag is set"))
+		}
+	}
+	err = session.Commit()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	} else {
+		if p.B4DeleteFlag == 0 {
+			t.Error(errors.New("B4DeleteFlag not set"))
+		}
+		if p.AfterDeletedFlag == 0 {
+			t.Error(errors.New("AfterDeletedFlag not set"))
+		}
+		if p.AfterInsertedFlag != 0 {
+			t.Error(errors.New("AfterInsertedFlag set"))
+		}
+		if p.AfterUpdatedFlag != 0 {
+			t.Error(errors.New("AfterUpdatedFlag set"))
+		}
+	}
+	session.Close()
+	// --
+}

+ 41 - 0
rows_test.go

@@ -0,0 +1,41 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestRows(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	type UserRows struct {
+		Id    int64
+		IsMan bool
+	}
+
+	assert.NoError(t, testEngine.Sync2(new(UserRows)))
+
+	cnt, err := testEngine.Insert(&UserRows{
+		IsMan: true,
+	})
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+
+	rows, err := testEngine.Rows(new(UserRows))
+	assert.NoError(t, err)
+	defer rows.Close()
+
+	cnt = 0
+	user := new(UserRows)
+	for rows.Next() {
+		err = rows.Scan(user)
+		assert.NoError(t, err)
+		cnt++
+	}
+	assert.EqualValues(t, 1, cnt)
+}

+ 23 - 2
session_cols_test.go

@@ -4,13 +4,34 @@
 
 package xorm
 
-import "testing"
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/xormplus/core"
+)
 
 func TestSetExpr(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
 	type User struct {
 		Id   int64
 		Show bool
 	}
 
-	testEngine.SetExpr("show", "NOT show").Id(1).Update(new(User))
+	assert.NoError(t, testEngine.Sync2(new(User)))
+
+	cnt, err := testEngine.Insert(&User{
+		Show: true,
+	})
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+
+	var not = "NOT"
+	if testEngine.dialect.DBType() == core.MSSQL {
+		not = "~"
+	}
+	cnt, err = testEngine.SetExpr("show", not+" `show`").Id(1).Update(new(User))
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
 }

+ 167 - 0
session_cond_test.go

@@ -5,6 +5,8 @@
 package xorm
 
 import (
+	"errors"
+	"fmt"
 	"testing"
 
 	"github.com/go-xorm/builder"
@@ -93,3 +95,168 @@ func TestBuilder(t *testing.T) {
 	assert.NoError(t, err)
 	assert.EqualValues(t, 1, len(conds), "records should exist")
 }
+
+func TestIn(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assert.NoError(t, testEngine.Sync2(new(Userinfo)))
+
+	cnt, err := testEngine.Insert([]Userinfo{
+		{
+			Username:   "user1",
+			Departname: "dev",
+		},
+		{
+			Username:   "user2",
+			Departname: "dev",
+		},
+		{
+			Username:   "user3",
+			Departname: "dev",
+		},
+	})
+	assert.NoError(t, err)
+	assert.EqualValues(t, 3, cnt)
+
+	var usrs []Userinfo
+	err = testEngine.Limit(3).Find(&usrs)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	if len(usrs) != 3 {
+		err = errors.New("there are not 3 records")
+		t.Error(err)
+		panic(err)
+	}
+
+	var ids []int64
+	var idsStr string
+	for _, u := range usrs {
+		ids = append(ids, u.Uid)
+		idsStr = fmt.Sprintf("%d,", u.Uid)
+	}
+	idsStr = idsStr[:len(idsStr)-1]
+
+	users := make([]Userinfo, 0)
+	err = testEngine.In("(id)", ids[0], ids[1], ids[2]).Find(&users)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	fmt.Println(users)
+	if len(users) != 3 {
+		err = errors.New("in uses should be " + idsStr + " total 3")
+		t.Error(err)
+		panic(err)
+	}
+
+	users = make([]Userinfo, 0)
+	err = testEngine.In("(id)", ids).Find(&users)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	fmt.Println(users)
+	if len(users) != 3 {
+		err = errors.New("in uses should be " + idsStr + " total 3")
+		t.Error(err)
+		panic(err)
+	}
+
+	for _, user := range users {
+		if user.Uid != ids[0] && user.Uid != ids[1] && user.Uid != ids[2] {
+			err = errors.New("in uses should be " + idsStr + " total 3")
+			t.Error(err)
+			panic(err)
+		}
+	}
+
+	users = make([]Userinfo, 0)
+	var idsInterface []interface{}
+	for _, id := range ids {
+		idsInterface = append(idsInterface, id)
+	}
+
+	department := "`" + testEngine.ColumnMapper.Obj2Table("Departname") + "`"
+	err = testEngine.Where(department+" = ?", "dev").In("(id)", idsInterface...).Find(&users)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	fmt.Println(users)
+
+	if len(users) != 3 {
+		err = errors.New("in uses should be " + idsStr + " total 3")
+		t.Error(err)
+		panic(err)
+	}
+
+	for _, user := range users {
+		if user.Uid != ids[0] && user.Uid != ids[1] && user.Uid != ids[2] {
+			err = errors.New("in uses should be " + idsStr + " total 3")
+			t.Error(err)
+			panic(err)
+		}
+	}
+
+	dev := testEngine.ColumnMapper.Obj2Table("Dev")
+
+	err = testEngine.In("(id)", 1).In("(id)", 2).In(department, dev).Find(&users)
+
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	fmt.Println(users)
+
+	cnt, err = testEngine.In("(id)", ids[0]).Update(&Userinfo{Departname: "dev-"})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if cnt != 1 {
+		err = errors.New("update records not 1")
+		t.Error(err)
+		panic(err)
+	}
+
+	user := new(Userinfo)
+	has, err := testEngine.Id(ids[0]).Get(user)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if !has {
+		err = errors.New("get record not 1")
+		t.Error(err)
+		panic(err)
+	}
+	if user.Departname != "dev-" {
+		err = errors.New("update not success")
+		t.Error(err)
+		panic(err)
+	}
+
+	cnt, err = testEngine.In("(id)", ids[0]).Update(&Userinfo{Departname: "dev"})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if cnt != 1 {
+		err = errors.New("update records not 1")
+		t.Error(err)
+		panic(err)
+	}
+
+	cnt, err = testEngine.In("(id)", ids[1]).Delete(&Userinfo{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if cnt != 1 {
+		err = errors.New("deleted records not 1")
+		t.Error(err)
+		panic(err)
+	}
+}

+ 125 - 0
session_delete_test.go

@@ -0,0 +1,125 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestDelete(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	type UserinfoDelete struct {
+		Uid   int64
+		IsMan bool
+	}
+
+	assert.NoError(t, testEngine.Sync2(new(UserinfoDelete)))
+
+	user := UserinfoDelete{Uid: 1}
+	cnt, err := testEngine.Insert(&user)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+
+	cnt, err = testEngine.Delete(&UserinfoDelete{Uid: 1})
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+
+	user.Uid = 0
+	user.IsMan = true
+	has, err := testEngine.Id(1).Get(&user)
+	assert.NoError(t, err)
+	assert.False(t, has)
+}
+
+func TestDeleted(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	type Deleted struct {
+		Id        int64 `xorm:"pk"`
+		Name      string
+		DeletedAt time.Time `xorm:"deleted"`
+	}
+
+	err := testEngine.DropTables(&Deleted{})
+	assert.NoError(t, err)
+
+	err = testEngine.CreateTables(&Deleted{})
+	assert.NoError(t, err)
+
+	_, err = testEngine.InsertOne(&Deleted{Id: 1, Name: "11111"})
+	assert.NoError(t, err)
+
+	_, err = testEngine.InsertOne(&Deleted{Id: 2, Name: "22222"})
+	assert.NoError(t, err)
+
+	_, err = testEngine.InsertOne(&Deleted{Id: 3, Name: "33333"})
+	assert.NoError(t, err)
+
+	// Test normal Find()
+	var records1 []Deleted
+	err = testEngine.Where("`"+testEngine.ColumnMapper.Obj2Table("Id")+"` > 0").Find(&records1, &Deleted{})
+	assert.EqualValues(t, 3, len(records1))
+
+	// Test normal Get()
+	record1 := &Deleted{}
+	has, err := testEngine.Id(1).Get(record1)
+	assert.NoError(t, err)
+	assert.True(t, has)
+
+	// Test Delete() with deleted
+	affected, err := testEngine.Id(1).Delete(&Deleted{})
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, affected)
+
+	has, err = testEngine.Id(1).Get(&Deleted{})
+	assert.NoError(t, err)
+	assert.False(t, has)
+
+	var records2 []Deleted
+	err = testEngine.Where("`" + testEngine.ColumnMapper.Obj2Table("Id") + "` > 0").Find(&records2)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 2, len(records2))
+
+	// Test no rows affected after Delete() again.
+	affected, err = testEngine.Id(1).Delete(&Deleted{})
+	assert.NoError(t, err)
+	assert.EqualValues(t, 0, affected)
+
+	// Deleted.DeletedAt must not be updated.
+	affected, err = testEngine.Id(2).Update(&Deleted{Name: "2", DeletedAt: time.Now()})
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, affected)
+
+	record2 := &Deleted{}
+	has, err = testEngine.Id(2).Get(record2)
+	assert.NoError(t, err)
+	assert.True(t, record2.DeletedAt.IsZero())
+
+	// Test find all records whatever `deleted`.
+	var unscopedRecords1 []Deleted
+	err = testEngine.Unscoped().Where("`"+testEngine.ColumnMapper.Obj2Table("Id")+"` > 0").Find(&unscopedRecords1, &Deleted{})
+	assert.NoError(t, err)
+	assert.EqualValues(t, 3, len(unscopedRecords1))
+
+	// Delete() must really delete a record with Unscoped()
+	affected, err = testEngine.Unscoped().Id(1).Delete(&Deleted{})
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, affected)
+
+	var unscopedRecords2 []Deleted
+	err = testEngine.Unscoped().Where("`"+testEngine.ColumnMapper.Obj2Table("Id")+"` > 0").Find(&unscopedRecords2, &Deleted{})
+	assert.NoError(t, err)
+	assert.EqualValues(t, 2, len(unscopedRecords2))
+
+	var records3 []Deleted
+	err = testEngine.Where("`"+testEngine.ColumnMapper.Obj2Table("Id")+"` > 0").And("`"+testEngine.ColumnMapper.Obj2Table("Id")+"`> 1").
+		Or("`"+testEngine.ColumnMapper.Obj2Table("Id")+"` = ?", 3).Find(&records3)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 2, len(records3))
+}

+ 407 - 0
session_find_test.go

@@ -5,9 +5,12 @@
 package xorm
 
 import (
+	"errors"
+	"fmt"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/xormplus/core"
 )
 
 func TestJoinLimit(t *testing.T) {
@@ -57,3 +60,407 @@ func TestJoinLimit(t *testing.T) {
 		Find(&salaries)
 	assert.NoError(t, err)
 }
+
+func assertSync(t *testing.T, beans ...interface{}) {
+	for _, bean := range beans {
+		assert.NoError(t, testEngine.DropTables(bean))
+		assert.NoError(t, testEngine.Sync(bean))
+	}
+}
+
+func TestWhere(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	assertSync(t, new(Userinfo))
+
+	users := make([]Userinfo, 0)
+	err := testEngine.Where("(id) > ?", 2).Find(&users)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	fmt.Println(users)
+
+	err = testEngine.Where("(id) > ?", 2).And("(id) < ?", 10).Find(&users)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	fmt.Println(users)
+}
+
+func TestFind(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Userinfo))
+
+	users := make([]Userinfo, 0)
+
+	err := testEngine.Find(&users)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	for _, user := range users {
+		fmt.Println(user)
+	}
+
+	users2 := make([]Userinfo, 0)
+	userinfo := testEngine.TableMapper.Obj2Table("Userinfo")
+	err = testEngine.Sql("select * from " + testEngine.Quote(userinfo)).Find(&users2)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+}
+
+func TestFind2(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	users := make([]*Userinfo, 0)
+
+	assertSync(t, new(Userinfo))
+
+	err := testEngine.Find(&users)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	for _, user := range users {
+		fmt.Println(user)
+	}
+}
+
+type Team struct {
+	Id int64
+}
+
+type TeamUser struct {
+	OrgId  int64
+	Uid    int64
+	TeamId int64
+}
+
+func TestFind3(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	err := testEngine.Sync2(new(Team), new(TeamUser))
+	if err != nil {
+		t.Error(err)
+		panic(err.Error())
+	}
+
+	var teams []Team
+	err = testEngine.Cols("`team`.id").
+		Where("`team_user`.org_id=?", 1).
+		And("`team_user`.uid=?", 2).
+		Join("INNER", "`team_user`", "`team_user`.team_id=`team`.id").
+		Find(&teams)
+	if err != nil {
+		t.Error(err)
+		panic(err.Error())
+	}
+}
+
+func TestFindMap(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Userinfo))
+
+	users := make(map[int64]Userinfo)
+	err := testEngine.Find(&users)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	for _, user := range users {
+		fmt.Println(user)
+	}
+}
+
+func TestFindMap2(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Userinfo))
+
+	users := make(map[int64]*Userinfo)
+	err := testEngine.Find(&users)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	for id, user := range users {
+		fmt.Println(id, user)
+	}
+}
+
+func TestDistinct(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Userinfo))
+
+	_, err := testEngine.Insert(&Userinfo{
+		Username: "lunny",
+	})
+	assert.NoError(t, err)
+
+	users := make([]Userinfo, 0)
+	departname := testEngine.TableMapper.Obj2Table("Departname")
+	err = testEngine.Distinct(departname).Find(&users)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if len(users) != 1 {
+		t.Error(err)
+		panic(errors.New("should be one record"))
+	}
+
+	fmt.Println(users)
+
+	type Depart struct {
+		Departname string
+	}
+
+	users2 := make([]Depart, 0)
+	err = testEngine.Distinct(departname).Table(new(Userinfo)).Find(&users2)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if len(users2) != 1 {
+		t.Error(err)
+		panic(errors.New("should be one record"))
+	}
+	fmt.Println(users2)
+}
+
+func TestOrder(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Userinfo))
+
+	users := make([]Userinfo, 0)
+	err := testEngine.OrderBy("id desc").Find(&users)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	fmt.Println(users)
+
+	users2 := make([]Userinfo, 0)
+	err = testEngine.Asc("id", "username").Desc("height").Find(&users2)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	fmt.Println(users2)
+}
+
+func TestHaving(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Userinfo))
+
+	users := make([]Userinfo, 0)
+	err := testEngine.GroupBy("username").Having("username='xlw'").Find(&users)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	fmt.Println(users)
+
+	/*users = make([]Userinfo, 0)
+	err = testEngine.Cols("id, username").GroupBy("username").Having("username='xlw'").Find(&users)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	fmt.Println(users)*/
+}
+
+func TestOrderSameMapper(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	testEngine.unMapType(rValue(new(Userinfo)).Type())
+
+	mapper := testEngine.TableMapper
+	testEngine.SetMapper(core.SameMapper{})
+
+	defer func() {
+		testEngine.unMapType(rValue(new(Userinfo)).Type())
+		testEngine.SetMapper(mapper)
+	}()
+
+	assertSync(t, new(Userinfo))
+
+	users := make([]Userinfo, 0)
+	err := testEngine.OrderBy("(id) desc").Find(&users)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	fmt.Println(users)
+
+	users2 := make([]Userinfo, 0)
+	err = testEngine.Asc("(id)", "Username").Desc("Height").Find(&users2)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	fmt.Println(users2)
+}
+
+func TestHavingSameMapper(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	testEngine.unMapType(rValue(new(Userinfo)).Type())
+
+	mapper := testEngine.TableMapper
+	testEngine.SetMapper(core.SameMapper{})
+	defer func() {
+		testEngine.unMapType(rValue(new(Userinfo)).Type())
+		testEngine.SetMapper(mapper)
+	}()
+	assertSync(t, new(Userinfo))
+
+	users := make([]Userinfo, 0)
+	err := testEngine.GroupBy("`Username`").Having("`Username`='xlw'").Find(&users)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println(users)
+}
+
+func TestFindInts(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Userinfo))
+
+	userinfo := testEngine.TableMapper.Obj2Table("Userinfo")
+	var idsInt64 []int64
+	err := testEngine.Table(userinfo).Cols("id").Desc("id").Find(&idsInt64)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println(idsInt64)
+
+	var idsInt32 []int32
+	err = testEngine.Table(userinfo).Cols("id").Desc("id").Find(&idsInt32)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println(idsInt32)
+
+	var idsInt []int
+	err = testEngine.Table(userinfo).Cols("id").Desc("id").Find(&idsInt)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println(idsInt)
+
+	var idsUint []uint
+	err = testEngine.Table(userinfo).Cols("id").Desc("id").Find(&idsUint)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println(idsUint)
+
+	type MyInt int
+	var idsMyInt []MyInt
+	err = testEngine.Table(userinfo).Cols("id").Desc("id").Find(&idsMyInt)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println(idsMyInt)
+}
+
+func TestFindStrings(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Userinfo))
+	userinfo := testEngine.TableMapper.Obj2Table("Userinfo")
+	username := testEngine.ColumnMapper.Obj2Table("Username")
+	var idsString []string
+	err := testEngine.Table(userinfo).Cols(username).Desc("id").Find(&idsString)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println(idsString)
+}
+
+func TestFindMyString(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Userinfo))
+	userinfo := testEngine.TableMapper.Obj2Table("Userinfo")
+	username := testEngine.ColumnMapper.Obj2Table("Username")
+
+	var idsMyString []MyString
+	err := testEngine.Table(userinfo).Cols(username).Desc("id").Find(&idsMyString)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println(idsMyString)
+}
+
+func TestFindInterface(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Userinfo))
+
+	userinfo := testEngine.TableMapper.Obj2Table("Userinfo")
+	username := testEngine.ColumnMapper.Obj2Table("Username")
+	var idsInterface []interface{}
+	err := testEngine.Table(userinfo).Cols(username).Desc("id").Find(&idsInterface)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println(idsInterface)
+}
+
+func TestFindSliceBytes(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Userinfo))
+
+	userinfo := testEngine.TableMapper.Obj2Table("Userinfo")
+	var ids [][][]byte
+	err := testEngine.Table(userinfo).Desc("id").Find(&ids)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for _, record := range ids {
+		fmt.Println(record)
+	}
+}
+
+func TestFindSlicePtrString(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Userinfo))
+
+	userinfo := testEngine.TableMapper.Obj2Table("Userinfo")
+	var ids [][]*string
+	err := testEngine.Table(userinfo).Desc("id").Find(&ids)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for _, record := range ids {
+		fmt.Println(record)
+	}
+}
+
+func TestFindMapBytes(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Userinfo))
+
+	userinfo := testEngine.TableMapper.Obj2Table("Userinfo")
+	var ids []map[string][]byte
+	err := testEngine.Table(userinfo).Desc("id").Find(&ids)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for _, record := range ids {
+		fmt.Println(record)
+	}
+}
+
+func TestFindMapPtrString(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Userinfo))
+
+	userinfo := testEngine.TableMapper.Obj2Table("Userinfo")
+	var ids []map[string]*string
+	err := testEngine.Table(userinfo).Desc("id").Find(&ids)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for _, record := range ids {
+		fmt.Println(record)
+	}
+}

+ 47 - 0
session_get_test.go

@@ -10,6 +10,7 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/xormplus/core"
 )
 
 func TestGetVar(t *testing.T) {
@@ -108,3 +109,49 @@ func TestGetVar(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, "1.5", fmt.Sprintf("%v", v4))
 }
+
+func TestGetStruct(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	type UserinfoGet struct {
+		Uid   int `xorm:"pk autoincr"`
+		IsMan bool
+	}
+
+	assert.NoError(t, testEngine.Sync(new(UserinfoGet)))
+
+	var err error
+	if testEngine.dialect.DBType() == core.MSSQL {
+		_, err = testEngine.Exec("SET IDENTITY_INSERT userinfo_get ON")
+		assert.NoError(t, err)
+	}
+	cnt, err := testEngine.Insert(&UserinfoGet{Uid: 2})
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+
+	user := UserinfoGet{Uid: 2}
+	has, err := testEngine.Get(&user)
+	assert.NoError(t, err)
+	assert.True(t, has)
+
+	type NoIdUser struct {
+		User   string `xorm:"unique"`
+		Remain int64
+		Total  int64
+	}
+
+	assert.NoError(t, testEngine.Sync(&NoIdUser{}))
+
+	userCol := testEngine.ColumnMapper.Obj2Table("User")
+	_, err = testEngine.Where("`"+userCol+"` = ?", "xlw").Delete(&NoIdUser{})
+	assert.NoError(t, err)
+
+	cnt, err = testEngine.Insert(&NoIdUser{"xlw", 20, 100})
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+
+	noIdUser := new(NoIdUser)
+	has, err = testEngine.Where("`"+userCol+"` = ?", "xlw").Get(noIdUser)
+	assert.NoError(t, err)
+	assert.True(t, has)
+}

+ 475 - 0
session_insert_test.go

@@ -5,6 +5,7 @@
 package xorm
 
 import (
+	"errors"
 	"fmt"
 	"reflect"
 	"testing"
@@ -152,3 +153,477 @@ func TestInsertOneIfPkIsPointRename(t *testing.T) {
 	_, err := testEngine.InsertOne(&data)
 	assert.NoError(t, err)
 }
+
+func TestInsert(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Userinfo))
+
+	user := Userinfo{0, "xiaolunwen", "dev", "lunny", time.Now(),
+		Userdetail{Id: 1}, 1.78, []byte{1, 2, 3}, true}
+	cnt, err := testEngine.Insert(&user)
+	fmt.Println(user.Uid)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if cnt != 1 {
+		err = errors.New("insert not returned 1")
+		t.Error(err)
+		panic(err)
+	}
+
+	if user.Uid <= 0 {
+		err = errors.New("not return id error")
+		t.Error(err)
+		panic(err)
+	}
+
+	user.Uid = 0
+	cnt, err = testEngine.Insert(&user)
+	if err == nil {
+		err = errors.New("insert failed but no return error")
+		t.Error(err)
+		panic(err)
+	}
+	if cnt != 0 {
+		err = errors.New("insert not returned 1")
+		t.Error(err)
+		panic(err)
+		return
+	}
+}
+
+func TestInsertAutoIncr(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	assertSync(t, new(Userinfo))
+
+	// auto increment insert
+	user := Userinfo{Username: "xiaolunwen2", Departname: "dev", Alias: "lunny", Created: time.Now(),
+		Detail: Userdetail{Id: 1}, Height: 1.78, Avatar: []byte{1, 2, 3}, IsMan: true}
+	cnt, err := testEngine.Insert(&user)
+	fmt.Println(user.Uid)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if cnt != 1 {
+		err = errors.New("insert not returned 1")
+		t.Error(err)
+		panic(err)
+	}
+	if user.Uid <= 0 {
+		t.Error(errors.New("not return id error"))
+	}
+}
+
+type DefaultInsert struct {
+	Id      int64
+	Status  int `xorm:"default -1"`
+	Name    string
+	Created time.Time `xorm:"created"`
+	Updated time.Time `xorm:"updated"`
+}
+
+func TestInsertDefault(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	di := new(DefaultInsert)
+	err := testEngine.Sync2(di)
+	if err != nil {
+		t.Error(err)
+	}
+
+	var di2 = DefaultInsert{Name: "test"}
+	_, err = testEngine.Omit(testEngine.ColumnMapper.Obj2Table("Status")).Insert(&di2)
+	if err != nil {
+		t.Error(err)
+	}
+
+	has, err := testEngine.Desc("(id)").Get(di)
+	if err != nil {
+		t.Error(err)
+	}
+	if !has {
+		err = errors.New("error with no data")
+		t.Error(err)
+		panic(err)
+	}
+	if di.Status != -1 {
+		err = errors.New("inserted error data")
+		t.Error(err)
+		panic(err)
+	}
+	if di2.Updated.Unix() != di.Updated.Unix() {
+		err = errors.New("updated should equal")
+		t.Error(err, di.Updated, di2.Updated)
+		panic(err)
+	}
+	if di2.Created.Unix() != di.Created.Unix() {
+		err = errors.New("created should equal")
+		t.Error(err, di.Created, di2.Created)
+		panic(err)
+	}
+}
+
+type DefaultInsert2 struct {
+	Id        int64
+	Name      string
+	Url       string    `xorm:"text"`
+	CheckTime time.Time `xorm:"not null default '2000-01-01 00:00:00' TIMESTAMP"`
+}
+
+func TestInsertDefault2(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	di := new(DefaultInsert2)
+	err := testEngine.Sync2(di)
+	if err != nil {
+		t.Error(err)
+	}
+
+	var di2 = DefaultInsert2{Name: "test"}
+	_, err = testEngine.Omit(testEngine.ColumnMapper.Obj2Table("CheckTime")).Insert(&di2)
+	if err != nil {
+		t.Error(err)
+	}
+
+	has, err := testEngine.Desc("(id)").Get(di)
+	if err != nil {
+		t.Error(err)
+	}
+	if !has {
+		err = errors.New("error with no data")
+		t.Error(err)
+		panic(err)
+	}
+
+	has, err = testEngine.NoAutoCondition().Desc("(id)").Get(&di2)
+	if err != nil {
+		t.Error(err)
+	}
+
+	if !has {
+		err = errors.New("error with no data")
+		t.Error(err)
+		panic(err)
+	}
+
+	if *di != di2 {
+		err = fmt.Errorf("%v is not equal to %v", di, di2)
+		t.Error(err)
+		panic(err)
+	}
+
+	/*if di2.Updated.Unix() != di.Updated.Unix() {
+		err = errors.New("updated should equal")
+		t.Error(err, di.Updated, di2.Updated)
+		panic(err)
+	}
+	if di2.Created.Unix() != di.Created.Unix() {
+		err = errors.New("created should equal")
+		t.Error(err, di.Created, di2.Created)
+		panic(err)
+	}*/
+}
+
+type CreatedInsert struct {
+	Id      int64
+	Created time.Time `xorm:"created"`
+}
+
+type CreatedInsert2 struct {
+	Id      int64
+	Created int64 `xorm:"created"`
+}
+
+type CreatedInsert3 struct {
+	Id      int64
+	Created int `xorm:"created bigint"`
+}
+
+type CreatedInsert4 struct {
+	Id      int64
+	Created int `xorm:"created"`
+}
+
+type CreatedInsert5 struct {
+	Id      int64
+	Created time.Time `xorm:"created bigint"`
+}
+
+type CreatedInsert6 struct {
+	Id      int64
+	Created time.Time `xorm:"created bigint"`
+}
+
+func TestInsertCreated(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	di := new(CreatedInsert)
+	err := testEngine.Sync2(di)
+	if err != nil {
+		t.Fatal(err)
+	}
+	ci := &CreatedInsert{}
+	_, err = testEngine.Insert(ci)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	has, err := testEngine.Desc("(id)").Get(di)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !has {
+		t.Fatal(ErrNotExist)
+	}
+	if ci.Created.Unix() != di.Created.Unix() {
+		t.Fatal("should equal:", ci, di)
+	}
+	fmt.Println("ci:", ci, "di:", di)
+
+	di2 := new(CreatedInsert2)
+	err = testEngine.Sync2(di2)
+	if err != nil {
+		t.Fatal(err)
+	}
+	ci2 := &CreatedInsert2{}
+	_, err = testEngine.Insert(ci2)
+	if err != nil {
+		t.Fatal(err)
+	}
+	has, err = testEngine.Desc("(id)").Get(di2)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !has {
+		t.Fatal(ErrNotExist)
+	}
+	if ci2.Created != di2.Created {
+		t.Fatal("should equal:", ci2, di2)
+	}
+	fmt.Println("ci2:", ci2, "di2:", di2)
+
+	di3 := new(CreatedInsert3)
+	err = testEngine.Sync2(di3)
+	if err != nil {
+		t.Fatal(err)
+	}
+	ci3 := &CreatedInsert3{}
+	_, err = testEngine.Insert(ci3)
+	if err != nil {
+		t.Fatal(err)
+	}
+	has, err = testEngine.Desc("(id)").Get(di3)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !has {
+		t.Fatal(ErrNotExist)
+	}
+	if ci3.Created != di3.Created {
+		t.Fatal("should equal:", ci3, di3)
+	}
+	fmt.Println("ci3:", ci3, "di3:", di3)
+
+	di4 := new(CreatedInsert4)
+	err = testEngine.Sync2(di4)
+	if err != nil {
+		t.Fatal(err)
+	}
+	ci4 := &CreatedInsert4{}
+	_, err = testEngine.Insert(ci4)
+	if err != nil {
+		t.Fatal(err)
+	}
+	has, err = testEngine.Desc("(id)").Get(di4)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !has {
+		t.Fatal(ErrNotExist)
+	}
+	if ci4.Created != di4.Created {
+		t.Fatal("should equal:", ci4, di4)
+	}
+	fmt.Println("ci4:", ci4, "di4:", di4)
+
+	di5 := new(CreatedInsert5)
+	err = testEngine.Sync2(di5)
+	if err != nil {
+		t.Fatal(err)
+	}
+	ci5 := &CreatedInsert5{}
+	_, err = testEngine.Insert(ci5)
+	if err != nil {
+		t.Fatal(err)
+	}
+	has, err = testEngine.Desc("(id)").Get(di5)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !has {
+		t.Fatal(ErrNotExist)
+	}
+	if ci5.Created.Unix() != di5.Created.Unix() {
+		t.Fatal("should equal:", ci5, di5)
+	}
+	fmt.Println("ci5:", ci5, "di5:", di5)
+
+	di6 := new(CreatedInsert6)
+	err = testEngine.Sync2(di6)
+	if err != nil {
+		t.Fatal(err)
+	}
+	oldTime := time.Now().Add(-time.Hour)
+	ci6 := &CreatedInsert6{Created: oldTime}
+	_, err = testEngine.Insert(ci6)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	has, err = testEngine.Desc("(id)").Get(di6)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !has {
+		t.Fatal(ErrNotExist)
+	}
+	if ci6.Created.Unix() != di6.Created.Unix() {
+		t.Fatal("should equal:", ci6, di6)
+	}
+	fmt.Println("ci6:", ci6, "di6:", di6)
+}
+
+type JsonTime time.Time
+
+func (j JsonTime) format() string {
+	t := time.Time(j)
+	if t.IsZero() {
+		return ""
+	}
+
+	return t.Format("2006-01-02")
+}
+
+func (j JsonTime) MarshalText() ([]byte, error) {
+	return []byte(j.format()), nil
+}
+
+func (j JsonTime) MarshalJSON() ([]byte, error) {
+	return []byte(`"` + j.format() + `"`), nil
+}
+
+type MyJsonTime struct {
+	Id      int64    `json:"id"`
+	Created JsonTime `xorm:"created" json:"created_at"`
+}
+
+func TestCreatedJsonTime(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	di5 := new(MyJsonTime)
+	err := testEngine.Sync2(di5)
+	if err != nil {
+		t.Fatal(err)
+	}
+	ci5 := &MyJsonTime{}
+	_, err = testEngine.Insert(ci5)
+	if err != nil {
+		t.Fatal(err)
+	}
+	has, err := testEngine.Desc("(id)").Get(di5)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !has {
+		t.Fatal(ErrNotExist)
+	}
+	if time.Time(ci5.Created).Unix() != time.Time(di5.Created).Unix() {
+		t.Fatal("should equal:", time.Time(ci5.Created).Unix(), time.Time(di5.Created).Unix())
+	}
+	fmt.Println("ci5:", ci5, "di5:", di5)
+
+	var dis = make([]MyJsonTime, 0)
+	err = testEngine.Find(&dis)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestInsertMulti2(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	assertSync(t, new(Userinfo))
+
+	users := []Userinfo{
+		{Username: "xlw", Departname: "dev", Alias: "lunny2", Created: time.Now()},
+		{Username: "xlw2", Departname: "dev", Alias: "lunny3", Created: time.Now()},
+		{Username: "xlw11", Departname: "dev", Alias: "lunny2", Created: time.Now()},
+		{Username: "xlw22", Departname: "dev", Alias: "lunny3", Created: time.Now()},
+	}
+	cnt, err := testEngine.Insert(&users)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if cnt != int64(len(users)) {
+		err = errors.New("insert not returned 1")
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	users2 := []*Userinfo{
+		&Userinfo{Username: "1xlw", Departname: "dev", Alias: "lunny2", Created: time.Now()},
+		&Userinfo{Username: "1xlw2", Departname: "dev", Alias: "lunny3", Created: time.Now()},
+		&Userinfo{Username: "1xlw11", Departname: "dev", Alias: "lunny2", Created: time.Now()},
+		&Userinfo{Username: "1xlw22", Departname: "dev", Alias: "lunny3", Created: time.Now()},
+	}
+
+	cnt, err = testEngine.Insert(&users2)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	if cnt != int64(len(users2)) {
+		err = errors.New(fmt.Sprintf("insert not returned %v", len(users2)))
+		t.Error(err)
+		panic(err)
+	}
+}
+
+func TestInsertTwoTable(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	assertSync(t, new(Userinfo), new(Userdetail))
+
+	userdetail := Userdetail{ /*Id: 1, */ Intro: "I'm a very beautiful women.", Profile: "sfsaf"}
+	userinfo := Userinfo{Username: "xlw3", Departname: "dev", Alias: "lunny4", Created: time.Now(), Detail: userdetail}
+
+	cnt, err := testEngine.Insert(&userinfo, &userdetail)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	if userinfo.Uid <= 0 {
+		err = errors.New("not return id error")
+		t.Error(err)
+		panic(err)
+	}
+
+	if userdetail.Id <= 0 {
+		err = errors.New("not return id error")
+		t.Error(err)
+		panic(err)
+	}
+
+	if cnt != 2 {
+		err = errors.New("insert not returned 2")
+		t.Error(err)
+		panic(err)
+	}
+}

+ 38 - 0
session_iterate_test.go

@@ -0,0 +1,38 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestIterate(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	type UserIterate struct {
+		Id    int64
+		IsMan bool
+	}
+
+	assert.NoError(t, testEngine.Sync2(new(UserIterate)))
+
+	cnt, err := testEngine.Insert(&UserIterate{
+		IsMan: true,
+	})
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+
+	cnt = 0
+	err = testEngine.Iterate(new(UserIterate), func(i int, bean interface{}) error {
+		user := bean.(*UserIterate)
+		assert.EqualValues(t, 1, user.Id)
+		assert.EqualValues(t, true, user.IsMan)
+		cnt++
+		return nil
+	})
+	assert.EqualValues(t, 1, cnt)
+}

+ 12 - 0
session_pk_test.go

@@ -1105,3 +1105,15 @@ func TestMyStringId(t *testing.T) {
 		panic(err)
 	}
 }
+
+func TestSingleAutoIncrColumn(t *testing.T) {
+	type Account struct {
+		Id int64 `xorm:"pk autoincr"`
+	}
+
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Account))
+
+	_, err := testEngine.Insert(&Account{})
+	assert.NoError(t, err)
+}

+ 26 - 0
session_raw_test.go

@@ -5,6 +5,7 @@
 package xorm
 
 import (
+	"strconv"
 	"testing"
 	"time"
 
@@ -41,3 +42,28 @@ func TestQueryString(t *testing.T) {
 	assert.Equal(t, "28", records[0]["age"])
 	assert.Equal(t, "1.5", records[0]["money"])
 }
+
+func TestQuery(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	type UserinfoQuery struct {
+		Uid  int
+		Name string
+	}
+
+	assert.NoError(t, testEngine.Sync(new(UserinfoQuery)))
+
+	res, err := testEngine.Exec("INSERT INTO `userinfo_query` (uid, name) VALUES (?, ?)", 1, "user")
+	assert.NoError(t, err)
+	cnt, err := res.RowsAffected()
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+
+	results, err := testEngine.Query("select * from userinfo_query")
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, len(results))
+	id, err := strconv.Atoi(string(results[0]["uid"]))
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, id)
+	assert.Equal(t, "user", string(results[0]["name"]))
+}

+ 218 - 0
session_schema_test.go

@@ -0,0 +1,218 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+	"fmt"
+	"os"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestStoreEngine(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	assert.NoError(t, testEngine.DropTables("user_store_engine"))
+
+	type UserinfoStoreEngine struct {
+		Id   int64
+		Name string
+	}
+
+	assert.NoError(t, testEngine.StoreEngine("InnoDB").Table("user_store_engine").CreateTable(&UserinfoStoreEngine{}))
+}
+
+func TestCreateTable(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	assert.NoError(t, testEngine.DropTables("user_user"))
+
+	type UserinfoCreateTable struct {
+		Id   int64
+		Name string
+	}
+
+	assert.NoError(t, testEngine.Table("user_user").CreateTable(&UserinfoCreateTable{}))
+}
+
+func TestCreateMultiTables(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	session := testEngine.NewSession()
+	defer session.Close()
+
+	type UserinfoMultiTable struct {
+		Id   int64
+		Name string
+	}
+
+	user := &UserinfoMultiTable{}
+	assert.NoError(t, session.Begin())
+
+	for i := 0; i < 10; i++ {
+		tableName := fmt.Sprintf("user_%v", i)
+
+		assert.NoError(t, session.DropTable(tableName))
+
+		assert.NoError(t, session.Table(tableName).CreateTable(user))
+	}
+
+	assert.NoError(t, session.Commit())
+}
+
+type SyncTable1 struct {
+	Id   int64
+	Name string
+	Dev  int `xorm:"index"`
+}
+
+type SyncTable2 struct {
+	Id     int64
+	Name   string `xorm:"unique"`
+	Number string `xorm:"index"`
+	Dev    int
+	Age    int
+}
+
+func (SyncTable2) TableName() string {
+	return "sync_table1"
+}
+
+func TestSyncTable(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	assert.NoError(t, testEngine.Sync2(new(SyncTable1)))
+
+	assert.NoError(t, testEngine.Sync2(new(SyncTable2)))
+}
+
+func TestIsTableExist(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	exist, err := testEngine.IsTableExist(new(CustomTableName))
+	assert.NoError(t, err)
+	assert.False(t, exist)
+
+	assert.NoError(t, testEngine.CreateTables(new(CustomTableName)))
+
+	exist, err = testEngine.IsTableExist(new(CustomTableName))
+	assert.NoError(t, err)
+	assert.True(t, exist)
+}
+
+func TestIsTableEmpty(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	type NumericEmpty struct {
+		Numeric float64 `xorm:"numeric(26,2)"`
+	}
+
+	type PictureEmpty struct {
+		Id          int64
+		Url         string `xorm:"unique"` //image's url
+		Title       string
+		Description string
+		Created     time.Time `xorm:"created"`
+		ILike       int
+		PageView    int
+		From_url    string
+		Pre_url     string `xorm:"unique"` //pre view image's url
+		Uid         int64
+	}
+
+	assert.NoError(t, testEngine.DropTables(&PictureEmpty{}, &NumericEmpty{}))
+
+	assert.NoError(t, testEngine.Sync(new(PictureEmpty), new(NumericEmpty)))
+
+	isEmpty, err := testEngine.IsTableEmpty(&PictureEmpty{})
+	assert.NoError(t, err)
+	assert.True(t, isEmpty)
+
+	tbName := testEngine.TableMapper.Obj2Table("PictureEmpty")
+	isEmpty, err = testEngine.IsTableEmpty(tbName)
+	assert.NoError(t, err)
+	assert.True(t, isEmpty)
+}
+
+type CustomTableName struct {
+	Id   int64
+	Name string
+}
+
+func (c *CustomTableName) TableName() string {
+	return "customtablename"
+}
+
+func TestCustomTableName(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	c := new(CustomTableName)
+	assert.NoError(t, testEngine.DropTables(c))
+
+	assert.NoError(t, testEngine.CreateTables(c))
+}
+
+func TestDump(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	fp := testEngine.Dialect().URI().DbName + ".sql"
+	os.Remove(fp)
+	assert.NoError(t, testEngine.DumpAllToFile(fp))
+}
+
+type IndexOrUnique struct {
+	Id        int64
+	Index     int `xorm:"index"`
+	Unique    int `xorm:"unique"`
+	Group1    int `xorm:"index(ttt)"`
+	Group2    int `xorm:"index(ttt)"`
+	UniGroup1 int `xorm:"unique(lll)"`
+	UniGroup2 int `xorm:"unique(lll)"`
+}
+
+func TestIndexAndUnique(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	assert.NoError(t, testEngine.CreateTables(&IndexOrUnique{}))
+
+	assert.NoError(t, testEngine.DropTables(&IndexOrUnique{}))
+
+	assert.NoError(t, testEngine.CreateTables(&IndexOrUnique{}))
+
+	assert.NoError(t, testEngine.CreateIndexes(&IndexOrUnique{}))
+
+	assert.NoError(t, testEngine.CreateUniques(&IndexOrUnique{}))
+
+	assert.NoError(t, testEngine.DropIndexes(&IndexOrUnique{}))
+}
+
+func TestMetaInfo(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assert.NoError(t, testEngine.Sync2(new(CustomTableName), new(IndexOrUnique)))
+
+	tables, err := testEngine.DBMetas()
+	assert.NoError(t, err)
+	assert.EqualValues(t, 2, len(tables))
+	assert.EqualValues(t, "customtablename", tables[0].Name)
+	assert.EqualValues(t, "index_or_unique", tables[1].Name)
+}
+
+func TestCharst(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	err := testEngine.DropTables("user_charset")
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	err = testEngine.Charset("utf8").Table("user_charset").CreateTable(&Userinfo{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+}

+ 29 - 0
session_sum_test.go

@@ -9,6 +9,7 @@ import (
 	"strconv"
 	"testing"
 
+	"github.com/go-xorm/builder"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -99,3 +100,31 @@ func TestSumCustomColumn(t *testing.T) {
 	assert.NoError(t, err)
 	assert.EqualValues(t, 3, int(sumInt))
 }
+
+func TestCount(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	type UserinfoCount struct {
+		Departname string
+	}
+	assert.NoError(t, testEngine.Sync2(new(UserinfoCount)))
+
+	colName := testEngine.ColumnMapper.Obj2Table("Departname")
+	var cond builder.Cond = builder.Eq{
+		"`" + colName + "`": "dev",
+	}
+
+	total, err := testEngine.Where(cond).Count(new(UserinfoCount))
+	assert.NoError(t, err)
+	assert.EqualValues(t, 0, total)
+
+	cnt, err := testEngine.Insert(&UserinfoCount{
+		Departname: "dev",
+	})
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+
+	total, err = testEngine.Where(cond).Count(new(UserinfoCount))
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, total)
+}

+ 192 - 0
session_tx_test.go

@@ -0,0 +1,192 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+	"fmt"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/xormplus/core"
+)
+
+func TestTransaction(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Userinfo))
+
+	counter := func() {
+		total, err := testEngine.Count(&Userinfo{})
+		if err != nil {
+			t.Error(err)
+		}
+		fmt.Printf("----now total %v records\n", total)
+	}
+
+	counter()
+	//defer counter()
+
+	session := testEngine.NewSession()
+	defer session.Close()
+
+	err := session.Begin()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()}
+	_, err = session.Insert(&user1)
+	if err != nil {
+		session.Rollback()
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	user2 := Userinfo{Username: "yyy"}
+	_, err = session.Where("(id) = ?", 0).Update(&user2)
+	if err != nil {
+		session.Rollback()
+		fmt.Println(err)
+		//t.Error(err)
+		return
+	}
+
+	_, err = session.Delete(&user2)
+	if err != nil {
+		session.Rollback()
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	err = session.Commit()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+		return
+	}
+	// panic(err) !nashtsai! should remove this
+}
+
+func TestCombineTransaction(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Userinfo))
+
+	counter := func() {
+		total, err := testEngine.Count(&Userinfo{})
+		if err != nil {
+			t.Error(err)
+		}
+		fmt.Printf("----now total %v records\n", total)
+	}
+
+	counter()
+	//defer counter()
+	session := testEngine.NewSession()
+	defer session.Close()
+
+	err := session.Begin()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	user1 := Userinfo{Username: "xiaoxiao2", Departname: "dev", Alias: "lunny", Created: time.Now()}
+	_, err = session.Insert(&user1)
+	if err != nil {
+		session.Rollback()
+		t.Error(err)
+		panic(err)
+	}
+	user2 := Userinfo{Username: "zzz"}
+	_, err = session.Where("id = ?", 0).Update(&user2)
+	if err != nil {
+		session.Rollback()
+		t.Error(err)
+		panic(err)
+	}
+
+	_, err = session.Exec("delete from userinfo where username = ?", user2.Username)
+	if err != nil {
+		session.Rollback()
+		t.Error(err)
+		panic(err)
+	}
+
+	err = session.Commit()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+}
+
+func TestCombineTransactionSameMapper(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	oldMapper := testEngine.ColumnMapper
+	testEngine.unMapType(rValue(new(Userinfo)).Type())
+	testEngine.SetMapper(core.SameMapper{})
+	defer func() {
+		testEngine.unMapType(rValue(new(Userinfo)).Type())
+		testEngine.SetMapper(oldMapper)
+	}()
+
+	assertSync(t, new(Userinfo))
+
+	counter := func() {
+		total, err := testEngine.Count(&Userinfo{})
+		if err != nil {
+			t.Error(err)
+		}
+		fmt.Printf("----now total %v records\n", total)
+	}
+
+	counter()
+	defer counter()
+	session := testEngine.NewSession()
+	defer session.Close()
+
+	err := session.Begin()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	user1 := Userinfo{Username: "xiaoxiao2", Departname: "dev", Alias: "lunny", Created: time.Now()}
+	_, err = session.Insert(&user1)
+	if err != nil {
+		session.Rollback()
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	user2 := Userinfo{Username: "zzz"}
+	_, err = session.Where("(id) = ?", 0).Update(&user2)
+	if err != nil {
+		session.Rollback()
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	_, err = session.Exec("delete from `Userinfo` where `Username` = ?", user2.Username)
+	if err != nil {
+		session.Rollback()
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	err = session.Commit()
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+}

+ 1024 - 13
session_update_test.go

@@ -5,10 +5,14 @@
 package xorm
 
 import (
+	"errors"
+	"fmt"
+	"sync"
 	"testing"
 	"time"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/xormplus/core"
 )
 
 func TestUpdateMap(t *testing.T) {
@@ -74,26 +78,1033 @@ func TestUpdateLimit(t *testing.T) {
 	assert.EqualValues(t, 30, uts[1].Age)
 }
 
-func TestUpdate(t *testing.T) {
-	assert.NoError(t, prepareEngine())
+type ForUpdate struct {
+	Id   int64 `xorm:"pk"`
+	Name string
+}
+
+func setupForUpdate(engine *Engine) error {
+	v := new(ForUpdate)
+	err := testEngine.DropTables(v)
+	if err != nil {
+		return err
+	}
+	err = testEngine.CreateTables(v)
+	if err != nil {
+		return err
+	}
+
+	list := []ForUpdate{
+		{1, "data1"},
+		{2, "data2"},
+		{3, "data3"},
+	}
+
+	for _, f := range list {
+		_, err = testEngine.Insert(f)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func TestForUpdate(t *testing.T) {
+	if testEngine.DriverName() != "mysql" && testEngine.DriverName() != "mymysql" {
+		return
+	}
+
+	err := setupForUpdate(testEngine)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	session1 := testEngine.NewSession()
+	session2 := testEngine.NewSession()
+	session3 := testEngine.NewSession()
+	defer session1.Close()
+	defer session2.Close()
+	defer session3.Close()
+
+	// start transaction
+	err = session1.Begin()
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	// use lock
+	fList := make([]ForUpdate, 0)
+	session1.ForUpdate()
+	session1.Where("(id) = ?", 1)
+	err = session1.Find(&fList)
+	switch {
+	case err != nil:
+		t.Error(err)
+		return
+	case len(fList) != 1:
+		t.Errorf("find not returned single row")
+		return
+	case fList[0].Name != "data1":
+		t.Errorf("for_update.name must be `data1`")
+		return
+	}
+
+	// wait for lock
+	wg := &sync.WaitGroup{}
+
+	// lock is used
+	wg.Add(1)
+	go func() {
+		f2 := new(ForUpdate)
+		session2.Where("(id) = ?", 1).ForUpdate()
+		has, err := session2.Get(f2) // wait release lock
+		switch {
+		case err != nil:
+			t.Error(err)
+		case !has:
+			t.Errorf("cannot find target row. for_update.id = 1")
+		case f2.Name != "updated by session1":
+			t.Errorf("read lock failed")
+		}
+		wg.Done()
+	}()
+
+	// lock is NOT used
+	wg.Add(1)
+	go func() {
+		f3 := new(ForUpdate)
+		session3.Where("(id) = ?", 1)
+		has, err := session3.Get(f3) // wait release lock
+		switch {
+		case err != nil:
+			t.Error(err)
+		case !has:
+			t.Errorf("cannot find target row. for_update.id = 1")
+		case f3.Name != "data1":
+			t.Errorf("read lock failed")
+		}
+		wg.Done()
+	}()
+
+	// wait for go rountines
+	time.Sleep(50 * time.Millisecond)
+
+	f := new(ForUpdate)
+	f.Name = "updated by session1"
+	session1.Where("(id) = ?", 1)
+	session1.Update(f)
 
-	type UpdateTable2 struct {
-		Id      int64     `xorm:"autoincr pk"`
-		Msg     string    `xorm:"varchar(255)"`
-		Created time.Time `xorm:"datetime updated"`
+	// release lock
+	err = session1.Commit()
+	if err != nil {
+		t.Error(err)
+		return
 	}
 
-	assert.NoError(t, testEngine.Sync2(new(UpdateTable2)))
+	wg.Wait()
+}
+
+func TestWithIn(t *testing.T) {
+	type temp3 struct {
+		Id   int64  `xorm:"Id pk autoincr"`
+		Name string `xorm:"Name"`
+		Test bool   `xorm:"Test"`
+	}
+
+	assert.NoError(t, prepareEngine())
+	assert.NoError(t, testEngine.Sync(new(temp3)))
 
-	data := UpdateTable2{Msg: "test1"}
+	testEngine.Insert(&[]temp3{
+		{
+			Name: "user1",
+		},
+		{
+			Name: "user1",
+		},
+		{
+			Name: "user1",
+		},
+	})
 
-	cnt, err := testEngine.Insert(&data)
+	cnt, err := testEngine.In("Id", 1, 2, 3, 4).Update(&temp3{Name: "aa"}, &temp3{Name: "user1"})
 	assert.NoError(t, err)
-	assert.EqualValues(t, 1, cnt)
+	assert.EqualValues(t, 3, cnt)
+}
 
-	cnt, err = testEngine.Where("id = ?", data.Id).Update(&UpdateTable2{
-		Msg: "test2",
+type Condi map[string]interface{}
+
+type UpdateAllCols struct {
+	Id     int64
+	Bool   bool
+	String string
+	Ptr    *string
+}
+
+type UpdateMustCols struct {
+	Id     int64
+	Bool   bool
+	String string
+}
+
+type UpdateIncr struct {
+	Id  int64
+	Cnt int
+}
+
+type Article struct {
+	Id      int32  `xorm:"pk INT autoincr"`
+	Name    string `xorm:"VARCHAR(45)"`
+	Img     string `xorm:"VARCHAR(100)"`
+	Aside   string `xorm:"VARCHAR(200)"`
+	Desc    string `xorm:"VARCHAR(200)"`
+	Content string `xorm:"TEXT"`
+	Status  int8   `xorm:"TINYINT(4)"`
+}
+
+func TestUpdateMap2(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(UpdateMustCols))
+
+	_, err := testEngine.Table("update_must_cols").Where("id =?", 1).Update(map[string]interface{}{
+		"bool": true,
+	})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+}
+
+func TestUpdate1(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Userinfo))
+
+	_, err := testEngine.Insert(&Userinfo{
+		Username: "user1",
+	})
+
+	var ori Userinfo
+	has, err := testEngine.Get(&ori)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if !has {
+		t.Error(errors.New("not exist"))
+		panic(errors.New("not exist"))
+	}
+
+	// update by id
+	user := Userinfo{Username: "xxx", Height: 1.2}
+	cnt, err := testEngine.Id(ori.Uid).Update(&user)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if cnt != 1 {
+		err = errors.New("update not returned 1")
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	condi := Condi{"username": "zzz", "departname": ""}
+	cnt, err = testEngine.Table(&user).Id(ori.Uid).Update(&condi)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if cnt != 1 {
+		err = errors.New("update not returned 1")
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	cnt, err = testEngine.Update(&Userinfo{Username: "yyy"}, &user)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	total, err := testEngine.Count(&user)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	if cnt != total {
+		err = errors.New("insert not returned 1")
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	// nullable update
+	{
+		user := &Userinfo{Username: "not null data", Height: 180.5}
+		_, err := testEngine.Insert(user)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+		userID := user.Uid
+
+		has, err := testEngine.Id(userID).
+			And("username = ?", user.Username).
+			And("height = ?", user.Height).
+			And("departname = ?", "").
+			And("detail_id = ?", 0).
+			And("is_man = ?", 0).
+			Get(&Userinfo{})
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+		if !has {
+			err = errors.New("cannot insert properly")
+			t.Error(err)
+			panic(err)
+		}
+
+		updatedUser := &Userinfo{Username: "null data"}
+		cnt, err = testEngine.Id(userID).
+			Nullable("height", "departname", "is_man", "created").
+			Update(updatedUser)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+		if cnt != 1 {
+			err = errors.New("update not returned 1")
+			t.Error(err)
+			panic(err)
+		}
+
+		has, err = testEngine.Id(userID).
+			And("username = ?", updatedUser.Username).
+			And("height IS NULL").
+			And("departname IS NULL").
+			And("is_man IS NULL").
+			And("created IS NULL").
+			And("detail_id = ?", 0).
+			Get(&Userinfo{})
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+		if !has {
+			err = errors.New("cannot update with null properly")
+			t.Error(err)
+			panic(err)
+		}
+
+		cnt, err = testEngine.Id(userID).Delete(&Userinfo{})
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+		if cnt != 1 {
+			err = errors.New("delete not returned 1")
+			t.Error(err)
+			panic(err)
+		}
+	}
+
+	err = testEngine.StoreEngine("Innodb").Sync2(&Article{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	defer func() {
+		err = testEngine.DropTables(&Article{})
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+	}()
+
+	a := &Article{0, "1", "2", "3", "4", "5", 2}
+	cnt, err = testEngine.Insert(a)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	if cnt != 1 {
+		err = errors.New(fmt.Sprintf("insert not returned 1 but %d", cnt))
+		t.Error(err)
+		panic(err)
+	}
+
+	if a.Id == 0 {
+		err = errors.New("insert returned id is 0")
+		t.Error(err)
+		panic(err)
+	}
+
+	cnt, err = testEngine.Id(a.Id).Update(&Article{Name: "6"})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	if cnt != 1 {
+		err = errors.New(fmt.Sprintf("insert not returned 1 but %d", cnt))
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	var s = "test"
+
+	col1 := &UpdateAllCols{Ptr: &s}
+	err = testEngine.Sync(col1)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	_, err = testEngine.Insert(col1)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	col2 := &UpdateAllCols{col1.Id, true, "", nil}
+	_, err = testEngine.Id(col2.Id).AllCols().Update(col2)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	col3 := &UpdateAllCols{}
+	has, err = testEngine.Id(col2.Id).Get(col3)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	if !has {
+		err = errors.New(fmt.Sprintf("cannot get id %d", col2.Id))
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	if *col2 != *col3 {
+		err = errors.New(fmt.Sprintf("col2 should eq col3"))
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	{
+
+		col1 := &UpdateMustCols{}
+		err = testEngine.Sync(col1)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+
+		_, err = testEngine.Insert(col1)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+
+		col2 := &UpdateMustCols{col1.Id, true, ""}
+		boolStr := testEngine.ColumnMapper.Obj2Table("Bool")
+		stringStr := testEngine.ColumnMapper.Obj2Table("String")
+		_, err = testEngine.Id(col2.Id).MustCols(boolStr, stringStr).Update(col2)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+
+		col3 := &UpdateMustCols{}
+		has, err := testEngine.Id(col2.Id).Get(col3)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+
+		if !has {
+			err = errors.New(fmt.Sprintf("cannot get id %d", col2.Id))
+			t.Error(err)
+			panic(err)
+			return
+		}
+
+		if *col2 != *col3 {
+			err = errors.New(fmt.Sprintf("col2 should eq col3"))
+			t.Error(err)
+			panic(err)
+			return
+		}
+	}
+
+	{
+
+		col1 := &UpdateIncr{}
+		err = testEngine.Sync(col1)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+
+		_, err = testEngine.Insert(col1)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+
+		cnt, err := testEngine.Id(col1.Id).Incr(testEngine.ColumnMapper.Obj2Table("Cnt")).Update(col1)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+		if cnt != 1 {
+			err = errors.New("update incr failed")
+			t.Error(err)
+			panic(err)
+		}
+
+		newCol := new(UpdateIncr)
+		has, err := testEngine.Id(col1.Id).Get(newCol)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+		if !has {
+			err = errors.New("has incr failed")
+			t.Error(err)
+			panic(err)
+		}
+		if 1 != newCol.Cnt {
+			err = fmt.Errorf("incr failed %v %v %v", newCol.Cnt, newCol, col1)
+			t.Error(err)
+			panic(err)
+		}
+	}
+}
+
+type UpdatedUpdate struct {
+	Id      int64
+	Updated time.Time `xorm:"updated"`
+}
+
+type UpdatedUpdate2 struct {
+	Id      int64
+	Updated int64 `xorm:"updated"`
+}
+
+type UpdatedUpdate3 struct {
+	Id      int64
+	Updated int `xorm:"updated bigint"`
+}
+
+type UpdatedUpdate4 struct {
+	Id      int64
+	Updated int `xorm:"updated"`
+}
+
+type UpdatedUpdate5 struct {
+	Id      int64
+	Updated time.Time `xorm:"updated bigint"`
+}
+
+func TestUpdateUpdated(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	di := new(UpdatedUpdate)
+	err := testEngine.Sync2(di)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = testEngine.Insert(&UpdatedUpdate{})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	ci := &UpdatedUpdate{}
+	_, err = testEngine.Id(1).Update(ci)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	has, err := testEngine.Id(1).Get(di)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !has {
+		t.Fatal(ErrNotExist)
+	}
+	if ci.Updated.Unix() != di.Updated.Unix() {
+		t.Fatal("should equal:", ci, di)
+	}
+	fmt.Println("ci:", ci, "di:", di)
+
+	di2 := new(UpdatedUpdate2)
+	err = testEngine.Sync2(di2)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = testEngine.Insert(&UpdatedUpdate2{})
+	if err != nil {
+		t.Fatal(err)
+	}
+	ci2 := &UpdatedUpdate2{}
+	_, err = testEngine.Id(1).Update(ci2)
+	if err != nil {
+		t.Fatal(err)
+	}
+	has, err = testEngine.Id(1).Get(di2)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !has {
+		t.Fatal(ErrNotExist)
+	}
+	if ci2.Updated != di2.Updated {
+		t.Fatal("should equal:", ci2, di2)
+	}
+	fmt.Println("ci2:", ci2, "di2:", di2)
+
+	di3 := new(UpdatedUpdate3)
+	err = testEngine.Sync2(di3)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = testEngine.Insert(&UpdatedUpdate3{})
+	if err != nil {
+		t.Fatal(err)
+	}
+	ci3 := &UpdatedUpdate3{}
+	_, err = testEngine.Id(1).Update(ci3)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	has, err = testEngine.Id(1).Get(di3)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !has {
+		t.Fatal(ErrNotExist)
+	}
+	if ci3.Updated != di3.Updated {
+		t.Fatal("should equal:", ci3, di3)
+	}
+	fmt.Println("ci3:", ci3, "di3:", di3)
+
+	di4 := new(UpdatedUpdate4)
+	err = testEngine.Sync2(di4)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = testEngine.Insert(&UpdatedUpdate4{})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	ci4 := &UpdatedUpdate4{}
+	_, err = testEngine.Id(1).Update(ci4)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	has, err = testEngine.Id(1).Get(di4)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !has {
+		t.Fatal(ErrNotExist)
+	}
+	if ci4.Updated != di4.Updated {
+		t.Fatal("should equal:", ci4, di4)
+	}
+	fmt.Println("ci4:", ci4, "di4:", di4)
+
+	di5 := new(UpdatedUpdate5)
+	err = testEngine.Sync2(di5)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = testEngine.Insert(&UpdatedUpdate5{})
+	if err != nil {
+		t.Fatal(err)
+	}
+	ci5 := &UpdatedUpdate5{}
+	_, err = testEngine.Id(1).Update(ci5)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	has, err = testEngine.Id(1).Get(di5)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !has {
+		t.Fatal(ErrNotExist)
+	}
+	if ci5.Updated.Unix() != di5.Updated.Unix() {
+		t.Fatal("should equal:", ci5, di5)
+	}
+	fmt.Println("ci5:", ci5, "di5:", di5)
+}
+
+func TestUpdateSameMapper(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	oldMapper := testEngine.ColumnMapper
+	testEngine.unMapType(rValue(new(Userinfo)).Type())
+	testEngine.unMapType(rValue(new(Condi)).Type())
+	testEngine.unMapType(rValue(new(Article)).Type())
+	testEngine.unMapType(rValue(new(UpdateAllCols)).Type())
+	testEngine.unMapType(rValue(new(UpdateMustCols)).Type())
+	testEngine.unMapType(rValue(new(UpdateIncr)).Type())
+	testEngine.SetMapper(core.SameMapper{})
+	defer func() {
+		testEngine.unMapType(rValue(new(Userinfo)).Type())
+		testEngine.unMapType(rValue(new(Condi)).Type())
+		testEngine.unMapType(rValue(new(Article)).Type())
+		testEngine.unMapType(rValue(new(UpdateAllCols)).Type())
+		testEngine.unMapType(rValue(new(UpdateMustCols)).Type())
+		testEngine.unMapType(rValue(new(UpdateIncr)).Type())
+		testEngine.SetMapper(oldMapper)
+	}()
+
+	assertSync(t, new(Userinfo))
+
+	_, err := testEngine.Insert(&Userinfo{
+		Username: "user1",
 	})
 	assert.NoError(t, err)
-	assert.EqualValues(t, 1, cnt)
+
+	var ori Userinfo
+	has, err := testEngine.Get(&ori)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if !has {
+		t.Error(errors.New("not exist"))
+		panic(errors.New("not exist"))
+	}
+	// update by id
+	user := Userinfo{Username: "xxx", Height: 1.2}
+	cnt, err := testEngine.Id(ori.Uid).Update(&user)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if cnt != 1 {
+		err = errors.New("update not returned 1")
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	condi := Condi{"Username": "zzz", "Departname": ""}
+	cnt, err = testEngine.Table(&user).Id(ori.Uid).Update(&condi)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	if cnt != 1 {
+		err = errors.New("update not returned 1")
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	cnt, err = testEngine.Update(&Userinfo{Username: "yyy"}, &user)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	total, err := testEngine.Count(&user)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	if cnt != total {
+		err = errors.New("insert not returned 1")
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	err = testEngine.Sync(&Article{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	defer func() {
+		err = testEngine.DropTables(&Article{})
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+	}()
+
+	a := &Article{0, "1", "2", "3", "4", "5", 2}
+	cnt, err = testEngine.Insert(a)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	if cnt != 1 {
+		err = errors.New(fmt.Sprintf("insert not returned 1 but %d", cnt))
+		t.Error(err)
+		panic(err)
+	}
+
+	if a.Id == 0 {
+		err = errors.New("insert returned id is 0")
+		t.Error(err)
+		panic(err)
+	}
+
+	cnt, err = testEngine.Id(a.Id).Update(&Article{Name: "6"})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	if cnt != 1 {
+		err = errors.New(fmt.Sprintf("insert not returned 1 but %d", cnt))
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	col1 := &UpdateAllCols{}
+	err = testEngine.Sync(col1)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	_, err = testEngine.Insert(col1)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	col2 := &UpdateAllCols{col1.Id, true, "", nil}
+	_, err = testEngine.Id(col2.Id).AllCols().Update(col2)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	col3 := &UpdateAllCols{}
+	has, err = testEngine.Id(col2.Id).Get(col3)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	if !has {
+		err = errors.New(fmt.Sprintf("cannot get id %d", col2.Id))
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	if *col2 != *col3 {
+		err = errors.New(fmt.Sprintf("col2 should eq col3"))
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	{
+		col1 := &UpdateMustCols{}
+		err = testEngine.Sync(col1)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+
+		_, err = testEngine.Insert(col1)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+
+		col2 := &UpdateMustCols{col1.Id, true, ""}
+		boolStr := testEngine.ColumnMapper.Obj2Table("Bool")
+		stringStr := testEngine.ColumnMapper.Obj2Table("String")
+		_, err = testEngine.Id(col2.Id).MustCols(boolStr, stringStr).Update(col2)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+
+		col3 := &UpdateMustCols{}
+		has, err := testEngine.Id(col2.Id).Get(col3)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+
+		if !has {
+			err = errors.New(fmt.Sprintf("cannot get id %d", col2.Id))
+			t.Error(err)
+			panic(err)
+			return
+		}
+
+		if *col2 != *col3 {
+			err = errors.New(fmt.Sprintf("col2 should eq col3"))
+			t.Error(err)
+			panic(err)
+			return
+		}
+	}
+
+	{
+
+		col1 := &UpdateIncr{}
+		err = testEngine.Sync(col1)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+
+		_, err = testEngine.Insert(col1)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+
+		cnt, err := testEngine.Id(col1.Id).Incr("`Cnt`").Update(col1)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+		if cnt != 1 {
+			err = errors.New("update incr failed")
+			t.Error(err)
+			panic(err)
+		}
+
+		newCol := new(UpdateIncr)
+		has, err := testEngine.Id(col1.Id).Get(newCol)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+		if !has {
+			err = errors.New("has incr failed")
+			t.Error(err)
+			panic(err)
+		}
+		if 1 != newCol.Cnt {
+			err = errors.New("incr failed")
+			t.Error(err)
+			panic(err)
+		}
+	}
+}
+
+func TestUseBool(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Userinfo))
+
+	cnt1, err := testEngine.Count(&Userinfo{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	users := make([]Userinfo, 0)
+	err = testEngine.Find(&users)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	var fNumber int64
+	for _, u := range users {
+		if u.IsMan == false {
+			fNumber += 1
+		}
+	}
+
+	cnt2, err := testEngine.UseBool().Update(&Userinfo{IsMan: true})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if fNumber != cnt2 {
+		fmt.Println("cnt1", cnt1, "fNumber", fNumber, "cnt2", cnt2)
+		/*err = errors.New("Updated number is not corrected.")
+		  t.Error(err)
+		  panic(err)*/
+	}
+
+	_, err = testEngine.Update(&Userinfo{IsMan: true})
+	if err == nil {
+		err = errors.New("error condition")
+		t.Error(err)
+		panic(err)
+	}
+}
+
+func TestBool(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(Userinfo))
+
+	_, err := testEngine.UseBool().Update(&Userinfo{IsMan: true})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	users := make([]Userinfo, 0)
+	err = testEngine.Find(&users)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	for _, user := range users {
+		if !user.IsMan {
+			err = errors.New("update bool or find bool error")
+			t.Error(err)
+			panic(err)
+		}
+	}
+
+	_, err = testEngine.UseBool().Update(&Userinfo{IsMan: false})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	users = make([]Userinfo, 0)
+	err = testEngine.Find(&users)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	for _, user := range users {
+		if user.IsMan {
+			err = errors.New("update bool or find bool error")
+			t.Error(err)
+			panic(err)
+		}
+	}
 }

+ 1 - 1
statement_test.go

@@ -26,7 +26,7 @@ var colStrTests = []struct {
 }
 
 func TestColumnsStringGeneration(t *testing.T) {
-	if *db == "postgres" {
+	if dbType == "postgres" || dbType == "mssql" {
 		return
 	}
 

+ 39 - 0
tag_cache_test.go

@@ -0,0 +1,39 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCacheTag(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	type CacheDomain struct {
+		Id   int64 `xorm:"pk cache"`
+		Name string
+	}
+
+	assert.NoError(t, testEngine.CreateTables(&CacheDomain{}))
+
+	table := testEngine.TableInfo(&CacheDomain{})
+	assert.True(t, table.Cacher != nil)
+}
+
+func TestNoCacheTag(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	type NoCacheDomain struct {
+		Id   int64 `xorm:"pk nocache"`
+		Name string
+	}
+
+	assert.NoError(t, testEngine.CreateTables(&NoCacheDomain{}))
+
+	table := testEngine.TableInfo(&NoCacheDomain{})
+	assert.True(t, table.Cacher == nil)
+}

+ 538 - 0
tag_extends_test.go

@@ -0,0 +1,538 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+	"errors"
+	"fmt"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/xormplus/core"
+)
+
+type tempUser struct {
+	Id       int64
+	Username string
+}
+
+type tempUser2 struct {
+	TempUser   tempUser `xorm:"extends"`
+	Departname string
+}
+
+type tempUser3 struct {
+	Temp       *tempUser `xorm:"extends"`
+	Departname string
+}
+
+type tempUser4 struct {
+	TempUser2 tempUser2 `xorm:"extends"`
+}
+
+type Userinfo struct {
+	Uid        int64  `xorm:"id pk not null autoincr"`
+	Username   string `xorm:"unique"`
+	Departname string
+	Alias      string `xorm:"-"`
+	Created    time.Time
+	Detail     Userdetail `xorm:"detail_id int(11)"`
+	Height     float64
+	Avatar     []byte
+	IsMan      bool
+}
+
+type Userdetail struct {
+	Id      int64
+	Intro   string `xorm:"text"`
+	Profile string `xorm:"varchar(2000)"`
+}
+
+type UserAndDetail struct {
+	Userinfo   `xorm:"extends"`
+	Userdetail `xorm:"extends"`
+}
+
+func TestExtends(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	err := testEngine.DropTables(&tempUser2{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	err = testEngine.CreateTables(&tempUser2{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	tu := &tempUser2{tempUser{0, "extends"}, "dev depart"}
+	_, err = testEngine.Insert(tu)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	tu2 := &tempUser2{}
+	_, err = testEngine.Get(tu2)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	tu3 := &tempUser2{tempUser{0, "extends update"}, ""}
+	_, err = testEngine.Id(tu2.TempUser.Id).Update(tu3)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	err = testEngine.DropTables(&tempUser4{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	err = testEngine.CreateTables(&tempUser4{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	tu8 := &tempUser4{tempUser2{tempUser{0, "extends"}, "dev depart"}}
+	_, err = testEngine.Insert(tu8)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	tu9 := &tempUser4{}
+	_, err = testEngine.Get(tu9)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if tu9.TempUser2.TempUser.Username != tu8.TempUser2.TempUser.Username || tu9.TempUser2.Departname != tu8.TempUser2.Departname {
+		err = errors.New(fmt.Sprintln("not equal for", tu8, tu9))
+		t.Error(err)
+		panic(err)
+	}
+
+	tu10 := &tempUser4{tempUser2{tempUser{0, "extends update"}, ""}}
+	_, err = testEngine.Id(tu9.TempUser2.TempUser.Id).Update(tu10)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	err = testEngine.DropTables(&tempUser3{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	err = testEngine.CreateTables(&tempUser3{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	tu4 := &tempUser3{&tempUser{0, "extends"}, "dev depart"}
+	_, err = testEngine.Insert(tu4)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	tu5 := &tempUser3{}
+	_, err = testEngine.Get(tu5)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if tu5.Temp == nil {
+		err = errors.New("error get data extends")
+		t.Error(err)
+		panic(err)
+	}
+	if tu5.Temp.Id != 1 || tu5.Temp.Username != "extends" ||
+		tu5.Departname != "dev depart" {
+		err = errors.New("error get data extends")
+		t.Error(err)
+		panic(err)
+	}
+
+	tu6 := &tempUser3{&tempUser{0, "extends update"}, ""}
+	_, err = testEngine.Id(tu5.Temp.Id).Update(tu6)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	users := make([]tempUser3, 0)
+	err = testEngine.Find(&users)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if len(users) != 1 {
+		err = errors.New("error get data not 1")
+		t.Error(err)
+		panic(err)
+	}
+
+	assertSync(t, new(Userinfo), new(Userdetail))
+
+	detail := Userdetail{
+		Intro: "I'm in China",
+	}
+	_, err = testEngine.Insert(&detail)
+	assert.NoError(t, err)
+
+	_, err = testEngine.Insert(&Userinfo{
+		Username: "lunny",
+		Detail:   detail,
+	})
+	assert.NoError(t, err)
+
+	var info UserAndDetail
+	qt := testEngine.Quote
+	ui := testEngine.TableMapper.Obj2Table("Userinfo")
+	ud := testEngine.TableMapper.Obj2Table("Userdetail")
+	uiid := testEngine.TableMapper.Obj2Table("Id")
+	udid := "detail_id"
+	sql := fmt.Sprintf("select * from %s, %s where %s.%s = %s.%s",
+		qt(ui), qt(ud), qt(ui), qt(udid), qt(ud), qt(uiid))
+	b, err := testEngine.Sql(sql).NoCascade().Get(&info)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if !b {
+		err = errors.New("should has lest one record")
+		t.Error(err)
+		panic(err)
+	}
+	fmt.Println(info)
+	if info.Userinfo.Uid == 0 || info.Userdetail.Id == 0 {
+		err = errors.New("all of the id should has value")
+		t.Error(err)
+		panic(err)
+	}
+
+	fmt.Println("----join--info2")
+	var info2 UserAndDetail
+	b, err = testEngine.Table(&Userinfo{}).
+		Join("LEFT", qt(ud), qt(ui)+"."+qt("detail_id")+" = "+qt(ud)+"."+qt(uiid)).
+		NoCascade().Get(&info2)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if !b {
+		err = errors.New("should has lest one record")
+		t.Error(err)
+		panic(err)
+	}
+	if info2.Userinfo.Uid == 0 || info2.Userdetail.Id == 0 {
+		err = errors.New("all of the id should has value")
+		t.Error(err)
+		panic(err)
+	}
+	fmt.Println(info2)
+
+	fmt.Println("----join--infos2")
+	var infos2 = make([]UserAndDetail, 0)
+	err = testEngine.Table(&Userinfo{}).
+		Join("LEFT", qt(ud), qt(ui)+"."+qt("detail_id")+" = "+qt(ud)+"."+qt(uiid)).
+		NoCascade().
+		Find(&infos2)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	fmt.Println(infos2)
+}
+
+type MessageBase struct {
+	Id     int64 `xorm:"int(11) pk autoincr"`
+	TypeId int64 `xorm:"int(11) notnull"`
+}
+
+type Message struct {
+	MessageBase `xorm:"extends"`
+	Title       string    `xorm:"varchar(100) notnull"`
+	Content     string    `xorm:"text notnull"`
+	Uid         int64     `xorm:"int(11) notnull"`
+	ToUid       int64     `xorm:"int(11) notnull"`
+	CreateTime  time.Time `xorm:"datetime notnull created"`
+}
+
+type MessageUser struct {
+	Id   int64
+	Name string
+}
+
+type MessageType struct {
+	Id   int64
+	Name string
+}
+
+type MessageExtend3 struct {
+	Message  `xorm:"extends"`
+	Sender   MessageUser `xorm:"extends"`
+	Receiver MessageUser `xorm:"extends"`
+	Type     MessageType `xorm:"extends"`
+}
+
+type MessageExtend4 struct {
+	Message     `xorm:"extends"`
+	MessageUser `xorm:"extends"`
+	MessageType `xorm:"extends"`
+}
+
+func TestExtends2(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	err := testEngine.DropTables(&Message{}, &MessageUser{}, &MessageType{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	err = testEngine.CreateTables(&Message{}, &MessageUser{}, &MessageType{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	var sender = MessageUser{Name: "sender"}
+	var receiver = MessageUser{Name: "receiver"}
+	var msgtype = MessageType{Name: "type"}
+	_, err = testEngine.Insert(&sender, &receiver, &msgtype)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	msg := Message{
+		MessageBase: MessageBase{
+			Id: msgtype.Id,
+		},
+		Title:   "test",
+		Content: "test",
+		Uid:     sender.Id,
+		ToUid:   receiver.Id,
+	}
+	if testEngine.dialect.DBType() == core.MSSQL {
+		_, err = testEngine.Exec("SET IDENTITY_INSERT message ON")
+		assert.NoError(t, err)
+	}
+
+	_, err = testEngine.Insert(&msg)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	var mapper = testEngine.TableMapper.Obj2Table
+	userTableName := mapper("MessageUser")
+	typeTableName := mapper("MessageType")
+	msgTableName := mapper("Message")
+
+	list := make([]Message, 0)
+	err = testEngine.Table(msgTableName).Join("LEFT", []string{userTableName, "sender"}, "`sender`.`"+mapper("Id")+"`=`"+msgTableName+"`.`"+mapper("Uid")+"`").
+		Join("LEFT", []string{userTableName, "receiver"}, "`receiver`.`"+mapper("Id")+"`=`"+msgTableName+"`.`"+mapper("ToUid")+"`").
+		Join("LEFT", []string{typeTableName, "type"}, "`type`.`"+mapper("Id")+"`=`"+msgTableName+"`.`"+mapper("Id")+"`").
+		Find(&list)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	if len(list) != 1 {
+		err = errors.New(fmt.Sprintln("should have 1 message, got", len(list)))
+		t.Error(err)
+		panic(err)
+	}
+
+	if list[0].Id != msg.Id {
+		err = errors.New(fmt.Sprintln("should message equal", list[0], msg))
+		t.Error(err)
+		panic(err)
+	}
+}
+
+func TestExtends3(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	err := testEngine.DropTables(&Message{}, &MessageUser{}, &MessageType{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	err = testEngine.CreateTables(&Message{}, &MessageUser{}, &MessageType{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	var sender = MessageUser{Name: "sender"}
+	var receiver = MessageUser{Name: "receiver"}
+	var msgtype = MessageType{Name: "type"}
+	_, err = testEngine.Insert(&sender, &receiver, &msgtype)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	msg := Message{
+		MessageBase: MessageBase{
+			Id: msgtype.Id,
+		},
+		Title:   "test",
+		Content: "test",
+		Uid:     sender.Id,
+		ToUid:   receiver.Id,
+	}
+	if testEngine.dialect.DBType() == core.MSSQL {
+		_, err = testEngine.Exec("SET IDENTITY_INSERT message ON")
+		assert.NoError(t, err)
+	}
+	_, err = testEngine.Insert(&msg)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	var mapper = testEngine.TableMapper.Obj2Table
+	userTableName := mapper("MessageUser")
+	typeTableName := mapper("MessageType")
+	msgTableName := mapper("Message")
+
+	list := make([]MessageExtend3, 0)
+	err = testEngine.Table(msgTableName).Join("LEFT", []string{userTableName, "sender"}, "`sender`.`"+mapper("Id")+"`=`"+msgTableName+"`.`"+mapper("Uid")+"`").
+		Join("LEFT", []string{userTableName, "receiver"}, "`receiver`.`"+mapper("Id")+"`=`"+msgTableName+"`.`"+mapper("ToUid")+"`").
+		Join("LEFT", []string{typeTableName, "type"}, "`type`.`"+mapper("Id")+"`=`"+msgTableName+"`.`"+mapper("Id")+"`").
+		Find(&list)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	if len(list) != 1 {
+		err = errors.New(fmt.Sprintln("should have 1 message, got", len(list)))
+		t.Error(err)
+		panic(err)
+	}
+
+	if list[0].Message.Id != msg.Id {
+		err = errors.New(fmt.Sprintln("should message equal", list[0].Message, msg))
+		t.Error(err)
+		panic(err)
+	}
+
+	if list[0].Sender.Id != sender.Id || list[0].Sender.Name != sender.Name {
+		err = errors.New(fmt.Sprintln("should sender equal", list[0].Sender, sender))
+		t.Error(err)
+		panic(err)
+	}
+
+	if list[0].Receiver.Id != receiver.Id || list[0].Receiver.Name != receiver.Name {
+		err = errors.New(fmt.Sprintln("should receiver equal", list[0].Receiver, receiver))
+		t.Error(err)
+		panic(err)
+	}
+
+	if list[0].Type.Id != msgtype.Id || list[0].Type.Name != msgtype.Name {
+		err = errors.New(fmt.Sprintln("should msgtype equal", list[0].Type, msgtype))
+		t.Error(err)
+		panic(err)
+	}
+}
+
+func TestExtends4(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	err := testEngine.DropTables(&Message{}, &MessageUser{}, &MessageType{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	err = testEngine.CreateTables(&Message{}, &MessageUser{}, &MessageType{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	var sender = MessageUser{Name: "sender"}
+	var msgtype = MessageType{Name: "type"}
+	_, err = testEngine.Insert(&sender, &msgtype)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	msg := Message{
+		MessageBase: MessageBase{
+			Id: msgtype.Id,
+		},
+		Title:   "test",
+		Content: "test",
+		Uid:     sender.Id,
+	}
+	if testEngine.dialect.DBType() == core.MSSQL {
+		_, err = testEngine.Exec("SET IDENTITY_INSERT message ON")
+		assert.NoError(t, err)
+	}
+	_, err = testEngine.Insert(&msg)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	var mapper = testEngine.TableMapper.Obj2Table
+	userTableName := mapper("MessageUser")
+	typeTableName := mapper("MessageType")
+	msgTableName := mapper("Message")
+
+	list := make([]MessageExtend4, 0)
+	err = testEngine.Table(msgTableName).Join("LEFT", userTableName, "`"+userTableName+"`.`"+mapper("Id")+"`=`"+msgTableName+"`.`"+mapper("Uid")+"`").
+		Join("LEFT", typeTableName, "`"+typeTableName+"`.`"+mapper("Id")+"`=`"+msgTableName+"`.`"+mapper("Id")+"`").
+		Find(&list)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	if len(list) != 1 {
+		err = errors.New(fmt.Sprintln("should have 1 message, got", len(list)))
+		t.Error(err)
+		panic(err)
+	}
+
+	if list[0].Message.Id != msg.Id {
+		err = errors.New(fmt.Sprintln("should message equal", list[0].Message, msg))
+		t.Error(err)
+		panic(err)
+	}
+
+	if list[0].MessageUser.Id != sender.Id || list[0].MessageUser.Name != sender.Name {
+		err = errors.New(fmt.Sprintln("should sender equal", list[0].MessageUser, sender))
+		t.Error(err)
+		panic(err)
+	}
+
+	if list[0].MessageType.Id != msgtype.Id || list[0].MessageType.Name != msgtype.Name {
+		err = errors.New(fmt.Sprintln("should msgtype equal", list[0].MessageType, msgtype))
+		t.Error(err)
+		panic(err)
+	}
+}

+ 85 - 0
tag_id_test.go

@@ -0,0 +1,85 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/xormplus/core"
+)
+
+type IDGonicMapper struct {
+	ID int64
+}
+
+func TestGonicMapperID(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	oldMapper := testEngine.ColumnMapper
+	testEngine.unMapType(rValue(new(IDGonicMapper)).Type())
+	testEngine.SetMapper(core.LintGonicMapper)
+	defer func() {
+		testEngine.unMapType(rValue(new(IDGonicMapper)).Type())
+		testEngine.SetMapper(oldMapper)
+	}()
+
+	err := testEngine.CreateTables(new(IDGonicMapper))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	tables, err := testEngine.DBMetas()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for _, tb := range tables {
+		if tb.Name == "id_gonic_mapper" {
+			if len(tb.PKColumns()) != 1 || tb.PKColumns()[0].Name != "id" {
+				t.Fatal(tb)
+			}
+			return
+		}
+	}
+
+	t.Fatal("not table id_gonic_mapper")
+}
+
+type IDSameMapper struct {
+	ID int64
+}
+
+func TestSameMapperID(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	oldMapper := testEngine.ColumnMapper
+	testEngine.unMapType(rValue(new(IDSameMapper)).Type())
+	testEngine.SetMapper(core.SameMapper{})
+	defer func() {
+		testEngine.unMapType(rValue(new(IDSameMapper)).Type())
+		testEngine.SetMapper(oldMapper)
+	}()
+
+	err := testEngine.CreateTables(new(IDSameMapper))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	tables, err := testEngine.DBMetas()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for _, tb := range tables {
+		if tb.Name == "IDSameMapper" {
+			if len(tb.PKColumns()) != 1 || tb.PKColumns()[0].Name != "ID" {
+				t.Fatal(tb)
+			}
+			return
+		}
+	}
+	t.Fatal("not table IDSameMapper")
+}

+ 189 - 0
tag_test.go

@@ -0,0 +1,189 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+	"errors"
+	"fmt"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+type UserCU struct {
+	Id      int64
+	Name    string
+	Created time.Time `xorm:"created"`
+	Updated time.Time `xorm:"updated"`
+}
+
+func TestCreatedAndUpdated(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	u := new(UserCU)
+	err := testEngine.DropTables(u)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	err = testEngine.CreateTables(u)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	u.Name = "sss"
+	cnt, err := testEngine.Insert(u)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if cnt != 1 {
+		err = errors.New("insert not returned 1")
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	u.Name = "xxx"
+	cnt, err = testEngine.Id(u.Id).Update(u)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if cnt != 1 {
+		err = errors.New("update not returned 1")
+		t.Error(err)
+		panic(err)
+		return
+	}
+
+	u.Id = 0
+	u.Created = time.Now().Add(-time.Hour * 24 * 365)
+	u.Updated = u.Created
+	fmt.Println(u)
+	cnt, err = testEngine.NoAutoTime().Insert(u)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if cnt != 1 {
+		err = errors.New("insert not returned 1")
+		t.Error(err)
+		panic(err)
+		return
+	}
+}
+
+type StrangeName struct {
+	Id_t int64 `xorm:"pk autoincr"`
+	Name string
+}
+
+func TestStrangeName(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	err := testEngine.DropTables(new(StrangeName))
+	if err != nil {
+		t.Error(err)
+	}
+
+	err = testEngine.CreateTables(new(StrangeName))
+	if err != nil {
+		t.Error(err)
+	}
+
+	_, err = testEngine.Insert(&StrangeName{Name: "sfsfdsfds"})
+	if err != nil {
+		t.Error(err)
+	}
+
+	beans := make([]StrangeName, 0)
+	err = testEngine.Find(&beans)
+	if err != nil {
+		t.Error(err)
+	}
+}
+
+type CreatedUpdated struct {
+	Id       int64
+	Name     string
+	Value    float64   `xorm:"numeric"`
+	Created  time.Time `xorm:"created"`
+	Created2 time.Time `xorm:"created"`
+	Updated  time.Time `xorm:"updated"`
+}
+
+func TestCreatedUpdated(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	err := testEngine.Sync(&CreatedUpdated{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	c := &CreatedUpdated{Name: "test"}
+	_, err = testEngine.Insert(c)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	c2 := new(CreatedUpdated)
+	has, err := testEngine.Id(c.Id).Get(c2)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	if !has {
+		panic(errors.New("no id"))
+	}
+
+	c2.Value -= 1
+	_, err = testEngine.Id(c2.Id).Update(c2)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+}
+
+type Lowercase struct {
+	Id    int64
+	Name  string
+	ended int64 `xorm:"-"`
+}
+
+func TestLowerCase(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	err := testEngine.Sync(&Lowercase{})
+	_, err = testEngine.Where("(id) > 0").Delete(&Lowercase{})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	_, err = testEngine.Insert(&Lowercase{ended: 1})
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	ls := make([]Lowercase, 0)
+	err = testEngine.Find(&ls)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	if len(ls) != 1 {
+		err = errors.New("should be 1")
+		t.Error(err)
+		panic(err)
+	}
+}

+ 128 - 0
tag_version_test.go

@@ -0,0 +1,128 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+	"errors"
+	"fmt"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+type VersionS struct {
+	Id      int64
+	Name    string
+	Ver     int       `xorm:"version"`
+	Created time.Time `xorm:"created"`
+}
+
+func TestVersion1(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	err := testEngine.DropTables(new(VersionS))
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	err = testEngine.CreateTables(new(VersionS))
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	ver := &VersionS{Name: "sfsfdsfds"}
+	_, err = testEngine.Insert(ver)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	fmt.Println(ver)
+	if ver.Ver != 1 {
+		err = errors.New("insert error")
+		t.Error(err)
+		panic(err)
+	}
+
+	newVer := new(VersionS)
+	has, err := testEngine.Id(ver.Id).Get(newVer)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	if !has {
+		t.Error(errors.New(fmt.Sprintf("no version id is %v", ver.Id)))
+		panic(err)
+	}
+	fmt.Println(newVer)
+	if newVer.Ver != 1 {
+		err = errors.New("insert error")
+		t.Error(err)
+		panic(err)
+	}
+
+	newVer.Name = "-------"
+	_, err = testEngine.Id(ver.Id).Update(newVer)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	if newVer.Ver != 2 {
+		err = errors.New("update should set version back to struct")
+		t.Error(err)
+	}
+
+	newVer = new(VersionS)
+	has, err = testEngine.Id(ver.Id).Get(newVer)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	fmt.Println(newVer)
+	if newVer.Ver != 2 {
+		err = errors.New("insert error")
+		t.Error(err)
+		panic(err)
+	}
+}
+
+func TestVersion2(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	err := testEngine.DropTables(new(VersionS))
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	err = testEngine.CreateTables(new(VersionS))
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	var vers = []VersionS{
+		{Name: "sfsfdsfds"},
+		{Name: "xxxxx"},
+	}
+	_, err = testEngine.Insert(vers)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	fmt.Println(vers)
+
+	for _, v := range vers {
+		if v.Ver != 1 {
+			err := errors.New("version should be 1")
+			t.Error(err)
+			panic(err)
+		}
+	}
+}

+ 476 - 0
time_test.go

@@ -0,0 +1,476 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestTimeUserTime(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	type TimeUser struct {
+		Id       string
+		OperTime time.Time
+	}
+
+	assertSync(t, new(TimeUser))
+
+	var user = TimeUser{
+		Id:       "lunny",
+		OperTime: time.Now(),
+	}
+
+	fmt.Println("user", user.OperTime)
+
+	cnt, err := testEngine.Insert(&user)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+
+	var user2 TimeUser
+	has, err := testEngine.Get(&user2)
+	assert.NoError(t, err)
+	assert.True(t, has)
+	assert.EqualValues(t, user.OperTime.Unix(), user2.OperTime.Unix())
+	assert.EqualValues(t, formatTime(user.OperTime), formatTime(user2.OperTime))
+	fmt.Println("user2", user2.OperTime)
+}
+
+func TestTimeUserTimeDiffLoc(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	loc, err := time.LoadLocation("Asia/Shanghai")
+	assert.NoError(t, err)
+	testEngine.TZLocation = loc
+	dbLoc, err := time.LoadLocation("America/New_York")
+	assert.NoError(t, err)
+	testEngine.DatabaseTZ = dbLoc
+
+	type TimeUser struct {
+		Id       string
+		OperTime time.Time
+	}
+
+	assertSync(t, new(TimeUser))
+
+	var user = TimeUser{
+		Id:       "lunny",
+		OperTime: time.Now(),
+	}
+
+	fmt.Println("user", user.OperTime)
+
+	cnt, err := testEngine.Insert(&user)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+
+	var user2 TimeUser
+	has, err := testEngine.Get(&user2)
+	assert.NoError(t, err)
+	assert.True(t, has)
+	assert.EqualValues(t, user.OperTime.Unix(), user2.OperTime.Unix())
+	assert.EqualValues(t, formatTime(user.OperTime.In(loc)), formatTime(user2.OperTime))
+	fmt.Println("user2", user2.OperTime)
+}
+
+func TestTimeUserCreated(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	type UserCreated struct {
+		Id        string
+		CreatedAt time.Time `xorm:"created"`
+	}
+
+	assertSync(t, new(UserCreated))
+
+	var user = UserCreated{
+		Id: "lunny",
+	}
+
+	fmt.Println("user", user.CreatedAt)
+
+	cnt, err := testEngine.Insert(&user)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+
+	var user2 UserCreated
+	has, err := testEngine.Get(&user2)
+	assert.NoError(t, err)
+	assert.True(t, has)
+	assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix())
+	assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user2.CreatedAt))
+	fmt.Println("user2", user2.CreatedAt)
+}
+
+func TestTimeUserCreatedDiffLoc(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	loc, err := time.LoadLocation("Asia/Shanghai")
+	assert.NoError(t, err)
+	testEngine.TZLocation = loc
+	dbLoc, err := time.LoadLocation("America/New_York")
+	assert.NoError(t, err)
+	testEngine.DatabaseTZ = dbLoc
+
+	type UserCreated struct {
+		Id        string
+		CreatedAt time.Time `xorm:"created"`
+	}
+
+	assertSync(t, new(UserCreated))
+
+	var user = UserCreated{
+		Id: "lunny",
+	}
+
+	fmt.Println("user", user.CreatedAt)
+
+	cnt, err := testEngine.Insert(&user)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+
+	var user2 UserCreated
+	has, err := testEngine.Get(&user2)
+	assert.NoError(t, err)
+	assert.True(t, has)
+	assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix())
+	assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user2.CreatedAt))
+	fmt.Println("user2", user2.CreatedAt)
+}
+
+func TestTimeUserUpdated(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	type UserUpdated struct {
+		Id        string
+		CreatedAt time.Time `xorm:"created"`
+		UpdatedAt time.Time `xorm:"updated"`
+	}
+
+	assertSync(t, new(UserUpdated))
+
+	var user = UserUpdated{
+		Id: "lunny",
+	}
+
+	fmt.Println("user", user.CreatedAt, user.UpdatedAt)
+
+	cnt, err := testEngine.Insert(&user)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+
+	var user2 UserUpdated
+	has, err := testEngine.Get(&user2)
+	assert.NoError(t, err)
+	assert.True(t, has)
+	assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix())
+	assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user2.CreatedAt))
+	assert.EqualValues(t, user.UpdatedAt.Unix(), user2.UpdatedAt.Unix())
+	assert.EqualValues(t, formatTime(user.UpdatedAt), formatTime(user2.UpdatedAt))
+	fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt)
+
+	var user3 = UserUpdated{
+		Id: "lunny2",
+	}
+
+	cnt, err = testEngine.Update(&user3)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+	assert.True(t, user.UpdatedAt.Unix() <= user3.UpdatedAt.Unix())
+
+	var user4 UserUpdated
+	has, err = testEngine.Get(&user4)
+	assert.NoError(t, err)
+	assert.True(t, has)
+	assert.EqualValues(t, user.CreatedAt.Unix(), user4.CreatedAt.Unix())
+	assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user4.CreatedAt))
+	assert.EqualValues(t, user3.UpdatedAt.Unix(), user4.UpdatedAt.Unix())
+	assert.EqualValues(t, formatTime(user3.UpdatedAt), formatTime(user4.UpdatedAt))
+	fmt.Println("user3", user.CreatedAt, user4.UpdatedAt)
+}
+
+func TestTimeUserUpdatedDiffLoc(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	loc, err := time.LoadLocation("Asia/Shanghai")
+	assert.NoError(t, err)
+	testEngine.TZLocation = loc
+	dbLoc, err := time.LoadLocation("America/New_York")
+	assert.NoError(t, err)
+	testEngine.DatabaseTZ = dbLoc
+
+	type UserUpdated struct {
+		Id        string
+		CreatedAt time.Time `xorm:"created"`
+		UpdatedAt time.Time `xorm:"updated"`
+	}
+
+	assertSync(t, new(UserUpdated))
+
+	var user = UserUpdated{
+		Id: "lunny",
+	}
+
+	fmt.Println("user", user.CreatedAt, user.UpdatedAt)
+
+	cnt, err := testEngine.Insert(&user)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+
+	var user2 UserUpdated
+	has, err := testEngine.Get(&user2)
+	assert.NoError(t, err)
+	assert.True(t, has)
+	assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix())
+	assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user2.CreatedAt))
+	assert.EqualValues(t, user.UpdatedAt.Unix(), user2.UpdatedAt.Unix())
+	assert.EqualValues(t, formatTime(user.UpdatedAt), formatTime(user2.UpdatedAt))
+	fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt)
+
+	var user3 = UserUpdated{
+		Id: "lunny2",
+	}
+
+	cnt, err = testEngine.Update(&user3)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+	assert.True(t, user.UpdatedAt.Unix() <= user3.UpdatedAt.Unix())
+
+	var user4 UserUpdated
+	has, err = testEngine.Get(&user4)
+	assert.NoError(t, err)
+	assert.True(t, has)
+	assert.EqualValues(t, user.CreatedAt.Unix(), user4.CreatedAt.Unix())
+	assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user4.CreatedAt))
+	assert.EqualValues(t, user3.UpdatedAt.Unix(), user4.UpdatedAt.Unix())
+	assert.EqualValues(t, formatTime(user3.UpdatedAt), formatTime(user4.UpdatedAt))
+	fmt.Println("user3", user.CreatedAt, user4.UpdatedAt)
+}
+
+func TestTimeUserDeleted(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	type UserDeleted struct {
+		Id        string
+		CreatedAt time.Time `xorm:"created"`
+		UpdatedAt time.Time `xorm:"updated"`
+		DeletedAt time.Time `xorm:"deleted"`
+	}
+
+	assertSync(t, new(UserDeleted))
+
+	var user = UserDeleted{
+		Id: "lunny",
+	}
+
+	cnt, err := testEngine.Insert(&user)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+	fmt.Println("user", user.CreatedAt, user.UpdatedAt, user.DeletedAt)
+
+	var user2 UserDeleted
+	has, err := testEngine.Get(&user2)
+	assert.NoError(t, err)
+	assert.True(t, has)
+	assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix())
+	assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user2.CreatedAt))
+	assert.EqualValues(t, user.UpdatedAt.Unix(), user2.UpdatedAt.Unix())
+	assert.EqualValues(t, formatTime(user.UpdatedAt), formatTime(user2.UpdatedAt))
+	assert.True(t, isTimeZero(user2.DeletedAt))
+	fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt, user2.DeletedAt)
+
+	var user3 UserDeleted
+	cnt, err = testEngine.Where("id = ?", "lunny").Delete(&user3)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+	assert.True(t, !isTimeZero(user3.DeletedAt))
+
+	var user4 UserDeleted
+	has, err = testEngine.Unscoped().Get(&user4)
+	assert.NoError(t, err)
+	assert.True(t, has)
+	assert.EqualValues(t, user3.DeletedAt.Unix(), user4.DeletedAt.Unix())
+	assert.EqualValues(t, formatTime(user3.DeletedAt), formatTime(user4.DeletedAt))
+	fmt.Println("user3", user3.DeletedAt, user4.DeletedAt)
+}
+
+func TestTimeUserDeletedDiffLoc(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	loc, err := time.LoadLocation("Asia/Shanghai")
+	assert.NoError(t, err)
+	testEngine.TZLocation = loc
+	dbLoc, err := time.LoadLocation("America/New_York")
+	assert.NoError(t, err)
+	testEngine.DatabaseTZ = dbLoc
+
+	type UserDeleted struct {
+		Id        string
+		CreatedAt time.Time `xorm:"created"`
+		UpdatedAt time.Time `xorm:"updated"`
+		DeletedAt time.Time `xorm:"deleted"`
+	}
+
+	assertSync(t, new(UserDeleted))
+
+	var user = UserDeleted{
+		Id: "lunny",
+	}
+
+	cnt, err := testEngine.Insert(&user)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+	fmt.Println("user", user.CreatedAt, user.UpdatedAt, user.DeletedAt)
+
+	var user2 UserDeleted
+	has, err := testEngine.Get(&user2)
+	assert.NoError(t, err)
+	assert.True(t, has)
+	assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix())
+	assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user2.CreatedAt))
+	assert.EqualValues(t, user.UpdatedAt.Unix(), user2.UpdatedAt.Unix())
+	assert.EqualValues(t, formatTime(user.UpdatedAt), formatTime(user2.UpdatedAt))
+	assert.True(t, isTimeZero(user2.DeletedAt))
+	fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt, user2.DeletedAt)
+
+	var user3 UserDeleted
+	cnt, err = testEngine.Where("id = ?", "lunny").Delete(&user3)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+	assert.True(t, !isTimeZero(user3.DeletedAt))
+
+	var user4 UserDeleted
+	has, err = testEngine.Unscoped().Get(&user4)
+	assert.NoError(t, err)
+	assert.True(t, has)
+	assert.EqualValues(t, user3.DeletedAt.Unix(), user4.DeletedAt.Unix())
+	assert.EqualValues(t, formatTime(user3.DeletedAt), formatTime(user4.DeletedAt))
+	fmt.Println("user3", user3.DeletedAt, user4.DeletedAt)
+}
+
+type JsonDate time.Time
+
+func (j JsonDate) MarshalJSON() ([]byte, error) {
+	if time.Time(j).IsZero() {
+		return []byte(`""`), nil
+	}
+	return []byte(`"` + time.Time(j).Format("2006-01-02 15:04:05") + `"`), nil
+}
+
+func (j *JsonDate) UnmarshalJSON(value []byte) error {
+	var v = strings.TrimSpace(strings.Trim(string(value), "\""))
+
+	t, err := time.ParseInLocation("2006-01-02 15:04:05", v, time.Local)
+	if err != nil {
+		return err
+	}
+	*j = JsonDate(t)
+	return nil
+}
+
+func (j *JsonDate) Unix() int64 {
+	return (*time.Time)(j).Unix()
+}
+
+func TestCustomTimeUserDeleted(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	type UserDeleted struct {
+		Id        string
+		CreatedAt JsonDate `xorm:"created"`
+		UpdatedAt JsonDate `xorm:"updated"`
+		DeletedAt JsonDate `xorm:"deleted"`
+	}
+
+	assertSync(t, new(UserDeleted))
+
+	var user = UserDeleted{
+		Id: "lunny",
+	}
+
+	cnt, err := testEngine.Insert(&user)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+	fmt.Println("user", user.CreatedAt, user.UpdatedAt, user.DeletedAt)
+
+	var user2 UserDeleted
+	has, err := testEngine.Get(&user2)
+	assert.NoError(t, err)
+	assert.True(t, has)
+	assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix())
+	assert.EqualValues(t, formatTime(time.Time(user.CreatedAt)), formatTime(time.Time(user2.CreatedAt)))
+	assert.EqualValues(t, user.UpdatedAt.Unix(), user2.UpdatedAt.Unix())
+	assert.EqualValues(t, formatTime(time.Time(user.UpdatedAt)), formatTime(time.Time(user2.UpdatedAt)))
+	assert.True(t, isTimeZero(time.Time(user2.DeletedAt)))
+	fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt, user2.DeletedAt)
+
+	var user3 UserDeleted
+	cnt, err = testEngine.Where("id = ?", "lunny").Delete(&user3)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+	assert.True(t, !isTimeZero(time.Time(user3.DeletedAt)))
+
+	var user4 UserDeleted
+	has, err = testEngine.Unscoped().Get(&user4)
+	assert.NoError(t, err)
+	assert.True(t, has)
+	assert.EqualValues(t, user3.DeletedAt.Unix(), user4.DeletedAt.Unix())
+	assert.EqualValues(t, formatTime(time.Time(user3.DeletedAt)), formatTime(time.Time(user4.DeletedAt)))
+	fmt.Println("user3", user3.DeletedAt, user4.DeletedAt)
+}
+
+func TestCustomTimeUserDeletedDiffLoc(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	loc, err := time.LoadLocation("Asia/Shanghai")
+	assert.NoError(t, err)
+	testEngine.TZLocation = loc
+	dbLoc, err := time.LoadLocation("America/New_York")
+	assert.NoError(t, err)
+	testEngine.DatabaseTZ = dbLoc
+
+	type UserDeleted struct {
+		Id        string
+		CreatedAt JsonDate `xorm:"created"`
+		UpdatedAt JsonDate `xorm:"updated"`
+		DeletedAt JsonDate `xorm:"deleted"`
+	}
+
+	assertSync(t, new(UserDeleted))
+
+	var user = UserDeleted{
+		Id: "lunny",
+	}
+
+	cnt, err := testEngine.Insert(&user)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+	fmt.Println("user", user.CreatedAt, user.UpdatedAt, user.DeletedAt)
+
+	var user2 UserDeleted
+	has, err := testEngine.Get(&user2)
+	assert.NoError(t, err)
+	assert.True(t, has)
+	assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix())
+	assert.EqualValues(t, formatTime(time.Time(user.CreatedAt)), formatTime(time.Time(user2.CreatedAt)))
+	assert.EqualValues(t, user.UpdatedAt.Unix(), user2.UpdatedAt.Unix())
+	assert.EqualValues(t, formatTime(time.Time(user.UpdatedAt)), formatTime(time.Time(user2.UpdatedAt)))
+	assert.True(t, isTimeZero(time.Time(user2.DeletedAt)))
+	fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt, user2.DeletedAt)
+
+	var user3 UserDeleted
+	cnt, err = testEngine.Where("id = ?", "lunny").Delete(&user3)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+	assert.True(t, !isTimeZero(time.Time(user3.DeletedAt)))
+
+	var user4 UserDeleted
+	has, err = testEngine.Unscoped().Get(&user4)
+	assert.NoError(t, err)
+	assert.True(t, has)
+	assert.EqualValues(t, user3.DeletedAt.Unix(), user4.DeletedAt.Unix())
+	assert.EqualValues(t, formatTime(time.Time(user3.DeletedAt)), formatTime(time.Time(user4.DeletedAt)))
+	fmt.Println("user3", user3.DeletedAt, user4.DeletedAt)
+}

+ 404 - 0
types_null_test.go

@@ -0,0 +1,404 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+	"database/sql"
+	"database/sql/driver"
+	"errors"
+	"fmt"
+	"strconv"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+type NullType struct {
+	Id           int `xorm:"pk autoincr"`
+	Name         sql.NullString
+	Age          sql.NullInt64
+	Height       sql.NullFloat64
+	IsMan        sql.NullBool `xorm:"null"`
+	CustomStruct CustomStruct `xorm:"valchar(64) null"`
+}
+
+type CustomStruct struct {
+	Year  int
+	Month int
+	Day   int
+}
+
+func (CustomStruct) String() string {
+	return "CustomStruct"
+}
+
+func (m *CustomStruct) Scan(value interface{}) error {
+	if value == nil {
+		m.Year, m.Month, m.Day = 0, 0, 0
+		return nil
+	}
+
+	if s, ok := value.([]byte); ok {
+		seps := strings.Split(string(s), "/")
+		m.Year, _ = strconv.Atoi(seps[0])
+		m.Month, _ = strconv.Atoi(seps[1])
+		m.Day, _ = strconv.Atoi(seps[2])
+		return nil
+	}
+
+	return errors.New("scan data not fit []byte")
+}
+
+func (m CustomStruct) Value() (driver.Value, error) {
+	return fmt.Sprintf("%d/%d/%d", m.Year, m.Month, m.Day), nil
+}
+
+func TestCreateNullStructTable(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	err := testEngine.CreateTables(new(NullType))
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+}
+
+func TestDropNullStructTable(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	err := testEngine.DropTables(new(NullType))
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+}
+
+func TestNullStructInsert(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(NullType))
+
+	if true {
+		item := new(NullType)
+		_, err := testEngine.Insert(item)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+		fmt.Println(item)
+		if item.Id != 1 {
+			err = errors.New("insert error")
+			t.Error(err)
+			panic(err)
+		}
+	}
+
+	if true {
+		item := NullType{
+			Name:   sql.NullString{"haolei", true},
+			Age:    sql.NullInt64{34, true},
+			Height: sql.NullFloat64{1.72, true},
+			IsMan:  sql.NullBool{true, true},
+		}
+		_, err := testEngine.Insert(&item)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+		fmt.Println(item)
+		if item.Id != 2 {
+			err = errors.New("insert error")
+			t.Error(err)
+			panic(err)
+		}
+	}
+
+	if true {
+		items := []NullType{}
+
+		for i := 0; i < 5; i++ {
+			item := NullType{
+				Name:         sql.NullString{"haolei_" + fmt.Sprint(i+1), true},
+				Age:          sql.NullInt64{30 + int64(i), true},
+				Height:       sql.NullFloat64{1.5 + 1.1*float64(i), true},
+				IsMan:        sql.NullBool{true, true},
+				CustomStruct: CustomStruct{i, i + 1, i + 2},
+			}
+
+			items = append(items, item)
+		}
+
+		_, err := testEngine.Insert(&items)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+		fmt.Println(items)
+	}
+}
+
+func TestNullStructUpdate(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(NullType))
+
+	_, err := testEngine.Insert([]NullType{
+		{
+			Name: sql.NullString{
+				String: "name1",
+				Valid:  true,
+			},
+		},
+		{
+			Name: sql.NullString{
+				String: "name2",
+				Valid:  true,
+			},
+		},
+		{
+			Name: sql.NullString{
+				String: "name3",
+				Valid:  true,
+			},
+		},
+		{
+			Name: sql.NullString{
+				String: "name4",
+				Valid:  true,
+			},
+		},
+	})
+	assert.NoError(t, err)
+
+	if true { // 测试可插入NULL
+		item := new(NullType)
+		item.Age = sql.NullInt64{23, true}
+		item.Height = sql.NullFloat64{0, false} // update to NULL
+
+		affected, err := testEngine.Id(2).Cols("age", "height", "is_man").Update(item)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+		if affected != 1 {
+			err := errors.New("update failed")
+			t.Error(err)
+			panic(err)
+		}
+	}
+
+	if true { // 测试In update
+		item := new(NullType)
+		item.Age = sql.NullInt64{23, true}
+		affected, err := testEngine.In("id", 3, 4).Cols("age", "height", "is_man").Update(item)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+		if affected != 2 {
+			err := errors.New("update failed")
+			t.Error(err)
+			panic(err)
+		}
+	}
+
+	if true { // 测试where
+		item := new(NullType)
+		item.Name = sql.NullString{"nullname", true}
+		item.IsMan = sql.NullBool{true, true}
+		item.Age = sql.NullInt64{34, true}
+
+		_, err := testEngine.Where("age > ?", 34).Update(item)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+	}
+
+	if true { // 修改全部时,插入空值
+		item := &NullType{
+			Name:   sql.NullString{"winxxp", true},
+			Age:    sql.NullInt64{30, true},
+			Height: sql.NullFloat64{1.72, true},
+			// IsMan:  sql.NullBool{true, true},
+		}
+
+		_, err := testEngine.AllCols().Id(6).Update(item)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+		fmt.Println(item)
+	}
+
+}
+
+func TestNullStructFind(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(NullType))
+
+	_, err := testEngine.Insert([]NullType{
+		{
+			Name: sql.NullString{
+				String: "name1",
+				Valid:  false,
+			},
+		},
+		{
+			Name: sql.NullString{
+				String: "name2",
+				Valid:  true,
+			},
+		},
+		{
+			Name: sql.NullString{
+				String: "name3",
+				Valid:  true,
+			},
+		},
+		{
+			Name: sql.NullString{
+				String: "name4",
+				Valid:  true,
+			},
+		},
+	})
+	assert.NoError(t, err)
+
+	if true {
+		item := new(NullType)
+		has, err := testEngine.Id(1).Get(item)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+		if !has {
+			t.Error(errors.New("no find id 1"))
+			panic(err)
+		}
+		fmt.Println(item)
+		if item.Id != 1 || item.Name.Valid || item.Age.Valid || item.Height.Valid ||
+			item.IsMan.Valid {
+			err = errors.New("insert error")
+			t.Error(err)
+			panic(err)
+		}
+	}
+
+	if true {
+		item := new(NullType)
+		item.Id = 2
+
+		has, err := testEngine.Get(item)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+		if !has {
+			t.Error(errors.New("no find id 2"))
+			panic(err)
+		}
+		fmt.Println(item)
+	}
+
+	if true {
+		item := make([]NullType, 0)
+
+		err := testEngine.Id(2).Find(&item)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+
+		fmt.Println(item)
+	}
+
+	if true {
+		item := make([]NullType, 0)
+
+		err := testEngine.Asc("age").Find(&item)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+
+		for k, v := range item {
+			fmt.Println(k, v)
+		}
+	}
+}
+
+func TestNullStructIterate(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(NullType))
+
+	if true {
+		err := testEngine.Where("age IS NOT NULL").OrderBy("age").Iterate(new(NullType),
+			func(i int, bean interface{}) error {
+				nultype := bean.(*NullType)
+				fmt.Println(i, nultype)
+				return nil
+			})
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+	}
+}
+
+func TestNullStructCount(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(NullType))
+
+	if true {
+		item := new(NullType)
+		total, err := testEngine.Where("age IS NOT NULL").Count(item)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+		fmt.Println(total)
+	}
+}
+
+func TestNullStructRows(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(NullType))
+
+	item := new(NullType)
+	rows, err := testEngine.Where("id > ?", 1).Rows(item)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+	defer rows.Close()
+
+	for rows.Next() {
+		err = rows.Scan(item)
+		if err != nil {
+			t.Error(err)
+			panic(err)
+		}
+		fmt.Println(item)
+	}
+}
+
+func TestNullStructDelete(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+	assertSync(t, new(NullType))
+
+	item := new(NullType)
+
+	_, err := testEngine.Id(1).Delete(item)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+
+	_, err = testEngine.Where("id > ?", 1).Delete(item)
+	if err != nil {
+		t.Error(err)
+		panic(err)
+	}
+}

+ 238 - 0
types_test.go

@@ -5,9 +5,13 @@
 package xorm
 
 import (
+	"encoding/json"
+	"errors"
+	"fmt"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/xormplus/core"
 )
 
 func TestArrayField(t *testing.T) {
@@ -95,3 +99,237 @@ func TestGetBytes(t *testing.T) {
 	assert.Equal(t, true, has)
 	assert.Equal(t, "test", string(b.Data))
 }
+
+type ConvString string
+
+func (s *ConvString) FromDB(data []byte) error {
+	*s = ConvString("prefix---" + string(data))
+	return nil
+}
+
+func (s *ConvString) ToDB() ([]byte, error) {
+	return []byte(string(*s)), nil
+}
+
+type ConvConfig struct {
+	Name string
+	Id   int64
+}
+
+func (s *ConvConfig) FromDB(data []byte) error {
+	return json.Unmarshal(data, s)
+}
+
+func (s *ConvConfig) ToDB() ([]byte, error) {
+	return json.Marshal(s)
+}
+
+type SliceType []*ConvConfig
+
+func (s *SliceType) FromDB(data []byte) error {
+	return json.Unmarshal(data, s)
+}
+
+func (s *SliceType) ToDB() ([]byte, error) {
+	return json.Marshal(s)
+}
+
+type ConvStruct struct {
+	Conv  ConvString
+	Conv2 *ConvString
+	Cfg1  ConvConfig
+	Cfg2  *ConvConfig     `xorm:"TEXT"`
+	Cfg3  core.Conversion `xorm:"BLOB"`
+	Slice SliceType
+}
+
+func (c *ConvStruct) BeforeSet(name string, cell Cell) {
+	if name == "cfg3" || name == "Cfg3" {
+		c.Cfg3 = new(ConvConfig)
+	}
+}
+
+func TestConversion(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	c := new(ConvStruct)
+	assert.NoError(t, testEngine.DropTables(c))
+	assert.NoError(t, testEngine.Sync(c))
+
+	var s ConvString = "sssss"
+	c.Conv = "tttt"
+	c.Conv2 = &s
+	c.Cfg1 = ConvConfig{"mm", 1}
+	c.Cfg2 = &ConvConfig{"xx", 2}
+	c.Cfg3 = &ConvConfig{"zz", 3}
+	c.Slice = []*ConvConfig{{"yy", 4}, {"ff", 5}}
+
+	_, err := testEngine.Insert(c)
+	assert.NoError(t, err)
+
+	c1 := new(ConvStruct)
+	has, err := testEngine.Get(c1)
+	assert.NoError(t, err)
+	assert.True(t, has)
+	assert.EqualValues(t, "prefix---tttt", string(c1.Conv))
+	assert.NotNil(t, c1.Conv2)
+	assert.EqualValues(t, "prefix---"+s, *c1.Conv2)
+	assert.EqualValues(t, c.Cfg1, c1.Cfg1)
+	assert.NotNil(t, c1.Cfg2)
+	assert.EqualValues(t, *c.Cfg2, *c1.Cfg2)
+	assert.NotNil(t, c1.Cfg3)
+	assert.EqualValues(t, *c.Cfg3.(*ConvConfig), *c1.Cfg3.(*ConvConfig))
+	assert.EqualValues(t, 2, len(c1.Slice))
+	assert.EqualValues(t, *c.Slice[0], *c1.Slice[0])
+	assert.EqualValues(t, *c.Slice[1], *c1.Slice[1])
+}
+
+type MyInt int
+type MyUInt uint
+type MyFloat float64
+
+type MyStruct struct {
+	Type      MyInt
+	U         MyUInt
+	F         MyFloat
+	S         MyString
+	IA        []MyInt
+	UA        []MyUInt
+	FA        []MyFloat
+	SA        []MyString
+	NameArray []string
+	Name      string
+	UIA       []uint
+	UIA8      []uint8
+	UIA16     []uint16
+	UIA32     []uint32
+	UIA64     []uint64
+	UI        uint
+	//C64       complex64
+	MSS map[string]string
+}
+
+func TestCustomType1(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	err := testEngine.DropTables(&MyStruct{})
+	assert.NoError(t, err)
+
+	err = testEngine.CreateTables(&MyStruct{})
+	assert.NoError(t, err)
+
+	i := MyStruct{Name: "Test", Type: MyInt(1)}
+	i.U = 23
+	i.F = 1.34
+	i.S = "fafdsafdsaf"
+	i.UI = 2
+	i.IA = []MyInt{1, 3, 5}
+	i.UIA = []uint{1, 3}
+	i.UIA16 = []uint16{2}
+	i.UIA32 = []uint32{4, 5}
+	i.UIA64 = []uint64{6, 7, 9}
+	i.UIA8 = []uint8{1, 2, 3, 4}
+	i.NameArray = []string{"ssss", "fsdf", "lllll, ss"}
+	i.MSS = map[string]string{"s": "sfds,ss", "x": "lfjljsl"}
+
+	cnt, err := testEngine.Insert(&i)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+
+	fmt.Println(i)
+	i.NameArray = []string{}
+	i.MSS = map[string]string{}
+	i.F = 0
+	has, err := testEngine.Get(&i)
+	assert.NoError(t, err)
+	assert.True(t, has)
+
+	ss := []MyStruct{}
+	err = testEngine.Find(&ss)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, len(ss))
+	assert.EqualValues(t, i, ss[0])
+
+	sss := MyStruct{}
+	has, err = testEngine.Get(&sss)
+	assert.NoError(t, err)
+	assert.True(t, has)
+
+	sss.NameArray = []string{}
+	sss.MSS = map[string]string{}
+	cnt, err = testEngine.Delete(&sss)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, cnt)
+}
+
+type Status struct {
+	Name  string
+	Color string
+}
+
+var (
+	_        core.Conversion   = &Status{}
+	Registed Status            = Status{"Registed", "white"}
+	Approved Status            = Status{"Approved", "green"}
+	Removed  Status            = Status{"Removed", "red"}
+	Statuses map[string]Status = map[string]Status{
+		Registed.Name: Registed,
+		Approved.Name: Approved,
+		Removed.Name:  Removed,
+	}
+)
+
+func (s *Status) FromDB(bytes []byte) error {
+	if r, ok := Statuses[string(bytes)]; ok {
+		*s = r
+		return nil
+	} else {
+		return errors.New("no this data")
+	}
+}
+
+func (s *Status) ToDB() ([]byte, error) {
+	return []byte(s.Name), nil
+}
+
+type UserCus struct {
+	Id     int64
+	Name   string
+	Status Status `xorm:"varchar(40)"`
+}
+
+func TestCustomType2(t *testing.T) {
+	assert.NoError(t, prepareEngine())
+
+	err := testEngine.CreateTables(&UserCus{})
+	assert.NoError(t, err)
+
+	tableName := testEngine.TableMapper.Obj2Table("UserCus")
+	_, err = testEngine.Exec("delete from " + testEngine.Quote(tableName))
+	assert.NoError(t, err)
+
+	if testEngine.Dialect().DBType() == core.MSSQL {
+		return
+		/*_, err = engine.Exec("set IDENTITY_INSERT " + tableName + " on")
+		if err != nil {
+			t.Fatal(err)
+		}*/
+	}
+
+	_, err = testEngine.Insert(&UserCus{1, "xlw", Registed})
+	assert.NoError(t, err)
+
+	user := UserCus{}
+	exist, err := testEngine.Id(1).Get(&user)
+	assert.NoError(t, err)
+	assert.True(t, exist)
+
+	fmt.Println(user)
+
+	users := make([]UserCus, 0)
+	err = testEngine.Where("`"+testEngine.ColumnMapper.Obj2Table("Status")+"` = ?", "Registed").Find(&users)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, len(users))
+
+	fmt.Println(users)
+}

+ 33 - 7
xorm_test.go

@@ -4,15 +4,19 @@ import (
 	"flag"
 	"fmt"
 	"os"
+	"strings"
 	"testing"
 
+	_ "github.com/denisenkom/go-mssqldb"
 	_ "github.com/go-sql-driver/mysql"
+	"github.com/go-xorm/core"
 	_ "github.com/lib/pq"
 	_ "github.com/mattn/go-sqlite3"
 )
 
 var (
 	testEngine *Engine
+	dbType     string
 	connString string
 
 	db         = flag.String("db", "sqlite3", "the tested database")
@@ -31,6 +35,7 @@ func createEngine(dbType, connStr string) error {
 		}
 
 		testEngine.ShowSQL(*showSQL)
+		testEngine.logger.SetLevel(core.LOG_DEBUG)
 	}
 
 	tables, err := testEngine.DBMetas()
@@ -41,19 +46,23 @@ func createEngine(dbType, connStr string) error {
 	for _, table := range tables {
 		tableNames = append(tableNames, table.Name)
 	}
-	return testEngine.DropTables(tableNames...)
+	if err = testEngine.DropTables(tableNames...); err != nil {
+		return err
+	}
+	return nil
 }
 
 func prepareEngine() error {
-	return createEngine(*db, connString)
+	return createEngine(dbType, connString)
 }
 
 func TestMain(m *testing.M) {
 	flag.Parse()
 
+	dbType = *db
 	if *db == "sqlite3" {
 		if ptrConnStr == nil {
-			connString = "./test.db"
+			connString = "./test.db?cache=shared&mode=rwc"
 		} else {
 			connString = *ptrConnStr
 		}
@@ -65,11 +74,28 @@ func TestMain(m *testing.M) {
 		connString = *ptrConnStr
 	}
 
-	if err := prepareEngine(); err != nil {
-		fmt.Println(err)
-		return
+	dbs := strings.Split(*db, "::")
+	conns := strings.Split(connString, "::")
+
+	var res int
+	for i := 0; i < len(dbs); i++ {
+		dbType = dbs[i]
+		connString = conns[i]
+		testEngine = nil
+		fmt.Println("testing", dbType, connString)
+
+		if err := prepareEngine(); err != nil {
+			fmt.Println(err)
+			return
+		}
+
+		code := m.Run()
+		if code > 0 {
+			res = code
+		}
 	}
-	os.Exit(m.Run())
+
+	os.Exit(res)
 }
 
 func TestPing(t *testing.T) {