Merge branch 'master' into master

This commit is contained in:
Lunny Xiao 2021-04-11 17:04:05 +08:00
commit b8bdb512f0
61 changed files with 2210 additions and 651 deletions

View File

@ -2,57 +2,288 @@
kind: pipeline
name: testing
steps:
- name: restore-cache
image: meltwater/drone-cache
pull: always
settings:
backend: "filesystem"
restore: true
cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}'
archive_format: "gzip"
filesystem_cache_root: "/go"
mount:
- pkg.mod
- pkg.build
volumes:
- name: cache
path: /go
- name: test-vet
image: golang:1.11 # The lowest golang requirement
image: golang:1.15
environment:
GO111MODULE: "on"
GOPROXY: "https://goproxy.cn"
GOPROXY: "https://goproxy.io"
CGO_ENABLED: 1
GOMODCACHE: '/drone/src/pkg.mod'
GOCACHE: '/drone/src/pkg.build'
commands:
- make vet
- make test
- make fmt-check
volumes:
- name: cache
path: /go
when:
event:
- push
- pull_request
- name: test-sqlite
image: golang:1.12
- name: rebuild-cache
image: meltwater/drone-cache
pull: true
settings:
backend: "filesystem"
rebuild: true
cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}'
archive_format: "gzip"
filesystem_cache_root: "/go"
mount:
- pkg.mod
- pkg.build
volumes:
- name: cache
path: /go
volumes:
- name: cache
temp: {}
---
kind: pipeline
name: test-sqlite
depends_on:
- testing
steps:
- name: restore-cache
image: meltwater/drone-cache:dev
pull: always
settings:
backend: "filesystem"
restore: true
cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}'
archive_format: "gzip"
filesystem_cache_root: "/go"
mount:
- pkg.mod
- pkg.build
volumes:
- name: cache
path: /go
- name: test-sqlite3
image: golang:1.15
environment:
GO111MODULE: "on"
GOPROXY: "https://goproxy.cn"
GOPROXY: "https://goproxy.io"
CGO_ENABLED: 1
GOMODCACHE: '/drone/src/pkg.mod'
GOCACHE: '/drone/src/pkg.build'
commands:
- make test-sqlite3
- TEST_CACHE_ENABLE=true make test-sqlite3
- TEST_QUOTE_POLICY=reserved make test-sqlite3
volumes:
- name: cache
path: /go
- name: test-sqlite
image: golang:1.15
environment:
GO111MODULE: "on"
GOPROXY: "https://goproxy.io"
CGO_ENABLED: 1
GOMODCACHE: '/drone/src/pkg.mod'
GOCACHE: '/drone/src/pkg.build'
commands:
- make test-sqlite
- TEST_CACHE_ENABLE=true make test-sqlite
- TEST_QUOTE_POLICY=reserved make test-sqlite
when:
event:
- push
- pull_request
volumes:
- name: cache
path: /go
- name: rebuild-cache
image: meltwater/drone-cache:dev
pull: true
settings:
backend: "filesystem"
rebuild: true
cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}'
archive_format: "gzip"
filesystem_cache_root: "/go"
mount:
- pkg.mod
- pkg.build
volumes:
- name: cache
path: /go
volumes:
- name: cache
temp: {}
---
kind: pipeline
name: test-mysql
depends_on:
- testing
steps:
- name: restore-cache
image: meltwater/drone-cache
pull: always
settings:
backend: "filesystem"
restore: true
cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}'
archive_format: "gzip"
filesystem_cache_root: "/go"
mount:
- pkg.mod
- pkg.build
volumes:
- name: cache
path: /go
- name: test-mysql
image: golang:1.12
image: golang:1.15
environment:
GO111MODULE: "on"
GOPROXY: "https://goproxy.cn"
GOPROXY: "https://goproxy.io"
CGO_ENABLED: 1
GOMODCACHE: '/drone/src/pkg.mod'
GOCACHE: '/drone/src/pkg.build'
TEST_MYSQL_HOST: mysql
TEST_MYSQL_CHARSET: utf8
TEST_MYSQL_DBNAME: xorm_test
TEST_MYSQL_USERNAME: root
TEST_MYSQL_PASSWORD:
commands:
- make test
- make test-mysql
- TEST_CACHE_ENABLE=true make test-mysql
- TEST_QUOTE_POLICY=reserved make test-mysql
volumes:
- name: cache
path: /go
- name: test-mysql-utf8mb4
image: golang:1.15
depends_on:
- test-mysql
environment:
GO111MODULE: "on"
GOPROXY: "https://goproxy.io"
CGO_ENABLED: 1
GOMODCACHE: '/drone/src/pkg.mod'
GOCACHE: '/drone/src/pkg.build'
TEST_MYSQL_HOST: mysql
TEST_MYSQL_CHARSET: utf8mb4
TEST_MYSQL_DBNAME: xorm_test
TEST_MYSQL_USERNAME: root
TEST_MYSQL_PASSWORD:
commands:
- make test-mysql
- TEST_CACHE_ENABLE=true make test-mysql
- TEST_QUOTE_POLICY=reserved make test-mysql
when:
event:
- push
- pull_request
volumes:
- name: cache
path: /go
- name: test-mysql8
image: golang:1.12
- name: test-mymysql
pull: default
image: golang:1.15
depends_on:
- test-mysql-utf8mb4
environment:
GO111MODULE: "on"
GOPROXY: "https://goproxy.cn"
GOPROXY: "https://goproxy.io"
CGO_ENABLED: 1
GOMODCACHE: '/drone/src/pkg.mod'
GOCACHE: '/drone/src/pkg.build'
TEST_MYSQL_HOST: mysql:3306
TEST_MYSQL_DBNAME: xorm_test
TEST_MYSQL_USERNAME: root
TEST_MYSQL_PASSWORD:
commands:
- make test-mymysql
- TEST_CACHE_ENABLE=true make test-mymysql
- TEST_QUOTE_POLICY=reserved make test-mymysql
volumes:
- name: cache
path: /go
- name: rebuild-cache
image: meltwater/drone-cache
depends_on:
- test-mysql
- test-mysql-utf8mb4
- test-mymysql
pull: true
settings:
backend: "filesystem"
rebuild: true
cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}'
archive_format: "gzip"
filesystem_cache_root: "/go"
mount:
- pkg.mod
- pkg.build
volumes:
- name: cache
path: /go
volumes:
- name: cache
temp: {}
services:
- name: mysql
pull: default
image: mysql:5.7
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: xorm_test
---
kind: pipeline
name: test-mysql8
depends_on:
- test-mysql
- test-sqlite
steps:
- name: restore-cache
image: meltwater/drone-cache
pull: always
settings:
backend: "filesystem"
restore: true
cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}'
archive_format: "gzip"
filesystem_cache_root: "/go"
mount:
- pkg.mod
- pkg.build
volumes:
- name: cache
path: /go
- name: test-mysql8
image: golang:1.15
environment:
GO111MODULE: "on"
GOPROXY: "https://goproxy.io"
CGO_ENABLED: 1
GOMODCACHE: '/drone/src/pkg.mod'
GOCACHE: '/drone/src/pkg.build'
TEST_MYSQL_HOST: mysql8
TEST_MYSQL_CHARSET: utf8mb4
TEST_MYSQL_DBNAME: xorm_test
@ -62,19 +293,71 @@ steps:
- make test-mysql
- TEST_CACHE_ENABLE=true make test-mysql
- TEST_QUOTE_POLICY=reserved make test-mysql
when:
event:
- push
- pull_request
volumes:
- name: cache
path: /go
- name: test-mysql-utf8mb4
image: golang:1.12
- name: rebuild-cache
image: meltwater/drone-cache:dev
pull: true
depends_on:
- test-mysql
- test-mysql8
settings:
backend: "filesystem"
rebuild: true
cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}'
archive_format: "gzip"
filesystem_cache_root: "/go"
mount:
- pkg.mod
- pkg.build
volumes:
- name: cache
path: /go
volumes:
- name: cache
temp: {}
services:
- name: mysql8
pull: default
image: mysql:8.0
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: xorm_test
---
kind: pipeline
name: test-mariadb
depends_on:
- test-mysql8
steps:
- name: restore-cache
image: meltwater/drone-cache
pull: always
settings:
backend: "filesystem"
restore: true
cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}'
archive_format: "gzip"
filesystem_cache_root: "/go"
mount:
- pkg.mod
- pkg.build
volumes:
- name: cache
path: /go
- name: test-mariadb
image: golang:1.15
environment:
GO111MODULE: "on"
GOPROXY: "https://goproxy.cn"
TEST_MYSQL_HOST: mysql
GOPROXY: "https://goproxy.io"
CGO_ENABLED: 1
GOMODCACHE: '/drone/src/pkg.mod'
GOCACHE: '/drone/src/pkg.build'
TEST_MYSQL_HOST: mariadb
TEST_MYSQL_CHARSET: utf8mb4
TEST_MYSQL_DBNAME: xorm_test
TEST_MYSQL_USERNAME: root
@ -83,38 +366,71 @@ steps:
- make test-mysql
- TEST_CACHE_ENABLE=true make test-mysql
- TEST_QUOTE_POLICY=reserved make test-mysql
when:
event:
- push
- pull_request
volumes:
- name: cache
path: /go
- name: test-mymysql
pull: default
image: golang:1.12
- name: rebuild-cache
image: meltwater/drone-cache:dev
depends_on:
- test-mysql-utf8mb4
- test-mariadb
pull: true
settings:
backend: "filesystem"
rebuild: true
cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}'
archive_format: "gzip"
filesystem_cache_root: "/go"
mount:
- pkg.mod
- pkg.build
volumes:
- name: cache
path: /go
volumes:
- name: cache
temp: {}
services:
- name: mariadb
pull: default
image: mariadb:10.4
environment:
GO111MODULE: "on"
GOPROXY: "https://goproxy.cn"
TEST_MYSQL_HOST: mysql:3306
TEST_MYSQL_DBNAME: xorm_test
TEST_MYSQL_USERNAME: root
TEST_MYSQL_PASSWORD:
commands:
- make test-mymysql
- TEST_CACHE_ENABLE=true make test-mymysql
- TEST_QUOTE_POLICY=reserved make test-mymysql
when:
event:
- push
- pull_request
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: xorm_test
---
kind: pipeline
name: test-postgres
depends_on:
- test-mariadb
steps:
- name: restore-cache
image: meltwater/drone-cache
pull: always
settings:
backend: "filesystem"
restore: true
cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}'
archive_format: "gzip"
filesystem_cache_root: "/go"
mount:
- pkg.mod
- pkg.build
volumes:
- name: cache
path: /go
- name: test-postgres
pull: default
image: golang:1.12
image: golang:1.15
environment:
GO111MODULE: "on"
GOPROXY: "https://goproxy.cn"
GOPROXY: "https://goproxy.io"
CGO_ENABLED: 1
GOMODCACHE: '/drone/src/pkg.mod'
GOCACHE: '/drone/src/pkg.build'
TEST_PGSQL_HOST: pgsql
TEST_PGSQL_DBNAME: xorm_test
TEST_PGSQL_USERNAME: postgres
@ -123,19 +439,21 @@ steps:
- make test-postgres
- TEST_CACHE_ENABLE=true make test-postgres
- TEST_QUOTE_POLICY=reserved make test-postgres
when:
event:
- push
- pull_request
volumes:
- name: cache
path: /go
- name: test-postgres-schema
pull: default
image: golang:1.12
image: golang:1.15
depends_on:
- test-postgres
environment:
GO111MODULE: "on"
GOPROXY: "https://goproxy.cn"
GOPROXY: "https://goproxy.io"
CGO_ENABLED: 1
GOMODCACHE: '/drone/src/pkg.mod'
GOCACHE: '/drone/src/pkg.build'
TEST_PGSQL_HOST: pgsql
TEST_PGSQL_SCHEMA: xorm
TEST_PGSQL_DBNAME: xorm_test
@ -145,17 +463,72 @@ steps:
- make test-postgres
- TEST_CACHE_ENABLE=true make test-postgres
- TEST_QUOTE_POLICY=reserved make test-postgres
when:
event:
- push
- pull_request
volumes:
- name: cache
path: /go
- name: rebuild-cache
image: meltwater/drone-cache:dev
pull: true
depends_on:
- test-postgres-schema
settings:
backend: "filesystem"
rebuild: true
cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}'
archive_format: "gzip"
filesystem_cache_root: "/go"
mount:
- pkg.mod
- pkg.build
volumes:
- name: cache
path: /go
volumes:
- name: cache
temp: {}
services:
- name: pgsql
pull: default
image: postgres:9.5
environment:
POSTGRES_DB: xorm_test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
---
kind: pipeline
name: test-mssql
depends_on:
- test-postgres
steps:
- name: restore-cache
image: meltwater/drone-cache
pull: always
settings:
backend: "filesystem"
restore: true
cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}'
archive_format: "gzip"
filesystem_cache_root: "/go"
mount:
- pkg.mod
- pkg.build
volumes:
- name: cache
path: /go
- name: test-mssql
pull: default
image: golang:1.12
image: golang:1.15
environment:
GO111MODULE: "on"
GOPROXY: "https://goproxy.cn"
GOPROXY: "https://goproxy.io"
CGO_ENABLED: 1
GOMODCACHE: '/drone/src/pkg.mod'
GOCACHE: '/drone/src/pkg.build'
TEST_MSSQL_HOST: mssql
TEST_MSSQL_DBNAME: xorm_test
TEST_MSSQL_USERNAME: sa
@ -164,17 +537,71 @@ steps:
- make test-mssql
- TEST_CACHE_ENABLE=true make test-mssql
- TEST_QUOTE_POLICY=reserved make test-mssql
when:
event:
- push
- pull_request
- TEST_MSSQL_DEFAULT_VARCHAR=NVARCHAR TEST_MSSQL_DEFAULT_CHAR=NCHAR make test-mssql
volumes:
- name: cache
path: /go
- name: rebuild-cache
image: meltwater/drone-cache:dev
pull: true
settings:
backend: "filesystem"
rebuild: true
cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}'
archive_format: "gzip"
filesystem_cache_root: "/go"
mount:
- pkg.mod
- pkg.build
volumes:
- name: cache
path: /go
volumes:
- name: cache
temp: {}
services:
- name: mssql
pull: default
image: microsoft/mssql-server-linux:latest
environment:
ACCEPT_EULA: Y
SA_PASSWORD: yourStrong(!)Password
MSSQL_PID: Developer
---
kind: pipeline
name: test-tidb
depends_on:
- test-mssql
steps:
- name: restore-cache
image: meltwater/drone-cache
pull: always
settings:
backend: "filesystem"
restore: true
cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}'
archive_format: "gzip"
filesystem_cache_root: "/go"
mount:
- pkg.mod
- pkg.build
volumes:
- name: cache
path: /go
- name: test-tidb
pull: default
image: golang:1.12
image: golang:1.15
environment:
GO111MODULE: "on"
GOPROXY: "https://goproxy.cn"
GOPROXY: "https://goproxy.io"
CGO_ENABLED: 1
GOMODCACHE: '/drone/src/pkg.mod'
GOCACHE: '/drone/src/pkg.build'
TEST_TIDB_HOST: "tidb:4000"
TEST_TIDB_DBNAME: xorm_test
TEST_TIDB_USERNAME: root
@ -183,17 +610,66 @@ steps:
- make test-tidb
- TEST_CACHE_ENABLE=true make test-tidb
- TEST_QUOTE_POLICY=reserved make test-tidb
when:
event:
- push
- pull_request
volumes:
- name: cache
path: /go
- name: rebuild-cache
image: meltwater/drone-cache:dev
pull: true
settings:
backend: "filesystem"
rebuild: true
cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}'
archive_format: "gzip"
filesystem_cache_root: "/go"
mount:
- pkg.mod
- pkg.build
volumes:
- name: cache
path: /go
volumes:
- name: cache
temp: {}
services:
- name: tidb
pull: default
image: pingcap/tidb:v3.0.3
---
kind: pipeline
name: test-cockroach
depends_on:
- test-tidb
steps:
- name: restore-cache
image: meltwater/drone-cache
pull: always
settings:
backend: "filesystem"
restore: true
cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}'
archive_format: "gzip"
filesystem_cache_root: "/go"
mount:
- pkg.mod
- pkg.build
volumes:
- name: cache
path: /go
- name: test-cockroach
pull: default
image: golang:1.13
image: golang:1.15
environment:
GO111MODULE: "on"
GOPROXY: "https://goproxy.cn"
GOPROXY: "https://goproxy.io"
CGO_ENABLED: 1
GOMODCACHE: '/drone/src/pkg.mod'
GOCACHE: '/drone/src/pkg.build'
TEST_COCKROACH_HOST: "cockroach:26257"
TEST_COCKROACH_DBNAME: xorm_test
TEST_COCKROACH_USERNAME: root
@ -202,103 +678,62 @@ steps:
- sleep 10
- make test-cockroach
- TEST_CACHE_ENABLE=true make test-cockroach
when:
event:
- push
- pull_request
volumes:
- name: cache
path: /go
- name: merge_coverage
pull: default
image: golang:1.12
environment:
GO111MODULE: "on"
GOPROXY: "https://goproxy.cn"
depends_on:
- test-vet
- test-sqlite
- test-mysql
- test-mysql8
- test-mymysql
- test-postgres
- test-postgres-schema
- test-mssql
- test-tidb
- test-cockroach
commands:
- make coverage
when:
event:
- push
- pull_request
- name: rebuild-cache
image: meltwater/drone-cache:dev
pull: true
settings:
backend: "filesystem"
rebuild: true
cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}'
archive_format: "gzip"
filesystem_cache_root: "/go"
mount:
- pkg.mod
- pkg.build
volumes:
- name: cache
path: /go
volumes:
- name: cache
temp: {}
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: mysql8
pull: default
image: mysql:8.0
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
POSTGRES_PASSWORD: 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
- name: cockroach
pull: default
image: cockroachdb/cockroach:v19.2.4
commands:
- /cockroach/cockroach start --insecure
---
kind: pipeline
name: merge_coverage
depends_on:
- testing
- test-sqlite
- test-mysql
- test-mysql8
- test-mariadb
- test-postgres
- test-mssql
- test-tidb
- test-cockroach
steps:
- name: merge_coverage
pull: default
image: golang:1.15
environment:
GO111MODULE: "on"
GOPROXY: "https://goproxy.io"
commands:
- make coverage
when:
branch:
- master
event:
- push
- tag
- pull_request

