From 6db7dbd333204bcff50716e4840deee28e09c2f9 Mon Sep 17 00:00:00 2001
From: kolaente <konrad@kola-entertainments.de>
Date: Wed, 12 Dec 2018 02:01:41 +0100
Subject: [PATCH] Added test environment for mssql (#4282)

* Added test environment for m$sql

* Added template for test environment for m$sql

* Fix password

* Fix password (again)

* Fix password (again again)

* Fix db

* Ci trigger (Looking at you drone....)

* Ci trigger (Looking at you drone....)

* Ci trigger (Looking at you drone....)

* Ci trigger (Looking at you drone....)

* Create master database for mssql integration tests

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Create database only if master do not exist

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Fix mssql integration tests by using custom database "gitea"

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Moved defer

* bump xorm

* updated xorm

* Fixed build
---
 .drone.yml                                    | 23 ++++++
 .gitignore                                    |  3 +
 Gopkg.lock                                    |  4 +-
 Gopkg.toml                                    |  3 +-
 Makefile                                      | 19 ++++-
 integrations/integration_test.go              | 13 +++
 integrations/mssql.ini.tmpl                   | 72 +++++++++++++++++
 models/models.go                              |  7 +-
 .../github.com/go-xorm/xorm/context_cache.go  | 30 +++++++
 .../github.com/go-xorm/xorm/dialect_mssql.go  |  2 +-
 .../github.com/go-xorm/xorm/dialect_mysql.go  |  5 +-
 .../go-xorm/xorm/dialect_sqlite3.go           |  6 +-
 vendor/github.com/go-xorm/xorm/engine.go      | 17 +++-
 .../github.com/go-xorm/xorm/engine_group.go   | 10 +++
 .../github.com/go-xorm/xorm/engine_maxlife.go | 22 -----
 vendor/github.com/go-xorm/xorm/interface.go   |  9 ++-
 vendor/github.com/go-xorm/xorm/session.go     | 15 ++++
 .../github.com/go-xorm/xorm/session_delete.go |  2 +-
 .../github.com/go-xorm/xorm/session_find.go   |  6 +-
 vendor/github.com/go-xorm/xorm/session_get.go | 29 ++++++-
 .../github.com/go-xorm/xorm/session_insert.go |  6 +-
 .../github.com/go-xorm/xorm/session_query.go  | 80 ++++++++++++++++---
 vendor/github.com/go-xorm/xorm/session_raw.go | 26 +++++-
 .../github.com/go-xorm/xorm/session_update.go |  6 +-
 vendor/github.com/go-xorm/xorm/statement.go   |  6 +-
 vendor/github.com/go-xorm/xorm/transaction.go | 26 ++++++
 vendor/github.com/go-xorm/xorm/xorm.go        |  2 +
 27 files changed, 383 insertions(+), 66 deletions(-)
 create mode 100644 integrations/mssql.ini.tmpl
 create mode 100644 vendor/github.com/go-xorm/xorm/context_cache.go
 delete mode 100644 vendor/github.com/go-xorm/xorm/engine_maxlife.go
 create mode 100644 vendor/github.com/go-xorm/xorm/transaction.go

diff --git a/.drone.yml b/.drone.yml
index 60f9cdf6a2..8e14727207 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -176,6 +176,20 @@ pipeline:
     when:
       event: [ push, tag, pull_request ]
 
+  test-mssql:
+    image: golang:1.10
+    pull: true
+    group: test
+    environment:
+      TAGS: bindata
+      TEST_LDAP: "1"
+    commands:
+      - curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash
+      - apt-get install -y git-lfs
+      - make test-mssql
+    when:
+      event: [ push, tag, pull_request ]
+
   generate-coverage:
     image: golang:1.11
     pull: true
@@ -347,6 +361,15 @@ services:
     when:
       event: [ push, tag, pull_request ]
 
+  mssql:
+    image: microsoft/mssql-server-linux:latest
+    environment:
+      - ACCEPT_EULA=Y
+      - SA_PASSWORD=MwantsaSecurePassword1
+      - MSSQL_PID=Standard
+    when:
+      event: [ push, tag, pull_request ]
+
   ldap:
     image: gitea/test-openldap:latest
     when:
diff --git a/.gitignore b/.gitignore
index 9a5f01bb94..941ec41e04 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,11 +53,14 @@ coverage.all
 /integrations/gitea-integration-mysql
 /integrations/gitea-integration-pgsql
 /integrations/gitea-integration-sqlite
+/integrations/gitea-integration-mssql
 /integrations/indexers-mysql
 /integrations/indexers-pgsql
 /integrations/indexers-sqlite
+/integrations/indexers-mssql
 /integrations/mysql.ini
 /integrations/pgsql.ini
+/integrations/mssql.ini
 /node_modules
 
 
diff --git a/Gopkg.lock b/Gopkg.lock
index 640c62b8aa..7d463f50a3 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -406,11 +406,11 @@
   version = "v0.6.0"
 
 [[projects]]
-  digest = "1:22a1ac7f654095f6817076eb975368bab5481e42554d0121ea37e28a86a3f83d"
+  digest = "1:931a62a1aacc37a5e4c309a111642ec4da47b4dc453cd4ba5481b12eedb04a5d"
   name = "github.com/go-xorm/xorm"
   packages = ["."]
   pruneopts = "NUT"
-  revision = "ad69f7d8f0861a29438154bb0a20b60501298480"
+  revision = "401f4ee8ff8cbc40a4754cb12192fbe4f02f3979"
 
 [[projects]]
   branch = "master"
diff --git a/Gopkg.toml b/Gopkg.toml
index 2633d8b1dd..f0d2e27b81 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -38,8 +38,7 @@ ignored = ["google.golang.org/appengine*"]
 
 [[override]]
   name = "github.com/go-xorm/xorm"
-  #version = "0.6.5"
-  revision = "ad69f7d8f0861a29438154bb0a20b60501298480"
+  revision = "401f4ee8ff8cbc40a4754cb12192fbe4f02f3979"
 
 [[override]]
   name = "github.com/go-sql-driver/mysql"
diff --git a/Makefile b/Makefile
index f007665e71..13c6f600a9 100644
--- a/Makefile
+++ b/Makefile
@@ -54,6 +54,10 @@ TEST_PGSQL_HOST ?= pgsql:5432
 TEST_PGSQL_DBNAME ?= testgitea
 TEST_PGSQL_USERNAME ?= postgres
 TEST_PGSQL_PASSWORD ?= postgres
+TEST_MSSQL_HOST ?= mssql:1433
+TEST_MSSQL_DBNAME ?= gitea
+TEST_MSSQL_USERNAME ?= sa
+TEST_MSSQL_PASSWORD ?= MwantsaSecurePassword1
 
 ifeq ($(OS), Windows_NT)
 	EXECUTABLE := gitea.exe
@@ -74,9 +78,9 @@ clean:
 	$(GO) clean -i ./...
 	rm -rf $(EXECUTABLE) $(DIST) $(BINDATA) \
 		integrations*.test \
-		integrations/gitea-integration-pgsql/ integrations/gitea-integration-mysql/ integrations/gitea-integration-sqlite/ \
-		integrations/indexers-mysql/ integrations/indexers-pgsql integrations/indexers-sqlite \
-		integrations/mysql.ini integrations/pgsql.ini
+		integrations/gitea-integration-pgsql/ integrations/gitea-integration-mysql/ integrations/gitea-integration-sqlite/ integrations/gitea-integration-mssql/ \
+		integrations/indexers-mysql/ integrations/indexers-pgsql integrations/indexers-sqlite integrations/indexers-mssql \
+		integrations/mysql.ini integrations/pgsql.ini integrations/mssql.ini
 
 .PHONY: fmt
 fmt:
@@ -204,6 +208,11 @@ generate-ini:
 		-e 's|{{TEST_PGSQL_USERNAME}}|${TEST_PGSQL_USERNAME}|g' \
 		-e 's|{{TEST_PGSQL_PASSWORD}}|${TEST_PGSQL_PASSWORD}|g' \
 			integrations/pgsql.ini.tmpl > integrations/pgsql.ini
+	sed -e 's|{{TEST_MSSQL_HOST}}|${TEST_MSSQL_HOST}|g' \
+		-e 's|{{TEST_MSSQL_DBNAME}}|${TEST_MSSQL_DBNAME}|g' \
+		-e 's|{{TEST_MSSQL_USERNAME}}|${TEST_MSSQL_USERNAME}|g' \
+		-e 's|{{TEST_MSSQL_PASSWORD}}|${TEST_MSSQL_PASSWORD}|g' \
+			integrations/mssql.ini.tmpl > integrations/mssql.ini
 
 .PHONY: test-mysql
 test-mysql: integrations.test generate-ini
@@ -213,6 +222,10 @@ test-mysql: integrations.test generate-ini
 test-pgsql: integrations.test generate-ini
 	GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/pgsql.ini ./integrations.test
 
+.PHONY: test-mssql
+test-mssql: integrations.test generate-ini
+	GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mssql.ini ./integrations.test
+
 .PHONY: bench-sqlite
 bench-sqlite: integrations.sqlite.test
 	GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/sqlite.ini ./integrations.sqlite.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
diff --git a/integrations/integration_test.go b/integrations/integration_test.go
index ed165f6534..25f6cff27c 100644
--- a/integrations/integration_test.go
+++ b/integrations/integration_test.go
@@ -47,6 +47,8 @@ func TestMain(m *testing.M) {
 		helper = &testfixtures.PostgreSQL{}
 	} else if setting.UseSQLite3 {
 		helper = &testfixtures.SQLite{}
+	} else if setting.UseMSSQL {
+		helper = &testfixtures.SQLServer{}
 	} else {
 		fmt.Println("Unsupported RDBMS for integration tests")
 		os.Exit(1)
@@ -130,6 +132,17 @@ func initIntegrationTest() {
 		if _, err = db.Exec("CREATE DATABASE testgitea"); err != nil {
 			log.Fatalf("db.Exec: %v", err)
 		}
+	case setting.UseMSSQL:
+		host, port := models.ParseMSSQLHostPort(models.DbCfg.Host)
+		db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;",
+			host, port, "master", models.DbCfg.User, models.DbCfg.Passwd))
+		if err != nil {
+			log.Fatalf("sql.Open: %v", err)
+		}
+		if _, err := db.Exec("If(db_id(N'gitea') IS NULL) BEGIN CREATE DATABASE gitea; END;"); err != nil {
+			log.Fatalf("db.Exec: %v", err)
+		}
+		defer db.Close()
 	}
 	routers.GlobalInit()
 }
