* Add support for database schema * Require setting search_path for the db user * Add schema setting to admin/config.tmpl * Use a schema different from default for psql tests * Update postgres scripts to use custom schema * Update to xorm/core 0.7.3 and xorm/xorm c37aff9b3a * Fix migration test Co-authored-by: Antoine GIRARD <sapk@users.noreply.github.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>tags/v1.12.0-dev
@@ -79,6 +79,7 @@ TEST_PGSQL_HOST ?= pgsql:5432 | |||||
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_PGSQL_SCHEMA ?= gtestschema | |||||
TEST_MSSQL_HOST ?= mssql:1433 | TEST_MSSQL_HOST ?= mssql:1433 | ||||
TEST_MSSQL_DBNAME ?= gitea | TEST_MSSQL_DBNAME ?= gitea | ||||
TEST_MSSQL_USERNAME ?= sa | TEST_MSSQL_USERNAME ?= sa | ||||
@@ -306,6 +307,7 @@ generate-ini-pgsql: | |||||
-e 's|{{TEST_PGSQL_DBNAME}}|${TEST_PGSQL_DBNAME}|g' \ | -e 's|{{TEST_PGSQL_DBNAME}}|${TEST_PGSQL_DBNAME}|g' \ | ||||
-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' \ | ||||
-e 's|{{TEST_PGSQL_SCHEMA}}|${TEST_PGSQL_SCHEMA}|g' \ | |||||
integrations/pgsql.ini.tmpl > integrations/pgsql.ini | integrations/pgsql.ini.tmpl > integrations/pgsql.ini | ||||
.PHONY: test-pgsql | .PHONY: test-pgsql | ||||
@@ -209,6 +209,9 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. | |||||
- `NAME`: **gitea**: Database name. | - `NAME`: **gitea**: Database name. | ||||
- `USER`: **root**: Database username. | - `USER`: **root**: Database username. | ||||
- `PASSWD`: **\<empty\>**: Database user password. Use \`your password\` for quoting if you use special characters in the password. | - `PASSWD`: **\<empty\>**: Database user password. Use \`your password\` for quoting if you use special characters in the password. | ||||
- `SCHEMA`: **\<empty\>**: For PostgreSQL only, schema to use if different from "public". The schema must exist beforehand, | |||||
the user must have creation privileges on it, and the user search path must be set to the look into the schema first | |||||
(e.g. `ALTER USER user SET SEARCH_PATH = schema_name,"$user",public;`). | |||||
- `SSL_MODE`: **disable**: For PostgreSQL and MySQL only. | - `SSL_MODE`: **disable**: For PostgreSQL and MySQL only. | ||||
- `CHARSET`: **utf8**: For MySQL only, either "utf8" or "utf8mb4", default is "utf8". NOTICE: for "utf8mb4" you must use MySQL InnoDB > 5.6. Gitea is unable to check this. | - `CHARSET`: **utf8**: For MySQL only, either "utf8" or "utf8mb4", default is "utf8". NOTICE: for "utf8mb4" you must use MySQL InnoDB > 5.6. Gitea is unable to check this. | ||||
- `PATH`: **data/gitea.db**: For SQLite3 only, the database file path. | - `PATH`: **data/gitea.db**: For SQLite3 only, the database file path. | ||||
@@ -112,6 +112,6 @@ require ( | |||||
mvdan.cc/xurls/v2 v2.1.0 | mvdan.cc/xurls/v2 v2.1.0 | ||||
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 | strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 | ||||
xorm.io/builder v0.3.6 | xorm.io/builder v0.3.6 | ||||
xorm.io/core v0.7.2 | |||||
xorm.io/xorm v0.8.1 | |||||
xorm.io/core v0.7.3 | |||||
xorm.io/xorm v0.8.2-0.20200120024500-c37aff9b3a4a | |||||
) | ) |
@@ -760,7 +760,9 @@ xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8= | |||||
xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU= | xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU= | ||||
xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw= | xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw= | ||||
xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= | xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= | ||||
xorm.io/core v0.7.3 h1:W8ws1PlrnkS1CZU1YWaYLMQcQilwAmQXU0BJDJon+H0= | |||||
xorm.io/core v0.7.3/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= | |||||
xorm.io/xorm v0.8.0 h1:iALxgJrX8O00f8Jk22GbZwPmxJNgssV5Mv4uc2HL9PM= | xorm.io/xorm v0.8.0 h1:iALxgJrX8O00f8Jk22GbZwPmxJNgssV5Mv4uc2HL9PM= | ||||
xorm.io/xorm v0.8.0/go.mod h1:ZkJLEYLoVyg7amJK/5r779bHyzs2AU8f8VMiP6BM7uY= | xorm.io/xorm v0.8.0/go.mod h1:ZkJLEYLoVyg7amJK/5r779bHyzs2AU8f8VMiP6BM7uY= | ||||
xorm.io/xorm v0.8.1 h1:4f2KXuQxVdaX3RdI3Fw81NzMiSpZeyCZt8m3sEVeIkQ= | |||||
xorm.io/xorm v0.8.1/go.mod h1:ZkJLEYLoVyg7amJK/5r779bHyzs2AU8f8VMiP6BM7uY= | |||||
xorm.io/xorm v0.8.2-0.20200120024500-c37aff9b3a4a h1:hzGd080rlkZ5a7v6Tr3x8PJJnWPfKxGMMl92c8DNcww= | |||||
xorm.io/xorm v0.8.2-0.20200120024500-c37aff9b3a4a/go.mod h1:ZkJLEYLoVyg7amJK/5r779bHyzs2AU8f8VMiP6BM7uY= |
@@ -153,18 +153,53 @@ func initIntegrationTest() { | |||||
if err != nil { | if err != nil { | ||||
log.Fatalf("sql.Open: %v", err) | log.Fatalf("sql.Open: %v", err) | ||||
} | } | ||||
rows, err := db.Query(fmt.Sprintf("SELECT 1 FROM pg_database WHERE datname = '%s'", setting.Database.Name)) | |||||
dbrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM pg_database WHERE datname = '%s'", setting.Database.Name)) | |||||
if err != nil { | if err != nil { | ||||
log.Fatalf("db.Query: %v", err) | log.Fatalf("db.Query: %v", err) | ||||
} | } | ||||
defer rows.Close() | |||||
defer dbrows.Close() | |||||
if rows.Next() { | |||||
if !dbrows.Next() { | |||||
if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", setting.Database.Name)); err != nil { | |||||
log.Fatalf("db.Exec: CREATE DATABASE: %v", err) | |||||
} | |||||
} | |||||
// Check if we need to setup a specific schema | |||||
if len(setting.Database.Schema) == 0 { | |||||
break | break | ||||
} | } | ||||
if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", setting.Database.Name)); err != nil { | |||||
log.Fatalf("db.Exec: %v", err) | |||||
db.Close() | |||||
db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s", | |||||
setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name, setting.Database.SSLMode)) | |||||
// This is a different db object; requires a different Close() | |||||
defer db.Close() | |||||
if err != nil { | |||||
log.Fatalf("sql.Open: %v", err) | |||||
} | |||||
schrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM information_schema.schemata WHERE schema_name = '%s'", setting.Database.Schema)) | |||||
if err != nil { | |||||
log.Fatalf("db.Query: %v", err) | |||||
} | |||||
defer schrows.Close() | |||||
if !schrows.Next() { | |||||
// Create and setup a DB schema | |||||
if _, err = db.Exec(fmt.Sprintf("CREATE SCHEMA %s", setting.Database.Schema)); err != nil { | |||||
log.Fatalf("db.Exec: CREATE SCHEMA: %v", err) | |||||
} | |||||
} | |||||
// Make the user's default search path the created schema; this will affect new connections | |||||
if _, err = db.Exec(fmt.Sprintf(`ALTER USER "%s" SET search_path = %s`, setting.Database.User, setting.Database.Schema)); err != nil { | |||||
log.Fatalf("db.Exec: ALTER USER SET search_path: %v", err) | |||||
} | |||||
// Make the current connection's search the created schema | |||||
if _, err = db.Exec(fmt.Sprintf(`SET search_path = %s`, setting.Database.Schema)); err != nil { | |||||
log.Fatalf("db.Exec: ALTER USER SET search_path: %v", err) | |||||
} | } | ||||
case setting.Database.UseMSSQL: | case setting.Database.UseMSSQL: | ||||
host, port := setting.ParseMSSQLHostPort(setting.Database.Host) | host, port := setting.ParseMSSQLHostPort(setting.Database.Host) | ||||
db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", | db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", | ||||
@@ -168,6 +168,32 @@ func restoreOldDB(t *testing.T, version string) bool { | |||||
assert.NoError(t, err) | assert.NoError(t, err) | ||||
db.Close() | db.Close() | ||||
// Check if we need to setup a specific schema | |||||
if len(setting.Database.Schema) != 0 { | |||||
db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s", | |||||
setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name, setting.Database.SSLMode)) | |||||
if !assert.NoError(t, err) { | |||||
return false | |||||
} | |||||
schrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM information_schema.schemata WHERE schema_name = '%s'", setting.Database.Schema)) | |||||
if !assert.NoError(t, err) || !assert.NotEmpty(t, schrows) { | |||||
return false | |||||
} | |||||
if !schrows.Next() { | |||||
// Create and setup a DB schema | |||||
_, err = db.Exec(fmt.Sprintf("CREATE SCHEMA %s", setting.Database.Schema)) | |||||
assert.NoError(t, err) | |||||
} | |||||
schrows.Close() | |||||
// Make the user's default search path the created schema; this will affect new connections | |||||
_, err = db.Exec(fmt.Sprintf(`ALTER USER "%s" SET search_path = %s`, setting.Database.User, setting.Database.Schema)) | |||||
assert.NoError(t, err) | |||||
db.Close() | |||||
} | |||||
db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s", | db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s", | ||||
setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name, setting.Database.SSLMode)) | setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name, setting.Database.SSLMode)) | ||||
assert.NoError(t, err) | assert.NoError(t, err) | ||||
@@ -7,6 +7,7 @@ HOST = {{TEST_PGSQL_HOST}} | |||||
NAME = {{TEST_PGSQL_DBNAME}} | NAME = {{TEST_PGSQL_DBNAME}} | ||||
USER = {{TEST_PGSQL_USERNAME}} | USER = {{TEST_PGSQL_USERNAME}} | ||||
PASSWD = {{TEST_PGSQL_PASSWORD}} | PASSWD = {{TEST_PGSQL_PASSWORD}} | ||||
SCHEMA = {{TEST_PGSQL_SCHEMA}} | |||||
SSL_MODE = disable | SSL_MODE = disable | ||||
[indexer] | [indexer] | ||||
@@ -128,7 +128,12 @@ func getEngine() (*xorm.Engine, error) { | |||||
return nil, err | return nil, err | ||||
} | } | ||||
return xorm.NewEngine(setting.Database.Type, connStr) | |||||
engine, err := xorm.NewEngine(setting.Database.Type, connStr) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
engine.SetSchema(setting.Database.Schema) | |||||
return engine, nil | |||||
} | } | ||||
// NewTestEngine sets a new test xorm.Engine | // NewTestEngine sets a new test xorm.Engine | ||||
@@ -25,6 +25,7 @@ type InstallForm struct { | |||||
SSLMode string | SSLMode string | ||||
Charset string `binding:"Required;In(utf8,utf8mb4)"` | Charset string `binding:"Required;In(utf8,utf8mb4)"` | ||||
DbPath string | DbPath string | ||||
DbSchema string | |||||
AppName string `binding:"Required" locale:"install.app_name"` | AppName string `binding:"Required" locale:"install.app_name"` | ||||
RepoRootPath string `binding:"Required"` | RepoRootPath string `binding:"Required"` | ||||
@@ -30,6 +30,7 @@ var ( | |||||
Name string | Name string | ||||
User string | User string | ||||
Passwd string | Passwd string | ||||
Schema string | |||||
SSLMode string | SSLMode string | ||||
Path string | Path string | ||||
LogSQL bool | LogSQL bool | ||||
@@ -75,6 +76,7 @@ func InitDBConfig() { | |||||
if len(Database.Passwd) == 0 { | if len(Database.Passwd) == 0 { | ||||
Database.Passwd = sec.Key("PASSWD").String() | Database.Passwd = sec.Key("PASSWD").String() | ||||
} | } | ||||
Database.Schema = sec.Key("SCHEMA").String() | |||||
Database.SSLMode = sec.Key("SSL_MODE").MustString("disable") | Database.SSLMode = sec.Key("SSL_MODE").MustString("disable") | ||||
Database.Charset = sec.Key("CHARSET").In("utf8", []string{"utf8", "utf8mb4"}) | Database.Charset = sec.Key("CHARSET").In("utf8", []string{"utf8", "utf8mb4"}) | ||||
Database.Path = sec.Key("PATH").MustString(filepath.Join(AppDataPath, "gitea.db")) | Database.Path = sec.Key("PATH").MustString(filepath.Join(AppDataPath, "gitea.db")) | ||||
@@ -102,6 +102,8 @@ user = Username | |||||
password = Password | password = Password | ||||
db_name = Database Name | db_name = Database Name | ||||
db_helper = Note to MySQL users: please use the InnoDB storage engine and if you use "utf8mb4", your InnoDB version must be greater than 5.6 . | db_helper = Note to MySQL users: please use the InnoDB storage engine and if you use "utf8mb4", your InnoDB version must be greater than 5.6 . | ||||
db_schema = Schema | |||||
db_schema_helper = Leave blank for database default ("public"). | |||||
ssl_mode = SSL | ssl_mode = SSL | ||||
charset = Charset | charset = Charset | ||||
path = Path | path = Path | ||||
@@ -1953,6 +1955,7 @@ config.db_type = Type | |||||
config.db_host = Host | config.db_host = Host | ||||
config.db_name = Name | config.db_name = Name | ||||
config.db_user = Username | config.db_user = Username | ||||
config.db_schema = Schema | |||||
config.db_ssl_mode = SSL | config.db_ssl_mode = SSL | ||||
config.db_path = Path | config.db_path = Path | ||||
@@ -54,6 +54,7 @@ func Install(ctx *context.Context) { | |||||
form.DbPasswd = setting.Database.Passwd | form.DbPasswd = setting.Database.Passwd | ||||
form.DbName = setting.Database.Name | form.DbName = setting.Database.Name | ||||
form.DbPath = setting.Database.Path | form.DbPath = setting.Database.Path | ||||
form.DbSchema = setting.Database.Schema | |||||
form.Charset = setting.Database.Charset | form.Charset = setting.Database.Charset | ||||
ctx.Data["CurDbOption"] = "MySQL" | ctx.Data["CurDbOption"] = "MySQL" | ||||
@@ -147,6 +148,7 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) { | |||||
setting.Database.User = form.DbUser | setting.Database.User = form.DbUser | ||||
setting.Database.Passwd = form.DbPasswd | setting.Database.Passwd = form.DbPasswd | ||||
setting.Database.Name = form.DbName | setting.Database.Name = form.DbName | ||||
setting.Database.Schema = form.DbSchema | |||||
setting.Database.SSLMode = form.SSLMode | setting.Database.SSLMode = form.SSLMode | ||||
setting.Database.Charset = form.Charset | setting.Database.Charset = form.Charset | ||||
setting.Database.Path = form.DbPath | setting.Database.Path = form.DbPath | ||||
@@ -267,6 +269,7 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) { | |||||
cfg.Section("database").Key("NAME").SetValue(setting.Database.Name) | cfg.Section("database").Key("NAME").SetValue(setting.Database.Name) | ||||
cfg.Section("database").Key("USER").SetValue(setting.Database.User) | cfg.Section("database").Key("USER").SetValue(setting.Database.User) | ||||
cfg.Section("database").Key("PASSWD").SetValue(setting.Database.Passwd) | cfg.Section("database").Key("PASSWD").SetValue(setting.Database.Passwd) | ||||
cfg.Section("database").Key("SCHEMA").SetValue(setting.Database.Schema) | |||||
cfg.Section("database").Key("SSL_MODE").SetValue(setting.Database.SSLMode) | cfg.Section("database").Key("SSL_MODE").SetValue(setting.Database.SSLMode) | ||||
cfg.Section("database").Key("CHARSET").SetValue(setting.Database.Charset) | cfg.Section("database").Key("CHARSET").SetValue(setting.Database.Charset) | ||||
cfg.Section("database").Key("PATH").SetValue(setting.Database.Path) | cfg.Section("database").Key("PATH").SetValue(setting.Database.Path) | ||||
@@ -128,6 +128,8 @@ | |||||
<dd>{{if .DbCfg.User}}{{.DbCfg.User}}{{else}}-{{end}}</dd> | <dd>{{if .DbCfg.User}}{{.DbCfg.User}}{{else}}-{{end}}</dd> | ||||
{{end}} | {{end}} | ||||
{{if eq .DbCfg.Type "postgres"}} | {{if eq .DbCfg.Type "postgres"}} | ||||
<dt>{{.i18n.Tr "admin.config.db_schema"}}</dt> | |||||
<dd>{{if .DbCfg.Schema}}{{.DbCfg.Schema}}{{else}}-{{end}}</dd> | |||||
<dt>{{.i18n.Tr "admin.config.db_ssl_mode"}}</dt> | <dt>{{.i18n.Tr "admin.config.db_ssl_mode"}}</dt> | ||||
<dd>{{if .DbCfg.SSLMode}}{{.DbCfg.SSLMode}}{{else}}-{{end}}</dd> | <dd>{{if .DbCfg.SSLMode}}{{.DbCfg.SSLMode}}{{else}}-{{end}}</dd> | ||||
{{end}} | {{end}} | ||||
@@ -62,6 +62,11 @@ | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="inline field {{if .Err_DbSetting}}error{{end}}"> | |||||
<label for="db_schema">{{.i18n.Tr "install.db_schema"}}</label> | |||||
<input id="db_schema" name="db_schema" value="{{.db_schema}}"> | |||||
<span class="help">{{.i18n.Tr "install.db_schema_helper"}}</span> | |||||
</div> | |||||
</div> | </div> | ||||
<div id="mysql_settings" class="{{if not (eq .CurDbOption "MySQL")}}hide{{end}}"> | <div id="mysql_settings" class="{{if not (eq .CurDbOption "MySQL")}}hide{{end}}"> | ||||
@@ -613,7 +613,7 @@ mvdan.cc/xurls/v2 | |||||
strk.kbt.io/projects/go/libravatar | strk.kbt.io/projects/go/libravatar | ||||
# xorm.io/builder v0.3.6 | # xorm.io/builder v0.3.6 | ||||
xorm.io/builder | xorm.io/builder | ||||
# xorm.io/core v0.7.2 | |||||
# xorm.io/core v0.7.3 | |||||
xorm.io/core | xorm.io/core | ||||
# xorm.io/xorm v0.8.1 | |||||
# xorm.io/xorm v0.8.2-0.20200120024500-c37aff9b3a4a | |||||
xorm.io/xorm | xorm.io/xorm |
@@ -1,128 +1,8 @@ | |||||
--- | |||||
kind: pipeline | |||||
name: go1.10 | |||||
platform: | |||||
os: linux | |||||
arch: amd64 | |||||
clone: | |||||
disable: true | |||||
workspace: | |||||
base: /go | |||||
path: src/xorm.io/core | |||||
steps: | |||||
- name: git | |||||
pull: default | |||||
image: plugins/git:next | |||||
settings: | |||||
depth: 50 | |||||
tags: true | |||||
- name: test | |||||
pull: default | |||||
image: golang:1.10 | |||||
commands: | |||||
- go get github.com/stretchr/testify/assert | |||||
- go get github.com/go-xorm/sqlfiddle | |||||
- go get github.com/go-sql-driver/mysql | |||||
- go get github.com/mattn/go-sqlite3 | |||||
- go vet | |||||
- "go test -v -race -coverprofile=coverage.txt -covermode=atomic -dbConn=\"root:@tcp(mysql:3306)/core_test?charset=utf8mb4\"" | |||||
when: | |||||
event: | |||||
- push | |||||
- tag | |||||
- pull_request | |||||
services: | |||||
- name: mysql | |||||
pull: default | |||||
image: mysql:5.7 | |||||
environment: | |||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes | |||||
MYSQL_DATABASE: core_test | |||||
when: | |||||
event: | |||||
- push | |||||
- tag | |||||
- pull_request | |||||
--- | |||||
kind: pipeline | |||||
name: go1.11 | |||||
platform: | |||||
os: linux | |||||
arch: amd64 | |||||
clone: | |||||
disable: true | |||||
workspace: | |||||
base: /go | |||||
path: src/xorm.io/core | |||||
steps: | |||||
- name: git | |||||
pull: default | |||||
image: plugins/git:next | |||||
settings: | |||||
depth: 50 | |||||
tags: true | |||||
- name: test | |||||
pull: default | |||||
image: golang:1.11 | |||||
commands: | |||||
- go vet | |||||
- "go test -v -race -coverprofile=coverage.txt -covermode=atomic -dbConn=\"root:@tcp(mysql:3306)/core_test?charset=utf8mb4\"" | |||||
environment: | |||||
GO111MODULE: "on" | |||||
GOPROXY: https://goproxy.cn | |||||
when: | |||||
event: | |||||
- push | |||||
- tag | |||||
- pull_request | |||||
services: | |||||
- name: mysql | |||||
pull: default | |||||
image: mysql:5.7 | |||||
environment: | |||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes | |||||
MYSQL_DATABASE: core_test | |||||
when: | |||||
event: | |||||
- push | |||||
- tag | |||||
- pull_request | |||||
--- | --- | ||||
kind: pipeline | kind: pipeline | ||||
name: go1.12 | name: go1.12 | ||||
platform: | |||||
os: linux | |||||
arch: amd64 | |||||
clone: | |||||
disable: true | |||||
workspace: | |||||
base: /go | |||||
path: src/xorm.io/core | |||||
steps: | steps: | ||||
- name: git | |||||
pull: default | |||||
image: plugins/git:next | |||||
settings: | |||||
depth: 50 | |||||
tags: true | |||||
- name: test | - name: test | ||||
pull: default | pull: default | ||||
@@ -1,7 +1,7 @@ | |||||
Core is a lightweight wrapper of sql.DB. | Core is a lightweight wrapper of sql.DB. | ||||
[](https://drone.gitea.com/xorm/core) | [](https://drone.gitea.com/xorm/core) | ||||
[](http://gocover.io/xorm.io/core) | |||||
[](https://gocover.io/xorm.io/core) | |||||
[](https://goreportcard.com/report/xorm.io/core) | [](https://goreportcard.com/report/xorm.io/core) | ||||
# Open | # Open | ||||
@@ -37,7 +37,7 @@ type Column struct { | |||||
IsDeleted bool | IsDeleted bool | ||||
IsCascade bool | IsCascade bool | ||||
IsVersion bool | IsVersion bool | ||||
DefaultIsEmpty bool | |||||
DefaultIsEmpty bool // false means column has no default set, but not default value is empty | |||||
EnumOptions map[string]int | EnumOptions map[string]int | ||||
SetOptions map[string]int | SetOptions map[string]int | ||||
DisableTimeZone bool | DisableTimeZone bool | ||||
@@ -65,7 +65,7 @@ func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable | |||||
IsDeleted: false, | IsDeleted: false, | ||||
IsCascade: false, | IsCascade: false, | ||||
IsVersion: false, | IsVersion: false, | ||||
DefaultIsEmpty: false, | |||||
DefaultIsEmpty: true, // default should be no default | |||||
EnumOptions: make(map[string]int), | EnumOptions: make(map[string]int), | ||||
Comment: "", | Comment: "", | ||||
} | } | ||||
@@ -26,8 +26,8 @@ type Index struct { | |||||
func (index *Index) XName(tableName string) string { | func (index *Index) XName(tableName string) string { | ||||
if !strings.HasPrefix(index.Name, "UQE_") && | if !strings.HasPrefix(index.Name, "UQE_") && | ||||
!strings.HasPrefix(index.Name, "IDX_") { | !strings.HasPrefix(index.Name, "IDX_") { | ||||
tableName = strings.Replace(tableName, `"`, "", -1) | |||||
tableName = strings.Replace(tableName, `.`, "_", -1) | |||||
tableParts := strings.Split(strings.Replace(tableName, `"`, "", -1), ".") | |||||
tableName = tableParts[len(tableParts)-1] | |||||
if index.Type == UniqueType { | if index.Type == UniqueType { | ||||
return fmt.Sprintf("UQE_%v_%v", tableName, index.Name) | return fmt.Sprintf("UQE_%v_%v", tableName, index.Name) | ||||
} | } | ||||
@@ -1,204 +1,14 @@ | |||||
--- | --- | ||||
kind: pipeline | kind: pipeline | ||||
name: go1.10-test | |||||
workspace: | |||||
base: /go | |||||
path: src/gitea.com/xorm/xorm | |||||
steps: | |||||
- name: build | |||||
pull: default | |||||
image: golang:1.10 | |||||
commands: | |||||
- go get -t -d -v | |||||
- go build -v | |||||
when: | |||||
event: | |||||
- push | |||||
- pull_request | |||||
- name: test-sqlite | |||||
pull: default | |||||
image: golang:1.10 | |||||
depends_on: | |||||
- build | |||||
commands: | |||||
- "go test -v -race -db=\"sqlite3\" -conn_str=\"./test.db\" -coverprofile=coverage1-1.txt -covermode=atomic" | |||||
- "go test -v -race -db=\"sqlite3\" -conn_str=\"./test.db\" -cache=true -coverprofile=coverage1-2.txt -covermode=atomic" | |||||
when: | |||||
event: | |||||
- push | |||||
- pull_request | |||||
- name: test-mysql | |||||
pull: default | |||||
image: golang:1.10 | |||||
depends_on: | |||||
- build | |||||
commands: | |||||
- "go test -v -race -db=\"mysql\" -conn_str=\"root:@tcp(mysql)/xorm_test\" -coverprofile=coverage2-1.txt -covermode=atomic" | |||||
- "go test -v -race -db=\"mysql\" -conn_str=\"root:@tcp(mysql)/xorm_test\" -cache=true -coverprofile=coverage2-2.txt -covermode=atomic" | |||||
when: | |||||
event: | |||||
- push | |||||
- pull_request | |||||
- name: test-mysql-utf8mb4 | |||||
pull: default | |||||
image: golang:1.10 | |||||
depends_on: | |||||
- test-mysql | |||||
commands: | |||||
- "go test -v -race -db=\"mysql\" -conn_str=\"root:@tcp(mysql)/xorm_test?charset=utf8mb4\" -coverprofile=coverage2.1-1.txt -covermode=atomic" | |||||
- "go test -v -race -db=\"mysql\" -conn_str=\"root:@tcp(mysql)/xorm_test?charset=utf8mb4\" -cache=true -coverprofile=coverage2.1-2.txt -covermode=atomic" | |||||
when: | |||||
event: | |||||
- push | |||||
- pull_request | |||||
- name: test-mymysql | |||||
pull: default | |||||
image: golang:1.10 | |||||
depends_on: | |||||
- test-mysql-utf8mb4 | |||||
commands: | |||||
- "go test -v -race -db=\"mymysql\" -conn_str=\"tcp:mysql:3306*xorm_test/root/\" -coverprofile=coverage3-1.txt -covermode=atomic" | |||||
- "go test -v -race -db=\"mymysql\" -conn_str=\"tcp:mysql:3306*xorm_test/root/\" -cache=true -coverprofile=coverage3-2.txt -covermode=atomic" | |||||
when: | |||||
event: | |||||
- push | |||||
- pull_request | |||||
- name: test-postgres | |||||
pull: default | |||||
image: golang:1.10 | |||||
depends_on: | |||||
- build | |||||
commands: | |||||
- "go test -v -race -db=\"postgres\" -conn_str=\"postgres://postgres:@pgsql/xorm_test?sslmode=disable\" -coverprofile=coverage4-1.txt -covermode=atomic" | |||||
- "go test -v -race -db=\"postgres\" -conn_str=\"postgres://postgres:@pgsql/xorm_test?sslmode=disable\" -cache=true -coverprofile=coverage4-2.txt -covermode=atomic" | |||||
when: | |||||
event: | |||||
- push | |||||
- pull_request | |||||
- name: test-postgres-schema | |||||
pull: default | |||||
image: golang:1.10 | |||||
depends_on: | |||||
- build | |||||
commands: | |||||
- "go test -v -race -db=\"postgres\" -conn_str=\"postgres://postgres:@pgsql/xorm_test?sslmode=disable\" -schema=xorm -coverprofile=coverage5-1.txt -covermode=atomic" | |||||
- "go test -v -race -db=\"postgres\" -conn_str=\"postgres://postgres:@pgsql/xorm_test?sslmode=disable\" -schema=xorm -cache=true -coverprofile=coverage5-2.txt -covermode=atomic" | |||||
when: | |||||
event: | |||||
- push | |||||
- pull_request | |||||
- name: test-mssql | |||||
pull: default | |||||
image: golang:1.10 | |||||
depends_on: | |||||
- build | |||||
commands: | |||||
- "go test -v -race -db=\"mssql\" -conn_str=\"server=mssql;user id=sa;password=yourStrong(!)Password;database=xorm_test\" -coverprofile=coverage6-1.txt -covermode=atomic" | |||||
- "go test -v -race -db=\"mssql\" -conn_str=\"server=mssql;user id=sa;password=yourStrong(!)Password;database=xorm_test\" -cache=true -coverprofile=coverage6-2.txt -covermode=atomic" | |||||
when: | |||||
event: | |||||
- push | |||||
- pull_request | |||||
- name: test-tidb | |||||
pull: default | |||||
image: golang:1.10 | |||||
depends_on: | |||||
- build | |||||
commands: | |||||
- "go test -v -race -db=\"mysql\" -conn_str=\"root:@tcp(tidb:4000)/xorm_test\" -ignore_select_update=true -coverprofile=coverage7-1.txt -covermode=atomic" | |||||
- "go test -v -race -db=\"mysql\" -conn_str=\"root:@tcp(tidb:4000)/xorm_test\" -ignore_select_update=true -cache=true -coverprofile=coverage7-2.txt -covermode=atomic" | |||||
when: | |||||
event: | |||||
- push | |||||
- pull_request | |||||
- name: test-end | |||||
pull: default | |||||
image: golang:1.10 | |||||
depends_on: | |||||
- test-sqlite | |||||
- test-mysql | |||||
- test-mysql-utf8mb4 | |||||
- test-mymysql | |||||
- test-postgres | |||||
- test-postgres-schema | |||||
- test-mssql | |||||
- test-tidb | |||||
commands: | |||||
- echo "go1.10 build end" | |||||
when: | |||||
event: | |||||
- push | |||||
- pull_request | |||||
services: | |||||
- name: mysql | |||||
pull: default | |||||
image: mysql:5.7 | |||||
environment: | |||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes | |||||
MYSQL_DATABASE: xorm_test | |||||
when: | |||||
event: | |||||
- push | |||||
- tag | |||||
- pull_request | |||||
- name: pgsql | |||||
pull: default | |||||
image: postgres:9.5 | |||||
environment: | |||||
POSTGRES_DB: xorm_test | |||||
POSTGRES_USER: postgres | |||||
when: | |||||
event: | |||||
- push | |||||
- tag | |||||
- pull_request | |||||
- name: mssql | |||||
pull: default | |||||
image: microsoft/mssql-server-linux:latest | |||||
environment: | |||||
ACCEPT_EULA: Y | |||||
SA_PASSWORD: yourStrong(!)Password | |||||
MSSQL_PID: Developer | |||||
when: | |||||
event: | |||||
- push | |||||
- tag | |||||
- pull_request | |||||
- name: tidb | |||||
pull: default | |||||
image: pingcap/tidb:v3.0.3 | |||||
when: | |||||
event: | |||||
- push | |||||
- tag | |||||
- pull_request | |||||
--- | |||||
kind: pipeline | |||||
name: go1.13-test | |||||
name: testing | |||||
steps: | steps: | ||||
- name: build | |||||
- name: test-vet | |||||
pull: default | pull: default | ||||
image: golang:1.13 | |||||
image: golang:1.12 | |||||
environment: | environment: | ||||
GO111MODULE: "on" | GO111MODULE: "on" | ||||
GOPROXY: "https://goproxy.cn" | GOPROXY: "https://goproxy.cn" | ||||
commands: | commands: | ||||
- go build -v | |||||
- go vet | - go vet | ||||
when: | when: | ||||
event: | event: | ||||
@@ -207,7 +17,7 @@ steps: | |||||
- name: test-sqlite | - name: test-sqlite | ||||
pull: default | pull: default | ||||
image: golang:1.13 | |||||
image: golang:1.12 | |||||
environment: | environment: | ||||
GO111MODULE: "on" | GO111MODULE: "on" | ||||
GOPROXY: "https://goproxy.cn" | GOPROXY: "https://goproxy.cn" | ||||
@@ -221,7 +31,7 @@ steps: | |||||
- name: test-mysql | - name: test-mysql | ||||
pull: default | pull: default | ||||
image: golang:1.13 | |||||
image: golang:1.12 | |||||
environment: | environment: | ||||
GO111MODULE: "on" | GO111MODULE: "on" | ||||
GOPROXY: "https://goproxy.cn" | GOPROXY: "https://goproxy.cn" | ||||
@@ -235,7 +45,7 @@ steps: | |||||
- name: test-mysql-utf8mb4 | - name: test-mysql-utf8mb4 | ||||
pull: default | pull: default | ||||
image: golang:1.13 | |||||
image: golang:1.12 | |||||
depends_on: | depends_on: | ||||
- test-mysql | - test-mysql | ||||
environment: | environment: | ||||
@@ -251,7 +61,7 @@ steps: | |||||
- name: test-mymysql | - name: test-mymysql | ||||
pull: default | pull: default | ||||
image: golang:1.13 | |||||
image: golang:1.12 | |||||
depends_on: | depends_on: | ||||
- test-mysql-utf8mb4 | - test-mysql-utf8mb4 | ||||
environment: | environment: | ||||
@@ -267,7 +77,7 @@ steps: | |||||
- name: test-postgres | - name: test-postgres | ||||
pull: default | pull: default | ||||
image: golang:1.13 | |||||
image: golang:1.12 | |||||
environment: | environment: | ||||
GO111MODULE: "on" | GO111MODULE: "on" | ||||
GOPROXY: "https://goproxy.cn" | GOPROXY: "https://goproxy.cn" | ||||
@@ -281,7 +91,7 @@ steps: | |||||
- name: test-postgres-schema | - name: test-postgres-schema | ||||
pull: default | pull: default | ||||
image: golang:1.13 | |||||
image: golang:1.12 | |||||
environment: | environment: | ||||
GO111MODULE: "on" | GO111MODULE: "on" | ||||
GOPROXY: "https://goproxy.cn" | GOPROXY: "https://goproxy.cn" | ||||
@@ -295,7 +105,7 @@ steps: | |||||
- name: test-mssql | - name: test-mssql | ||||
pull: default | pull: default | ||||
image: golang:1.13 | |||||
image: golang:1.12 | |||||
environment: | environment: | ||||
GO111MODULE: "on" | GO111MODULE: "on" | ||||
GOPROXY: "https://goproxy.cn" | GOPROXY: "https://goproxy.cn" | ||||
@@ -309,7 +119,7 @@ steps: | |||||
- name: test-tidb | - name: test-tidb | ||||
pull: default | pull: default | ||||
image: golang:1.13 | |||||
image: golang:1.12 | |||||
environment: | environment: | ||||
GO111MODULE: "on" | GO111MODULE: "on" | ||||
GOPROXY: "https://goproxy.cn" | GOPROXY: "https://goproxy.cn" | ||||
@@ -323,12 +133,12 @@ steps: | |||||
- name: merge_coverage | - name: merge_coverage | ||||
pull: default | pull: default | ||||
image: golang:1.13 | |||||
image: golang:1.12 | |||||
environment: | environment: | ||||
GO111MODULE: "on" | GO111MODULE: "on" | ||||
GOPROXY: "https://goproxy.cn" | GOPROXY: "https://goproxy.cn" | ||||
depends_on: | depends_on: | ||||
- build | |||||
- test-vet | |||||
- test-sqlite | - test-sqlite | ||||
- test-mysql | - test-mysql | ||||
- test-mysql-utf8mb4 | - test-mysql-utf8mb4 | ||||
@@ -901,7 +901,7 @@ func (db *postgres) TableCheckSql(tableName string) (string, []interface{}) { | |||||
} | } | ||||
func (db *postgres) ModifyColumnSql(tableName string, col *core.Column) string { | func (db *postgres) ModifyColumnSql(tableName string, col *core.Column) string { | ||||
if len(db.Schema) == 0 { | |||||
if len(db.Schema) == 0 || strings.Contains(tableName, ".") { | |||||
return fmt.Sprintf("alter table %s ALTER COLUMN %s TYPE %s", | return fmt.Sprintf("alter table %s ALTER COLUMN %s TYPE %s", | ||||
tableName, col.Name, db.SqlType(col)) | tableName, col.Name, db.SqlType(col)) | ||||
} | } | ||||
@@ -913,8 +913,8 @@ func (db *postgres) DropIndexSql(tableName string, index *core.Index) string { | |||||
quote := db.Quote | quote := db.Quote | ||||
idxName := index.Name | idxName := index.Name | ||||
tableName = strings.Replace(tableName, `"`, "", -1) | |||||
tableName = strings.Replace(tableName, `.`, "_", -1) | |||||
tableParts := strings.Split(strings.Replace(tableName, `"`, "", -1), ".") | |||||
tableName = tableParts[len(tableParts)-1] | |||||
if !strings.HasPrefix(idxName, "UQE_") && | if !strings.HasPrefix(idxName, "UQE_") && | ||||
!strings.HasPrefix(idxName, "IDX_") { | !strings.HasPrefix(idxName, "IDX_") { | ||||
@@ -729,66 +729,7 @@ func (session *Session) insertMapInterface(m map[string]interface{}) (int64, err | |||||
args = append(args, m[colName]) | args = append(args, m[colName]) | ||||
} | } | ||||
w := builder.NewWriter() | |||||
if session.statement.cond.IsValid() { | |||||
if _, err := w.WriteString(fmt.Sprintf("INSERT INTO %s (", session.engine.Quote(tableName))); err != nil { | |||||
return 0, err | |||||
} | |||||
if err := writeStrings(w, append(columns, exprs.colNames...), "`", "`"); err != nil { | |||||
return 0, err | |||||
} | |||||
if _, err := w.WriteString(") SELECT "); err != nil { | |||||
return 0, err | |||||
} | |||||
if err := session.statement.writeArgs(w, args); err != nil { | |||||
return 0, err | |||||
} | |||||
if len(exprs.args) > 0 { | |||||
if _, err := w.WriteString(","); err != nil { | |||||
return 0, err | |||||
} | |||||
if err := exprs.writeArgs(w); err != nil { | |||||
return 0, err | |||||
} | |||||
} | |||||
if _, err := w.WriteString(fmt.Sprintf(" FROM %s WHERE ", session.engine.Quote(tableName))); err != nil { | |||||
return 0, err | |||||
} | |||||
if err := session.statement.cond.WriteTo(w); err != nil { | |||||
return 0, err | |||||
} | |||||
} else { | |||||
qm := strings.Repeat("?,", len(columns)) | |||||
qm = qm[:len(qm)-1] | |||||
if _, err := w.WriteString(fmt.Sprintf("INSERT INTO %s (`%s`) VALUES (%s)", session.engine.Quote(tableName), strings.Join(columns, "`,`"), qm)); err != nil { | |||||
return 0, err | |||||
} | |||||
w.Append(args...) | |||||
} | |||||
sql := w.String() | |||||
args = w.Args() | |||||
if err := session.cacheInsert(tableName); err != nil { | |||||
return 0, err | |||||
} | |||||
res, err := session.exec(sql, args...) | |||||
if err != nil { | |||||
return 0, err | |||||
} | |||||
affected, err := res.RowsAffected() | |||||
if err != nil { | |||||
return 0, err | |||||
} | |||||
return affected, nil | |||||
return session.insertMap(columns, args) | |||||
} | } | ||||
func (session *Session) insertMapString(m map[string]string) (int64, error) { | func (session *Session) insertMapString(m map[string]string) (int64, error) { | ||||
@@ -808,6 +749,7 @@ func (session *Session) insertMapString(m map[string]string) (int64, error) { | |||||
columns = append(columns, k) | columns = append(columns, k) | ||||
} | } | ||||
} | } | ||||
sort.Strings(columns) | sort.Strings(columns) | ||||
var args = make([]interface{}, 0, len(m)) | var args = make([]interface{}, 0, len(m)) | ||||
@@ -815,7 +757,18 @@ func (session *Session) insertMapString(m map[string]string) (int64, error) { | |||||
args = append(args, m[colName]) | args = append(args, m[colName]) | ||||
} | } | ||||
return session.insertMap(columns, args) | |||||
} | |||||
func (session *Session) insertMap(columns []string, args []interface{}) (int64, error) { | |||||
tableName := session.statement.TableName() | |||||
if len(tableName) <= 0 { | |||||
return 0, ErrTableNotFound | |||||
} | |||||
exprs := session.statement.exprColumns | |||||
w := builder.NewWriter() | w := builder.NewWriter() | ||||
// if insert where | |||||
if session.statement.cond.IsValid() { | if session.statement.cond.IsValid() { | ||||
if _, err := w.WriteString(fmt.Sprintf("INSERT INTO %s (", session.engine.Quote(tableName))); err != nil { | if _, err := w.WriteString(fmt.Sprintf("INSERT INTO %s (", session.engine.Quote(tableName))); err != nil { | ||||
return 0, err | return 0, err | ||||
@@ -853,10 +806,29 @@ func (session *Session) insertMapString(m map[string]string) (int64, error) { | |||||
qm := strings.Repeat("?,", len(columns)) | qm := strings.Repeat("?,", len(columns)) | ||||
qm = qm[:len(qm)-1] | qm = qm[:len(qm)-1] | ||||
if _, err := w.WriteString(fmt.Sprintf("INSERT INTO %s (`%s`) VALUES (%s)", session.engine.Quote(tableName), strings.Join(columns, "`,`"), qm)); err != nil { | |||||
if _, err := w.WriteString(fmt.Sprintf("INSERT INTO %s (", session.engine.Quote(tableName))); err != nil { | |||||
return 0, err | return 0, err | ||||
} | } | ||||
if err := writeStrings(w, append(columns, exprs.colNames...), "`", "`"); err != nil { | |||||
return 0, err | |||||
} | |||||
if _, err := w.WriteString(fmt.Sprintf(") VALUES (%s", qm)); err != nil { | |||||
return 0, err | |||||
} | |||||
w.Append(args...) | w.Append(args...) | ||||
if len(exprs.args) > 0 { | |||||
if _, err := w.WriteString(","); err != nil { | |||||
return 0, err | |||||
} | |||||
if err := exprs.writeArgs(w); err != nil { | |||||
return 0, err | |||||
} | |||||
} | |||||
if _, err := w.WriteString(")"); err != nil { | |||||
return 0, err | |||||
} | |||||
} | } | ||||
sql := w.String() | sql := w.String() | ||||
@@ -239,14 +239,20 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 | |||||
for i, colName := range exprColumns.colNames { | for i, colName := range exprColumns.colNames { | ||||
switch tp := exprColumns.args[i].(type) { | switch tp := exprColumns.args[i].(type) { | ||||
case string: | case string: | ||||
colNames = append(colNames, session.engine.Quote(colName)+" = "+tp) | |||||
if len(tp) == 0 { | |||||
tp = "''" | |||||
} | |||||
colNames = append(colNames, session.engine.Quote(colName)+"="+tp) | |||||
case *builder.Builder: | case *builder.Builder: | ||||
subQuery, subArgs, err := builder.ToSQL(tp) | subQuery, subArgs, err := builder.ToSQL(tp) | ||||
if err != nil { | if err != nil { | ||||
return 0, err | return 0, err | ||||
} | } | ||||
colNames = append(colNames, session.engine.Quote(colName)+" = ("+subQuery+")") | |||||
colNames = append(colNames, session.engine.Quote(colName)+"=("+subQuery+")") | |||||
args = append(args, subArgs...) | args = append(args, subArgs...) | ||||
default: | |||||
colNames = append(colNames, session.engine.Quote(colName)+"=?") | |||||
args = append(args, exprColumns.args[i]) | |||||
} | } | ||||
} | } | ||||
@@ -69,10 +69,18 @@ func (exprs *exprParams) writeArgs(w *builder.BytesWriter) error { | |||||
if _, err := w.WriteString(")"); err != nil { | if _, err := w.WriteString(")"); err != nil { | ||||
return err | return err | ||||
} | } | ||||
default: | |||||
case string: | |||||
if arg == "" { | |||||
arg = "''" | |||||
} | |||||
if _, err := w.WriteString(fmt.Sprintf("%v", arg)); err != nil { | if _, err := w.WriteString(fmt.Sprintf("%v", arg)); err != nil { | ||||
return err | return err | ||||
} | } | ||||
default: | |||||
if _, err := w.WriteString("?"); err != nil { | |||||
return err | |||||
} | |||||
w.Append(arg) | |||||
} | } | ||||
if i != len(exprs.args)-1 { | if i != len(exprs.args)-1 { | ||||
if _, err := w.WriteString(","); err != nil { | if _, err := w.WriteString(","); err != nil { | ||||