1
.gitignore vendored
View File

@ -36,3 +36,4 @@ test.db.sql
*coverage.out
test.db
integrations/*.sql
integrations/test_sqlite*

View File

@ -3,6 +3,68 @@
This changelog goes through all the changes that have been made in each release
without substantial changes to our git log.
## [1.0.7](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1336) - 2021-01-21
* BUGFIXES
* Fix bug for mssql (#1854)
* MISC
* fix_bugs_for_mssql (#1852)
## [1.0.6](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1308) - 2021-01-05
* BUGFIXES
* Fix bug when modify column on mssql (#1849)
* Fix find and count bug with cols (#1826)
* Fix update bug (#1823)
* Fix json tag with other type (#1822)
* ENHANCEMENTS
* prevent panic when struct with unexport field (#1839)
* Automatically convert datetime to int64 (#1715)
* MISC
* Fix index (#1841)
* Performance improvement for columnsbyName (#1788)
## [1.0.5](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1299) - 2020-09-08
* BUGFIXES
* Fix bug of ToDB when update on a nil pointer (#1786)
* Fix warnings with schema Sync2 with default varchar as NVARCHAR (#1783)
* Do not ever quote asterisk symbol. Fixes #1780 (#1781)
* Fix bug on get columns for postgres (#1779)
## [1.0.4](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1286) - 2020-09-02
* FEATURES
* Add params for mssql to allow redefine varchar as nvarchar or char as nchar (#1741)
* BUGFIXES
* Fix mysql dialect error from invalid db identifier in orderby clause (#1743) (#1751)
* ENHANCEMENTS
* Support get dataSourceName on ContextHook for monitor which DB executed SQL (#1740)
* MISC
* Correct default detection in MariaDB >= 10.2.7 (#1778)
## [1.0.3](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1281) - 2020-07-10
* BUGFIXES
* Fix dump of sqlite (#1639)
* ENHANCEMENTS
* Fix index name parsing in SQLite dialect (#1737)
* add hooks for Commit and Rollback (#1733)
## [1.0.2](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1261) - 2020-06-16
* FEATURES
* Add Hook (#1644)
* BUGFIXES
* Fix bug when ID used but no reference table given (#1709)
* Fix find and count bug (#1651)
* ENHANCEMENTS
* chore: improve snakeCasedName performance (#1688)
* Fix find with another struct (#1666)
* fix GetColumns missing ordinal position (#1660)
* MISC
* chore: improve titleCasedName performance (#1691)
## [1.0.1](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1253) - 2020-03-25
* BUGFIXES

View File

@ -6,7 +6,9 @@ GOFMT ?= gofmt -s
TAGS ?=
SED_INPLACE := sed -i
GOFILES := $(shell find . -name "*.go" -type f)
GO_DIRS := caches contexts integrations convert core dialects internal log migrate names schemas tags
GOFILES := $(wildcard *.go)
GOFILES += $(shell find $(GO_DIRS) -name "*.go" -type f)
INTEGRATION_PACKAGES := xorm.io/xorm/integrations
PACKAGES ?= $(filter-out $(INTEGRATION_PACKAGES),$(shell $(GO) list ./...))
@ -20,6 +22,9 @@ TEST_MSSQL_HOST ?= mssql:1433
TEST_MSSQL_DBNAME ?= gitea
TEST_MSSQL_USERNAME ?= sa
TEST_MSSQL_PASSWORD ?= MwantsaSecurePassword1
TEST_MSSQL_DEFAULT_VARCHAR ?= varchar
TEST_MSSQL_DEFAULT_CHAR ?= char
TEST_MSSQL_DO_NVARCHAR_OVERRIDE_TEST ?= true
TEST_MYSQL_HOST ?= mysql:3306
TEST_MYSQL_CHARSET ?= utf8
@ -96,7 +101,8 @@ help:
@echo " - test-mysql run integration tests for mysql"
@echo " - test-mssql run integration tests for mssql"
@echo " - test-postgres run integration tests for postgres"
@echo " - test-sqlite run integration tests for sqlite"
@echo " - test-sqlite3 run integration tests for sqlite"
@echo " - test-sqlite run integration tests for pure go sqlite"
@echo " - test-tidb run integration tests for tidb"
@echo " - vet examines Go source code and reports suspicious constructs"
@ -144,12 +150,16 @@ test-cockroach\#%: go-check
test-mssql: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -db=mssql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \
-conn_str="server=$(TEST_MSSQL_HOST);user id=$(TEST_MSSQL_USERNAME);password=$(TEST_MSSQL_PASSWORD);database=$(TEST_MSSQL_DBNAME)" \
-default_varchar=$(TEST_MSSQL_DEFAULT_VARCHAR) -default_char=$(TEST_MSSQL_DEFAULT_CHAR) \
-do_nvarchar_override_test=$(TEST_MSSQL_DO_NVARCHAR_OVERRIDE_TEST) \
-coverprofile=mssql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic
.PNONY: test-mssql\#%
test-mssql\#%: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -db=mssql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \
-conn_str="server=$(TEST_MSSQL_HOST);user id=$(TEST_MSSQL_USERNAME);password=$(TEST_MSSQL_PASSWORD);database=$(TEST_MSSQL_DBNAME)" \
-default_varchar=$(TEST_MSSQL_DEFAULT_VARCHAR) -default_char=$(TEST_MSSQL_DEFAULT_CHAR) \
-do_nvarchar_override_test=$(TEST_MSSQL_DO_NVARCHAR_OVERRIDE_TEST) \
-coverprofile=mssql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic
.PNONY: test-mymysql
@ -188,21 +198,37 @@ test-postgres\#%: go-check
-conn_str="postgres://$(TEST_PGSQL_USERNAME):$(TEST_PGSQL_PASSWORD)@$(TEST_PGSQL_HOST)/$(TEST_PGSQL_DBNAME)?sslmode=disable" \
-quote=$(TEST_QUOTE_POLICY) -coverprofile=postgres.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic
.PHONY: test-sqlite3
test-sqlite3: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \
-quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite3.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic
.PHONY: test-sqlite3-schema
test-sqlite3-schema: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -schema=xorm -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \
-quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite3.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic
.PHONY: test-sqlite3\#%
test-sqlite3\#%: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \
-quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite3.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic
.PHONY: test-sqlite
test-sqlite: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \
$(GO) test $(INTEGRATION_PACKAGES) -v -race -cache=$(TEST_CACHE_ENABLE) -db=sqlite -conn_str="./test.db?cache=shared&mode=rwc" \
-quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic
.PHONY: test-sqlite-schema
test-sqlite-schema: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -schema=xorm -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \
$(GO) test $(INTEGRATION_PACKAGES) -v -race -schema=xorm -cache=$(TEST_CACHE_ENABLE) -db=sqlite -conn_str="./test.db?cache=shared&mode=rwc" \
-quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic
.PHONY: test-sqlite\#%
test-sqlite\#%: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \
$(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -cache=$(TEST_CACHE_ENABLE) -db=sqlite -conn_str="./test.db?cache=shared&mode=rwc" \
-quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic
.PNONY: test-tidb
test-tidb: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -db=mysql -cache=$(TEST_CACHE_ENABLE) -ignore_select_update=true \

View File

@ -4,9 +4,7 @@
Xorm is a simple and powerful ORM for Go.
[![Build Status](https://drone.gitea.com/api/badges/xorm/xorm/status.svg)](https://drone.gitea.com/xorm/xorm) [![](http://gocover.io/_badge/xorm.io/xorm)](https://gocover.io/xorm.io/xorm)
[![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm)
[![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3)
[![Build Status](https://drone.gitea.com/api/badges/xorm/xorm/status.svg)](https://drone.gitea.com/xorm/xorm) [![](http://gocover.io/_badge/xorm.io/xorm)](https://gocover.io/xorm.io/xorm) [![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm) [![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3)
## Notice
@ -315,7 +313,7 @@ err := engine.Where(builder.NotIn("a", 1, 2).And(builder.In("b", "c", "d", "e"))
// SELECT id, name ... FROM user WHERE a NOT IN (?, ?) AND b IN (?, ?, ?)
```
* Multiple operations in one go routine, no transation here but resue session memory
* Multiple operations in one go routine, no transaction here but resue session memory
```Go
session := engine.NewSession()
@ -338,7 +336,7 @@ if _, err := session.Exec("delete from userinfo where username = ?", user2.Usern
return nil
```
* Transation should be on one go routine. There is transaction and resue session memory
* Transaction should be on one go routine. There is transaction and resue session memory
```Go
session := engine.NewSession()

View File

@ -4,9 +4,7 @@
xorm 是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。
[![Build Status](https://drone.gitea.com/api/badges/xorm/xorm/status.svg)](https://drone.gitea.com/xorm/xorm) [![](http://gocover.io/_badge/xorm.io/xorm)](https://gocover.io/xorm.io/xorm)
[![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm)
[![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3)
[![Build Status](https://drone.gitea.com/api/badges/xorm/xorm/status.svg)](https://drone.gitea.com/xorm/xorm) [![](http://gocover.io/_badge/xorm.io/xorm)](https://gocover.io/xorm.io/xorm) [![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm) [![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3)
## Notice

View File

@ -19,6 +19,7 @@ type LevelDBStore struct {
var _ CacheStore = &LevelDBStore{}
// NewLevelDBStore creates a leveldb store
func NewLevelDBStore(dbfile string) (*LevelDBStore, error) {
db := &LevelDBStore{}
h, err := leveldb.OpenFile(dbfile, nil)
@ -29,6 +30,7 @@ func NewLevelDBStore(dbfile string) (*LevelDBStore, error) {
return db, nil
}
// Put implements CacheStore
func (s *LevelDBStore) Put(key string, value interface{}) error {
val, err := Encode(value)
if err != nil {
@ -50,6 +52,7 @@ func (s *LevelDBStore) Put(key string, value interface{}) error {
return err
}
// Get implements CacheStore
func (s *LevelDBStore) Get(key string) (interface{}, error) {
data, err := s.store.Get([]byte(key), nil)
if err != nil {
@ -75,6 +78,7 @@ func (s *LevelDBStore) Get(key string) (interface{}, error) {
return s.v, err
}
// Del implements CacheStore
func (s *LevelDBStore) Del(key string) error {
err := s.store.Delete([]byte(key), nil)
if err != nil {
@ -89,6 +93,7 @@ func (s *LevelDBStore) Del(key string) error {
return err
}
// Close implements CacheStore
func (s *LevelDBStore) Close() {
s.store.Close()
}

View File

@ -6,6 +6,7 @@ package caches
import "sync"
// Manager represents a cache manager
type Manager struct {
cacher Cacher
disableGlobalCache bool
@ -14,6 +15,7 @@ type Manager struct {
cacherLock sync.RWMutex
}
// NewManager creates a cache manager
func NewManager() *Manager {
return &Manager{
cachers: make(map[string]Cacher),
@ -27,12 +29,14 @@ func (mgr *Manager) SetDisableGlobalCache(disable bool) {
}
}
// SetCacher set cacher of table
func (mgr *Manager) SetCacher(tableName string, cacher Cacher) {
mgr.cacherLock.Lock()
mgr.cachers[tableName] = cacher
mgr.cacherLock.Unlock()
}
// GetCacher returns a cache of a table
func (mgr *Manager) GetCacher(tableName string) Cacher {
var cacher Cacher
var ok bool

View File

@ -31,6 +31,7 @@ func NewContextHook(ctx context.Context, sql string, args []interface{}) *Contex
}
}
// End finish the hook invokation
func (c *ContextHook) End(ctx context.Context, result sql.Result, err error) {
c.Ctx = ctx
c.Result = result
@ -38,19 +39,23 @@ func (c *ContextHook) End(ctx context.Context, result sql.Result, err error) {
c.ExecuteTime = time.Now().Sub(c.start)
}
// Hook represents a hook behaviour
type Hook interface {
BeforeProcess(c *ContextHook) (context.Context, error)
AfterProcess(c *ContextHook) error
}
// Hooks implements Hook interface but contains multiple Hook
type Hooks struct {
hooks []Hook
}
// AddHook adds a Hook
func (h *Hooks) AddHook(hooks ...Hook) {
h.hooks = append(h.hooks, hooks...)
}
// BeforeProcess invoked before execute the process
func (h *Hooks) BeforeProcess(c *ContextHook) (context.Context, error) {
ctx := c.Ctx
for _, h := range h.hooks {
@ -63,6 +68,7 @@ func (h *Hooks) BeforeProcess(c *ContextHook) (context.Context, error) {
return ctx, nil
}
// AfterProcess invoked after exetue the process
func (h *Hooks) AfterProcess(c *ContextHook) error {
firstErr := c.Err
for _, h := range h.hooks {

View File

@ -15,6 +15,7 @@ import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/mattn/go-sqlite3"
_ "modernc.org/sqlite"
)
var (
@ -27,7 +28,7 @@ func TestMain(m *testing.M) {
flag.Parse()
switch *dbtype {
case "sqlite3":
case "sqlite3", "sqlite":
createTableSql = "CREATE TABLE IF NOT EXISTS `user` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NULL, " +
"`title` TEXT NULL, `age` FLOAT NULL, `alias` TEXT NULL, `nick_name` TEXT NULL, `created` datetime);"
case "mysql":
@ -45,8 +46,11 @@ func TestMain(m *testing.M) {
func testOpen() (*DB, error) {
switch *dbtype {
case "sqlite3":
os.Remove("./test.db")
os.Remove("./test_sqlite3.db")
return Open("sqlite3", "./test.db")
case "sqlite":
os.Remove("./test_sqlite.db")
return Open("sqlite", "./test.db")
case "mysql":
return Open("mysql", *dbConn)
default:

View File

@ -19,6 +19,7 @@ var (
type Tx struct {
*sql.Tx
db *DB
ctx context.Context
}
func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
@ -32,13 +33,41 @@ func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
if err := db.afterProcess(hookCtx); err != nil {
return nil, err
}
return &Tx{tx, db}, nil
return &Tx{tx, db, ctx}, nil
}
func (db *DB) Begin() (*Tx, error) {
return db.BeginTx(context.Background(), nil)
}
func (tx *Tx) Commit() error {
hookCtx := contexts.NewContextHook(tx.ctx, "COMMIT", nil)
ctx, err := tx.db.beforeProcess(hookCtx)
if err != nil {
return err
}
err = tx.Tx.Commit()
hookCtx.End(ctx, nil, err)
if err := tx.db.afterProcess(hookCtx); err != nil {
return err
}
return nil
}
func (tx *Tx) Rollback() error {
hookCtx := contexts.NewContextHook(tx.ctx, "ROLLBACK", nil)
ctx, err := tx.db.beforeProcess(hookCtx)
if err != nil {
return err
}
err = tx.Tx.Rollback()
hookCtx.End(ctx, nil, err)
if err := tx.db.afterProcess(hookCtx); err != nil {
return err
}
return nil
}
func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error) {
names := make(map[string]int)
var i int

View File

@ -79,32 +79,34 @@ type Base struct {
quoter schemas.Quoter
}
func (b *Base) Quoter() schemas.Quoter {
return b.quoter
// Quoter returns the current database Quoter
func (db *Base) Quoter() schemas.Quoter {
return db.quoter
}
func (b *Base) Init(dialect Dialect, uri *URI) error {
b.dialect, b.uri = dialect, uri
// Init initialize the dialect
func (db *Base) Init(dialect Dialect, uri *URI) error {
db.dialect, db.uri = dialect, uri
return nil
}
func (b *Base) URI() *URI {
return b.uri
// URI returns the uri of database
func (db *Base) URI() *URI {
return db.uri
}
func (b *Base) DBType() schemas.DBType {
return b.uri.DBType
}
func (b *Base) FormatBytes(bs []byte) string {
// FormatBytes formats bytes
func (db *Base) FormatBytes(bs []byte) string {
return fmt.Sprintf("0x%x", bs)
}
// DropTableSQL returns drop table SQL
func (db *Base) DropTableSQL(tableName string) (string, bool) {
quote := db.dialect.Quoter().Quote
return fmt.Sprintf("DROP TABLE IF EXISTS %s", quote(tableName)), true
}
// HasRecords returns true if the SQL has records returned
func (db *Base) HasRecords(queryer core.Queryer, ctx context.Context, query string, args ...interface{}) (bool, error) {
rows, err := queryer.QueryContext(ctx, query, args...)
if err != nil {
@ -118,6 +120,7 @@ func (db *Base) HasRecords(queryer core.Queryer, ctx context.Context, query stri
return false, nil
}
// IsColumnExist returns true if the column of the table exist
func (db *Base) IsColumnExist(queryer core.Queryer, ctx context.Context, tableName, colName string) (bool, error) {
quote := db.dialect.Quoter().Quote
query := fmt.Sprintf(
@ -132,11 +135,13 @@ func (db *Base) IsColumnExist(queryer core.Queryer, ctx context.Context, tableNa
return db.HasRecords(queryer, ctx, query, db.uri.DBName, tableName, colName)
}
// AddColumnSQL returns a SQL to add a column
func (db *Base) AddColumnSQL(tableName string, col *schemas.Column) string {
s, _ := ColumnString(db.dialect, col, true)
return fmt.Sprintf("ALTER TABLE %v ADD %v", db.dialect.Quoter().Quote(tableName), s)
}
// CreateIndexSQL returns a SQL to create index
func (db *Base) CreateIndexSQL(tableName string, index *schemas.Index) string {
quoter := db.dialect.Quoter()
var unique string
@ -150,6 +155,7 @@ func (db *Base) CreateIndexSQL(tableName string, index *schemas.Index) string {
quoter.Join(index.Cols, ","))
}
// DropIndexSQL returns a SQL to drop index
func (db *Base) DropIndexSQL(tableName string, index *schemas.Index) string {
quote := db.dialect.Quoter().Quote
var name string
@ -161,16 +167,19 @@ func (db *Base) DropIndexSQL(tableName string, index *schemas.Index) string {
return fmt.Sprintf("DROP INDEX %v ON %s", quote(name), quote(tableName))
}
// ModifyColumnSQL returns a SQL to modify SQL
func (db *Base) ModifyColumnSQL(tableName string, col *schemas.Column) string {
s, _ := ColumnString(db.dialect, col, false)
return fmt.Sprintf("alter table %s MODIFY COLUMN %s", tableName, s)
return fmt.Sprintf("ALTER TABLE %s MODIFY COLUMN %s", tableName, s)
}
func (b *Base) ForUpdateSQL(query string) string {
// ForUpdateSQL returns for updateSQL
func (db *Base) ForUpdateSQL(query string) string {
return query + " FOR UPDATE"
}
func (b *Base) SetParams(params map[string]string) {
// SetParams set params
func (db *Base) SetParams(params map[string]string) {
}
var (
@ -206,6 +215,7 @@ func regDrvsNDialects() bool {
"postgres": {"postgres", func() Driver { return &pqDriver{} }, func() Dialect { return &postgres{} }},
"pgx": {"postgres", func() Driver { return &pqDriverPgx{} }, func() Dialect { return &postgres{} }},
"sqlite3": {"sqlite3", func() Driver { return &sqlite3Driver{} }, func() Dialect { return &sqlite3{} }},
"sqlite": {"sqlite3", func() Driver { return &sqlite3Driver{} }, func() Dialect { return &sqlite3{} }},
"oci8": {"oracle", func() Driver { return &oci8Driver{} }, func() Dialect { return &oracle{} }},
"goracle": {"oracle", func() Driver { return &goracleDriver{} }, func() Dialect { return &oracle{} }},
}

View File

@ -214,13 +214,45 @@ var (
type mssql struct {
Base
defaultVarchar string
defaultChar string
}
func (db *mssql) Init(uri *URI) error {
db.quoter = mssqlQuoter
db.defaultChar = "CHAR"
db.defaultVarchar = "VARCHAR"
return db.Base.Init(db, uri)
}
func (db *mssql) SetParams(params map[string]string) {
defaultVarchar, ok := params["DEFAULT_VARCHAR"]
if ok {
var t = strings.ToUpper(defaultVarchar)
switch t {
case "NVARCHAR", "VARCHAR":
db.defaultVarchar = t
default:
db.defaultVarchar = "VARCHAR"
}
} else {
db.defaultVarchar = "VARCHAR"
}
defaultChar, ok := params["DEFAULT_CHAR"]
if ok {
var t = strings.ToUpper(defaultChar)
switch t {
case "NCHAR", "CHAR":
db.defaultChar = t
default:
db.defaultChar = "CHAR"
}
} else {
db.defaultChar = "CHAR"
}
}
func (db *mssql) SQLType(c *schemas.Column) string {
var res string
switch t := c.SQLType.Name; t {
@ -231,6 +263,7 @@ func (db *mssql) SQLType(c *schemas.Column) string {
} else if strings.EqualFold(c.Default, "false") {
c.Default = "0"
}
return res
case schemas.Serial:
c.IsAutoIncrement = true
c.IsPrimaryKey = true
@ -251,10 +284,10 @@ func (db *mssql) SQLType(c *schemas.Column) string {
case schemas.TimeStampz:
res = "DATETIMEOFFSET"
c.Length = 7
case schemas.MediumInt:
case schemas.MediumInt, schemas.UnsignedInt:
res = schemas.Int
case schemas.Text, schemas.MediumText, schemas.TinyText, schemas.LongText, schemas.Json:
res = schemas.Varchar + "(MAX)"
res = db.defaultVarchar + "(MAX)"
case schemas.Double:
res = schemas.Real
case schemas.Uuid:
@ -263,15 +296,35 @@ func (db *mssql) SQLType(c *schemas.Column) string {
case schemas.TinyInt:
res = schemas.TinyInt
c.Length = 0
case schemas.BigInt:
case schemas.BigInt, schemas.UnsignedBigInt:
res = schemas.BigInt
c.Length = 0
case schemas.NVarchar:
res = t
if c.Length == -1 {
res += "(MAX)"
}
case schemas.Varchar:
res = db.defaultVarchar
if c.Length == -1 {
res += "(MAX)"
}
case schemas.Char:
res = db.defaultChar
if c.Length == -1 {
res += "(MAX)"
}
case schemas.NChar:
res = t
if c.Length == -1 {
res += "(MAX)"
}
default:
res = t
}
if res == schemas.Int {
return schemas.Int
if res == schemas.Int || res == schemas.Bit || res == schemas.DateTime {
return res
}
hasLen1 := (c.Length > 0)
@ -317,6 +370,11 @@ func (db *mssql) DropTableSQL(tableName string) (string, bool) {
"DROP TABLE \"%s\"", tableName, tableName), true
}
func (db *mssql) ModifyColumnSQL(tableName string, col *schemas.Column) string {
s, _ := ColumnString(db.dialect, col, false)
return fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s", tableName, s)
}
func (db *mssql) IndexCheckSQL(tableName, idxName string) (string, []interface{}) {
args := []interface{}{idxName}
sql := "select name from sysindexes where id=object_id('" + tableName + "') and name=?"
@ -389,8 +447,18 @@ func (db *mssql) GetColumns(queryer core.Queryer, ctx context.Context, tableName
col.SQLType = schemas.SQLType{Name: schemas.TimeStampz, DefaultLength: 0, DefaultLength2: 0}
case "NVARCHAR":
col.SQLType = schemas.SQLType{Name: schemas.NVarchar, DefaultLength: 0, DefaultLength2: 0}
if col.Length > 0 {
col.Length /= 2
col.Length2 /= 2
}
case "IMAGE":
col.SQLType = schemas.SQLType{Name: schemas.VarBinary, DefaultLength: 0, DefaultLength2: 0}
case "NCHAR":
if col.Length > 0 {
col.Length /= 2
col.Length2 /= 2
}
fallthrough
default:
if _, ok := schemas.SqlTypes[ct]; ok {
col.SQLType = schemas.SQLType{Name: ct, DefaultLength: 0, DefaultLength2: 0}
@ -472,7 +540,7 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =?
colName = strings.Trim(colName, "` ")
var isRegular bool
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
if (strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName)) && len(indexName) > (5+len(tableName)) {
indexName = indexName[5+len(tableName):]
isRegular = true
}

View File

@ -254,6 +254,10 @@ func (db *mysql) SQLType(c *schemas.Column) string {
c.Length = 40
case schemas.Json:
res = schemas.Text
case schemas.UnsignedInt:
res = schemas.Int
case schemas.UnsignedBigInt:
res = schemas.BigInt
default:
res = t
}
@ -271,6 +275,11 @@ func (db *mysql) SQLType(c *schemas.Column) string {
} else if hasLen1 {
res += "(" + strconv.Itoa(c.Length) + ")"
}
if c.SQLType.Name == schemas.UnsignedBigInt || c.SQLType.Name == schemas.UnsignedInt {
res += " UNSIGNED"
}
return res
}
@ -307,8 +316,17 @@ func (db *mysql) AddColumnSQL(tableName string, col *schemas.Column) string {
func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) {
args := []interface{}{db.uri.DBName, tableName}
alreadyQuoted := "(INSTR(VERSION(), 'maria') > 0 && " +
"(SUBSTRING_INDEX(VERSION(), '.', 1) > 10 || " +
"(SUBSTRING_INDEX(VERSION(), '.', 1) = 10 && " +
"(SUBSTRING_INDEX(SUBSTRING(VERSION(), 4), '.', 1) > 2 || " +
"(SUBSTRING_INDEX(SUBSTRING(VERSION(), 4), '.', 1) = 2 && " +
"SUBSTRING_INDEX(SUBSTRING(VERSION(), 6), '-', 1) >= 7)))))"
s := "SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`," +
" `COLUMN_KEY`, `EXTRA`,`COLUMN_COMMENT` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?"
" `COLUMN_KEY`, `EXTRA`, `COLUMN_COMMENT`, " +
alreadyQuoted + " AS NEEDS_QUOTE " +
"FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" +
" ORDER BY `COLUMNS`.ORDINAL_POSITION"
rows, err := queryer.QueryContext(ctx, s, args...)
if err != nil {
@ -322,27 +340,35 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName
col := new(schemas.Column)
col.Indexes = make(map[string]int)
var columnName, isNullable, colType, colKey, extra, comment string
var columnName, nullableStr, colType, colKey, extra, comment string
var alreadyQuoted, isUnsigned bool
var colDefault *string
err = rows.Scan(&columnName, &isNullable, &colDefault, &colType, &colKey, &extra, &comment)
err = rows.Scan(&columnName, &nullableStr, &colDefault, &colType, &colKey, &extra, &comment, &alreadyQuoted)
if err != nil {
return nil, nil, err
}
col.Name = strings.Trim(columnName, "` ")
col.Comment = comment
if "YES" == isNullable {
if nullableStr == "YES" {
col.Nullable = true
}
if colDefault != nil {
if colDefault != nil && (!alreadyQuoted || *colDefault != "NULL") {
col.Default = *colDefault
col.DefaultIsEmpty = false
} else {
col.DefaultIsEmpty = true
}
fields := strings.Fields(colType)
if len(fields) == 2 && fields[1] == "unsigned" {
isUnsigned = true
}
colType = fields[0]
cts := strings.Split(colType, "(")
colName := cts[0]
// Remove the /* mariadb-5.3 */ suffix from coltypes
colName = strings.TrimSuffix(colName, "/* mariadb-5.3 */")
colType = strings.ToUpper(colName)
var len1, len2 int
if len(cts) == 2 {
@ -377,11 +403,8 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName
}
}
}
if colType == "FLOAT UNSIGNED" {
colType = "FLOAT"
}
if colType == "DOUBLE UNSIGNED" {
colType = "DOUBLE"
if isUnsigned {
colType = "UNSIGNED " + colType
}
col.Length = len1
col.Length2 = len2
@ -403,9 +426,9 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName
}
if !col.DefaultIsEmpty {
if col.SQLType.IsText() {
if !alreadyQuoted && col.SQLType.IsText() {
col.Default = "'" + col.Default + "'"
} else if col.SQLType.IsTime() && col.Default != "CURRENT_TIMESTAMP" {
} else if col.SQLType.IsTime() && !alreadyQuoted && col.Default != "CURRENT_TIMESTAMP" {
col.Default = "'" + col.Default + "'"
}
}

View File

@ -500,8 +500,8 @@ var (
}
oracleQuoter = schemas.Quoter{
Prefix: '[',
Suffix: ']',
Prefix: '"',
Suffix: '"',
IsReserved: schemas.AlwaysReserve,
}
)

