* 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.21.12.1
| @@ -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: | |||
| @@ -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 | |||
| @@ -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" | |||
| @@ -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" | |||
| @@ -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 . | |||
| @@ -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() | |||
| } | |||
| @@ -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 | |||
| @@ -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 { | |||
| @@ -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] | |||
| } | |||
| @@ -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: | |||
| @@ -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 | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| @@ -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) | |||
| @@ -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) | |||
| } | |||
| } | |||
| @@ -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 | |||
| @@ -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) | |||
| } | |||
| } | |||
| @@ -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...) | |||
| } | |||
| @@ -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...) | |||
| @@ -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 | |||
| } | |||
| @@ -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) | |||
| } | |||
| } | |||
| @@ -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)) | |||
| @@ -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...) | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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) | |||
| } | |||
| } | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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 ( | |||