diff --git a/integrations/mssql.ini.tmpl b/integrations/mssql.ini.tmpl
new file mode 100644
index 0000000000..08f7e9ca37
--- /dev/null
+++ b/integrations/mssql.ini.tmpl
@@ -0,0 +1,72 @@
+APP_NAME = Gitea: Git with a cup of tea
+RUN_MODE = prod
+
+[database]
+DB_TYPE  = mssql
+HOST     = {{TEST_MSSQL_HOST}}
+NAME     = {{TEST_MSSQL_DBNAME}}
+USER     = {{TEST_MSSQL_USERNAME}}
+PASSWD   = {{TEST_MSSQL_PASSWORD}}
+SSL_MODE = disable
+
+[indexer]
+ISSUE_INDEXER_PATH = integrations/indexers-mssql/issues.bleve
+REPO_INDEXER_ENABLED = true
+REPO_INDEXER_PATH = integrations/indexers-mssql/repos.bleve
+
+[repository]
+ROOT = integrations/gitea-integration-mssql/gitea-repositories
+
+[repository.local]
+LOCAL_COPY_PATH = tmp/local-repo-mssql
+LOCAL_WIKI_PATH = tmp/local-wiki-mssql
+
+[server]
+SSH_DOMAIN       = localhost
+HTTP_PORT        = 3003
+ROOT_URL         = http://localhost:3003/
+DISABLE_SSH      = false
+SSH_LISTEN_HOST  = localhost
+SSH_PORT         = 2201
+START_SSH_SERVER = true
+LFS_START_SERVER = true
+LFS_CONTENT_PATH = data/lfs-mssql
+OFFLINE_MODE     = false
+LFS_JWT_SECRET   = Tv_MjmZuHqpIY6GFl12ebgkRAMt4RlWt0v4EHKSXO0w
+APP_DATA_PATH    = integrations/gitea-integration-mssql/data
+
+[mailer]
+ENABLED = false
+
+[service]
+REGISTER_EMAIL_CONFIRM            = false
+ENABLE_NOTIFY_MAIL                = false
+DISABLE_REGISTRATION              = false
+ENABLE_CAPTCHA                    = false
+REQUIRE_SIGNIN_VIEW               = false
+DEFAULT_KEEP_EMAIL_PRIVATE        = false
+DEFAULT_ALLOW_CREATE_ORGANIZATION = true
+NO_REPLY_ADDRESS                  = noreply.example.org
+
+[picture]
+DISABLE_GRAVATAR        = false
+ENABLE_FEDERATED_AVATAR = false
+
+[session]
+PROVIDER = file
+PROVIDER_CONFIG = data/sessions-mssql
+
+[log]
+MODE      = console,file
+ROOT_PATH = mssql-log
+
+[log.console]
+LEVEL = Warn
+
+[log.file]
+LEVEL = Debug
+
+[security]
+INSTALL_LOCK   = true
+SECRET_KEY     = 9pCviYTWSb
+INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTU1NTE2MTh9.hhSVGOANkaKk3vfCd2jDOIww4pUk0xtg9JRde5UogyQ
diff --git a/models/models.go b/models/models.go
index 0123eab12d..daef7c07e8 100644
--- a/models/models.go
+++ b/models/models.go
@@ -36,7 +36,7 @@ type Engine interface {
 	Count(...interface{}) (int64, error)
 	Decr(column string, arg ...interface{}) *xorm.Session
 	Delete(interface{}) (int64, error)
-	Exec(string, ...interface{}) (sql.Result, error)
+	Exec(...interface{}) (sql.Result, error)
 	Find(interface{}, ...interface{}) error
 	Get(interface{}) (bool, error)
 	ID(interface{}) *xorm.Session
@@ -200,7 +200,8 @@ func getPostgreSQLConnectionString(DBHost, DBUser, DBPasswd, DBName, DBParam, DB
 	return
 }
 
-func parseMSSQLHostPort(info string) (string, string) {
+// ParseMSSQLHostPort splits the host into host and port
+func ParseMSSQLHostPort(info string) (string, string) {
 	host, port := "127.0.0.1", "1433"
 	if strings.Contains(info, ":") {
 		host = strings.Split(info, ":")[0]
@@ -235,7 +236,7 @@ func getEngine() (*xorm.Engine, error) {
 	case "postgres":
 		connStr = getPostgreSQLConnectionString(DbCfg.Host, DbCfg.User, DbCfg.Passwd, DbCfg.Name, Param, DbCfg.SSLMode)
 	case "mssql":
-		host, port := parseMSSQLHostPort(DbCfg.Host)
+		host, port := ParseMSSQLHostPort(DbCfg.Host)
 		connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, DbCfg.Name, DbCfg.User, DbCfg.Passwd)
 	case "sqlite3":
 		if !EnableSQLite3 {
diff --git a/vendor/github.com/go-xorm/xorm/context_cache.go b/vendor/github.com/go-xorm/xorm/context_cache.go
new file mode 100644
index 0000000000..1bc2288496
--- /dev/null
+++ b/vendor/github.com/go-xorm/xorm/context_cache.go
@@ -0,0 +1,30 @@
+// Copyright 2018 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
+
+// ContextCache is the interface that operates the cache data.
+type ContextCache interface {
+	// Put puts value into cache with key.
+	Put(key string, val interface{})
+	// Get gets cached value by given key.
+	Get(key string) interface{}
+}
+
+type memoryContextCache map[string]interface{}
+
+// NewMemoryContextCache return memoryContextCache
+func NewMemoryContextCache() memoryContextCache {
+	return make(map[string]interface{})
+}
+
+// Put puts value into cache with key.
+func (m memoryContextCache) Put(key string, val interface{}) {
+	m[key] = val
+}
+
+// Get gets cached value by given key.
+func (m memoryContextCache) Get(key string) interface{} {
+	return m[key]
+}
diff --git a/vendor/github.com/go-xorm/xorm/dialect_mssql.go b/vendor/github.com/go-xorm/xorm/dialect_mssql.go
index 6d2291dc1d..fb1247094c 100644
--- a/vendor/github.com/go-xorm/xorm/dialect_mssql.go
+++ b/vendor/github.com/go-xorm/xorm/dialect_mssql.go
@@ -218,7 +218,7 @@ func (db *mssql) SqlType(c *core.Column) string {
 		res = core.Bit
 		if strings.EqualFold(c.Default, "true") {
 			c.Default = "1"
-		} else {
+		} else if strings.EqualFold(c.Default, "false") {
 			c.Default = "0"
 		}
 	case core.Serial:
diff --git a/vendor/github.com/go-xorm/xorm/dialect_mysql.go b/vendor/github.com/go-xorm/xorm/dialect_mysql.go
index f2b4ff7a78..9f5ae3b2e5 100644
--- a/vendor/github.com/go-xorm/xorm/dialect_mysql.go
+++ b/vendor/github.com/go-xorm/xorm/dialect_mysql.go
@@ -551,9 +551,12 @@ func (db *mysql) CreateTableSql(table *core.Table, tableName, storeEngine, chars
 
 	if len(charset) == 0 {
 		charset = db.URI().Charset
-	} else if len(charset) > 0 {
+	} 
+	if len(charset) != 0 {
 		sql += " DEFAULT CHARSET " + charset
 	}
+	
+	
 
 	if db.rowFormat != "" {
 		sql += " ROW_FORMAT=" + db.rowFormat
diff --git a/vendor/github.com/go-xorm/xorm/dialect_sqlite3.go b/vendor/github.com/go-xorm/xorm/dialect_sqlite3.go
index a55b1615e7..e129481466 100644
--- a/vendor/github.com/go-xorm/xorm/dialect_sqlite3.go
+++ b/vendor/github.com/go-xorm/xorm/dialect_sqlite3.go
@@ -233,7 +233,7 @@ func (db *sqlite3) TableCheckSql(tableName string) (string, []interface{}) {
 }
 
 func (db *sqlite3) DropIndexSql(tableName string, index *core.Index) string {
-	//var unique string
+	// var unique string
 	quote := db.Quote
 	idxName := index.Name
 
@@ -452,5 +452,9 @@ type sqlite3Driver struct {
 }
 
 func (p *sqlite3Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) {
+	if strings.Contains(dataSourceName, "?") {
+		dataSourceName = dataSourceName[:strings.Index(dataSourceName, "?")]
+	}
+
 	return &core.Uri{DbType: core.SQLITE, DbName: dataSourceName}, nil
 }
diff --git a/vendor/github.com/go-xorm/xorm/engine.go b/vendor/github.com/go-xorm/xorm/engine.go
index 846889c364..89a96d9fdb 100644
--- a/vendor/github.com/go-xorm/xorm/engine.go
+++ b/vendor/github.com/go-xorm/xorm/engine.go
@@ -177,6 +177,14 @@ func (engine *Engine) QuoteStr() string {
 	return engine.dialect.QuoteStr()
 }
 
+func (engine *Engine) quoteColumns(columnStr string) string {
+	columns := strings.Split(columnStr, ",")
+	for i := 0; i < len(columns); i++ {
+		columns[i] = engine.Quote(strings.TrimSpace(columns[i]))
+	}
+	return strings.Join(columns, ",")
+}
+
 // Quote Use QuoteStr quote the string sql
 func (engine *Engine) Quote(value string) string {
 	value = strings.TrimSpace(value)
@@ -237,6 +245,11 @@ func (engine *Engine) AutoIncrStr() string {
 	return engine.dialect.AutoIncrStr()
 }
 
+// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
+func (engine *Engine) SetConnMaxLifetime(d time.Duration) {
+	engine.db.SetConnMaxLifetime(d)
+}
+
 // SetMaxOpenConns is only available for go 1.2+
 func (engine *Engine) SetMaxOpenConns(conns int) {
 	engine.db.SetMaxOpenConns(conns)
@@ -1333,10 +1346,10 @@ func (engine *Engine) DropIndexes(bean interface{}) error {
 }
 
 // Exec raw sql
-func (engine *Engine) Exec(sql string, args ...interface{}) (sql.Result, error) {
+func (engine *Engine) Exec(sqlorArgs ...interface{}) (sql.Result, error) {
 	session := engine.NewSession()
 	defer session.Close()
-	return session.Exec(sql, args...)
+	return session.Exec(sqlorArgs...)
 }
 
 // Query a raw sql and return records as []map[string][]byte
diff --git a/vendor/github.com/go-xorm/xorm/engine_group.go b/vendor/github.com/go-xorm/xorm/engine_group.go
index 1de425f372..5eee3e6183 100644
--- a/vendor/github.com/go-xorm/xorm/engine_group.go
+++ b/vendor/github.com/go-xorm/xorm/engine_group.go
@@ -5,6 +5,8 @@
 package xorm
 
 import (
+	"time"
+
 	"github.com/go-xorm/core"
 )
 
@@ -99,6 +101,14 @@ func (eg *EngineGroup) SetColumnMapper(mapper core.IMapper) {
 	}
 }
 
+// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
+func (eg *EngineGroup) SetConnMaxLifetime(d time.Duration) {
+	eg.Engine.SetConnMaxLifetime(d)
+	for i := 0; i < len(eg.slaves); i++ {
+		eg.slaves[i].SetConnMaxLifetime(d)
+	}
+}
+
 // SetDefaultCacher set the default cacher
 func (eg *EngineGroup) SetDefaultCacher(cacher core.Cacher) {
 	eg.Engine.SetDefaultCacher(cacher)
diff --git a/vendor/github.com/go-xorm/xorm/engine_maxlife.go b/vendor/github.com/go-xorm/xorm/engine_maxlife.go
deleted file mode 100644
index 22666c5f44..0000000000
--- a/vendor/github.com/go-xorm/xorm/engine_maxlife.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// 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.
-
-// +build go1.6
-
-package xorm
-
-import "time"
-
-// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
-func (engine *Engine) SetConnMaxLifetime(d time.Duration) {
-	engine.db.SetConnMaxLifetime(d)
-}
-
-// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
-func (eg *EngineGroup) SetConnMaxLifetime(d time.Duration) {
-	eg.Engine.SetConnMaxLifetime(d)
-	for i := 0; i < len(eg.slaves); i++ {
-		eg.slaves[i].SetConnMaxLifetime(d)
-	}
-}
diff --git a/vendor/github.com/go-xorm/xorm/interface.go b/vendor/github.com/go-xorm/xorm/interface.go
index 0bc12ba006..33d2078e44 100644
--- a/vendor/github.com/go-xorm/xorm/interface.go
+++ b/vendor/github.com/go-xorm/xorm/interface.go
@@ -27,7 +27,7 @@ type Interface interface {
 	Delete(interface{}) (int64, error)
 	Distinct(columns ...string) *Session
 	DropIndexes(bean interface{}) error
-	Exec(string, ...interface{}) (sql.Result, error)
+	Exec(sqlOrAgrs ...interface{}) (sql.Result, error)
 	Exist(bean ...interface{}) (bool, error)
 	Find(interface{}, ...interface{}) error
 	FindAndCount(interface{}, ...interface{}) (int64, error)
@@ -72,6 +72,7 @@ type EngineInterface interface {
 
 	Before(func(interface{})) *Session
 	Charset(charset string) *Session
+	ClearCache(...interface{}) error
 	CreateTables(...interface{}) error
 	DBMetas() ([]*core.Table, error)
 	Dialect() core.Dialect
@@ -83,16 +84,22 @@ type EngineInterface interface {
 	GetTableMapper() core.IMapper
 	GetTZDatabase() *time.Location
 	GetTZLocation() *time.Location
+	MapCacher(interface{}, core.Cacher) error
 	NewSession() *Session
 	NoAutoTime() *Session
 	Quote(string) string
 	SetCacher(string, core.Cacher)
+	SetConnMaxLifetime(time.Duration)
 	SetDefaultCacher(core.Cacher)
+	SetLogger(logger core.ILogger)
 	SetLogLevel(core.LogLevel)
 	SetMapper(core.IMapper)
+	SetMaxOpenConns(int)
+	SetMaxIdleConns(int)
 	SetSchema(string)
 	SetTZDatabase(tz *time.Location)
 	SetTZLocation(tz *time.Location)
+	ShowExecTime(...bool)
 	ShowSQL(show ...bool)
 	Sync(...interface{}) error
 	Sync2(...interface{}) error
diff --git a/vendor/github.com/go-xorm/xorm/session.go b/vendor/github.com/go-xorm/xorm/session.go
index 3775eb0116..e3437b9181 100644
--- a/vendor/github.com/go-xorm/xorm/session.go
+++ b/vendor/github.com/go-xorm/xorm/session.go
@@ -102,6 +102,12 @@ func (session *Session) Close() {
 	}
 }
 
+// ContextCache enable context cache or not
+func (session *Session) ContextCache(context ContextCache) *Session {
+	session.statement.context = context
+	return session
+}
+
 // IsClosed returns if session is closed
 func (session *Session) IsClosed() bool {
 	return session.db == nil
@@ -839,3 +845,12 @@ func (session *Session) Unscoped() *Session {
 	session.statement.Unscoped()
 	return session
 }
+
+func (session *Session) incrVersionFieldValue(fieldValue *reflect.Value) {
+	switch fieldValue.Kind() {
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		fieldValue.SetInt(fieldValue.Int() + 1)
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		fieldValue.SetUint(fieldValue.Uint() + 1)
+	}
+}
diff --git a/vendor/github.com/go-xorm/xorm/session_delete.go b/vendor/github.com/go-xorm/xorm/session_delete.go
index d9cf3ea937..dcce543a3a 100644
--- a/vendor/github.com/go-xorm/xorm/session_delete.go
+++ b/vendor/github.com/go-xorm/xorm/session_delete.go
@@ -199,7 +199,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) {
 		})
 	}
 
-	if cacher := session.engine.getCacher(tableName); cacher != nil && session.statement.UseCache {
+	if cacher := session.engine.getCacher(tableNameNoQuote); cacher != nil && session.statement.UseCache {
 		session.cacheDelete(table, tableNameNoQuote, deleteSQL, argsForCache...)
 	}
 
diff --git a/vendor/github.com/go-xorm/xorm/session_find.go b/vendor/github.com/go-xorm/xorm/session_find.go
index 46bbf26c98..a5b4f79342 100644
--- a/vendor/github.com/go-xorm/xorm/session_find.go
+++ b/vendor/github.com/go-xorm/xorm/session_find.go
@@ -135,7 +135,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{})
 			if session.statement.JoinStr == "" {
 				if columnStr == "" {
 					if session.statement.GroupByStr != "" {
-						columnStr = session.statement.Engine.Quote(strings.Replace(session.statement.GroupByStr, ",", session.engine.Quote(","), -1))
+						columnStr = session.engine.quoteColumns(session.statement.GroupByStr)
 					} else {
 						columnStr = session.statement.genColumnStr()
 					}
@@ -143,7 +143,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{})
 			} else {
 				if columnStr == "" {
 					if session.statement.GroupByStr != "" {
-						columnStr = session.statement.Engine.Quote(strings.Replace(session.statement.GroupByStr, ",", session.engine.Quote(","), -1))
+						columnStr = session.engine.quoteColumns(session.statement.GroupByStr)
 					} else {
 						columnStr = "*"
 					}
@@ -176,7 +176,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{})
 	}
 
 	if session.canCache() {
-		if cacher := session.engine.getCacher(table.Name); cacher != nil &&
+		if cacher := session.engine.getCacher(session.statement.TableName()); cacher != nil &&
 			!session.statement.IsDistinct &&
 			!session.statement.unscoped {
 			err = session.cacheFind(sliceElementType, sqlStr, rowsSlicePtr, args...)
diff --git a/vendor/github.com/go-xorm/xorm/session_get.go b/vendor/github.com/go-xorm/xorm/session_get.go
index 3b2c9493c2..1cea31c5f8 100644
--- a/vendor/github.com/go-xorm/xorm/session_get.go
+++ b/vendor/github.com/go-xorm/xorm/session_get.go
@@ -7,6 +7,7 @@ package xorm
 import (
 	"database/sql"
 	"errors"
+	"fmt"
 	"reflect"
 	"strconv"
 
@@ -57,7 +58,7 @@ func (session *Session) get(bean interface{}) (bool, error) {
 	table := session.statement.RefTable
 
 	if session.canCache() && beanValue.Elem().Kind() == reflect.Struct {
-		if cacher := session.engine.getCacher(table.Name); cacher != nil &&
+		if cacher := session.engine.getCacher(session.statement.TableName()); cacher != nil &&
 			!session.statement.unscoped {
 			has, err := session.cacheGet(bean, sqlStr, args...)
 			if err != ErrCacheFailed {
@@ -66,7 +67,28 @@ func (session *Session) get(bean interface{}) (bool, error) {
 		}
 	}
 
-	return session.nocacheGet(beanValue.Elem().Kind(), table, bean, sqlStr, args...)
+	context := session.statement.context
+	if context != nil {
+		res := context.Get(fmt.Sprintf("%v-%v", sqlStr, args))
+		if res != nil {
+			structValue := reflect.Indirect(reflect.ValueOf(bean))
+			structValue.Set(reflect.Indirect(reflect.ValueOf(res)))
+			session.lastSQL = ""
+			session.lastSQLArgs = nil
+			return true, nil
+		}
+	}
+
+	has, err := session.nocacheGet(beanValue.Elem().Kind(), table, bean, sqlStr, args...)
+	if err != nil || !has {
+		return has, err
+	}
+
+	if context != nil {
+		context.Put(fmt.Sprintf("%v-%v", sqlStr, args), bean)
+	}
+
+	return true, nil
 }
 
 func (session *Session) nocacheGet(beanKind reflect.Kind, table *core.Table, bean interface{}, sqlStr string, args ...interface{}) (bool, error) {
@@ -77,6 +99,9 @@ func (session *Session) nocacheGet(beanKind reflect.Kind, table *core.Table, bea
 	defer rows.Close()
 
 	if !rows.Next() {
+		if rows.Err() != nil {
+			return false, rows.Err()
+		}
 		return false, nil
 	}
 
diff --git a/vendor/github.com/go-xorm/xorm/session_insert.go b/vendor/github.com/go-xorm/xorm/session_insert.go
index 2ea58fdaf9..e673e87425 100644
--- a/vendor/github.com/go-xorm/xorm/session_insert.go
+++ b/vendor/github.com/go-xorm/xorm/session_insert.go
@@ -397,7 +397,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
 			if err != nil {
 				session.engine.logger.Error(err)
 			} else if verValue.IsValid() && verValue.CanSet() {
-				verValue.SetInt(1)
+				session.incrVersionFieldValue(verValue)
 			}
 		}
 
@@ -440,7 +440,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
 			if err != nil {
 				session.engine.logger.Error(err)
 			} else if verValue.IsValid() && verValue.CanSet() {
-				verValue.SetInt(1)
+				session.incrVersionFieldValue(verValue)
 			}
 		}
 
@@ -481,7 +481,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
 			if err != nil {
 				session.engine.logger.Error(err)
 			} else if verValue.IsValid() && verValue.CanSet() {
-				verValue.SetInt(1)
+				session.incrVersionFieldValue(verValue)
 			}
 		}
 
diff --git a/vendor/github.com/go-xorm/xorm/session_query.go b/vendor/github.com/go-xorm/xorm/session_query.go
index 5c9aeb3916..6d597cc459 100644
--- a/vendor/github.com/go-xorm/xorm/session_query.go
+++ b/vendor/github.com/go-xorm/xorm/session_query.go
@@ -17,17 +17,7 @@ import (
 
 func (session *Session) genQuerySQL(sqlorArgs ...interface{}) (string, []interface{}, error) {
 	if len(sqlorArgs) > 0 {
-		switch sqlorArgs[0].(type) {
-		case string:
-			return sqlorArgs[0].(string), sqlorArgs[1:], nil
-		case *builder.Builder:
-			return sqlorArgs[0].(*builder.Builder).ToSQL()
-		case builder.Builder:
-			bd := sqlorArgs[0].(builder.Builder)
-			return bd.ToSQL()
-		default:
-			return "", nil, ErrUnSupportedType
-		}
+		return convertSQLOrArgs(sqlorArgs...)
 	}
 
 	if session.statement.RawSQL != "" {
@@ -45,7 +35,7 @@ func (session *Session) genQuerySQL(sqlorArgs ...interface{}) (string, []interfa
 		if session.statement.JoinStr == "" {
 			if columnStr == "" {
 				if session.statement.GroupByStr != "" {
-					columnStr = session.statement.Engine.Quote(strings.Replace(session.statement.GroupByStr, ",", session.engine.Quote(","), -1))
+					columnStr = session.engine.quoteColumns(session.statement.GroupByStr)
 				} else {
 					columnStr = session.statement.genColumnStr()
 				}
@@ -53,7 +43,7 @@ func (session *Session) genQuerySQL(sqlorArgs ...interface{}) (string, []interfa
 		} else {
 			if columnStr == "" {
 				if session.statement.GroupByStr != "" {
-					columnStr = session.statement.Engine.Quote(strings.Replace(session.statement.GroupByStr, ",", session.engine.Quote(","), -1))
+					columnStr = session.engine.quoteColumns(session.statement.GroupByStr)
 				} else {
 					columnStr = "*"
 				}
@@ -176,6 +166,34 @@ func row2mapStr(rows *core.Rows, fields []string) (resultsMap map[string]string,
 	return result, nil
 }
 
+func row2sliceStr(rows *core.Rows, fields []string) (results []string, err error) {
+	result := make([]string, 0, len(fields))
+	scanResultContainers := make([]interface{}, len(fields))
+	for i := 0; i < len(fields); i++ {
+		var scanResultContainer interface{}
+		scanResultContainers[i] = &scanResultContainer
+	}
+	if err := rows.Scan(scanResultContainers...); err != nil {
+		return nil, err
+	}
+
+	for i := 0; i < len(fields); i++ {
+		rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[i]))
+		// if row is null then as empty string
+		if rawValue.Interface() == nil {
+			result = append(result, "")
+			continue
+		}
+
+		if data, err := value2String(&rawValue); err == nil {
+			result = append(result, data)
+		} else {
+			return nil, err
+		}
+	}
+	return result, nil
+}
+
 func rows2Strings(rows *core.Rows) (resultsSlice []map[string]string, err error) {
 	fields, err := rows.Columns()
 	if err != nil {
@@ -192,6 +210,22 @@ func rows2Strings(rows *core.Rows) (resultsSlice []map[string]string, err error)
 	return resultsSlice, nil
 }
 
+func rows2SliceString(rows *core.Rows) (resultsSlice [][]string, err error) {
+	fields, err := rows.Columns()
+	if err != nil {
+		return nil, err
+	}
+	for rows.Next() {
+		record, err := row2sliceStr(rows, fields)
+		if err != nil {
+			return nil, err
+		}
+		resultsSlice = append(resultsSlice, record)
+	}
+
+	return resultsSlice, nil
+}
+
 // QueryString runs a raw sql and return records as []map[string]string
 func (session *Session) QueryString(sqlorArgs ...interface{}) ([]map[string]string, error) {
 	if session.isAutoClose {
@@ -212,6 +246,26 @@ func (session *Session) QueryString(sqlorArgs ...interface{}) ([]map[string]stri
 	return rows2Strings(rows)
 }
 
+// QuerySliceString runs a raw sql and return records as [][]string
+func (session *Session) QuerySliceString(sqlorArgs ...interface{}) ([][]string, error) {
+	if session.isAutoClose {
+		defer session.Close()
+	}
+
+	sqlStr, args, err := session.genQuerySQL(sqlorArgs...)
+	if err != nil {
+		return nil, err
+	}
+
+	rows, err := session.queryRows(sqlStr, args...)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+
+	return rows2SliceString(rows)
+}
+
 func row2mapInterface(rows *core.Rows, fields []string) (resultsMap map[string]interface{}, err error) {
 	resultsMap = make(map[string]interface{}, len(fields))
 	scanResultContainers := make([]interface{}, len(fields))
diff --git a/vendor/github.com/go-xorm/xorm/session_raw.go b/vendor/github.com/go-xorm/xorm/session_raw.go
index 69bf9b3c6b..47823d6706 100644
--- a/vendor/github.com/go-xorm/xorm/session_raw.go
+++ b/vendor/github.com/go-xorm/xorm/session_raw.go
@@ -9,6 +9,7 @@ import (
 	"reflect"
 	"time"
 
+	"github.com/go-xorm/builder"
 	"github.com/go-xorm/core"
 )
 
@@ -193,11 +194,34 @@ func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, er
 	return session.DB().Exec(sqlStr, args...)
 }
 
+func convertSQLOrArgs(sqlorArgs ...interface{}) (string, []interface{}, error) {
+	switch sqlorArgs[0].(type) {
+	case string:
+		return sqlorArgs[0].(string), sqlorArgs[1:], nil
+	case *builder.Builder:
+		return sqlorArgs[0].(*builder.Builder).ToSQL()
+	case builder.Builder:
+		bd := sqlorArgs[0].(builder.Builder)
+		return bd.ToSQL()
+	}
+
+	return "", nil, ErrUnSupportedType
+}
+
 // Exec raw sql
-func (session *Session) Exec(sqlStr string, args ...interface{}) (sql.Result, error) {
+func (session *Session) Exec(sqlorArgs ...interface{}) (sql.Result, error) {
 	if session.isAutoClose {
 		defer session.Close()
 	}
 
+	if len(sqlorArgs) == 0 {
+		return nil, ErrUnSupportedType
+	}
+
+	sqlStr, args, err := convertSQLOrArgs(sqlorArgs...)
+	if err != nil {
+		return nil, err
+	}
+
 	return session.exec(sqlStr, args...)
 }
diff --git a/vendor/github.com/go-xorm/xorm/session_update.go b/vendor/github.com/go-xorm/xorm/session_update.go
index 84c7e7fecf..37b34ff3dd 100644
--- a/vendor/github.com/go-xorm/xorm/session_update.go
+++ b/vendor/github.com/go-xorm/xorm/session_update.go
@@ -116,7 +116,7 @@ func (session *Session) cacheUpdate(table *core.Table, tableName, sqlStr string,
 					} else {
 						session.engine.logger.Debug("[cacheUpdate] set bean field", bean, colName, fieldValue.Interface())
 						if col.IsVersion && session.statement.checkVersion {
-							fieldValue.SetInt(fieldValue.Int() + 1)
+							session.incrVersionFieldValue(fieldValue)
 						} else {
 							fieldValue.Set(reflect.ValueOf(args[idx]))
 						}
@@ -357,7 +357,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
 		return 0, err
 	} else if doIncVer {
 		if verValue != nil && verValue.IsValid() && verValue.CanSet() {
-			verValue.SetInt(verValue.Int() + 1)
+			session.incrVersionFieldValue(verValue)
 		}
 	}
 
@@ -443,7 +443,7 @@ func (session *Session) genUpdateColumns(bean interface{}) ([]string, []interfac
 			}
 		}
 
-		if col.IsDeleted || col.IsCreated {
+		if (col.IsDeleted && !session.statement.unscoped) || col.IsCreated {
 			continue
 		}
 
diff --git a/vendor/github.com/go-xorm/xorm/statement.go b/vendor/github.com/go-xorm/xorm/statement.go
index 7856936f5c..a7f7010ad2 100644
--- a/vendor/github.com/go-xorm/xorm/statement.go
+++ b/vendor/github.com/go-xorm/xorm/statement.go
@@ -59,6 +59,7 @@ type Statement struct {
 	exprColumns     map[string]exprParam
 	cond            builder.Cond
 	bufferSize      int
+	context         ContextCache
 }
 
 // Init reset all the statement's fields
@@ -99,6 +100,7 @@ func (statement *Statement) Init() {
 	statement.exprColumns = make(map[string]exprParam)
 	statement.cond = builder.NewCond()
 	statement.bufferSize = 0
+	statement.context = nil
 }
 
 // NoAutoCondition if you do not want convert bean's field as query condition, then use this function
@@ -933,7 +935,7 @@ func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{},
 		if len(statement.JoinStr) == 0 {
 			if len(columnStr) == 0 {
 				if len(statement.GroupByStr) > 0 {
-					columnStr = statement.Engine.Quote(strings.Replace(statement.GroupByStr, ",", statement.Engine.Quote(","), -1))
+					columnStr = statement.Engine.quoteColumns(statement.GroupByStr)
 				} else {
 					columnStr = statement.genColumnStr()
 				}
@@ -941,7 +943,7 @@ func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{},
 		} else {
 			if len(columnStr) == 0 {
 				if len(statement.GroupByStr) > 0 {
-					columnStr = statement.Engine.Quote(strings.Replace(statement.GroupByStr, ",", statement.Engine.Quote(","), -1))
+					columnStr = statement.Engine.quoteColumns(statement.GroupByStr)
 				}
 			}
 		}
diff --git a/vendor/github.com/go-xorm/xorm/transaction.go b/vendor/github.com/go-xorm/xorm/transaction.go
new file mode 100644
index 0000000000..4104103fd5
--- /dev/null
+++ b/vendor/github.com/go-xorm/xorm/transaction.go
@@ -0,0 +1,26 @@
+// Copyright 2018 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
+
+// Transaction Execute sql wrapped in a transaction(abbr as tx), tx will automatic commit if no errors occurred
+func (engine *Engine) Transaction(f func(*Session) (interface{}, error)) (interface{}, error) {
+	session := engine.NewSession()
+	defer session.Close()
+
+	if err := session.Begin(); err != nil {
+		return nil, err
+	}
+
+	result, err := f(session)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := session.Commit(); err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
diff --git a/vendor/github.com/go-xorm/xorm/xorm.go b/vendor/github.com/go-xorm/xorm/xorm.go
index 141c4897d5..739de8d429 100644
--- a/vendor/github.com/go-xorm/xorm/xorm.go
+++ b/vendor/github.com/go-xorm/xorm/xorm.go
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+// +build go1.8
+
 package xorm
 
 import (