View File

@ -833,12 +833,12 @@ func (db *postgres) SQLType(c *schemas.Column) string {
case schemas.Bit:
res = schemas.Boolean
return res
case schemas.MediumInt, schemas.Int, schemas.Integer:
case schemas.MediumInt, schemas.Int, schemas.Integer, schemas.UnsignedInt:
if c.IsAutoIncrement {
return schemas.Serial
}
return schemas.Integer
case schemas.BigInt:
case schemas.BigInt, schemas.UnsignedBigInt:
if c.IsAutoIncrement {
return schemas.BigSerial
}
@ -857,6 +857,8 @@ func (db *postgres) SQLType(c *schemas.Column) string {
res = schemas.Real
case schemas.TinyText, schemas.MediumText, schemas.LongText:
res = schemas.Text
case schemas.NChar:
res = schemas.Char
case schemas.NVarchar:
res = schemas.Varchar
case schemas.Uuid:
@ -1050,6 +1052,10 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A
}
}
if colDefault != nil && *colDefault == "unique_rowid()" { // ignore the system column added by cockroach
continue
}
col.Name = strings.Trim(colName, `" `)
if colDefault != nil {
@ -1086,8 +1092,10 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A
col.Nullable = (isNullable == "YES")
switch strings.ToLower(dataType) {
case "character varying", "character", "string":
case "character varying", "string":
col.SQLType = schemas.SQLType{Name: schemas.Varchar, DefaultLength: 0, DefaultLength2: 0}
case "character":
col.SQLType = schemas.SQLType{Name: schemas.Char, DefaultLength: 0, DefaultLength2: 0}
case "timestamp without time zone":
col.SQLType = schemas.SQLType{Name: schemas.DateTime, DefaultLength: 0, DefaultLength2: 0}
case "timestamp with time zone":

View File

@ -193,7 +193,8 @@ func (db *sqlite3) SQLType(c *schemas.Column) string {
case schemas.Char, schemas.Varchar, schemas.NVarchar, schemas.TinyText,
schemas.Text, schemas.MediumText, schemas.LongText, schemas.Json:
return schemas.Text
case schemas.Bit, schemas.TinyInt, schemas.SmallInt, schemas.MediumInt, schemas.Int, schemas.Integer, schemas.BigInt:
case schemas.Bit, schemas.TinyInt, schemas.SmallInt, schemas.MediumInt, schemas.Int, schemas.Integer, schemas.BigInt,
schemas.UnsignedBigInt, schemas.UnsignedInt:
return schemas.Integer
case schemas.Float, schemas.Double, schemas.Real:
return schemas.Real
@ -483,7 +484,7 @@ func (db *sqlite3) GetIndexes(queryer core.Queryer, ctx context.Context, tableNa
continue
}
indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []")
indexName := strings.Trim(strings.TrimSpace(sql[nNStart+6:nNEnd]), "`[]'\"")
var isRegular bool
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
index.Name = indexName[5+len(tableName):]

View File

@ -19,7 +19,11 @@ func FormatTime(dialect Dialect, sqlTypeName string, t time.Time) (v interface{}
case schemas.Date:
v = t.Format("2006-01-02")
case schemas.DateTime, schemas.TimeStamp, schemas.Varchar: // !DarthPestilane! format time when sqlTypeName is schemas.Varchar.
if dialect.URI().DBType == schemas.ORACLE {
v = t
} else {
v = t.Format("2006-01-02 15:04:05")
}
case schemas.TimeStampz:
if dialect.URI().DBType == schemas.MSSQL {
v = t.Format("2006-01-02T15:04:05.9999999Z07:00")

253
engine.go
View File

@ -61,6 +61,10 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) {
return nil, err
}
return newEngine(driverName, dataSourceName, dialect, db)
}
func newEngine(driverName, dataSourceName string, dialect dialects.Dialect, db *core.DB) (*Engine, error) {
cacherMgr := caches.NewManager()
mapper := names.NewCacheMapper(new(names.SnakeMapper))
tagParser := tags.NewParser("xorm", dialect, mapper, mapper, cacherMgr)
@ -88,7 +92,7 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) {
engine.SetLogger(log.NewLoggerAdapter(logger))
runtime.SetFinalizer(engine, func(engine *Engine) {
engine.Close()
_ = engine.Close()
})
return engine, nil
@ -101,6 +105,23 @@ func NewEngineWithParams(driverName string, dataSourceName string, params map[st
return engine, err
}
// NewEngineWithDB new a db manager with db. The params will be passed to db.
func NewEngineWithDB(driverName string, dataSourceName string, db *core.DB) (*Engine, error) {
dialect, err := dialects.OpenDialect(driverName, dataSourceName)
if err != nil {
return nil, err
}
return newEngine(driverName, dataSourceName, dialect, db)
}
// NewEngineWithDialectAndDB new a db manager according to the parameter.
// If you do not want to use your own dialect or db, please use NewEngine.
// For creating dialect, you can call dialects.OpenDialect. And, for creating db,
// you can call core.Open or core.FromDB.
func NewEngineWithDialectAndDB(driverName, dataSourceName string, dialect dialects.Dialect, db *core.DB) (*Engine, error) {
return newEngine(driverName, dataSourceName, dialect, db)
}
// EnableSessionID if enable session id
func (engine *Engine) EnableSessionID(enable bool) {
engine.logSessionID = enable
@ -143,10 +164,12 @@ func (engine *Engine) Logger() log.ContextLogger {
func (engine *Engine) SetLogger(logger interface{}) {
var realLogger log.ContextLogger
switch t := logger.(type) {
case log.Logger:
realLogger = log.NewLoggerAdapter(t)
case log.ContextLogger:
realLogger = t
case log.Logger:
realLogger = log.NewLoggerAdapter(t)
default:
panic("logger should implement either log.ContextLogger or log.Logger")
}
engine.logger = realLogger
engine.DB().Logger = realLogger
@ -188,6 +211,11 @@ func (engine *Engine) SetColumnMapper(mapper names.Mapper) {
engine.tagParser.SetColumnMapper(mapper)
}
// SetTagIdentifier set the tag identifier
func (engine *Engine) SetTagIdentifier(tagIdentifier string) {
engine.tagParser.SetIdentifier(tagIdentifier)
}
// Quote Use QuoteStr quote the string sql
func (engine *Engine) Quote(value string) string {
value = strings.TrimSpace(value)
@ -347,13 +375,16 @@ func (engine *Engine) loadTableInfo(table *schemas.Table) error {
var seq int
for _, index := range indexes {
for _, name := range index.Cols {
parts := strings.Split(name, " ")
parts := strings.Split(strings.TrimSpace(name), " ")
if len(parts) > 1 {
if parts[1] == "DESC" {
seq = 1
} else if parts[1] == "ASC" {
seq = 0
}
}
if col := table.GetColumn(parts[0]); col != nil {
var colName = strings.Trim(parts[0], `"`)
if col := table.GetColumn(colName); col != nil {
col.Indexes[index.Name] = index.Type
} else {
return fmt.Errorf("Unknown col %s seq %d, in index %v of table %v, columns %v", name, seq, index.Name, table.Name, table.ColumnsSeq())
@ -412,6 +443,82 @@ func (engine *Engine) DumpTables(tables []*schemas.Table, w io.Writer, tp ...sch
return engine.dumpTables(tables, w, tp...)
}
func formatColumnValue(dstDialect dialects.Dialect, d interface{}, col *schemas.Column) string {
if d == nil {
return "NULL"
}
if dq, ok := d.(bool); ok && (dstDialect.URI().DBType == schemas.SQLITE ||
dstDialect.URI().DBType == schemas.MSSQL) {
if dq {
return "1"
}
return "0"
}
if col.SQLType.IsText() {
var v = fmt.Sprintf("%s", d)
return "'" + strings.Replace(v, "'", "''", -1) + "'"
} else if col.SQLType.IsTime() {
var v = fmt.Sprintf("%s", d)
if strings.HasSuffix(v, " +0000 UTC") {
return fmt.Sprintf("'%s'", v[0:len(v)-len(" +0000 UTC")])
} else if strings.HasSuffix(v, " +0000 +0000") {
return fmt.Sprintf("'%s'", v[0:len(v)-len(" +0000 +0000")])
}
return "'" + strings.Replace(v, "'", "''", -1) + "'"
} else if col.SQLType.IsBlob() {
if reflect.TypeOf(d).Kind() == reflect.Slice {
return fmt.Sprintf("%s", dstDialect.FormatBytes(d.([]byte)))
} else if reflect.TypeOf(d).Kind() == reflect.String {
return fmt.Sprintf("'%s'", d.(string))
}
} else if col.SQLType.IsNumeric() {
switch reflect.TypeOf(d).Kind() {
case reflect.Slice:
if col.SQLType.Name == schemas.Bool {
return fmt.Sprintf("%v", strconv.FormatBool(d.([]byte)[0] != byte('0')))
}
return fmt.Sprintf("%s", string(d.([]byte)))
case reflect.Int16, reflect.Int8, reflect.Int32, reflect.Int64, reflect.Int:
if col.SQLType.Name == schemas.Bool {
v := reflect.ValueOf(d).Int() > 0
if dstDialect.URI().DBType == schemas.SQLITE {
if v {
return "1"
}
return "0"
}
return fmt.Sprintf("%v", strconv.FormatBool(v))
}
return fmt.Sprintf("%v", d)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if col.SQLType.Name == schemas.Bool {
v := reflect.ValueOf(d).Uint() > 0
if dstDialect.URI().DBType == schemas.SQLITE {
if v {
return "1"
}
return "0"
}
return fmt.Sprintf("%v", strconv.FormatBool(v))
}
return fmt.Sprintf("%v", d)
default:
return fmt.Sprintf("%v", d)
}
}
s := fmt.Sprintf("%v", d)
if strings.Contains(s, ":") || strings.Contains(s, "-") {
if strings.HasSuffix(s, " +0000 UTC") {
return fmt.Sprintf("'%s'", s[0:len(s)-len(" +0000 UTC")])
}
return fmt.Sprintf("'%s'", s)
}
return s
}
// dumpTables dump database all table structs and data to w with specify db type
func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error {
var dstDialect dialects.Dialect
@ -424,7 +531,10 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch
}
uri := engine.dialect.URI()
destURI := *uri
destURI := dialects.URI{
DBType: tp[0],
DBName: uri.DBName,
}
dstDialect.Init(&destURI)
}
@ -495,59 +605,9 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch
if col == nil {
return errors.New("unknow column error")
}
if d == nil {
temp += ", NULL"
} else if col.SQLType.IsText() || col.SQLType.IsTime() {
var v = fmt.Sprintf("%s", d)
if strings.HasSuffix(v, " +0000 UTC") {
temp += fmt.Sprintf(", '%s'", v[0:len(v)-len(" +0000 UTC")])
} else {
temp += ", '" + strings.Replace(v, "'", "''", -1) + "'"
temp += "," + formatColumnValue(dstDialect, d, col)
}
} else if col.SQLType.IsBlob() {
if reflect.TypeOf(d).Kind() == reflect.Slice {
temp += fmt.Sprintf(", %s", dstDialect.FormatBytes(d.([]byte)))
} else if reflect.TypeOf(d).Kind() == reflect.String {
temp += fmt.Sprintf(", '%s'", d.(string))
}
} else if col.SQLType.IsNumeric() {
switch reflect.TypeOf(d).Kind() {
case reflect.Slice:
if col.SQLType.Name == schemas.Bool {
temp += fmt.Sprintf(", %v", strconv.FormatBool(d.([]byte)[0] != byte('0')))
} else {
temp += fmt.Sprintf(", %s", string(d.([]byte)))
}
case reflect.Int16, reflect.Int8, reflect.Int32, reflect.Int64, reflect.Int:
if col.SQLType.Name == schemas.Bool {
temp += fmt.Sprintf(", %v", strconv.FormatBool(reflect.ValueOf(d).Int() > 0))
} else {
temp += fmt.Sprintf(", %v", d)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if col.SQLType.Name == schemas.Bool {
temp += fmt.Sprintf(", %v", strconv.FormatBool(reflect.ValueOf(d).Uint() > 0))
} else {
temp += fmt.Sprintf(", %v", d)
}
default:
temp += fmt.Sprintf(", %v", d)
}
} else {
s := fmt.Sprintf("%v", d)
if strings.Contains(s, ":") || strings.Contains(s, "-") {
if strings.HasSuffix(s, " +0000 UTC") {
temp += fmt.Sprintf(", '%s'", s[0:len(s)-len(" +0000 UTC")])
} else {
temp += fmt.Sprintf(", '%s'", s)
}
} else {
temp += fmt.Sprintf(", %s", s)
}
}
}
_, err = io.WriteString(w, temp[2:]+");\n")
_, err = io.WriteString(w, temp[1:]+");\n")
if err != nil {
return err
}
@ -816,81 +876,11 @@ func (engine *Engine) IsTableExist(beanOrTableName interface{}) (bool, error) {
return session.IsTableExist(beanOrTableName)
}
// IDOf get id from one struct
func (engine *Engine) IDOf(bean interface{}) (schemas.PK, error) {
return engine.IDOfV(reflect.ValueOf(bean))
}
// TableName returns table name with schema prefix if has
func (engine *Engine) TableName(bean interface{}, includeSchema ...bool) string {
return dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean, includeSchema...)
}
// IDOfV get id from one value of struct
func (engine *Engine) IDOfV(rv reflect.Value) (schemas.PK, error) {
return engine.idOfV(rv)
}
func (engine *Engine) idOfV(rv reflect.Value) (schemas.PK, error) {
v := reflect.Indirect(rv)
table, err := engine.tagParser.ParseWithCache(v)
if err != nil {
return nil, err
}
pk := make([]interface{}, len(table.PrimaryKeys))
for i, col := range table.PKColumns() {
var err error
fieldName := col.FieldName
for {
parts := strings.SplitN(fieldName, ".", 2)
if len(parts) == 1 {
break
}
v = v.FieldByName(parts[0])
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return nil, ErrUnSupportedType
}
fieldName = parts[1]
}
pkField := v.FieldByName(fieldName)
switch pkField.Kind() {
case reflect.String:
pk[i], err = engine.idTypeAssertion(col, pkField.String())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
pk[i], err = engine.idTypeAssertion(col, strconv.FormatInt(pkField.Int(), 10))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
// id of uint will be converted to int64
pk[i], err = engine.idTypeAssertion(col, strconv.FormatUint(pkField.Uint(), 10))
}
if err != nil {
return nil, err
}
}
return schemas.PK(pk), nil
}
func (engine *Engine) idTypeAssertion(col *schemas.Column, sid string) (interface{}, error) {
if col.SQLType.IsNumeric() {
n, err := strconv.ParseInt(sid, 10, 64)
if err != nil {
return nil, err
}
return n, nil
} else if col.SQLType.IsText() {
return sid, nil
} else {
return nil, errors.New("not supported")
}
}
// CreateIndexes create indexes
func (engine *Engine) CreateIndexes(bean interface{}) error {
session := engine.NewSession()
@ -1288,6 +1278,7 @@ func (engine *Engine) SetSchema(schema string) {
engine.dialect.URI().SetSchema(schema)
}
// AddHook adds a context Hook
func (engine *Engine) AddHook(hook contexts.Hook) {
engine.db.AddHook(hook)
}
@ -1303,7 +1294,7 @@ func (engine *Engine) tbNameWithSchema(v string) string {
return dialects.TableNameWithSchema(engine.dialect, v)
}
// ContextHook creates a session with the context
// Context creates a session with the context
func (engine *Engine) Context(ctx context.Context) *Session {
session := engine.NewSession()
session.isAutoClose = true
@ -1333,11 +1324,11 @@ func (engine *Engine) Transaction(f func(*Session) (interface{}, error)) (interf
result, err := f(session)
if err != nil {
return nil, err
return result, err
}
if err := session.Commit(); err != nil {
return nil, err
return result, err
}
return result, nil

View File

@ -79,7 +79,7 @@ func (eg *EngineGroup) Close() error {
return nil
}
// ContextHook returned a group session
// Context returned a group session
func (eg *EngineGroup) Context(ctx context.Context) *Session {
sess := eg.NewSession()
sess.isAutoClose = true
@ -144,6 +144,7 @@ func (eg *EngineGroup) SetLogger(logger interface{}) {
}
}
// AddHook adds Hook
func (eg *EngineGroup) AddHook(hook contexts.Hook) {
eg.Engine.AddHook(hook)
for i := 0; i < len(eg.slaves); i++ {
@ -167,6 +168,14 @@ func (eg *EngineGroup) SetMapper(mapper names.Mapper) {
}
}
// SetTagIdentifier set the tag identifier
func (eg *EngineGroup) SetTagIdentifier(tagIdentifier string) {
eg.Engine.SetTagIdentifier(tagIdentifier)
for i := 0; i < len(eg.slaves); i++ {
eg.slaves[i].SetTagIdentifier(tagIdentifier)
}
}
// SetMaxIdleConns set the max idle connections on pool, default is 2
func (eg *EngineGroup) SetMaxIdleConns(conns int) {
eg.Engine.DB().SetMaxIdleConns(conns)

15
go.mod
View File

@ -1,16 +1,15 @@
module xorm.io/xorm
go 1.11
go 1.13
require (
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4
github.com/go-sql-driver/mysql v1.4.1
github.com/kr/pretty v0.1.0 // indirect
github.com/lib/pq v1.0.0
github.com/mattn/go-sqlite3 v1.10.0
github.com/denisenkom/go-mssqldb v0.9.0
github.com/go-sql-driver/mysql v1.5.0
github.com/lib/pq v1.7.0
github.com/mattn/go-sqlite3 v1.14.6
github.com/stretchr/testify v1.4.0
github.com/syndtr/goleveldb v1.0.0
github.com/ziutek/mymysql v1.5.4
google.golang.org/appengine v1.6.0 // indirect
xorm.io/builder v0.3.7
modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84
xorm.io/builder v0.3.8
)

201
go.sum
View File

@ -1,152 +1,89 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4 h1:YcpmyvADGYw5LqMnHqSkyIELsHCGF6PkrmM31V8rF7o=
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/denisenkom/go-mssqldb v0.9.0 h1:RSohk2RsiZqLZ0zCjtfn3S4Gp4exhpBWHyQ7D0yGjAk=
github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw=
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
@ -154,8 +91,32 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI=
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009 h1:u0oCo5b9wyLr++HF3AN9JicGhkUxJhMz51+8TIZH9N0=
modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878=
modernc.org/ccgo/v3 v3.9.0 h1:JbcEIqjw4Agf+0g3Tc85YvfYqkkFOv6xBwS4zkfqSoA=
modernc.org/ccgo/v3 v3.9.0/go.mod h1:nQbgkn8mwzPdp4mm6BT6+p85ugQ7FrGgIcYaE7nSrpY=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
modernc.org/libc v1.8.0 h1:Pp4uv9g0csgBMpGPABKtkieF6O5MGhfGo6ZiOdlYfR8=
modernc.org/libc v1.8.0/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY=
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM=
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84 h1:rgEUzE849tFlHSoeCrKyS9cZAljC+DY7MdMHKq6R6sY=
modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84/go.mod h1:PGzq6qlhyYjL6uVbSgS6WoF7ZopTW/sI7+7p+mb4ZVU=
modernc.org/strutil v1.1.0 h1:+1/yCzZxY2pZwwrsbH+4T7BQMoLQ9QiBshRC9eicYsc=
modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
modernc.org/tcl v1.5.0 h1:euZSUNfE0Fd4W8VqXI1Ly1v7fqDJoBuAV88Ea+SnaSs=
modernc.org/tcl v1.5.0/go.mod h1:gb57hj4pO8fRrK54zveIfFXBaMHK3SKJNWcmRw1cRzc=
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
modernc.org/z v1.0.1 h1:WyIDpEpAIx4Hel6q/Pcgj/VhaQV5XPJ2I6ryIYbjnpc=
modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
xorm.io/builder v0.3.8 h1:P/wPgRqa9kX5uE0aA1/ukJ23u9KH0aSRpHLwDKXigSE=
xorm.io/builder v0.3.8/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=

View File

@ -20,6 +20,7 @@ import (
_ "github.com/mattn/go-sqlite3"
"github.com/stretchr/testify/assert"
_ "github.com/ziutek/mymysql/godrv"
_ "modernc.org/sqlite"
)
func TestPing(t *testing.T) {
@ -95,17 +96,21 @@ func TestDump(t *testing.T) {
type TestDumpStruct struct {
Id int64
Name string
IsMan bool
Created time.Time `xorm:"created"`
}
assertSync(t, new(TestDumpStruct))
testEngine.Insert([]TestDumpStruct{
{Name: "1"},
cnt, err := testEngine.Insert([]TestDumpStruct{
{Name: "1", IsMan: true},
{Name: "2\n"},
{Name: "3;"},
{Name: "4\n;\n''"},
{Name: "5'\n"},
})
assert.NoError(t, err)
assert.EqualValues(t, 5, cnt)
fp := fmt.Sprintf("%v.sql", testEngine.Dialect().URI().DBType)
os.Remove(fp)
@ -116,7 +121,7 @@ func TestDump(t *testing.T) {
sess := testEngine.NewSession()
defer sess.Close()
assert.NoError(t, sess.Begin())
_, err := sess.ImportFile(fp)
_, err = sess.ImportFile(fp)
assert.NoError(t, err)
assert.NoError(t, sess.Commit())
@ -128,6 +133,49 @@ func TestDump(t *testing.T) {
}
}
func TestDumpTables(t *testing.T) {
assert.NoError(t, PrepareEngine())
type TestDumpTableStruct struct {
Id int64
Name string
IsMan bool
Created time.Time `xorm:"created"`
}
assertSync(t, new(TestDumpTableStruct))
testEngine.Insert([]TestDumpTableStruct{
{Name: "1", IsMan: true},
{Name: "2\n"},
{Name: "3;"},
{Name: "4\n;\n''"},
{Name: "5'\n"},
})
fp := fmt.Sprintf("%v-table.sql", testEngine.Dialect().URI().DBType)
os.Remove(fp)
tb, err := testEngine.TableInfo(new(TestDumpTableStruct))
assert.NoError(t, err)
assert.NoError(t, testEngine.(*xorm.Engine).DumpTablesToFile([]*schemas.Table{tb}, fp))
assert.NoError(t, PrepareEngine())
sess := testEngine.NewSession()
defer sess.Close()
assert.NoError(t, sess.Begin())
_, err = sess.ImportFile(fp)
assert.NoError(t, err)
assert.NoError(t, sess.Commit())
for _, tp := range []schemas.DBType{schemas.SQLITE, schemas.MYSQL, schemas.POSTGRES, schemas.MSSQL} {
name := fmt.Sprintf("dump_%v-table.sql", tp)
t.Run(name, func(t *testing.T) {
assert.NoError(t, testEngine.(*xorm.Engine).DumpTablesToFile([]*schemas.Table{tb}, name, tp))
})
}
}
func TestSetSchema(t *testing.T) {
assert.NoError(t, PrepareEngine())
@ -139,3 +187,16 @@ func TestSetSchema(t *testing.T) {
assert.EqualValues(t, oldSchema, testEngine.Dialect().URI().Schema)
}
}
func TestImport(t *testing.T) {
if testEngine.Dialect().URI().DBType != schemas.MYSQL {
t.Skip()
return
}
sess := testEngine.NewSession()
defer sess.Close()
assert.NoError(t, sess.Begin())
_, err := sess.ImportFile("./testdata/import1.sql")
assert.NoError(t, err)
assert.NoError(t, sess.Commit())
}

View File

@ -502,13 +502,58 @@ func TestFindAndCountOneFunc(t *testing.T) {
assert.EqualValues(t, 1, cnt)
results = make([]FindAndCountStruct, 0, 1)
cnt, err = testEngine.Where("msg = ?", true).Limit(1).FindAndCount(&results)
cnt, err = testEngine.Where("1=1").Limit(1).FindAndCount(&results)
assert.NoError(t, err)
assert.EqualValues(t, 1, len(results))
assert.EqualValues(t, 2, cnt)
assert.EqualValues(t, FindAndCountStruct{
Id: 1,
Content: "111",
Msg: false,
}, results[0])
results = make([]FindAndCountStruct, 0, 1)
cnt, err = testEngine.Where("1=1").Limit(1).FindAndCount(&results)
assert.NoError(t, err)
assert.EqualValues(t, 1, len(results))
assert.EqualValues(t, 2, cnt)
assert.EqualValues(t, FindAndCountStruct{
Id: 1,
Content: "111",
Msg: false,
}, results[0])
results = make([]FindAndCountStruct, 0, 1)
cnt, err = testEngine.Where("1=1").Limit(1, 1).FindAndCount(&results)
assert.NoError(t, err)
assert.EqualValues(t, 1, len(results))
assert.EqualValues(t, 2, cnt)
assert.EqualValues(t, FindAndCountStruct{
Id: 2,
Content: "222",
Msg: true,
}, results[0])
results = make([]FindAndCountStruct, 0, 1)
cnt, err = testEngine.Where("1=1").Limit(1, 1).FindAndCount(&results)
assert.NoError(t, err)
assert.EqualValues(t, 1, len(results))
assert.EqualValues(t, 2, cnt)
assert.EqualValues(t, FindAndCountStruct{
Id: 2,
Content: "222",
Msg: true,
}, results[0])
results = make([]FindAndCountStruct, 0, 1)
cnt, err = testEngine.Where("msg = ?", true).Select("id, content, msg").
Limit(1).FindAndCount(&results)
assert.NoError(t, err)
assert.EqualValues(t, 1, len(results))
assert.EqualValues(t, 1, cnt)
results = make([]FindAndCountStruct, 0, 1)
cnt, err = testEngine.Where("msg = ?", true).Select("id, content, msg").
cnt, err = testEngine.Where("msg = ?", true).Cols("id", "content", "msg").
Limit(1).FindAndCount(&results)
assert.NoError(t, err)
assert.EqualValues(t, 1, len(results))
@ -708,6 +753,13 @@ func TestFindExtends(t *testing.T) {
err = testEngine.Find(&results)
assert.NoError(t, err)
assert.EqualValues(t, 2, len(results))
results = make([]FindExtendsA, 0, 2)
err = testEngine.Find(&results, &FindExtendsB{
ID: 1,
})
assert.NoError(t, err)
assert.EqualValues(t, 1, len(results))
}
func TestFindExtends3(t *testing.T) {

View File

@ -6,11 +6,13 @@ package integrations
import (
"database/sql"
"errors"
"fmt"
"strconv"
"testing"
"time"
"xorm.io/xorm"
"xorm.io/xorm/contexts"
"xorm.io/xorm/schemas"
@ -394,6 +396,60 @@ func TestJSONString(t *testing.T) {
assert.NoError(t, err)
assert.EqualValues(t, 1, len(jss))
assert.True(t, `["1","2"]` == jss[0].Content || `["1", "2"]` == jss[0].Content)
type JsonAnonymousStruct struct {
Id int64
JsonString `xorm:"'json_string' JSON LONGTEXT"`
}
assertSync(t, new(JsonAnonymousStruct))
_, err = testEngine.Insert(&JsonAnonymousStruct{
JsonString: JsonString{
Content: "1",
},
})
assert.NoError(t, err)
var jas JsonAnonymousStruct
has, err = testEngine.Get(&jas)
assert.NoError(t, err)
assert.True(t, has)
assert.EqualValues(t, 1, jas.Id)
assert.EqualValues(t, "1", jas.Content)
var jass []JsonAnonymousStruct
err = testEngine.Find(&jass)
assert.NoError(t, err)
assert.EqualValues(t, 1, len(jass))
assert.EqualValues(t, "1", jass[0].Content)
type JsonStruct struct {
Id int64
JSON JsonString `xorm:"'json_string' JSON LONGTEXT"`
}
assertSync(t, new(JsonStruct))
_, err = testEngine.Insert(&JsonStruct{
JSON: JsonString{
Content: "2",
},
})
assert.NoError(t, err)
var jst JsonStruct
has, err = testEngine.Get(&jst)
assert.NoError(t, err)
assert.True(t, has)
assert.EqualValues(t, 1, jst.Id)
assert.EqualValues(t, "2", jst.JSON.Content)
var jsts []JsonStruct
err = testEngine.Find(&jsts)
assert.NoError(t, err)
assert.EqualValues(t, 1, len(jsts))
assert.EqualValues(t, "2", jsts[0].JSON.Content)
}
func TestGetActionMapping(t *testing.T) {
@ -696,3 +752,17 @@ func TestGetViaMapCond(t *testing.T) {
assert.NoError(t, err)
assert.False(t, has)
}
func TestGetNil(t *testing.T) {
type GetNil struct {
Id int64
}
assert.NoError(t, PrepareEngine())
assertSync(t, new(GetNil))
var gn *GetNil
has, err := testEngine.Get(gn)
assert.True(t, errors.Is(err, xorm.ErrObjectIsNil))
assert.False(t, has)
}

View File

@ -10,6 +10,7 @@ import (
"time"
"github.com/stretchr/testify/assert"
"xorm.io/xorm/schemas"
)
func TestStoreEngine(t *testing.T) {
@ -102,6 +103,9 @@ func TestSyncTable(t *testing.T) {
assert.NoError(t, err)
assert.EqualValues(t, 1, len(tables))
assert.EqualValues(t, "sync_table1", tables[0].Name)
tableInfo, err := testEngine.TableInfo(new(SyncTable1))
assert.NoError(t, err)
assert.EqualValues(t, testEngine.Dialect().SQLType(tables[0].GetColumn("name")), testEngine.Dialect().SQLType(tableInfo.GetColumn("name")))
assert.NoError(t, testEngine.Sync2(new(SyncTable2)))
@ -109,6 +113,9 @@ func TestSyncTable(t *testing.T) {
assert.NoError(t, err)
assert.EqualValues(t, 1, len(tables))
assert.EqualValues(t, "sync_table1", tables[0].Name)
tableInfo, err = testEngine.TableInfo(new(SyncTable2))
assert.NoError(t, err)
assert.EqualValues(t, testEngine.Dialect().SQLType(tables[0].GetColumn("name")), testEngine.Dialect().SQLType(tableInfo.GetColumn("name")))
assert.NoError(t, testEngine.Sync2(new(SyncTable3)))
@ -116,6 +123,9 @@ func TestSyncTable(t *testing.T) {
assert.NoError(t, err)
assert.EqualValues(t, 1, len(tables))
assert.EqualValues(t, "sync_table1", tables[0].Name)
tableInfo, err = testEngine.TableInfo(new(SyncTable3))
assert.NoError(t, err)
assert.EqualValues(t, testEngine.Dialect().SQLType(tables[0].GetColumn("name")), testEngine.Dialect().SQLType(tableInfo.GetColumn("name")))
}
func TestSyncTable2(t *testing.T) {
@ -143,6 +153,63 @@ func TestSyncTable2(t *testing.T) {
assert.EqualValues(t, colMapper.Obj2Table("NewCol"), tables[0].Columns()[3].Name)
}
func TestSyncTable3(t *testing.T) {
type SyncTable5 struct {
Id int64
Name string
Text string `xorm:"TEXT"`
Char byte `xorm:"CHAR(1)"`
TenChar [10]byte `xorm:"CHAR(10)"`
TenVarChar string `xorm:"VARCHAR(10)"`
}
assert.NoError(t, PrepareEngine())
assert.NoError(t, testEngine.Sync2(new(SyncTable5)))
tables, err := testEngine.DBMetas()
assert.NoError(t, err)
tableInfo, err := testEngine.TableInfo(new(SyncTable5))
assert.NoError(t, err)
assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("name")), testEngine.Dialect().SQLType(tables[0].GetColumn("name")))
assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("text")), testEngine.Dialect().SQLType(tables[0].GetColumn("text")))
assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("char")), testEngine.Dialect().SQLType(tables[0].GetColumn("char")))
assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("ten_char")), testEngine.Dialect().SQLType(tables[0].GetColumn("ten_char")))
assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("ten_var_char")), testEngine.Dialect().SQLType(tables[0].GetColumn("ten_var_char")))
if *doNVarcharTest {
var oldDefaultVarchar string
var oldDefaultChar string
oldDefaultVarchar, *defaultVarchar = *defaultVarchar, "nvarchar"
oldDefaultChar, *defaultChar = *defaultChar, "nchar"
testEngine.Dialect().SetParams(map[string]string{
"DEFAULT_VARCHAR": *defaultVarchar,
"DEFAULT_CHAR": *defaultChar,
})
defer func() {
*defaultVarchar = oldDefaultVarchar
*defaultChar = oldDefaultChar
testEngine.Dialect().SetParams(map[string]string{
"DEFAULT_VARCHAR": *defaultVarchar,
"DEFAULT_CHAR": *defaultChar,
})
}()
assert.NoError(t, PrepareEngine())
assert.NoError(t, testEngine.Sync2(new(SyncTable5)))
tables, err := testEngine.DBMetas()
assert.NoError(t, err)
tableInfo, err := testEngine.TableInfo(new(SyncTable5))
assert.NoError(t, err)
assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("name")), testEngine.Dialect().SQLType(tables[0].GetColumn("name")))
assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("text")), testEngine.Dialect().SQLType(tables[0].GetColumn("text")))
assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("char")), testEngine.Dialect().SQLType(tables[0].GetColumn("char")))
assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("ten_char")), testEngine.Dialect().SQLType(tables[0].GetColumn("ten_char")))
assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("ten_var_char")), testEngine.Dialect().SQLType(tables[0].GetColumn("ten_var_char")))
}
}
func TestIsTableExist(t *testing.T) {
assert.NoError(t, PrepareEngine())
@ -330,3 +397,30 @@ func TestSync2_Default(t *testing.T) {
assertSync(t, new(TestSync2Default))
assert.NoError(t, testEngine.Sync2(new(TestSync2Default)))
}
func TestModifyColum(t *testing.T) {
// Since SQLITE don't support modify column SQL, currrently just ignore
if testEngine.Dialect().URI().DBType == schemas.SQLITE {
return
}
type TestModifyColumn struct {
Id int64
UserId int64 `xorm:"default(1)"`
IsMember bool `xorm:"default(true)"`
Name string `xorm:"char(10)"`
}
assert.NoError(t, PrepareEngine())
assertSync(t, new(TestModifyColumn))
alterSQL := testEngine.Dialect().ModifyColumnSQL("test_modify_column", &schemas.Column{
Name: "name",
SQLType: schemas.SQLType{
Name: "VARCHAR",
},
Length: 16,
Nullable: false,
})
_, err := testEngine.Exec(alterSQL)
assert.NoError(t, err)
}

View File

@ -54,3 +54,11 @@ func TestMustLogSQL(t *testing.T) {
_, err := testEngine.Table("userinfo").MustLogSQL(true).Get(new(Userinfo))
assert.NoError(t, err)
}
func TestEnableSessionId(t *testing.T) {
assert.NoError(t, PrepareEngine())
testEngine.EnableSessionID(true)
assertSync(t, new(Userinfo))
_, err := testEngine.Table("userinfo").MustLogSQL(true).Get(new(Userinfo))
assert.NoError(t, err)
}

View File

@ -12,8 +12,10 @@ import (
"github.com/stretchr/testify/assert"
"xorm.io/xorm"
"xorm.io/xorm/internal/statements"
"xorm.io/xorm/internal/utils"
"xorm.io/xorm/names"
"xorm.io/xorm/schemas"
)
func TestUpdateMap(t *testing.T) {
@ -39,6 +41,27 @@ func TestUpdateMap(t *testing.T) {
})
assert.NoError(t, err)
assert.EqualValues(t, 1, cnt)
cnt, err = testEngine.Table("update_table").ID(tb.Id).Update(map[string]interface{}{
"name": "test2",
"age": 36,
})
assert.Error(t, err)
assert.True(t, statements.IsIDConditionWithNoTableErr(err))
assert.EqualValues(t, 0, cnt)
cnt, err = testEngine.Table("update_table").Update(map[string]interface{}{
"name": "test2",
"age": 36,
}, &UpdateTable{
Id: tb.Id,
})
assert.NoError(t, err)
if testEngine.Dialect().URI().DBType == schemas.MYSQL {
assert.EqualValues(t, 0, cnt)
} else {
assert.EqualValues(t, 1, cnt)
}
}
func TestUpdateLimit(t *testing.T) {
@ -179,6 +202,9 @@ func TestForUpdate(t *testing.T) {
// lock is NOT used
wg.Add(1)
wg2 := &sync.WaitGroup{}
wg2.Add(1)
go func() {
f3 := new(ForUpdate)
session3.Where("id = ?", 1)
@ -192,10 +218,10 @@ func TestForUpdate(t *testing.T) {
t.Errorf("read lock failed")
}
wg.Done()
wg2.Done()
}()
// wait for go rountines
time.Sleep(50 * time.Millisecond)
wg2.Wait()
f := new(ForUpdate)
f.Name = "updated by session1"
@ -988,7 +1014,7 @@ func TestUpdateMapContent(t *testing.T) {
assert.EqualValues(t, false, c2.IsMan)
assert.EqualValues(t, 2, c2.Gender)
cnt, err = testEngine.Table(testEngine.TableName(new(UpdateMapContent))).ID(c.Id).Update(map[string]interface{}{
cnt, err = testEngine.Table(new(UpdateMapContent)).ID(c.Id).Update(map[string]interface{}{
"age": 15,
"is_man": true,
"gender": 1,
@ -1341,3 +1367,50 @@ func TestUpdateMultiplePK(t *testing.T) {
_, err = testEngine.ID(&MySlice{test.Id, test.Name}).Update(test)
assert.NoError(t, err)
}
type TestFieldType1 struct {
cb []byte
}
func (a *TestFieldType1) FromDB(src []byte) error {
a.cb = src
return nil
}
func (a TestFieldType1) ToDB() ([]byte, error) {
return a.cb, nil
}
type TestTable1 struct {
Id int64
Field1 *TestFieldType1 `xorm:"text"`
UpdateTime time.Time
}
func TestNilFromDB(t *testing.T) {
assert.NoError(t, PrepareEngine())
assertSync(t, new(TestTable1))
cnt, err := testEngine.Insert(&TestTable1{
Field1: &TestFieldType1{
cb: []byte("string"),
},
UpdateTime: time.Now(),
})
assert.NoError(t, err)
assert.EqualValues(t, 1, cnt)
cnt, err = testEngine.Update(TestTable1{
UpdateTime: time.Now().Add(time.Second),
}, TestTable1{
Id: 1,
})
assert.NoError(t, err)
assert.EqualValues(t, 1, cnt)
cnt, err = testEngine.Insert(&TestTable1{
UpdateTime: time.Now(),
})
assert.NoError(t, err)
assert.EqualValues(t, 1, cnt)
}

279
integrations/testdata/import1.sql vendored Normal file
View File

@ -0,0 +1,279 @@
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
-- 基本用户信息表
CREATE TABLE IF NOT EXISTS `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
KEY `uid` (`id`),
`user_name` varchar(128) CHARACTER SET utf8mb4 NOT NULL,
KEY `user_name` (`user_name`),
`email` varchar(32) NOT NULL,
KEY `email` (`email`),
`pass` varchar(256) NOT NULL,
`passwd` varchar(16) NOT NULL,
`uuid` TEXT NULL DEFAULT NULL COMMENT 'uuid',
`t` int(11) NOT NULL DEFAULT '0',
`u` bigint(20) NOT NULL,
`d` bigint(20) NOT NULL,
`plan` varchar(2) CHARACTER SET utf8mb4 NOT NULL DEFAULT 'A',
`node_group` INT NOT NULL DEFAULT '0',
`auto_reset_day` INT NOT NULL DEFAULT '0',
`auto_reset_bandwidth` DECIMAL(12,2) NOT NULL DEFAULT '0.00',
`transfer_enable` BIGINT(20) NOT NULL,
`port` int(11) NOT NULL,
`protocol_param` VARCHAR(128) NULL DEFAULT NULL,
`obfs_param` VARCHAR(128) NULL DEFAULT NULL,
`switch` tinyint(4) NOT NULL DEFAULT '1',
`enable` tinyint(4) NOT NULL DEFAULT '1',
`type` tinyint(4) NOT NULL DEFAULT '1',
`last_get_gift_time` int(11) NOT NULL DEFAULT '0',
`last_check_in_time` int(11) NOT NULL DEFAULT '0',
`last_rest_pass_time` int(11) NOT NULL DEFAULT '0',
`reg_date` datetime NOT NULL,
`invite_num` int(8) NOT NULL,
`money` decimal(12,2) NOT NULL,
`ref_by` int(11) NOT NULL DEFAULT '0',
`expire_time` int(11) NOT NULL DEFAULT '0',
`is_email_verify` tinyint(4) NOT NULL DEFAULT '0',
`reg_ip` varchar(128) NOT NULL DEFAULT '127.0.0.1',
`node_speedlimit` DECIMAL(12,2) NOT NULL DEFAULT '0.00',
`node_connector` int(11) NOT NULL DEFAULT '0',
`forbidden_ip` LONGTEXT NULL DEFAULT '',
`forbidden_port` LONGTEXT NULL DEFAULT '',
`disconnect_ip` LONGTEXT NULL DEFAULT '',
`is_hide` INT NOT NULL DEFAULT '0',
`last_detect_ban_time` datetime DEFAULT '1989-06-04 00:05:00',
`all_detect_number` int(11) NOT NULL DEFAULT '0',
`is_multi_user` INT NOT NULL DEFAULT '0',
`telegram_id` BIGINT NULL,
`is_admin` int(2) NOT NULL DEFAULT '0',
`im_type` int(11) DEFAULT '1',
`im_value` text,
`last_day_t` bigint(20) NOT NULL DEFAULT '0',
`mail_notified` int(11) NOT NULL DEFAULT '0',
`class` int(11) NOT NULL DEFAULT '0',
`class_expire` datetime NOT NULL DEFAULT '1989-06-04 00:05:00',
`expire_in` datetime NOT NULL DEFAULT '2099-06-04 00:05:00',
`theme` text NOT NULL,
`ga_token` text NOT NULL,
`ga_enable` int(11) NOT NULL DEFAULT '0',
`pac` LONGTEXT,
`remark` text
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 用户流量信息表
-- TODO: 重写流量信息提取逻辑
CREATE TABLE IF NOT EXISTS `user_traffic_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
`user_id` int(11) NOT NULL,
`u` BIGINT(20) NOT NULL,
`d` BIGINT(20) NOT NULL,
`node_id` int(11) NOT NULL,
`rate` float NOT NULL,
`traffic` varchar(32) NOT NULL,
`log_time` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 用户订阅 TOKEN 信息表
CREATE TABLE IF NOT EXISTS `user_token` (
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
`token` varchar(256) NOT NULL,
`user_id` int(11) NOT NULL,
`create_time` int(11) NOT NULL,
`expire_time` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 充值码使用信息表
CREATE TABLE IF NOT EXISTS `charge_code` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
`code` text NOT NULL,
`type` int(11) NOT NULL,
`number` DECIMAL(11,2) NOT NULL,
`isused` int(11) NOT NULL DEFAULT '0',
`userid` bigint(20) NOT NULL,
`usedatetime` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 邀请码使用信息表
CREATE TABLE IF NOT EXISTS `invite_code` (
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
`code` varchar(128) NOT NULL,
KEY `user_id` (`user_id`),
`user_id` int(11) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT '2016-06-01 00:00:00'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 公告信息表
CREATE TABLE IF NOT EXISTS `announcement` (
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
`date` datetime NOT NULL,
`content` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`markdown` LONGTEXT NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 节点信息表
CREATE TABLE IF NOT EXISTS `node` (
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
`name` varchar(128) NOT NULL,
`type` int(3) NOT NULL,
`online_user` int(11) NOT NULL,
`mu_only` INT NULL DEFAULT '0',
`online` BOOLEAN NOT NULL DEFAULT TRUE,
`server` varchar(128) NOT NULL,
`method` varchar(64) NOT NULL,
`info` varchar(128) NOT NULL,
`status` varchar(128) NOT NULL,
`node_group` INT NOT NULL DEFAULT '0',
`sort` int(3) NOT NULL,
`custom_method` tinyint(1) NOT NULL DEFAULT '0',
`traffic_rate` float NOT NULL DEFAULT '1',
`node_class` int(11) NOT NULL DEFAULT '0',
`node_speedlimit` DECIMAL(12,2) NOT NULL DEFAULT '0.00',
`node_connector` int(11) NOT NULL DEFAULT '0',
`node_bandwidth` bigint(20) NOT NULL DEFAULT '0',
`node_bandwidth_limit` bigint(20) NOT NULL DEFAULT '0',
`bandwidthlimit_resetday` int(11) NOT NULL DEFAULT '0',
`node_heartbeat` bigint(20) NOT NULL DEFAULT '0',
`node_ip` text
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- TODO: 修改 VPN 节点的结算说明
-- 商店数据表
CREATE TABLE `shop` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`name` TEXT NOT NULL,
`price` DECIMAL(12,2) NOT NULL,
`content` TEXT NOT NULL,
`auto_renew` INT NOT NULL,
`status` INT NOT NULL DEFAULT '1',
`auto_reset_bandwidth` INT NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 优惠券数据表
CREATE TABLE `coupon` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`code` TEXT NOT NULL,
`onetime` INT NOT NULL,
`expire` BIGINT NOT NULL,
`shop` TEXT NOT NULL,
`credit` INT NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 购买记录数据表
CREATE TABLE `bought` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`userid` BIGINT NOT NULL,
`shopid` BIGINT NOT NULL,
`coupon` TEXT NOT NULL,
`datetime` BIGINT NOT NULL,
`renew` BIGINT(11) NOT NULL,
`price` DECIMAL(12,2) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 工单数据表
CREATE TABLE `ticket` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`title` LONGTEXT NOT NULL,
`status` INT NOT NULL DEFAULT '1',
`content` LONGTEXT NOT NULL,
`rootid` BIGINT NOT NULL,`userid` BIGINT NOT NULL,
`datetime` BIGINT NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 返利记录数据表
CREATE TABLE `payback` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`total` DECIMAL(12,2) NOT NULL,
`userid` BIGINT NOT NULL,
`ref_by` BIGINT NOT NULL,
`ref_get` DECIMAL(12,2) NOT NULL,
`datetime` BIGINT NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 审计规则数据表
CREATE TABLE `detect_list` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`name` LONGTEXT NOT NULL,
`type` INT NOT NULL,
`text` LONGTEXT NOT NULL,
`regex` LONGTEXT NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 审计记录数据表
CREATE TABLE `detect_log` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`user_id` BIGINT NOT NULL,
`node_id` INT NOT NULL,
`list_id` BIGINT NOT NULL,
`datetime` BIGINT NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 中转规则数据表
CREATE TABLE IF NOT EXISTS `relay` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
`user_id` bigint(20) NOT NULL,
`source_node_id` bigint(20) NOT NULL,
`dist_node_id` bigint(20) NOT NULL,
`dist_ip` text NOT NULL,
`port` int(11) NOT NULL,
`priority` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 用户订阅日志
CREATE TABLE IF NOT EXISTS `user_subscribe_log` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_name` varchar(128) NOT NULL COMMENT '用户名',
`user_id` int(11) NOT NULL COMMENT '用户 ID',
`email` varchar(32) NOT NULL COMMENT '用户邮箱',
`subscribe_type` varchar(20) NOT NULL COMMENT '获取的订阅类型',
`request_ip` varchar(128) NOT NULL COMMENT '请求 IP',
`request_time` datetime NOT NULL COMMENT '请求时间',
`request_user_agent` text COMMENT '请求 UA 信息',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户订阅日志';
-- 审计封禁日志
CREATE TABLE IF NOT EXISTS `detect_ban_log` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_name` varchar(128) NOT NULL COMMENT '用户名',
`user_id` int(11) NOT NULL COMMENT '用户 ID',
`email` varchar(32) NOT NULL COMMENT '用户邮箱',
`detect_number` int(11) NOT NULL COMMENT '本次违规次数',
`ban_time` int(11) NOT NULL COMMENT '本次封禁时长',
`start_time` bigint(20) NOT NULL COMMENT '统计开始时间',
`end_time` bigint(20) NOT NULL COMMENT '统计结束时间',
`all_detect_number` int(11) NOT NULL COMMENT '累计违规次数',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审计封禁日志';
-- 管理员操作记录
CREATE TABLE IF NOT EXISTS `gconfig` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`key` varchar(128) NOT NULL COMMENT '配置键名',
`type` varchar(32) NOT NULL COMMENT '值类型',
`value` text NOT NULL COMMENT '配置值',
`oldvalue` text NOT NULL COMMENT '之前的配置值',
`name` varchar(128) NOT NULL COMMENT '配置名称',
`comment` text NOT NULL COMMENT '配置描述',
`operator_id` int(11) NOT NULL COMMENT '操作员 ID',
`operator_name` varchar(128) NOT NULL COMMENT '操作员名称',
`operator_email` varchar(32) NOT NULL COMMENT '操作员邮箱',
`last_update` bigint(20) NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='网站配置';

View File

@ -8,6 +8,7 @@ import (
"database/sql"
"flag"
"fmt"
"net/url"
"os"
"strings"
"testing"
@ -35,7 +36,10 @@ var (
schema = flag.String("schema", "", "specify the schema")
ignoreSelectUpdate = flag.Bool("ignore_select_update", false, "ignore select update if implementation difference, only for tidb")
ingoreUpdateLimit = flag.Bool("ignore_update_limit", false, "ignore update limit if implementation difference, only for cockroach")
doNVarcharTest = flag.Bool("do_nvarchar_override_test", false, "do nvarchar override test in sync table, only for mssql")
quotePolicyStr = flag.String("quote", "always", "quote could be always, none, reversed")
defaultVarchar = flag.String("default_varchar", "varchar", "default varchar type, mssql only, could be varchar or nvarchar, default is varchar")
defaultChar = flag.String("default_char", "char", "default char type, mssql only, could be char or nchar, default is char")
tableMapper names.Mapper
colMapper names.Mapper
)
@ -94,6 +98,13 @@ func createEngine(dbType, connStr string) error {
return fmt.Errorf("db.Exec: %v", err)
}
db.Close()
case schemas.SQLITE, "sqlite":
u, err := url.Parse(connStr)
if err != nil {
return err
}
connStr = u.Path
*ignoreSelectUpdate = true
default:
*ignoreSelectUpdate = true
}
@ -137,6 +148,11 @@ func createEngine(dbType, connStr string) error {
} else {
testEngine.SetQuotePolicy(dialects.QuotePolicyAlways)
}
testEngine.Dialect().SetParams(map[string]string{
"DEFAULT_VARCHAR": *defaultVarchar,
"DEFAULT_CHAR": *defaultChar,
})
}
tableMapper = testEngine.GetTableMapper()
@ -156,17 +172,25 @@ func createEngine(dbType, connStr string) error {
return nil
}
// PrepareEngine prepare tests ORM engine
func PrepareEngine() error {
return createEngine(dbType, connString)
}
// MainTest the tests entrance
func MainTest(m *testing.M) {
flag.Parse()
dbType = *db
if *db == "sqlite3" {
if ptrConnStr == nil {
connString = "./test.db?cache=shared&mode=rwc"
connString = "./test_sqlite3.db?cache=shared&mode=rwc"
} else {
connString = *ptrConnStr
}
} else if *db == "sqlite" {
if ptrConnStr == nil {
connString = "./test_sqlite.db?cache=shared&mode=rwc"
} else {
connString = *ptrConnStr
}

View File

@ -375,3 +375,30 @@ func TestCustomType2(t *testing.T) {
fmt.Println(users)
}
func TestUnsigned(t *testing.T) {
type MyUnsignedStruct struct {
Id uint64
}
assert.NoError(t, PrepareEngine())
assertSync(t, new(MyUnsignedStruct))
tables, err := testEngine.DBMetas()
assert.NoError(t, err)
assert.EqualValues(t, 1, len(tables))
assert.EqualValues(t, 1, len(tables[0].Columns()))
switch testEngine.Dialect().URI().DBType {
case schemas.SQLITE:
assert.EqualValues(t, "INTEGER", tables[0].Columns()[0].SQLType.Name)
case schemas.MYSQL:
assert.EqualValues(t, "UNSIGNED BIGINT", tables[0].Columns()[0].SQLType.Name)
case schemas.POSTGRES:
assert.EqualValues(t, "BIGINT", tables[0].Columns()[0].SQLType.Name)
case schemas.MSSQL:
assert.EqualValues(t, "BIGINT", tables[0].Columns()[0].SQLType.Name)
default:
assert.False(t, true, "Unsigned is not implemented")
}
}

View File

@ -101,6 +101,7 @@ type EngineInterface interface {
SetCacher(string, caches.Cacher)
SetConnMaxLifetime(time.Duration)
SetColumnMapper(names.Mapper)
SetTagIdentifier(string)
SetDefaultCacher(caches.Cacher)
SetLogger(logger interface{})
SetLogLevel(log.LogLevel)
@ -120,6 +121,7 @@ type EngineInterface interface {
TableInfo(bean interface{}) (*schemas.Table, error)
TableName(interface{}, ...bool) string
UnMapType(reflect.Type)
EnableSessionID(bool)
}
var (

View File

@ -6,15 +6,15 @@ package json
import "encoding/json"
// JSONInterface represents an interface to handle json data
type JSONInterface interface {
// Interface represents an interface to handle json data
type Interface interface {
Marshal(v interface{}) ([]byte, error)
Unmarshal(data []byte, v interface{}) error
}
var (
// DefaultJSONHandler default json handler
DefaultJSONHandler JSONInterface = StdJSON{}
DefaultJSONHandler Interface = StdJSON{}
)
// StdJSON implements JSONInterface via encoding/json

View File

@ -20,6 +20,21 @@ var (
uintType = reflect.TypeOf(uint64(0))
)
// ErrIDConditionWithNoTable represents an error there is no reference table with an ID condition
type ErrIDConditionWithNoTable struct {
ID schemas.PK
}
func (err ErrIDConditionWithNoTable) Error() string {
return fmt.Sprintf("ID condition %#v need reference table", err.ID)
}
// IsIDConditionWithNoTableErr return true if the err is ErrIDConditionWithNoTable
func IsIDConditionWithNoTableErr(err error) bool {
_, ok := err.(ErrIDConditionWithNoTable)
return ok
}
// ID generate "where id = ? " statement or for composite key "where key1 = ? and key2 = ?"
func (statement *Statement) ID(id interface{}) *Statement {
switch t := id.(type) {
@ -58,13 +73,17 @@ func (statement *Statement) ID(id interface{}) *Statement {
return statement
}
// ProcessIDParam handles the process of id condition
func (statement *Statement) ProcessIDParam() error {
if statement.idParam == nil || statement.RefTable == nil {
if statement.idParam == nil {
return nil
}
if statement.RefTable == nil {
return ErrIDConditionWithNoTable{statement.idParam}
}
if len(statement.RefTable.PrimaryKeys) != len(statement.idParam) {
fmt.Println("=====", statement.RefTable.PrimaryKeys, statement.idParam)
return fmt.Errorf("ID condition is error, expect %d primarykeys, there are %d",
len(statement.RefTable.PrimaryKeys),
len(statement.idParam),

View File

@ -704,7 +704,7 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{},
col.SQLType.IsBlob() || col.SQLType.Name == schemas.TimeStampz) {
continue
}
if col.SQLType.IsJson() {
if col.IsJSON {
continue
}
@ -813,7 +813,7 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{},
continue
}
} else {
if col.SQLType.IsJson() {
if col.IsJSON {
if col.SQLType.IsText() {
bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface())
if err != nil {

View File

@ -130,7 +130,7 @@ func (statement *Statement) BuildUpdates(tableValue reflect.Value,
}
}
if structConvert, ok := fieldValue.Interface().(convert.Conversion); ok {
if structConvert, ok := fieldValue.Interface().(convert.Conversion); ok && !fieldValue.IsNil() {
data, err := structConvert.ToDB()
if err != nil {
return nil, nil, err
@ -204,7 +204,7 @@ func (statement *Statement) BuildUpdates(tableValue reflect.Value,
continue
}
} else {
if !col.SQLType.IsJson() {
if !col.IsJSON {
table, err := statement.tagParser.ParseWithCache(fieldValue)
if err != nil {
val = fieldValue.Interface()

View File

@ -36,6 +36,8 @@ func (statement *Statement) Value2Interface(col *schemas.Column, fieldValue refl
}
}
isNil := fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil()
if !isNil {
if fieldConvert, ok := fieldValue.Interface().(convert.Conversion); ok {
data, err := fieldConvert.ToDB()
if err != nil {
@ -49,6 +51,7 @@ func (statement *Statement) Value2Interface(col *schemas.Column, fieldValue refl
}
return string(data), nil
}
}
fieldType := fieldValue.Type()
k := fieldType.Kind()
@ -83,7 +86,7 @@ func (statement *Statement) Value2Interface(col *schemas.Column, fieldValue refl
return t.Float64, nil
}
if !col.SQLType.IsJson() {
if !col.IsJSON {
// !<winxxp>! 增加支持driver.Valuer接口的结构如sql.NullString
if v, ok := fieldValue.Interface().(driver.Valuer); ok {
return v.Value()

View File

@ -8,6 +8,7 @@ import (
"fmt"
)
// IndexName returns index name
func IndexName(tableName, idxName string) string {
return fmt.Sprintf("IDX_%v_%v", tableName, idxName)
}

View File

@ -8,6 +8,7 @@ import (
"reflect"
)
// ReflectValue returns value of a bean
func ReflectValue(bean interface{}) reflect.Value {
return reflect.Indirect(reflect.ValueOf(bean))
}

View File

@ -8,6 +8,7 @@ import (
"strings"
)
// IsSubQuery returns true if it contains a sub query
func IsSubQuery(tbName string) bool {
const selStr = "select"
if len(tbName) <= len(selStr)+1 {

View File

@ -8,10 +8,12 @@ import (
"strings"
)
// IndexNoCase index a string in a string with no care of capitalize
func IndexNoCase(s, sep string) int {
return strings.Index(strings.ToLower(s), strings.ToLower(sep))
}
// SplitNoCase split a string by a seperator with no care of capitalize
func SplitNoCase(s, sep string) []string {
idx := IndexNoCase(s, sep)
if idx < 0 {
@ -20,6 +22,7 @@ func SplitNoCase(s, sep string) []string {
return strings.Split(s, s[idx:idx+len(sep)])
}
// SplitNNoCase split n by a seperator with no care of capitalize
func SplitNNoCase(s, sep string, n int) []string {
idx := IndexNoCase(s, sep)
if idx < 0 {
@ -27,4 +30,3 @@ func SplitNNoCase(s, sep string, n int) []string {
}
return strings.SplitN(s, s[idx:idx+len(sep)], n)
}

View File

@ -9,6 +9,7 @@ import (
"time"
)
// Zeroable represents an interface which could know if it's a zero value
type Zeroable interface {
IsZero() bool
}
@ -21,39 +22,39 @@ func IsZero(k interface{}) bool {
return true
}
switch k.(type) {
switch t := k.(type) {
case int:
return k.(int) == 0
return t == 0
case int8:
return k.(int8) == 0
return t == 0
case int16:
return k.(int16) == 0
return t == 0
case int32:
return k.(int32) == 0
return t == 0
case int64:
return k.(int64) == 0
return t == 0
case uint:
return k.(uint) == 0
return t == 0
case uint8:
return k.(uint8) == 0
return t == 0
case uint16:
return k.(uint16) == 0
return t == 0
case uint32:
return k.(uint32) == 0
return t == 0
case uint64:
return k.(uint64) == 0
return t == 0
case float32:
return k.(float32) == 0
return t == 0
case float64:
return k.(float64) == 0
return t == 0
case bool:
return k.(bool) == false
return !t
case string:
return k.(string) == ""
return t == ""
case *time.Time:
return k.(*time.Time) == nilTime || IsTimeZero(*k.(*time.Time))
return t == nilTime || IsTimeZero(*t)
case time.Time:
return IsTimeZero(k.(time.Time))
return IsTimeZero(t)
case Zeroable:
return k.(Zeroable) == nil || k.(Zeroable).IsZero()
case reflect.Value: // for go version less than 1.13 because reflect.Value has no method IsZero
@ -65,6 +66,7 @@ func IsZero(k interface{}) bool {
var zeroType = reflect.TypeOf((*Zeroable)(nil)).Elem()
// IsValueZero returns true if the reflect Value is a zero
func IsValueZero(v reflect.Value) bool {
switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Slice:
@ -88,6 +90,7 @@ func IsValueZero(v reflect.Value) bool {
return false
}
// IsStructZero returns true if the Value is a struct and all fields is zero
func IsStructZero(v reflect.Value) bool {
if !v.IsValid() || v.NumField() == 0 {
return true
@ -120,6 +123,7 @@ func IsStructZero(v reflect.Value) bool {
return true
}
// IsArrayZero returns true is a slice of array is zero
func IsArrayZero(v reflect.Value) bool {
if !v.IsValid() || v.Len() == 0 {
return true
@ -134,11 +138,13 @@ func IsArrayZero(v reflect.Value) bool {
return true
}
// represents all zero times
const (
ZeroTime0 = "0000-00-00 00:00:00"
ZeroTime1 = "0001-01-01 00:00:00"
)
// IsTimeZero return true if a time is zero
func IsTimeZero(t time.Time) bool {
return t.IsZero() || t.Format("2006-01-02 15:04:05") == ZeroTime0 ||
t.Format("2006-01-02 15:04:05") == ZeroTime1

View File

@ -42,6 +42,7 @@ var (
// enumerate all the context keys
var (
SessionIDKey = "__xorm_session_id"
SessionKey = "__xorm_session_key"
SessionShowSQLKey = "__xorm_show_sql"
)

View File

@ -110,10 +110,7 @@ func (m *Migrate) RollbackLast() error {
return err
}
if err := m.RollbackMigration(lastRunnedMigration); err != nil {
return err
}
return nil
return m.RollbackMigration(lastRunnedMigration)
}
func (m *Migrate) getLastRunnedMigration() (*Migration, error) {

View File

@ -106,10 +106,7 @@ func TestInitSchema(t *testing.T) {
if err := tx.Sync2(&Person{}); err != nil {
return err
}
if err := tx.Sync2(&Pet{}); err != nil {
return err
}
return nil
return tx.Sync2(&Pet{})
})
err = m.Migrate()

View File

@ -7,6 +7,7 @@ package names
import (
"strings"
"sync"
"unsafe"
)
// Mapper represents a name convertation between struct's fields name and table's column name
@ -77,19 +78,24 @@ func (m SameMapper) Table2Obj(t string) string {
type SnakeMapper struct {
}
func snakeCasedName(name string) string {
newstr := make([]rune, 0)
for idx, chr := range name {
if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
if idx > 0 {
newstr = append(newstr, '_')
}
chr -= ('A' - 'a')
}
newstr = append(newstr, chr)
func b2s(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
return string(newstr)
func snakeCasedName(name string) string {
newstr := make([]byte, 0, len(name)+1)
for i := 0; i < len(name); i++ {
c := name[i]
if isUpper := 'A' <= c && c <= 'Z'; isUpper {
if i > 0 {
newstr = append(newstr, '_')
}
c += 'a' - 'A'
}
newstr = append(newstr, c)
}
return b2s(newstr)
}
func (mapper SnakeMapper) Obj2Table(name string) string {
@ -97,27 +103,28 @@ func (mapper SnakeMapper) Obj2Table(name string) string {
}
func titleCasedName(name string) string {
newstr := make([]rune, 0)
newstr := make([]byte, 0, len(name))
upNextChar := true
name = strings.ToLower(name)
for _, chr := range name {
for i := 0; i < len(name); i++ {
c := name[i]
switch {
case upNextChar:
upNextChar = false
if 'a' <= chr && chr <= 'z' {
chr -= ('a' - 'A')
if 'a' <= c && c <= 'z' {
c -= 'a' - 'A'
}
case chr == '_':
case c == '_':
upNextChar = true
continue
}
newstr = append(newstr, chr)
newstr = append(newstr, c)
}
return string(newstr)
return b2s(newstr)
}
func (mapper SnakeMapper) Table2Obj(name string) string {

View File

@ -5,6 +5,7 @@
package names
import (
"strings"
"testing"
)
@ -47,3 +48,23 @@ func TestGonicMapperToObj(t *testing.T) {
}
}
}
func BenchmarkSnakeCasedName(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
s := strings.Repeat("FooBar", 32)
for i := 0; i < b.N; i++ {
_ = snakeCasedName(s)
}
}
func BenchmarkTitleCasedName(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
s := strings.Repeat("foo_bar", 32)
for i := 0; i < b.N; i++ {
_ = titleCasedName(s)
}
}

View File

@ -5,8 +5,10 @@
package schemas
import (
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"time"
)
@ -49,6 +51,7 @@ type Column struct {
func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable bool) *Column {
return &Column{
Name: name,
IsJSON: sqlType.IsJson(),
TableName: "",
FieldName: fieldName,
SQLType: sqlType,
@ -115,3 +118,17 @@ func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) {
return &fieldValue, nil
}
// ConvertID converts id content to suitable type according column type
func (col *Column) ConvertID(sid string) (interface{}, error) {
if col.SQLType.IsNumeric() {
n, err := strconv.ParseInt(sid, 10, 64)
if err != nil {
return nil, err
}
return n, nil
} else if col.SQLType.IsText() {
return sid, nil
}
return nil, errors.New("not supported")
}

View File

@ -82,10 +82,8 @@ func (q Quoter) JoinWrite(b *strings.Builder, a []string, sep string) error {
return err
}
}
if s != "*" {
q.QuoteTo(b, strings.TrimSpace(s))
}
}
return nil
}
@ -143,7 +141,7 @@ func (q Quoter) quoteWordTo(buf *strings.Builder, word string) error {
}
isReserved := q.IsReserved(realWord)
if isReserved {
if isReserved && realWord != "*" {
if err := buf.WriteByte(q.Prefix); err != nil {
return err
}
@ -151,7 +149,7 @@ func (q Quoter) quoteWordTo(buf *strings.Builder, word string) error {
if _, err := buf.WriteString(realWord); err != nil {
return err
}
if isReserved {
if isReserved && realWord != "*" {
return buf.WriteByte(q.Suffix)
}

View File

@ -22,6 +22,7 @@ func TestAlwaysQuoteTo(t *testing.T) {
{"[mytable]", "`mytable`"},
{"[mytable]", `[mytable]`},
{`["mytable"]`, `"mytable"`},
{`[mytable].*`, `[mytable].*`},
{"[myschema].[mytable]", "myschema.mytable"},
{"[myschema].[mytable]", "`myschema`.mytable"},
{"[myschema].[mytable]", "myschema.`mytable`"},
@ -65,6 +66,7 @@ func TestReversedQuoteTo(t *testing.T) {
{"[mytable]", "mytable"},
{"[mytable]", "`mytable`"},
{"[mytable]", `[mytable]`},
{"[mytable].*", `[mytable].*`},
{`"mytable"`, `"mytable"`},
{"myschema.[mytable]", "myschema.mytable"},
{"myschema.[mytable]", "`myschema`.mytable"},
@ -98,6 +100,7 @@ func TestNoQuoteTo(t *testing.T) {
{"mytable", "mytable"},
{"mytable", "`mytable`"},
{"mytable", `[mytable]`},
{"mytable.*", `[mytable].*`},
{`"mytable"`, `"mytable"`},
{"myschema.mytable", "myschema.mytable"},
{"myschema.mytable", "`myschema`.mytable"},
@ -127,6 +130,8 @@ func TestJoin(t *testing.T) {
assert.EqualValues(t, "[a],[b]", quoter.Join([]string{"a", " b"}, ","))
assert.EqualValues(t, "[a].*,[b].[c]", quoter.Join([]string{"a.*", " b.c"}, ","))
assert.EqualValues(t, "[f1], [f2], [f3]", quoter.Join(cols, ", "))
quoter.IsReserved = AlwaysNoReserve
@ -134,11 +139,11 @@ func TestJoin(t *testing.T) {
}
func TestStrings(t *testing.T) {
cols := []string{"f1", "f2", "t3.f3"}
cols := []string{"f1", "f2", "t3.f3", "t4.*"}
quoter := Quoter{'[', ']', AlwaysReserve}
quotedCols := quoter.Strings(cols)
assert.EqualValues(t, []string{"[f1]", "[f2]", "[t3].[f3]"}, quotedCols)
assert.EqualValues(t, []string{"[f1]", "[f2]", "[t3].[f3]", "[t4].*"}, quotedCols)
}
func TestTrim(t *testing.T) {

View File

@ -5,7 +5,9 @@
package schemas
import (
"fmt"
"reflect"
"strconv"
"strings"
)
@ -28,6 +30,7 @@ type Table struct {
Comment string
}
// NewEmptyTable creates an empty table
func NewEmptyTable() *Table {
return NewTable("", nil)
}
@ -44,23 +47,21 @@ func NewTable(name string, t reflect.Type) *Table {
}
}
// Columns returns table's columns
func (table *Table) Columns() []*Column {
return table.columns
}
// ColumnsSeq returns table's column names according sequence
func (table *Table) ColumnsSeq() []string {
return table.columnsSeq
}
func (table *Table) columnsByName(name string) []*Column {
for k, cols := range table.columnsMap {
if strings.EqualFold(k, name) {
return cols
}
}
return nil
return table.columnsMap[strings.ToLower(name)]
}
// GetColumn returns column according column name, if column not found, return nil
func (table *Table) GetColumn(name string) *Column {
cols := table.columnsByName(name)
if cols != nil {
@ -70,6 +71,7 @@ func (table *Table) GetColumn(name string) *Column {
return nil
}
// GetColumnIdx returns column according name and idx
func (table *Table) GetColumnIdx(name string, idx int) *Column {
cols := table.columnsByName(name)
if cols != nil && idx < len(cols) {
@ -144,3 +146,45 @@ func (table *Table) AddColumn(col *Column) {
func (table *Table) AddIndex(index *Index) {
table.Indexes[index.Name] = index
}
// IDOfV get id from one value of struct
func (table *Table) IDOfV(rv reflect.Value) (PK, error) {
v := reflect.Indirect(rv)
pk := make([]interface{}, len(table.PrimaryKeys))
for i, col := range table.PKColumns() {
var err error
fieldName := col.FieldName
for {
parts := strings.SplitN(fieldName, ".", 2)
if len(parts) == 1 {
break
}
v = v.FieldByName(parts[0])
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("Unsupported read value of column %s from field %s", col.Name, col.FieldName)
}
fieldName = parts[1]
}
pkField := v.FieldByName(fieldName)
switch pkField.Kind() {
case reflect.String:
pk[i], err = col.ConvertID(pkField.String())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
pk[i], err = col.ConvertID(strconv.FormatInt(pkField.Int(), 10))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
// id of uint will be converted to int64
pk[i], err = col.ConvertID(strconv.FormatUint(pkField.Uint(), 10))
}
if err != nil {
return nil, err
}
}
return PK(pk), nil
}

View File

@ -68,14 +68,21 @@ func (s *SQLType) IsJson() bool {
return s.Name == Json || s.Name == Jsonb
}
func (s *SQLType) IsXML() bool {
return s.Name == XML
}
var (
Bit = "BIT"
UnsignedBit = "UNSIGNED BIT"
TinyInt = "TINYINT"
SmallInt = "SMALLINT"
MediumInt = "MEDIUMINT"
Int = "INT"
UnsignedInt = "UNSIGNED INT"
Integer = "INTEGER"
BigInt = "BIGINT"
UnsignedBigInt = "UNSIGNED BIGINT"
Enum = "ENUM"
Set = "SET"
@ -128,22 +135,28 @@ var (
Json = "JSON"
Jsonb = "JSONB"
XML = "XML"
Array = "ARRAY"
SqlTypes = map[string]int{
Bit: NUMERIC_TYPE,
UnsignedBit: NUMERIC_TYPE,
TinyInt: NUMERIC_TYPE,
SmallInt: NUMERIC_TYPE,
MediumInt: NUMERIC_TYPE,
Int: NUMERIC_TYPE,
UnsignedInt: NUMERIC_TYPE,
Integer: NUMERIC_TYPE,
BigInt: NUMERIC_TYPE,
UnsignedBigInt: NUMERIC_TYPE,
Enum: TEXT_TYPE,
Set: TEXT_TYPE,
Json: TEXT_TYPE,
Jsonb: TEXT_TYPE,
XML: TEXT_TYPE,
Char: TEXT_TYPE,
NChar: TEXT_TYPE,
Varchar: TEXT_TYPE,
@ -273,10 +286,14 @@ var (
// Type2SQLType generate SQLType acorrding Go's type
func Type2SQLType(t reflect.Type) (st SQLType) {
switch k := t.Kind(); k {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
st = SQLType{Int, 0, 0}
case reflect.Int64, reflect.Uint64:
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
st = SQLType{UnsignedInt, 0, 0}
case reflect.Int64:
st = SQLType{BigInt, 0, 0}
case reflect.Uint64:
st = SQLType{UnsignedBigInt, 0, 0}
case reflect.Float32:
st = SQLType{Float, 0, 0}
case reflect.Float64:

View File

@ -107,7 +107,7 @@ func newSession(engine *Engine) *Session {
ctx = engine.defaultContext
}
return &Session{
session := &Session{
ctx: ctx,
engine: engine,
tx: nil,
@ -136,6 +136,10 @@ func newSession(engine *Engine) *Session {
sessionType: engineSession,
}
if engine.logSessionID {
session.ctx = context.WithValue(session.ctx, log.SessionKey, session)
}
return session
}
// Close release the connection from pool
@ -165,6 +169,11 @@ func (session *Session) db() *core.DB {
return session.engine.db
}
// Engine returns session Engine
func (session *Session) Engine() *Engine {
return session.engine
}
func (session *Session) getQueryer() core.Queryer {
if session.tx != nil {
return session.tx
@ -495,7 +504,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b
fieldType := fieldValue.Type()
hasAssigned := false
if col.SQLType.IsJson() {
if col.IsJSON {
var bs []byte
if rawValueType.Kind() == reflect.String {
bs = []byte(vv.String())
@ -675,7 +684,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b
session.engine.logger.Errorf("sql.Sanner error: %v", err)
hasAssigned = false
}
} else if col.SQLType.IsJson() {
} else if col.IsJSON {
if rawValueType.Kind() == reflect.String {
hasAssigned = true
x := reflect.New(fieldType)
@ -887,7 +896,7 @@ func (session *Session) incrVersionFieldValue(fieldValue *reflect.Value) {
}
}
// ContextHook sets the context on this session
// Context sets the context on this session
func (session *Session) Context(ctx context.Context) *Session {
session.ctx = ctx
return session

View File

@ -57,9 +57,18 @@ func (session *Session) FindAndCount(rowsSlicePtr interface{}, condiBean ...inte
if session.statement.SelectStr != "" {
session.statement.SelectStr = ""
}
if len(session.statement.ColumnMap) > 0 {
session.statement.ColumnMap = []string{}
}
if session.statement.OrderStr != "" {
session.statement.OrderStr = ""
}
if session.statement.LimitN != nil {
session.statement.LimitN = nil
}
if session.statement.Start > 0 {
session.statement.Start = 0
}
// session has stored the conditions so we use `unscoped` to avoid duplicated condition.
return session.Unscoped().Count(reflect.New(sliceElementType).Interface())
@ -108,8 +117,11 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{})
)
if tp == tpStruct {
if !session.statement.NoAutoCondition && len(condiBean) > 0 {
var err error
autoCond, err = session.statement.BuildConds(table, condiBean[0], true, true, false, true, addedTableName)
condTable, err := session.engine.tagParser.Parse(reflect.ValueOf(condiBean[0]))
if err != nil {
return err
}
autoCond, err = session.statement.BuildConds(condTable, condiBean[0], true, true, false, true, addedTableName)
if err != nil {
return err
}
@ -317,7 +329,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
}
var pk schemas.PK = make([]interface{}, len(table.PrimaryKeys))
for i, col := range table.PKColumns() {
pk[i], err = session.engine.idTypeAssertion(col, res[i])
pk[i], err = col.ConvertID(res[i])
if err != nil {
return err
}
@ -367,7 +379,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
} else {
session.engine.logger.Debugf("[cache] cache hit bean: %v, %v, %v", tableName, id, bean)
pk, err := session.engine.IDOf(bean)
pk, err := table.IDOfV(reflect.ValueOf(bean))
if err != nil {
return err
}
@ -416,7 +428,6 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
if err != nil {
return err
}
session.statement = statement
vs := reflect.Indirect(reflect.ValueOf(beans))
@ -425,7 +436,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
if rv.Kind() != reflect.Ptr {
rv = rv.Addr()
}
id, err := session.engine.idOfV(rv)
id, err := table.IDOfV(rv)
if err != nil {
return err
}

View File

@ -16,6 +16,11 @@ import (
"xorm.io/xorm/schemas"
)
var (
// ErrObjectIsNil return error of object is nil
ErrObjectIsNil = errors.New("object should not be nil")
)
// Get retrieve one record from database, bean's non-empty fields
// will be as conditions
func (session *Session) Get(bean interface{}) (bool, error) {
@ -37,6 +42,8 @@ func (session *Session) get(bean interface{}) (bool, error) {
return false, errors.New("needs a pointer to a value")
} else if beanValue.Elem().Kind() == reflect.Ptr {
return false, errors.New("a pointer to a pointer is not allowed")
} else if beanValue.IsNil() {
return false, ErrObjectIsNil
}
if beanValue.Elem().Kind() == reflect.Struct {

View File

@ -448,16 +448,30 @@ func (session *Session) ImportFile(ddlPath string) ([]sql.Result, error) {
// Import SQL DDL from io.Reader
func (session *Session) Import(r io.Reader) ([]sql.Result, error) {
var results []sql.Result
var lastError error
scanner := bufio.NewScanner(r)
var (
results []sql.Result
lastError error
inSingleQuote bool
startComment bool
)
var inSingleQuote bool
scanner := bufio.NewScanner(r)
semiColSpliter := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
var oriInSingleQuote = inSingleQuote
for i, b := range data {
if startComment {
if b == '\n' {
startComment = false
}
} else {
if i > 0 && data[i-1] == '-' && data[i] == '-' {
startComment = true
continue
}
if b == '\'' {
inSingleQuote = !inSingleQuote
}
@ -465,10 +479,12 @@ func (session *Session) Import(r io.Reader) ([]sql.Result, error) {
return i + 1, data[0:i], nil
}
}
}
// If we're at EOF, we have a final, non-terminated line. Return it.
if atEOF {
return len(data), data, nil
}
inSingleQuote = oriInSingleQuote
// Request more data.
return 0, nil, nil
}
@ -479,10 +495,10 @@ func (session *Session) Import(r io.Reader) ([]sql.Result, error) {
query := strings.Trim(scanner.Text(), " \t\n\r")
if len(query) > 0 {
result, err := session.Exec(query)
results = append(results, result)
if err != nil {
return nil, err
}
results = append(results, result)
}
}

View File

@ -4,12 +4,6 @@
package xorm
import (
"time"
"xorm.io/xorm/log"
)
// Begin a transaction
func (session *Session) Begin() error {
if session.isAutoCommit {
@ -33,24 +27,7 @@ func (session *Session) Rollback() error {
session.isCommitedOrRollbacked = true
session.isAutoCommit = true
start := time.Now()
needSQL := session.DB().NeedLogSQL(session.ctx)
if needSQL {
session.engine.logger.BeforeSQL(log.LogContext{
Ctx: session.ctx,
SQL: "ROLL BACK",
})
}
err := session.tx.Rollback()
if needSQL {
session.engine.logger.AfterSQL(log.LogContext{
Ctx: session.ctx,
SQL: "ROLL BACK",
ExecuteTime: time.Now().Sub(start),
Err: err,
})
}
return err
return session.tx.Rollback()
}
return nil
}
@ -62,25 +39,7 @@ func (session *Session) Commit() error {
session.isCommitedOrRollbacked = true
session.isAutoCommit = true
start := time.Now()
needSQL := session.DB().NeedLogSQL(session.ctx)
if needSQL {
session.engine.logger.BeforeSQL(log.LogContext{
Ctx: session.ctx,
SQL: "COMMIT",
})
}
err := session.tx.Commit()
if needSQL {
session.engine.logger.AfterSQL(log.LogContext{
Ctx: session.ctx,
SQL: "COMMIT",
ExecuteTime: time.Now().Sub(start),
Err: err,
})
}
if err != nil {
if err := session.tx.Commit(); err != nil {
return err
}
@ -125,3 +84,8 @@ func (session *Session) Commit() error {
}
return nil
}
// IsInTx if current session is in a transaction
func (session *Session) IsInTx() bool {
return !session.isAutoCommit
}

View File

@ -206,7 +206,11 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
colNames = append(colNames, session.engine.Quote(table.Updated)+" = ?")
col := table.UpdatedColumn()
val, t := session.engine.nowTime(col)
if session.engine.dialect.URI().DBType == schemas.ORACLE {
args = append(args, t)
} else {
args = append(args, val)
}
var colName = col.Name
if isStruct {
@ -269,8 +273,15 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
k = ct.Elem().Kind()
}
if k == reflect.Struct {
var refTable = session.statement.RefTable
if refTable == nil {
refTable, err = session.engine.TableInfo(condiBean[0])
if err != nil {
return 0, err
}
}
var err error
autoCond, err = session.statement.BuildConds(session.statement.RefTable, condiBean[0], true, true, false, true, false)
autoCond, err = session.statement.BuildConds(refTable, condiBean[0], true, true, false, true, false)
if err != nil {
return 0, err
}

View File

@ -63,6 +63,11 @@ func (parser *Parser) SetColumnMapper(mapper names.Mapper) {
parser.columnMapper = mapper
}
func (parser *Parser) SetIdentifier(identifier string) {
parser.ClearCaches()
parser.identifier = identifier
}
func (parser *Parser) ParseWithCache(v reflect.Value) (*schemas.Table, error) {
t := v.Type()
tableI, ok := parser.tableCache.Load(t)
@ -115,6 +120,7 @@ func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) {
t := v.Type()
if t.Kind() == reflect.Ptr {
t = t.Elem()
v = v.Elem()
}
if t.Kind() != reflect.Struct {
return nil, ErrUnsupportedType
@ -252,7 +258,7 @@ func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) {
addIndex(indexName, table, col, indexType)
}
}
} else {
} else if fieldValue.CanSet() {
var sqlType schemas.SQLType
if fieldValue.CanAddr() {
if _, ok := fieldValue.Addr().Interface().(convert.Conversion); ok {
@ -271,6 +277,8 @@ func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) {
if fieldType.Kind() == reflect.Int64 && (strings.ToUpper(col.FieldName) == "ID" || strings.HasSuffix(strings.ToUpper(col.FieldName), ".ID")) {
idFieldColName = col.Name
}
} else {
continue
}
if col.IsAutoIncrement {
col.Nullable = false

View File

@ -42,3 +42,64 @@ func TestParseTableName(t *testing.T) {
assert.NoError(t, err)
assert.EqualValues(t, "p_parseTableName", table.Name)
}
func TestUnexportField(t *testing.T) {
parser := NewParser(
"xorm",
dialects.QueryDialect("mysql"),
names.SnakeMapper{},
names.SnakeMapper{},
caches.NewManager(),
)
type VanilaStruct struct {
private int
Public int
}
table, err := parser.Parse(reflect.ValueOf(new(VanilaStruct)))
assert.NoError(t, err)
assert.EqualValues(t, "vanila_struct", table.Name)
assert.EqualValues(t, 1, len(table.Columns()))
for _, col := range table.Columns() {
assert.EqualValues(t, "public", col.Name)
assert.NotEqual(t, "private", col.Name)
}
type TaggedStruct struct {
private int `xorm:"private"`
Public int `xorm:"-"`
}
table, err = parser.Parse(reflect.ValueOf(new(TaggedStruct)))
assert.NoError(t, err)
assert.EqualValues(t, "tagged_struct", table.Name)
assert.EqualValues(t, 1, len(table.Columns()))
for _, col := range table.Columns() {
assert.EqualValues(t, "private", col.Name)
assert.NotEqual(t, "public", col.Name)
}
}
func TestParseWithOtherIdentifier(t *testing.T) {
parser := NewParser(
"xorm",
dialects.QueryDialect("mysql"),
names.GonicMapper{},
names.SnakeMapper{},
caches.NewManager(),
)
type StructWithDBTag struct {
FieldFoo string `db:"foo"`
}
parser.SetIdentifier("db")
table, err := parser.Parse(reflect.ValueOf(new(StructWithDBTag)))
assert.NoError(t, err)
assert.EqualValues(t, "struct_with_db_tag", table.Name)
assert.EqualValues(t, 1, len(table.Columns()))
for _, col := range table.Columns() {
assert.EqualValues(t, "foo", col.Name)
}
}

View File

@ -226,6 +226,9 @@ func CommentTagHandler(ctx *Context) error {
// SQLTypeTagHandler describes SQL Type tag handler
func SQLTypeTagHandler(ctx *Context) error {
ctx.col.SQLType = schemas.SQLType{Name: ctx.tagName}
if strings.EqualFold(ctx.tagName, "JSON") {
ctx.col.IsJSON = true
}
if len(ctx.params) > 0 {
if ctx.tagName == schemas.Enum {
ctx.col.EnumOptions = make(map[string]int)