* 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 buildtags/v1.7.0-dev
when: | when: | ||||
event: [ push, tag, pull_request ] | 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: | generate-coverage: | ||||
image: golang:1.11 | image: golang:1.11 | ||||
pull: true | pull: true | ||||
when: | when: | ||||
event: [ push, tag, pull_request ] | 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: | ldap: | ||||
image: gitea/test-openldap:latest | image: gitea/test-openldap:latest | ||||
when: | when: |
/integrations/gitea-integration-mysql | /integrations/gitea-integration-mysql | ||||
/integrations/gitea-integration-pgsql | /integrations/gitea-integration-pgsql | ||||
/integrations/gitea-integration-sqlite | /integrations/gitea-integration-sqlite | ||||
/integrations/gitea-integration-mssql | |||||
/integrations/indexers-mysql | /integrations/indexers-mysql | ||||
/integrations/indexers-pgsql | /integrations/indexers-pgsql | ||||
/integrations/indexers-sqlite | /integrations/indexers-sqlite | ||||
/integrations/indexers-mssql | |||||
/integrations/mysql.ini | /integrations/mysql.ini | ||||
/integrations/pgsql.ini | /integrations/pgsql.ini | ||||
/integrations/mssql.ini | |||||
/node_modules | /node_modules | ||||
version = "v0.6.0" | version = "v0.6.0" | ||||
[[projects]] | [[projects]] | ||||
digest = "1:22a1ac7f654095f6817076eb975368bab5481e42554d0121ea37e28a86a3f83d" | |||||
digest = "1:931a62a1aacc37a5e4c309a111642ec4da47b4dc453cd4ba5481b12eedb04a5d" | |||||
name = "github.com/go-xorm/xorm" | name = "github.com/go-xorm/xorm" | ||||
packages = ["."] | packages = ["."] | ||||
pruneopts = "NUT" | pruneopts = "NUT" | ||||
revision = "ad69f7d8f0861a29438154bb0a20b60501298480" | |||||
revision = "401f4ee8ff8cbc40a4754cb12192fbe4f02f3979" | |||||
[[projects]] | [[projects]] | ||||
branch = "master" | branch = "master" |
[[override]] | [[override]] | ||||
name = "github.com/go-xorm/xorm" | name = "github.com/go-xorm/xorm" | ||||
#version = "0.6.5" | |||||
revision = "ad69f7d8f0861a29438154bb0a20b60501298480" | |||||
revision = "401f4ee8ff8cbc40a4754cb12192fbe4f02f3979" | |||||
[[override]] | [[override]] | ||||
name = "github.com/go-sql-driver/mysql" | name = "github.com/go-sql-driver/mysql" |
TEST_PGSQL_DBNAME ?= testgitea | TEST_PGSQL_DBNAME ?= testgitea | ||||
TEST_PGSQL_USERNAME ?= postgres | TEST_PGSQL_USERNAME ?= postgres | ||||
TEST_PGSQL_PASSWORD ?= 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) | ifeq ($(OS), Windows_NT) | ||||
EXECUTABLE := gitea.exe | EXECUTABLE := gitea.exe | ||||
$(GO) clean -i ./... | $(GO) clean -i ./... | ||||
rm -rf $(EXECUTABLE) $(DIST) $(BINDATA) \ | rm -rf $(EXECUTABLE) $(DIST) $(BINDATA) \ | ||||
integrations*.test \ | 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 | .PHONY: fmt | ||||
fmt: | fmt: | ||||
-e 's|{{TEST_PGSQL_USERNAME}}|${TEST_PGSQL_USERNAME}|g' \ | -e 's|{{TEST_PGSQL_USERNAME}}|${TEST_PGSQL_USERNAME}|g' \ | ||||
-e 's|{{TEST_PGSQL_PASSWORD}}|${TEST_PGSQL_PASSWORD}|g' \ | -e 's|{{TEST_PGSQL_PASSWORD}}|${TEST_PGSQL_PASSWORD}|g' \ | ||||
integrations/pgsql.ini.tmpl > integrations/pgsql.ini | 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 | .PHONY: test-mysql | ||||
test-mysql: integrations.test generate-ini | test-mysql: integrations.test generate-ini | ||||
test-pgsql: integrations.test generate-ini | test-pgsql: integrations.test generate-ini | ||||
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/pgsql.ini ./integrations.test | 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 | .PHONY: bench-sqlite | ||||
bench-sqlite: integrations.sqlite.test | 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 . | GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/sqlite.ini ./integrations.sqlite.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench . |
helper = &testfixtures.PostgreSQL{} | helper = &testfixtures.PostgreSQL{} | ||||
} else if setting.UseSQLite3 { | } else if setting.UseSQLite3 { | ||||
helper = &testfixtures.SQLite{} | helper = &testfixtures.SQLite{} | ||||
} else if setting.UseMSSQL { | |||||
helper = &testfixtures.SQLServer{} | |||||
} else { | } else { | ||||
fmt.Println("Unsupported RDBMS for integration tests") | fmt.Println("Unsupported RDBMS for integration tests") | ||||
os.Exit(1) | os.Exit(1) | ||||
if _, err = db.Exec("CREATE DATABASE testgitea"); err != nil { | if _, err = db.Exec("CREATE DATABASE testgitea"); err != nil { | ||||
log.Fatalf("db.Exec: %v", err) | 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() | routers.GlobalInit() | ||||
} | } |
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 |
Count(...interface{}) (int64, error) | Count(...interface{}) (int64, error) | ||||
Decr(column string, arg ...interface{}) *xorm.Session | Decr(column string, arg ...interface{}) *xorm.Session | ||||
Delete(interface{}) (int64, error) | Delete(interface{}) (int64, error) | ||||
Exec(string, ...interface{}) (sql.Result, error) | |||||
Exec(...interface{}) (sql.Result, error) | |||||
Find(interface{}, ...interface{}) error | Find(interface{}, ...interface{}) error | ||||
Get(interface{}) (bool, error) | Get(interface{}) (bool, error) | ||||
ID(interface{}) *xorm.Session | ID(interface{}) *xorm.Session | ||||
return | 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" | host, port := "127.0.0.1", "1433" | ||||
if strings.Contains(info, ":") { | if strings.Contains(info, ":") { | ||||
host = strings.Split(info, ":")[0] | host = strings.Split(info, ":")[0] | ||||
case "postgres": | case "postgres": | ||||
connStr = getPostgreSQLConnectionString(DbCfg.Host, DbCfg.User, DbCfg.Passwd, DbCfg.Name, Param, DbCfg.SSLMode) | connStr = getPostgreSQLConnectionString(DbCfg.Host, DbCfg.User, DbCfg.Passwd, DbCfg.Name, Param, DbCfg.SSLMode) | ||||
case "mssql": | 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) | connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, DbCfg.Name, DbCfg.User, DbCfg.Passwd) | ||||
case "sqlite3": | case "sqlite3": | ||||
if !EnableSQLite3 { | if !EnableSQLite3 { |
// 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] | |||||
} |
res = core.Bit | res = core.Bit | ||||
if strings.EqualFold(c.Default, "true") { | if strings.EqualFold(c.Default, "true") { | ||||
c.Default = "1" | c.Default = "1" | ||||
} else { | |||||
} else if strings.EqualFold(c.Default, "false") { | |||||
c.Default = "0" | c.Default = "0" | ||||
} | } | ||||
case core.Serial: | case core.Serial: |
if len(charset) == 0 { | if len(charset) == 0 { | ||||
charset = db.URI().Charset | charset = db.URI().Charset | ||||
} else if len(charset) > 0 { | |||||
} | |||||
if len(charset) != 0 { | |||||
sql += " DEFAULT CHARSET " + charset | sql += " DEFAULT CHARSET " + charset | ||||
} | } | ||||
if db.rowFormat != "" { | if db.rowFormat != "" { | ||||
sql += " ROW_FORMAT=" + db.rowFormat | sql += " ROW_FORMAT=" + db.rowFormat |
} | } | ||||
func (db *sqlite3) DropIndexSql(tableName string, index *core.Index) string { | func (db *sqlite3) DropIndexSql(tableName string, index *core.Index) string { | ||||
//var unique string | |||||
// var unique string | |||||
quote := db.Quote | quote := db.Quote | ||||
idxName := index.Name | idxName := index.Name | ||||
} | } | ||||
func (p *sqlite3Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) { | 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 | return &core.Uri{DbType: core.SQLITE, DbName: dataSourceName}, nil | ||||
} | } |
return engine.dialect.QuoteStr() | 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 | // Quote Use QuoteStr quote the string sql | ||||
func (engine *Engine) Quote(value string) string { | func (engine *Engine) Quote(value string) string { | ||||
value = strings.TrimSpace(value) | value = strings.TrimSpace(value) | ||||
return engine.dialect.AutoIncrStr() | 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+ | // SetMaxOpenConns is only available for go 1.2+ | ||||
func (engine *Engine) SetMaxOpenConns(conns int) { | func (engine *Engine) SetMaxOpenConns(conns int) { | ||||
engine.db.SetMaxOpenConns(conns) | engine.db.SetMaxOpenConns(conns) | ||||
} | } | ||||
// Exec raw sql | // 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() | session := engine.NewSession() | ||||
defer session.Close() | defer session.Close() | ||||
return session.Exec(sql, args...) | |||||
return session.Exec(sqlorArgs...) | |||||
} | } | ||||
// Query a raw sql and return records as []map[string][]byte | // Query a raw sql and return records as []map[string][]byte |
package xorm | package xorm | ||||
import ( | import ( | ||||
"time" | |||||
"github.com/go-xorm/core" | "github.com/go-xorm/core" | ||||
) | ) | ||||
} | } | ||||
} | } | ||||
// 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 | // SetDefaultCacher set the default cacher | ||||
func (eg *EngineGroup) SetDefaultCacher(cacher core.Cacher) { | func (eg *EngineGroup) SetDefaultCacher(cacher core.Cacher) { | ||||
eg.Engine.SetDefaultCacher(cacher) | eg.Engine.SetDefaultCacher(cacher) |
// 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) | |||||
} | |||||
} |
Delete(interface{}) (int64, error) | Delete(interface{}) (int64, error) | ||||
Distinct(columns ...string) *Session | Distinct(columns ...string) *Session | ||||
DropIndexes(bean interface{}) error | DropIndexes(bean interface{}) error | ||||
Exec(string, ...interface{}) (sql.Result, error) | |||||
Exec(sqlOrAgrs ...interface{}) (sql.Result, error) | |||||
Exist(bean ...interface{}) (bool, error) | Exist(bean ...interface{}) (bool, error) | ||||
Find(interface{}, ...interface{}) error | Find(interface{}, ...interface{}) error | ||||
FindAndCount(interface{}, ...interface{}) (int64, error) | FindAndCount(interface{}, ...interface{}) (int64, error) | ||||
Before(func(interface{})) *Session | Before(func(interface{})) *Session | ||||
Charset(charset string) *Session | Charset(charset string) *Session | ||||
ClearCache(...interface{}) error | |||||
CreateTables(...interface{}) error | CreateTables(...interface{}) error | ||||
DBMetas() ([]*core.Table, error) | DBMetas() ([]*core.Table, error) | ||||
Dialect() core.Dialect | Dialect() core.Dialect | ||||
GetTableMapper() core.IMapper | GetTableMapper() core.IMapper | ||||
GetTZDatabase() *time.Location | GetTZDatabase() *time.Location | ||||
GetTZLocation() *time.Location | GetTZLocation() *time.Location | ||||
MapCacher(interface{}, core.Cacher) error | |||||
NewSession() *Session | NewSession() *Session | ||||
NoAutoTime() *Session | NoAutoTime() *Session | ||||
Quote(string) string | Quote(string) string | ||||
SetCacher(string, core.Cacher) | SetCacher(string, core.Cacher) | ||||
SetConnMaxLifetime(time.Duration) | |||||
SetDefaultCacher(core.Cacher) | SetDefaultCacher(core.Cacher) | ||||
SetLogger(logger core.ILogger) | |||||
SetLogLevel(core.LogLevel) | SetLogLevel(core.LogLevel) | ||||
SetMapper(core.IMapper) | SetMapper(core.IMapper) | ||||
SetMaxOpenConns(int) | |||||
SetMaxIdleConns(int) | |||||
SetSchema(string) | SetSchema(string) | ||||
SetTZDatabase(tz *time.Location) | SetTZDatabase(tz *time.Location) | ||||
SetTZLocation(tz *time.Location) | SetTZLocation(tz *time.Location) | ||||
ShowExecTime(...bool) | |||||
ShowSQL(show ...bool) | ShowSQL(show ...bool) | ||||
Sync(...interface{}) error | Sync(...interface{}) error | ||||
Sync2(...interface{}) error | Sync2(...interface{}) error |
} | } | ||||
} | } | ||||
// 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 | // IsClosed returns if session is closed | ||||
func (session *Session) IsClosed() bool { | func (session *Session) IsClosed() bool { | ||||
return session.db == nil | return session.db == nil | ||||
session.statement.Unscoped() | session.statement.Unscoped() | ||||
return session | 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) | |||||
} | |||||
} |
}) | }) | ||||
} | } | ||||
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...) | session.cacheDelete(table, tableNameNoQuote, deleteSQL, argsForCache...) | ||||
} | } | ||||
if session.statement.JoinStr == "" { | if session.statement.JoinStr == "" { | ||||
if columnStr == "" { | if columnStr == "" { | ||||
if session.statement.GroupByStr != "" { | 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 { | } else { | ||||
columnStr = session.statement.genColumnStr() | columnStr = session.statement.genColumnStr() | ||||
} | } | ||||
} else { | } else { | ||||
if columnStr == "" { | if columnStr == "" { | ||||
if session.statement.GroupByStr != "" { | 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 { | } else { | ||||
columnStr = "*" | columnStr = "*" | ||||
} | } | ||||
} | } | ||||
if session.canCache() { | 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.IsDistinct && | ||||
!session.statement.unscoped { | !session.statement.unscoped { | ||||
err = session.cacheFind(sliceElementType, sqlStr, rowsSlicePtr, args...) | err = session.cacheFind(sliceElementType, sqlStr, rowsSlicePtr, args...) |
import ( | import ( | ||||
"database/sql" | "database/sql" | ||||
"errors" | "errors" | ||||
"fmt" | |||||
"reflect" | "reflect" | ||||
"strconv" | "strconv" | ||||
table := session.statement.RefTable | table := session.statement.RefTable | ||||
if session.canCache() && beanValue.Elem().Kind() == reflect.Struct { | 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 { | !session.statement.unscoped { | ||||
has, err := session.cacheGet(bean, sqlStr, args...) | has, err := session.cacheGet(bean, sqlStr, args...) | ||||
if err != ErrCacheFailed { | if err != ErrCacheFailed { | ||||
} | } | ||||
} | } | ||||
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) { | func (session *Session) nocacheGet(beanKind reflect.Kind, table *core.Table, bean interface{}, sqlStr string, args ...interface{}) (bool, error) { | ||||
defer rows.Close() | defer rows.Close() | ||||
if !rows.Next() { | if !rows.Next() { | ||||
if rows.Err() != nil { | |||||
return false, rows.Err() | |||||
} | |||||
return false, nil | return false, nil | ||||
} | } | ||||
if err != nil { | if err != nil { | ||||
session.engine.logger.Error(err) | session.engine.logger.Error(err) | ||||
} else if verValue.IsValid() && verValue.CanSet() { | } else if verValue.IsValid() && verValue.CanSet() { | ||||
verValue.SetInt(1) | |||||
session.incrVersionFieldValue(verValue) | |||||
} | } | ||||
} | } | ||||
if err != nil { | if err != nil { | ||||
session.engine.logger.Error(err) | session.engine.logger.Error(err) | ||||
} else if verValue.IsValid() && verValue.CanSet() { | } else if verValue.IsValid() && verValue.CanSet() { | ||||
verValue.SetInt(1) | |||||
session.incrVersionFieldValue(verValue) | |||||
} | } | ||||
} | } | ||||
if err != nil { | if err != nil { | ||||
session.engine.logger.Error(err) | session.engine.logger.Error(err) | ||||
} else if verValue.IsValid() && verValue.CanSet() { | } else if verValue.IsValid() && verValue.CanSet() { | ||||
verValue.SetInt(1) | |||||
session.incrVersionFieldValue(verValue) | |||||
} | } | ||||
} | } | ||||
func (session *Session) genQuerySQL(sqlorArgs ...interface{}) (string, []interface{}, error) { | func (session *Session) genQuerySQL(sqlorArgs ...interface{}) (string, []interface{}, error) { | ||||
if len(sqlorArgs) > 0 { | 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 != "" { | if session.statement.RawSQL != "" { | ||||
if session.statement.JoinStr == "" { | if session.statement.JoinStr == "" { | ||||
if columnStr == "" { | if columnStr == "" { | ||||
if session.statement.GroupByStr != "" { | 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 { | } else { | ||||
columnStr = session.statement.genColumnStr() | columnStr = session.statement.genColumnStr() | ||||
} | } | ||||
} else { | } else { | ||||
if columnStr == "" { | if columnStr == "" { | ||||
if session.statement.GroupByStr != "" { | 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 { | } else { | ||||
columnStr = "*" | columnStr = "*" | ||||
} | } | ||||
return result, nil | 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) { | func rows2Strings(rows *core.Rows) (resultsSlice []map[string]string, err error) { | ||||
fields, err := rows.Columns() | fields, err := rows.Columns() | ||||
if err != nil { | if err != nil { | ||||
return resultsSlice, nil | 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 | // QueryString runs a raw sql and return records as []map[string]string | ||||
func (session *Session) QueryString(sqlorArgs ...interface{}) ([]map[string]string, error) { | func (session *Session) QueryString(sqlorArgs ...interface{}) ([]map[string]string, error) { | ||||
if session.isAutoClose { | if session.isAutoClose { | ||||
return rows2Strings(rows) | 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) { | func row2mapInterface(rows *core.Rows, fields []string) (resultsMap map[string]interface{}, err error) { | ||||
resultsMap = make(map[string]interface{}, len(fields)) | resultsMap = make(map[string]interface{}, len(fields)) | ||||
scanResultContainers := make([]interface{}, len(fields)) | scanResultContainers := make([]interface{}, len(fields)) |
"reflect" | "reflect" | ||||
"time" | "time" | ||||
"github.com/go-xorm/builder" | |||||
"github.com/go-xorm/core" | "github.com/go-xorm/core" | ||||
) | ) | ||||
return session.DB().Exec(sqlStr, args...) | 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 | // 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 { | if session.isAutoClose { | ||||
defer session.Close() | 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...) | return session.exec(sqlStr, args...) | ||||
} | } |
} else { | } else { | ||||
session.engine.logger.Debug("[cacheUpdate] set bean field", bean, colName, fieldValue.Interface()) | session.engine.logger.Debug("[cacheUpdate] set bean field", bean, colName, fieldValue.Interface()) | ||||
if col.IsVersion && session.statement.checkVersion { | if col.IsVersion && session.statement.checkVersion { | ||||
fieldValue.SetInt(fieldValue.Int() + 1) | |||||
session.incrVersionFieldValue(fieldValue) | |||||
} else { | } else { | ||||
fieldValue.Set(reflect.ValueOf(args[idx])) | fieldValue.Set(reflect.ValueOf(args[idx])) | ||||
} | } | ||||
return 0, err | return 0, err | ||||
} else if doIncVer { | } else if doIncVer { | ||||
if verValue != nil && verValue.IsValid() && verValue.CanSet() { | if verValue != nil && verValue.IsValid() && verValue.CanSet() { | ||||
verValue.SetInt(verValue.Int() + 1) | |||||
session.incrVersionFieldValue(verValue) | |||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
if col.IsDeleted || col.IsCreated { | |||||
if (col.IsDeleted && !session.statement.unscoped) || col.IsCreated { | |||||
continue | continue | ||||
} | } | ||||
exprColumns map[string]exprParam | exprColumns map[string]exprParam | ||||
cond builder.Cond | cond builder.Cond | ||||
bufferSize int | bufferSize int | ||||
context ContextCache | |||||
} | } | ||||
// Init reset all the statement's fields | // Init reset all the statement's fields | ||||
statement.exprColumns = make(map[string]exprParam) | statement.exprColumns = make(map[string]exprParam) | ||||
statement.cond = builder.NewCond() | statement.cond = builder.NewCond() | ||||
statement.bufferSize = 0 | statement.bufferSize = 0 | ||||
statement.context = nil | |||||
} | } | ||||
// NoAutoCondition if you do not want convert bean's field as query condition, then use this function | // NoAutoCondition if you do not want convert bean's field as query condition, then use this function | ||||
if len(statement.JoinStr) == 0 { | if len(statement.JoinStr) == 0 { | ||||
if len(columnStr) == 0 { | if len(columnStr) == 0 { | ||||
if len(statement.GroupByStr) > 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 { | } else { | ||||
columnStr = statement.genColumnStr() | columnStr = statement.genColumnStr() | ||||
} | } | ||||
} else { | } else { | ||||
if len(columnStr) == 0 { | if len(columnStr) == 0 { | ||||
if len(statement.GroupByStr) > 0 { | if len(statement.GroupByStr) > 0 { | ||||
columnStr = statement.Engine.Quote(strings.Replace(statement.GroupByStr, ",", statement.Engine.Quote(","), -1)) | |||||
columnStr = statement.Engine.quoteColumns(statement.GroupByStr) | |||||
} | } | ||||
} | } | ||||
} | } |
// 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 | |||||
} |
// Use of this source code is governed by a BSD-style | // Use of this source code is governed by a BSD-style | ||||
// license that can be found in the LICENSE file. | // license that can be found in the LICENSE file. | ||||
// +build go1.8 | |||||
package xorm | package xorm | ||||
import ( | import ( |