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

1
.gitignore vendored
View File

@ -36,3 +36,4 @@ test.db.sql
*coverage.out *coverage.out
test.db test.db
integrations/*.sql 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 This changelog goes through all the changes that have been made in each release
without substantial changes to our git log. 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 ## [1.0.1](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1253) - 2020-03-25
* BUGFIXES * BUGFIXES

View File

@ -6,7 +6,9 @@ GOFMT ?= gofmt -s
TAGS ?= TAGS ?=
SED_INPLACE := sed -i 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 INTEGRATION_PACKAGES := xorm.io/xorm/integrations
PACKAGES ?= $(filter-out $(INTEGRATION_PACKAGES),$(shell $(GO) list ./...)) PACKAGES ?= $(filter-out $(INTEGRATION_PACKAGES),$(shell $(GO) list ./...))
@ -20,6 +22,9 @@ TEST_MSSQL_HOST ?= mssql:1433
TEST_MSSQL_DBNAME ?= gitea TEST_MSSQL_DBNAME ?= gitea
TEST_MSSQL_USERNAME ?= sa TEST_MSSQL_USERNAME ?= sa
TEST_MSSQL_PASSWORD ?= MwantsaSecurePassword1 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_HOST ?= mysql:3306
TEST_MYSQL_CHARSET ?= utf8 TEST_MYSQL_CHARSET ?= utf8
@ -96,7 +101,8 @@ help:
@echo " - test-mysql run integration tests for mysql" @echo " - test-mysql run integration tests for mysql"
@echo " - test-mssql run integration tests for mssql" @echo " - test-mssql run integration tests for mssql"
@echo " - test-postgres run integration tests for postgres" @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 " - test-tidb run integration tests for tidb"
@echo " - vet examines Go source code and reports suspicious constructs" @echo " - vet examines Go source code and reports suspicious constructs"
@ -144,12 +150,16 @@ test-cockroach\#%: go-check
test-mssql: go-check test-mssql: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -db=mssql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ $(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)" \ -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 -coverprofile=mssql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic
.PNONY: test-mssql\#% .PNONY: test-mssql\#%
test-mssql\#%: go-check test-mssql\#%: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -db=mssql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ $(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)" \ -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 -coverprofile=mssql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic
.PNONY: test-mymysql .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" \ -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 -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 .PHONY: test-sqlite
test-sqlite: go-check 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 -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic
.PHONY: test-sqlite-schema .PHONY: test-sqlite-schema
test-sqlite-schema: go-check 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 -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic
.PHONY: test-sqlite\#% .PHONY: test-sqlite\#%
test-sqlite\#%: go-check 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 -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic
.PNONY: test-tidb .PNONY: test-tidb
test-tidb: go-check test-tidb: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -db=mysql -cache=$(TEST_CACHE_ENABLE) -ignore_select_update=true \ $(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. 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) [![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)
[![](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 ## 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 (?, ?, ?) // 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 ```Go
session := engine.NewSession() session := engine.NewSession()
@ -338,7 +336,7 @@ if _, err := session.Exec("delete from userinfo where username = ?", user2.Usern
return nil 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 ```Go
session := engine.NewSession() session := engine.NewSession()

View File

@ -4,9 +4,7 @@
xorm 是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。 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) [![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)
[![](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 ## Notice

View File

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

View File

@ -6,6 +6,7 @@ package caches
import "sync" import "sync"
// Manager represents a cache manager
type Manager struct { type Manager struct {
cacher Cacher cacher Cacher
disableGlobalCache bool disableGlobalCache bool
@ -14,6 +15,7 @@ type Manager struct {
cacherLock sync.RWMutex cacherLock sync.RWMutex
} }
// NewManager creates a cache manager
func NewManager() *Manager { func NewManager() *Manager {
return &Manager{ return &Manager{
cachers: make(map[string]Cacher), 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) { func (mgr *Manager) SetCacher(tableName string, cacher Cacher) {
mgr.cacherLock.Lock() mgr.cacherLock.Lock()
mgr.cachers[tableName] = cacher mgr.cachers[tableName] = cacher
mgr.cacherLock.Unlock() mgr.cacherLock.Unlock()
} }
// GetCacher returns a cache of a table
func (mgr *Manager) GetCacher(tableName string) Cacher { func (mgr *Manager) GetCacher(tableName string) Cacher {
var cacher Cacher var cacher Cacher
var ok bool 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) { func (c *ContextHook) End(ctx context.Context, result sql.Result, err error) {
c.Ctx = ctx c.Ctx = ctx
c.Result = result 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) c.ExecuteTime = time.Now().Sub(c.start)
} }
// Hook represents a hook behaviour
type Hook interface { type Hook interface {
BeforeProcess(c *ContextHook) (context.Context, error) BeforeProcess(c *ContextHook) (context.Context, error)
AfterProcess(c *ContextHook) error AfterProcess(c *ContextHook) error
} }
// Hooks implements Hook interface but contains multiple Hook
type Hooks struct { type Hooks struct {
hooks []Hook hooks []Hook
} }
// AddHook adds a Hook
func (h *Hooks) AddHook(hooks ...Hook) { func (h *Hooks) AddHook(hooks ...Hook) {
h.hooks = append(h.hooks, hooks...) h.hooks = append(h.hooks, hooks...)
} }
// BeforeProcess invoked before execute the process
func (h *Hooks) BeforeProcess(c *ContextHook) (context.Context, error) { func (h *Hooks) BeforeProcess(c *ContextHook) (context.Context, error) {
ctx := c.Ctx ctx := c.Ctx
for _, h := range h.hooks { for _, h := range h.hooks {
@ -63,6 +68,7 @@ func (h *Hooks) BeforeProcess(c *ContextHook) (context.Context, error) {
return ctx, nil return ctx, nil
} }
// AfterProcess invoked after exetue the process
func (h *Hooks) AfterProcess(c *ContextHook) error { func (h *Hooks) AfterProcess(c *ContextHook) error {
firstErr := c.Err firstErr := c.Err
for _, h := range h.hooks { for _, h := range h.hooks {

View File

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

View File

@ -18,7 +18,8 @@ var (
// Tx represents a transaction // Tx represents a transaction
type Tx struct { type Tx struct {
*sql.Tx *sql.Tx
db *DB db *DB
ctx context.Context
} }
func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { 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 { if err := db.afterProcess(hookCtx); err != nil {
return nil, err return nil, err
} }
return &Tx{tx, db}, nil return &Tx{tx, db, ctx}, nil
} }
func (db *DB) Begin() (*Tx, error) { func (db *DB) Begin() (*Tx, error) {
return db.BeginTx(context.Background(), nil) 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) { func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error) {
names := make(map[string]int) names := make(map[string]int)
var i int var i int

View File

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

View File

@ -214,13 +214,45 @@ var (
type mssql struct { type mssql struct {
Base Base
defaultVarchar string
defaultChar string
} }
func (db *mssql) Init(uri *URI) error { func (db *mssql) Init(uri *URI) error {
db.quoter = mssqlQuoter db.quoter = mssqlQuoter
db.defaultChar = "CHAR"
db.defaultVarchar = "VARCHAR"
return db.Base.Init(db, uri) 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 { func (db *mssql) SQLType(c *schemas.Column) string {
var res string var res string
switch t := c.SQLType.Name; t { 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") { } else if strings.EqualFold(c.Default, "false") {
c.Default = "0" c.Default = "0"
} }
return res
case schemas.Serial: case schemas.Serial:
c.IsAutoIncrement = true c.IsAutoIncrement = true
c.IsPrimaryKey = true c.IsPrimaryKey = true
@ -251,10 +284,10 @@ func (db *mssql) SQLType(c *schemas.Column) string {
case schemas.TimeStampz: case schemas.TimeStampz:
res = "DATETIMEOFFSET" res = "DATETIMEOFFSET"
c.Length = 7 c.Length = 7
case schemas.MediumInt: case schemas.MediumInt, schemas.UnsignedInt:
res = schemas.Int res = schemas.Int
case schemas.Text, schemas.MediumText, schemas.TinyText, schemas.LongText, schemas.Json: case schemas.Text, schemas.MediumText, schemas.TinyText, schemas.LongText, schemas.Json:
res = schemas.Varchar + "(MAX)" res = db.defaultVarchar + "(MAX)"
case schemas.Double: case schemas.Double:
res = schemas.Real res = schemas.Real
case schemas.Uuid: case schemas.Uuid:
@ -263,15 +296,35 @@ func (db *mssql) SQLType(c *schemas.Column) string {
case schemas.TinyInt: case schemas.TinyInt:
res = schemas.TinyInt res = schemas.TinyInt
c.Length = 0 c.Length = 0
case schemas.BigInt: case schemas.BigInt, schemas.UnsignedBigInt:
res = schemas.BigInt res = schemas.BigInt
c.Length = 0 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: default:
res = t res = t
} }
if res == schemas.Int { if res == schemas.Int || res == schemas.Bit || res == schemas.DateTime {
return schemas.Int return res
} }
hasLen1 := (c.Length > 0) hasLen1 := (c.Length > 0)
@ -317,6 +370,11 @@ func (db *mssql) DropTableSQL(tableName string) (string, bool) {
"DROP TABLE \"%s\"", tableName, tableName), true "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{}) { func (db *mssql) IndexCheckSQL(tableName, idxName string) (string, []interface{}) {
args := []interface{}{idxName} args := []interface{}{idxName}
sql := "select name from sysindexes where id=object_id('" + tableName + "') and name=?" 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} col.SQLType = schemas.SQLType{Name: schemas.TimeStampz, DefaultLength: 0, DefaultLength2: 0}
case "NVARCHAR": case "NVARCHAR":
col.SQLType = schemas.SQLType{Name: schemas.NVarchar, DefaultLength: 0, DefaultLength2: 0} col.SQLType = schemas.SQLType{Name: schemas.NVarchar, DefaultLength: 0, DefaultLength2: 0}
if col.Length > 0 {
col.Length /= 2
col.Length2 /= 2
}
case "IMAGE": case "IMAGE":
col.SQLType = schemas.SQLType{Name: schemas.VarBinary, DefaultLength: 0, DefaultLength2: 0} 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: default:
if _, ok := schemas.SqlTypes[ct]; ok { if _, ok := schemas.SqlTypes[ct]; ok {
col.SQLType = schemas.SQLType{Name: ct, DefaultLength: 0, DefaultLength2: 0} 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, "` ") colName = strings.Trim(colName, "` ")
var isRegular bool 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):] indexName = indexName[5+len(tableName):]
isRegular = true isRegular = true
} }

View File

@ -254,6 +254,10 @@ func (db *mysql) SQLType(c *schemas.Column) string {
c.Length = 40 c.Length = 40
case schemas.Json: case schemas.Json:
res = schemas.Text res = schemas.Text
case schemas.UnsignedInt:
res = schemas.Int
case schemas.UnsignedBigInt:
res = schemas.BigInt
default: default:
res = t res = t
} }
@ -271,6 +275,11 @@ func (db *mysql) SQLType(c *schemas.Column) string {
} else if hasLen1 { } else if hasLen1 {
res += "(" + strconv.Itoa(c.Length) + ")" res += "(" + strconv.Itoa(c.Length) + ")"
} }
if c.SQLType.Name == schemas.UnsignedBigInt || c.SQLType.Name == schemas.UnsignedInt {
res += " UNSIGNED"
}
return res 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) { func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) {
args := []interface{}{db.uri.DBName, tableName} 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`," + 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...) rows, err := queryer.QueryContext(ctx, s, args...)
if err != nil { if err != nil {
@ -322,27 +340,35 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName
col := new(schemas.Column) col := new(schemas.Column)
col.Indexes = make(map[string]int) 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 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }
col.Name = strings.Trim(columnName, "` ") col.Name = strings.Trim(columnName, "` ")
col.Comment = comment col.Comment = comment
if "YES" == isNullable { if nullableStr == "YES" {
col.Nullable = true col.Nullable = true
} }
if colDefault != nil { if colDefault != nil && (!alreadyQuoted || *colDefault != "NULL") {
col.Default = *colDefault col.Default = *colDefault
col.DefaultIsEmpty = false col.DefaultIsEmpty = false
} else { } else {
col.DefaultIsEmpty = true col.DefaultIsEmpty = true
} }
fields := strings.Fields(colType)
if len(fields) == 2 && fields[1] == "unsigned" {
isUnsigned = true
}
colType = fields[0]
cts := strings.Split(colType, "(") cts := strings.Split(colType, "(")
colName := cts[0] colName := cts[0]
// Remove the /* mariadb-5.3 */ suffix from coltypes
colName = strings.TrimSuffix(colName, "/* mariadb-5.3 */")
colType = strings.ToUpper(colName) colType = strings.ToUpper(colName)
var len1, len2 int var len1, len2 int
if len(cts) == 2 { if len(cts) == 2 {
@ -377,11 +403,8 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName
} }
} }
} }
if colType == "FLOAT UNSIGNED" { if isUnsigned {
colType = "FLOAT" colType = "UNSIGNED " + colType
}
if colType == "DOUBLE UNSIGNED" {
colType = "DOUBLE"
} }
col.Length = len1 col.Length = len1
col.Length2 = len2 col.Length2 = len2
@ -403,9 +426,9 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName
} }
if !col.DefaultIsEmpty { if !col.DefaultIsEmpty {
if col.SQLType.IsText() { if !alreadyQuoted && col.SQLType.IsText() {
col.Default = "'" + col.Default + "'" 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 + "'" col.Default = "'" + col.Default + "'"
} }
} }

View File

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

View File

@ -833,12 +833,12 @@ func (db *postgres) SQLType(c *schemas.Column) string {
case schemas.Bit: case schemas.Bit:
res = schemas.Boolean res = schemas.Boolean
return res return res
case schemas.MediumInt, schemas.Int, schemas.Integer: case schemas.MediumInt, schemas.Int, schemas.Integer, schemas.UnsignedInt:
if c.IsAutoIncrement { if c.IsAutoIncrement {
return schemas.Serial return schemas.Serial
} }
return schemas.Integer return schemas.Integer
case schemas.BigInt: case schemas.BigInt, schemas.UnsignedBigInt:
if c.IsAutoIncrement { if c.IsAutoIncrement {
return schemas.BigSerial return schemas.BigSerial
} }
@ -857,6 +857,8 @@ func (db *postgres) SQLType(c *schemas.Column) string {
res = schemas.Real res = schemas.Real
case schemas.TinyText, schemas.MediumText, schemas.LongText: case schemas.TinyText, schemas.MediumText, schemas.LongText:
res = schemas.Text res = schemas.Text
case schemas.NChar:
res = schemas.Char
case schemas.NVarchar: case schemas.NVarchar:
res = schemas.Varchar res = schemas.Varchar
case schemas.Uuid: case schemas.Uuid:
@ -1015,7 +1017,7 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A
schema := db.getSchema() schema := db.getSchema()
if schema != "" { if schema != "" {
s = fmt.Sprintf(s, "AND s.table_schema = $2") s = fmt.Sprintf(s, " AND s.table_schema = $2")
args = append(args, schema) args = append(args, schema)
} else { } else {
s = fmt.Sprintf(s, "") s = fmt.Sprintf(s, "")
@ -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, `" `) col.Name = strings.Trim(colName, `" `)
if colDefault != nil { 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") col.Nullable = (isNullable == "YES")
switch strings.ToLower(dataType) { switch strings.ToLower(dataType) {
case "character varying", "character", "string": case "character varying", "string":
col.SQLType = schemas.SQLType{Name: schemas.Varchar, DefaultLength: 0, DefaultLength2: 0} 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": case "timestamp without time zone":
col.SQLType = schemas.SQLType{Name: schemas.DateTime, DefaultLength: 0, DefaultLength2: 0} col.SQLType = schemas.SQLType{Name: schemas.DateTime, DefaultLength: 0, DefaultLength2: 0}
case "timestamp with time zone": 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, case schemas.Char, schemas.Varchar, schemas.NVarchar, schemas.TinyText,
schemas.Text, schemas.MediumText, schemas.LongText, schemas.Json: schemas.Text, schemas.MediumText, schemas.LongText, schemas.Json:
return schemas.Text 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 return schemas.Integer
case schemas.Float, schemas.Double, schemas.Real: case schemas.Float, schemas.Double, schemas.Real:
return schemas.Real return schemas.Real
@ -483,7 +484,7 @@ func (db *sqlite3) GetIndexes(queryer core.Queryer, ctx context.Context, tableNa
continue continue
} }
indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []") indexName := strings.Trim(strings.TrimSpace(sql[nNStart+6:nNEnd]), "`[]'\"")
var isRegular bool 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) {
index.Name = indexName[5+len(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: case schemas.Date:
v = t.Format("2006-01-02") v = t.Format("2006-01-02")
case schemas.DateTime, schemas.TimeStamp, schemas.Varchar: // !DarthPestilane! format time when sqlTypeName is schemas.Varchar. case schemas.DateTime, schemas.TimeStamp, schemas.Varchar: // !DarthPestilane! format time when sqlTypeName is schemas.Varchar.
v = t.Format("2006-01-02 15:04:05") if dialect.URI().DBType == schemas.ORACLE {
v = t
} else {
v = t.Format("2006-01-02 15:04:05")
}
case schemas.TimeStampz: case schemas.TimeStampz:
if dialect.URI().DBType == schemas.MSSQL { if dialect.URI().DBType == schemas.MSSQL {
v = t.Format("2006-01-02T15:04:05.9999999Z07:00") 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 nil, err
} }
return newEngine(driverName, dataSourceName, dialect, db)
}
func newEngine(driverName, dataSourceName string, dialect dialects.Dialect, db *core.DB) (*Engine, error) {
cacherMgr := caches.NewManager() cacherMgr := caches.NewManager()
mapper := names.NewCacheMapper(new(names.SnakeMapper)) mapper := names.NewCacheMapper(new(names.SnakeMapper))
tagParser := tags.NewParser("xorm", dialect, mapper, mapper, cacherMgr) 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)) engine.SetLogger(log.NewLoggerAdapter(logger))
runtime.SetFinalizer(engine, func(engine *Engine) { runtime.SetFinalizer(engine, func(engine *Engine) {
engine.Close() _ = engine.Close()
}) })
return engine, nil return engine, nil
@ -101,6 +105,23 @@ func NewEngineWithParams(driverName string, dataSourceName string, params map[st
return engine, err 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 // EnableSessionID if enable session id
func (engine *Engine) EnableSessionID(enable bool) { func (engine *Engine) EnableSessionID(enable bool) {
engine.logSessionID = enable engine.logSessionID = enable
@ -143,10 +164,12 @@ func (engine *Engine) Logger() log.ContextLogger {
func (engine *Engine) SetLogger(logger interface{}) { func (engine *Engine) SetLogger(logger interface{}) {
var realLogger log.ContextLogger var realLogger log.ContextLogger
switch t := logger.(type) { switch t := logger.(type) {
case log.Logger:
realLogger = log.NewLoggerAdapter(t)
case log.ContextLogger: case log.ContextLogger:
realLogger = t realLogger = t
case log.Logger:
realLogger = log.NewLoggerAdapter(t)
default:
panic("logger should implement either log.ContextLogger or log.Logger")
} }
engine.logger = realLogger engine.logger = realLogger
engine.DB().Logger = realLogger engine.DB().Logger = realLogger
@ -188,6 +211,11 @@ func (engine *Engine) SetColumnMapper(mapper names.Mapper) {
engine.tagParser.SetColumnMapper(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 // Quote Use QuoteStr quote the string sql
func (engine *Engine) Quote(value string) string { func (engine *Engine) Quote(value string) string {
value = strings.TrimSpace(value) value = strings.TrimSpace(value)
@ -347,13 +375,16 @@ func (engine *Engine) loadTableInfo(table *schemas.Table) error {
var seq int var seq int
for _, index := range indexes { for _, index := range indexes {
for _, name := range index.Cols { for _, name := range index.Cols {
parts := strings.Split(name, " ") parts := strings.Split(strings.TrimSpace(name), " ")
if len(parts) > 1 { if len(parts) > 1 {
if parts[1] == "DESC" { if parts[1] == "DESC" {
seq = 1 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 col.Indexes[index.Name] = index.Type
} else { } 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()) 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...) 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 // 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 { func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error {
var dstDialect dialects.Dialect var dstDialect dialects.Dialect
@ -424,7 +531,10 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch
} }
uri := engine.dialect.URI() uri := engine.dialect.URI()
destURI := *uri destURI := dialects.URI{
DBType: tp[0],
DBName: uri.DBName,
}
dstDialect.Init(&destURI) dstDialect.Init(&destURI)
} }
@ -495,59 +605,9 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch
if col == nil { if col == nil {
return errors.New("unknow column error") return errors.New("unknow column error")
} }
temp += "," + formatColumnValue(dstDialect, d, col)
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) + "'"
}
} 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 { if err != nil {
return err return err
} }
@ -816,81 +876,11 @@ func (engine *Engine) IsTableExist(beanOrTableName interface{}) (bool, error) {
return session.IsTableExist(beanOrTableName) 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 // TableName returns table name with schema prefix if has
func (engine *Engine) TableName(bean interface{}, includeSchema ...bool) string { func (engine *Engine) TableName(bean interface{}, includeSchema ...bool) string {
return dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean, includeSchema...) 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 // CreateIndexes create indexes
func (engine *Engine) CreateIndexes(bean interface{}) error { func (engine *Engine) CreateIndexes(bean interface{}) error {
session := engine.NewSession() session := engine.NewSession()
@ -1288,6 +1278,7 @@ func (engine *Engine) SetSchema(schema string) {
engine.dialect.URI().SetSchema(schema) engine.dialect.URI().SetSchema(schema)
} }
// AddHook adds a context Hook
func (engine *Engine) AddHook(hook contexts.Hook) { func (engine *Engine) AddHook(hook contexts.Hook) {
engine.db.AddHook(hook) engine.db.AddHook(hook)
} }
@ -1303,7 +1294,7 @@ func (engine *Engine) tbNameWithSchema(v string) string {
return dialects.TableNameWithSchema(engine.dialect, v) 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 { func (engine *Engine) Context(ctx context.Context) *Session {
session := engine.NewSession() session := engine.NewSession()
session.isAutoClose = true session.isAutoClose = true
@ -1333,11 +1324,11 @@ func (engine *Engine) Transaction(f func(*Session) (interface{}, error)) (interf
result, err := f(session) result, err := f(session)
if err != nil { if err != nil {
return nil, err return result, err
} }
if err := session.Commit(); err != nil { if err := session.Commit(); err != nil {
return nil, err return result, err
} }
return result, nil return result, nil

View File

@ -79,7 +79,7 @@ func (eg *EngineGroup) Close() error {
return nil return nil
} }
// ContextHook returned a group session // Context returned a group session
func (eg *EngineGroup) Context(ctx context.Context) *Session { func (eg *EngineGroup) Context(ctx context.Context) *Session {
sess := eg.NewSession() sess := eg.NewSession()
sess.isAutoClose = true sess.isAutoClose = true
@ -144,6 +144,7 @@ func (eg *EngineGroup) SetLogger(logger interface{}) {
} }
} }
// AddHook adds Hook
func (eg *EngineGroup) AddHook(hook contexts.Hook) { func (eg *EngineGroup) AddHook(hook contexts.Hook) {
eg.Engine.AddHook(hook) eg.Engine.AddHook(hook)
for i := 0; i < len(eg.slaves); i++ { 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 // SetMaxIdleConns set the max idle connections on pool, default is 2
func (eg *EngineGroup) SetMaxIdleConns(conns int) { func (eg *EngineGroup) SetMaxIdleConns(conns int) {
eg.Engine.DB().SetMaxIdleConns(conns) eg.Engine.DB().SetMaxIdleConns(conns)

15
go.mod
View File

@ -1,16 +1,15 @@
module xorm.io/xorm module xorm.io/xorm
go 1.11 go 1.13
require ( require (
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4 github.com/denisenkom/go-mssqldb v0.9.0
github.com/go-sql-driver/mysql v1.4.1 github.com/go-sql-driver/mysql v1.5.0
github.com/kr/pretty v0.1.0 // indirect github.com/lib/pq v1.7.0
github.com/lib/pq v1.0.0 github.com/mattn/go-sqlite3 v1.14.6
github.com/mattn/go-sqlite3 v1.10.0
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.4.0
github.com/syndtr/goleveldb v1.0.0 github.com/syndtr/goleveldb v1.0.0
github.com/ziutek/mymysql v1.5.4 github.com/ziutek/mymysql v1.5.4
google.golang.org/appengine v1.6.0 // indirect modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84
xorm.io/builder v0.3.7 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 h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= 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/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/denisenkom/go-mssqldb v0.9.0 h1:RSohk2RsiZqLZ0zCjtfn3S4Gp4exhpBWHyQ7D0yGjAk=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4 h1:YcpmyvADGYw5LqMnHqSkyIELsHCGF6PkrmM31V8rF7o= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
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/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 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/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-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
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/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 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/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 h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 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.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 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/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
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/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 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 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 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 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= 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 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= 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-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/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/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20190423024810-112230192c58/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-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-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.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.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 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.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 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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= modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009 h1:u0oCo5b9wyLr++HF3AN9JicGhkUxJhMz51+8TIZH9N0=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= modernc.org/ccgo/v3 v3.9.0 h1:JbcEIqjw4Agf+0g3Tc85YvfYqkkFOv6xBwS4zkfqSoA=
xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI= modernc.org/ccgo/v3 v3.9.0/go.mod h1:nQbgkn8mwzPdp4mm6BT6+p85ugQ7FrGgIcYaE7nSrpY=
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= 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/mattn/go-sqlite3"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
_ "github.com/ziutek/mymysql/godrv" _ "github.com/ziutek/mymysql/godrv"
_ "modernc.org/sqlite"
) )
func TestPing(t *testing.T) { func TestPing(t *testing.T) {
@ -93,19 +94,23 @@ func TestDump(t *testing.T) {
assert.NoError(t, PrepareEngine()) assert.NoError(t, PrepareEngine())
type TestDumpStruct struct { type TestDumpStruct struct {
Id int64 Id int64
Name string Name string
IsMan bool
Created time.Time `xorm:"created"`
} }
assertSync(t, new(TestDumpStruct)) assertSync(t, new(TestDumpStruct))
testEngine.Insert([]TestDumpStruct{ cnt, err := testEngine.Insert([]TestDumpStruct{
{Name: "1"}, {Name: "1", IsMan: true},
{Name: "2\n"}, {Name: "2\n"},
{Name: "3;"}, {Name: "3;"},
{Name: "4\n;\n''"}, {Name: "4\n;\n''"},
{Name: "5'\n"}, {Name: "5'\n"},
}) })
assert.NoError(t, err)
assert.EqualValues(t, 5, cnt)
fp := fmt.Sprintf("%v.sql", testEngine.Dialect().URI().DBType) fp := fmt.Sprintf("%v.sql", testEngine.Dialect().URI().DBType)
os.Remove(fp) os.Remove(fp)
@ -116,7 +121,7 @@ func TestDump(t *testing.T) {
sess := testEngine.NewSession() sess := testEngine.NewSession()
defer sess.Close() defer sess.Close()
assert.NoError(t, sess.Begin()) assert.NoError(t, sess.Begin())
_, err := sess.ImportFile(fp) _, err = sess.ImportFile(fp)
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, sess.Commit()) 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) { func TestSetSchema(t *testing.T) {
assert.NoError(t, PrepareEngine()) assert.NoError(t, PrepareEngine())
@ -139,3 +187,16 @@ func TestSetSchema(t *testing.T) {
assert.EqualValues(t, oldSchema, testEngine.Dialect().URI().Schema) 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) assert.EqualValues(t, 1, cnt)
results = make([]FindAndCountStruct, 0, 1) 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.NoError(t, err)
assert.EqualValues(t, 1, len(results)) assert.EqualValues(t, 1, len(results))
assert.EqualValues(t, 1, cnt) assert.EqualValues(t, 1, cnt)
results = make([]FindAndCountStruct, 0, 1) 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) Limit(1).FindAndCount(&results)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 1, len(results)) assert.EqualValues(t, 1, len(results))
@ -708,6 +753,13 @@ func TestFindExtends(t *testing.T) {
err = testEngine.Find(&results) err = testEngine.Find(&results)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 2, len(results)) 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) { func TestFindExtends3(t *testing.T) {

View File

@ -6,11 +6,13 @@ package integrations
import ( import (
"database/sql" "database/sql"
"errors"
"fmt" "fmt"
"strconv" "strconv"
"testing" "testing"
"time" "time"
"xorm.io/xorm"
"xorm.io/xorm/contexts" "xorm.io/xorm/contexts"
"xorm.io/xorm/schemas" "xorm.io/xorm/schemas"
@ -394,6 +396,60 @@ func TestJSONString(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 1, len(jss)) assert.EqualValues(t, 1, len(jss))
assert.True(t, `["1","2"]` == jss[0].Content || `["1", "2"]` == jss[0].Content) 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) { func TestGetActionMapping(t *testing.T) {
@ -696,3 +752,17 @@ func TestGetViaMapCond(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.False(t, has) 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" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"xorm.io/xorm/schemas"
) )
func TestStoreEngine(t *testing.T) { func TestStoreEngine(t *testing.T) {
@ -102,6 +103,9 @@ func TestSyncTable(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 1, len(tables)) assert.EqualValues(t, 1, len(tables))
assert.EqualValues(t, "sync_table1", tables[0].Name) 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))) assert.NoError(t, testEngine.Sync2(new(SyncTable2)))
@ -109,6 +113,9 @@ func TestSyncTable(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 1, len(tables)) assert.EqualValues(t, 1, len(tables))
assert.EqualValues(t, "sync_table1", tables[0].Name) 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))) assert.NoError(t, testEngine.Sync2(new(SyncTable3)))
@ -116,6 +123,9 @@ func TestSyncTable(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 1, len(tables)) assert.EqualValues(t, 1, len(tables))
assert.EqualValues(t, "sync_table1", tables[0].Name) 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) { 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) 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) { func TestIsTableExist(t *testing.T) {
assert.NoError(t, PrepareEngine()) assert.NoError(t, PrepareEngine())
@ -330,3 +397,30 @@ func TestSync2_Default(t *testing.T) {
assertSync(t, new(TestSync2Default)) assertSync(t, new(TestSync2Default))
assert.NoError(t, testEngine.Sync2(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)) _, err := testEngine.Table("userinfo").MustLogSQL(true).Get(new(Userinfo))
assert.NoError(t, err) 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" "github.com/stretchr/testify/assert"
"xorm.io/xorm" "xorm.io/xorm"
"xorm.io/xorm/internal/statements"
"xorm.io/xorm/internal/utils" "xorm.io/xorm/internal/utils"
"xorm.io/xorm/names" "xorm.io/xorm/names"
"xorm.io/xorm/schemas"
) )
func TestUpdateMap(t *testing.T) { func TestUpdateMap(t *testing.T) {
@ -39,6 +41,27 @@ func TestUpdateMap(t *testing.T) {
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 1, cnt) 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) { func TestUpdateLimit(t *testing.T) {
@ -179,6 +202,9 @@ func TestForUpdate(t *testing.T) {
// lock is NOT used // lock is NOT used
wg.Add(1) wg.Add(1)
wg2 := &sync.WaitGroup{}
wg2.Add(1)
go func() { go func() {
f3 := new(ForUpdate) f3 := new(ForUpdate)
session3.Where("id = ?", 1) session3.Where("id = ?", 1)
@ -192,10 +218,10 @@ func TestForUpdate(t *testing.T) {
t.Errorf("read lock failed") t.Errorf("read lock failed")
} }
wg.Done() wg.Done()
wg2.Done()
}() }()
// wait for go rountines wg2.Wait()
time.Sleep(50 * time.Millisecond)
f := new(ForUpdate) f := new(ForUpdate)
f.Name = "updated by session1" f.Name = "updated by session1"
@ -988,7 +1014,7 @@ func TestUpdateMapContent(t *testing.T) {
assert.EqualValues(t, false, c2.IsMan) assert.EqualValues(t, false, c2.IsMan)
assert.EqualValues(t, 2, c2.Gender) 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, "age": 15,
"is_man": true, "is_man": true,
"gender": 1, "gender": 1,
@ -1341,3 +1367,50 @@ func TestUpdateMultiplePK(t *testing.T) {
_, err = testEngine.ID(&MySlice{test.Id, test.Name}).Update(test) _, err = testEngine.ID(&MySlice{test.Id, test.Name}).Update(test)
assert.NoError(t, err) 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" "database/sql"
"flag" "flag"
"fmt" "fmt"
"net/url"
"os" "os"
"strings" "strings"
"testing" "testing"
@ -35,7 +36,10 @@ var (
schema = flag.String("schema", "", "specify the schema") schema = flag.String("schema", "", "specify the schema")
ignoreSelectUpdate = flag.Bool("ignore_select_update", false, "ignore select update if implementation difference, only for tidb") 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") 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") 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 tableMapper names.Mapper
colMapper names.Mapper colMapper names.Mapper
) )
@ -94,6 +98,13 @@ func createEngine(dbType, connStr string) error {
return fmt.Errorf("db.Exec: %v", err) return fmt.Errorf("db.Exec: %v", err)
} }
db.Close() db.Close()
case schemas.SQLITE, "sqlite":
u, err := url.Parse(connStr)
if err != nil {
return err
}
connStr = u.Path
*ignoreSelectUpdate = true
default: default:
*ignoreSelectUpdate = true *ignoreSelectUpdate = true
} }
@ -137,6 +148,11 @@ func createEngine(dbType, connStr string) error {
} else { } else {
testEngine.SetQuotePolicy(dialects.QuotePolicyAlways) testEngine.SetQuotePolicy(dialects.QuotePolicyAlways)
} }
testEngine.Dialect().SetParams(map[string]string{
"DEFAULT_VARCHAR": *defaultVarchar,
"DEFAULT_CHAR": *defaultChar,
})
} }
tableMapper = testEngine.GetTableMapper() tableMapper = testEngine.GetTableMapper()
@ -156,17 +172,25 @@ func createEngine(dbType, connStr string) error {
return nil return nil
} }
// PrepareEngine prepare tests ORM engine
func PrepareEngine() error { func PrepareEngine() error {
return createEngine(dbType, connString) return createEngine(dbType, connString)
} }
// MainTest the tests entrance
func MainTest(m *testing.M) { func MainTest(m *testing.M) {
flag.Parse() flag.Parse()
dbType = *db dbType = *db
if *db == "sqlite3" { if *db == "sqlite3" {
if ptrConnStr == nil { 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 { } else {
connString = *ptrConnStr connString = *ptrConnStr
} }

View File

@ -375,3 +375,30 @@ func TestCustomType2(t *testing.T) {
fmt.Println(users) 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) SetCacher(string, caches.Cacher)
SetConnMaxLifetime(time.Duration) SetConnMaxLifetime(time.Duration)
SetColumnMapper(names.Mapper) SetColumnMapper(names.Mapper)
SetTagIdentifier(string)
SetDefaultCacher(caches.Cacher) SetDefaultCacher(caches.Cacher)
SetLogger(logger interface{}) SetLogger(logger interface{})
SetLogLevel(log.LogLevel) SetLogLevel(log.LogLevel)
@ -120,6 +121,7 @@ type EngineInterface interface {
TableInfo(bean interface{}) (*schemas.Table, error) TableInfo(bean interface{}) (*schemas.Table, error)
TableName(interface{}, ...bool) string TableName(interface{}, ...bool) string
UnMapType(reflect.Type) UnMapType(reflect.Type)
EnableSessionID(bool)
} }
var ( var (

View File

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

View File

@ -20,6 +20,21 @@ var (
uintType = reflect.TypeOf(uint64(0)) 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 = ?" // ID generate "where id = ? " statement or for composite key "where key1 = ? and key2 = ?"
func (statement *Statement) ID(id interface{}) *Statement { func (statement *Statement) ID(id interface{}) *Statement {
switch t := id.(type) { switch t := id.(type) {
@ -58,13 +73,17 @@ func (statement *Statement) ID(id interface{}) *Statement {
return statement return statement
} }
// ProcessIDParam handles the process of id condition
func (statement *Statement) ProcessIDParam() error { func (statement *Statement) ProcessIDParam() error {
if statement.idParam == nil || statement.RefTable == nil { if statement.idParam == nil {
return nil return nil
} }
if statement.RefTable == nil {
return ErrIDConditionWithNoTable{statement.idParam}
}
if len(statement.RefTable.PrimaryKeys) != len(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", return fmt.Errorf("ID condition is error, expect %d primarykeys, there are %d",
len(statement.RefTable.PrimaryKeys), len(statement.RefTable.PrimaryKeys),
len(statement.idParam), 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) { col.SQLType.IsBlob() || col.SQLType.Name == schemas.TimeStampz) {
continue continue
} }
if col.SQLType.IsJson() { if col.IsJSON {
continue continue
} }
@ -813,7 +813,7 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{},
continue continue
} }
} else { } else {
if col.SQLType.IsJson() { if col.IsJSON {
if col.SQLType.IsText() { if col.SQLType.IsText() {
bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface())
if err != nil { 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() data, err := structConvert.ToDB()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -204,7 +204,7 @@ func (statement *Statement) BuildUpdates(tableValue reflect.Value,
continue continue
} }
} else { } else {
if !col.SQLType.IsJson() { if !col.IsJSON {
table, err := statement.tagParser.ParseWithCache(fieldValue) table, err := statement.tagParser.ParseWithCache(fieldValue)
if err != nil { if err != nil {
val = fieldValue.Interface() val = fieldValue.Interface()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@
package names package names
import ( import (
"strings"
"testing" "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 package schemas
import ( import (
"errors"
"fmt" "fmt"
"reflect" "reflect"
"strconv"
"strings" "strings"
"time" "time"
) )
@ -49,6 +51,7 @@ type Column struct {
func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable bool) *Column { func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable bool) *Column {
return &Column{ return &Column{
Name: name, Name: name,
IsJSON: sqlType.IsJson(),
TableName: "", TableName: "",
FieldName: fieldName, FieldName: fieldName,
SQLType: sqlType, SQLType: sqlType,
@ -115,3 +118,17 @@ func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) {
return &fieldValue, nil 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,9 +82,7 @@ func (q Quoter) JoinWrite(b *strings.Builder, a []string, sep string) error {
return err return err
} }
} }
if s != "*" { q.QuoteTo(b, strings.TrimSpace(s))
q.QuoteTo(b, strings.TrimSpace(s))
}
} }
return nil return nil
} }
@ -143,7 +141,7 @@ func (q Quoter) quoteWordTo(buf *strings.Builder, word string) error {
} }
isReserved := q.IsReserved(realWord) isReserved := q.IsReserved(realWord)
if isReserved { if isReserved && realWord != "*" {
if err := buf.WriteByte(q.Prefix); err != nil { if err := buf.WriteByte(q.Prefix); err != nil {
return err return err
} }
@ -151,7 +149,7 @@ func (q Quoter) quoteWordTo(buf *strings.Builder, word string) error {
if _, err := buf.WriteString(realWord); err != nil { if _, err := buf.WriteString(realWord); err != nil {
return err return err
} }
if isReserved { if isReserved && realWord != "*" {
return buf.WriteByte(q.Suffix) return buf.WriteByte(q.Suffix)
} }

View File

@ -22,6 +22,7 @@ func TestAlwaysQuoteTo(t *testing.T) {
{"[mytable]", "`mytable`"}, {"[mytable]", "`mytable`"},
{"[mytable]", `[mytable]`}, {"[mytable]", `[mytable]`},
{`["mytable"]`, `"mytable"`}, {`["mytable"]`, `"mytable"`},
{`[mytable].*`, `[mytable].*`},
{"[myschema].[mytable]", "myschema.mytable"}, {"[myschema].[mytable]", "myschema.mytable"},
{"[myschema].[mytable]", "`myschema`.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]`}, {"[mytable]", `[mytable]`},
{"[mytable].*", `[mytable].*`},
{`"mytable"`, `"mytable"`}, {`"mytable"`, `"mytable"`},
{"myschema.[mytable]", "myschema.mytable"}, {"myschema.[mytable]", "myschema.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]`}, {"mytable", `[mytable]`},
{"mytable.*", `[mytable].*`},
{`"mytable"`, `"mytable"`}, {`"mytable"`, `"mytable"`},
{"myschema.mytable", "myschema.mytable"}, {"myschema.mytable", "myschema.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]", 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, ", ")) assert.EqualValues(t, "[f1], [f2], [f3]", quoter.Join(cols, ", "))
quoter.IsReserved = AlwaysNoReserve quoter.IsReserved = AlwaysNoReserve
@ -134,11 +139,11 @@ func TestJoin(t *testing.T) {
} }
func TestStrings(t *testing.T) { func TestStrings(t *testing.T) {
cols := []string{"f1", "f2", "t3.f3"} cols := []string{"f1", "f2", "t3.f3", "t4.*"}
quoter := Quoter{'[', ']', AlwaysReserve} quoter := Quoter{'[', ']', AlwaysReserve}
quotedCols := quoter.Strings(cols) 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) { func TestTrim(t *testing.T) {

View File

@ -5,7 +5,9 @@
package schemas package schemas
import ( import (
"fmt"
"reflect" "reflect"
"strconv"
"strings" "strings"
) )
@ -28,6 +30,7 @@ type Table struct {
Comment string Comment string
} }
// NewEmptyTable creates an empty table
func NewEmptyTable() *Table { func NewEmptyTable() *Table {
return NewTable("", nil) 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 { func (table *Table) Columns() []*Column {
return table.columns return table.columns
} }
// ColumnsSeq returns table's column names according sequence
func (table *Table) ColumnsSeq() []string { func (table *Table) ColumnsSeq() []string {
return table.columnsSeq return table.columnsSeq
} }
func (table *Table) columnsByName(name string) []*Column { func (table *Table) columnsByName(name string) []*Column {
for k, cols := range table.columnsMap { return table.columnsMap[strings.ToLower(name)]
if strings.EqualFold(k, name) {
return cols
}
}
return nil
} }
// GetColumn returns column according column name, if column not found, return nil
func (table *Table) GetColumn(name string) *Column { func (table *Table) GetColumn(name string) *Column {
cols := table.columnsByName(name) cols := table.columnsByName(name)
if cols != nil { if cols != nil {
@ -70,6 +71,7 @@ func (table *Table) GetColumn(name string) *Column {
return nil return nil
} }
// GetColumnIdx returns column according name and idx
func (table *Table) GetColumnIdx(name string, idx int) *Column { func (table *Table) GetColumnIdx(name string, idx int) *Column {
cols := table.columnsByName(name) cols := table.columnsByName(name)
if cols != nil && idx < len(cols) { if cols != nil && idx < len(cols) {
@ -144,3 +146,45 @@ func (table *Table) AddColumn(col *Column) {
func (table *Table) AddIndex(index *Index) { func (table *Table) AddIndex(index *Index) {
table.Indexes[index.Name] = 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 return s.Name == Json || s.Name == Jsonb
} }
func (s *SQLType) IsXML() bool {
return s.Name == XML
}
var ( var (
Bit = "BIT" Bit = "BIT"
TinyInt = "TINYINT" UnsignedBit = "UNSIGNED BIT"
SmallInt = "SMALLINT" TinyInt = "TINYINT"
MediumInt = "MEDIUMINT" SmallInt = "SMALLINT"
Int = "INT" MediumInt = "MEDIUMINT"
Integer = "INTEGER" Int = "INT"
BigInt = "BIGINT" UnsignedInt = "UNSIGNED INT"
Integer = "INTEGER"
BigInt = "BIGINT"
UnsignedBigInt = "UNSIGNED BIGINT"
Enum = "ENUM" Enum = "ENUM"
Set = "SET" Set = "SET"
@ -128,22 +135,28 @@ var (
Json = "JSON" Json = "JSON"
Jsonb = "JSONB" Jsonb = "JSONB"
XML = "XML"
Array = "ARRAY" Array = "ARRAY"
SqlTypes = map[string]int{ SqlTypes = map[string]int{
Bit: NUMERIC_TYPE, Bit: NUMERIC_TYPE,
TinyInt: NUMERIC_TYPE, UnsignedBit: NUMERIC_TYPE,
SmallInt: NUMERIC_TYPE, TinyInt: NUMERIC_TYPE,
MediumInt: NUMERIC_TYPE, SmallInt: NUMERIC_TYPE,
Int: NUMERIC_TYPE, MediumInt: NUMERIC_TYPE,
Integer: NUMERIC_TYPE, Int: NUMERIC_TYPE,
BigInt: NUMERIC_TYPE, UnsignedInt: NUMERIC_TYPE,
Integer: NUMERIC_TYPE,
BigInt: NUMERIC_TYPE,
UnsignedBigInt: NUMERIC_TYPE,
Enum: TEXT_TYPE, Enum: TEXT_TYPE,
Set: TEXT_TYPE, Set: TEXT_TYPE,
Json: TEXT_TYPE, Json: TEXT_TYPE,
Jsonb: TEXT_TYPE, Jsonb: TEXT_TYPE,
XML: TEXT_TYPE,
Char: TEXT_TYPE, Char: TEXT_TYPE,
NChar: TEXT_TYPE, NChar: TEXT_TYPE,
Varchar: TEXT_TYPE, Varchar: TEXT_TYPE,
@ -273,10 +286,14 @@ var (
// Type2SQLType generate SQLType acorrding Go's type // Type2SQLType generate SQLType acorrding Go's type
func Type2SQLType(t reflect.Type) (st SQLType) { func Type2SQLType(t reflect.Type) (st SQLType) {
switch k := t.Kind(); k { 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} 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} st = SQLType{BigInt, 0, 0}
case reflect.Uint64:
st = SQLType{UnsignedBigInt, 0, 0}
case reflect.Float32: case reflect.Float32:
st = SQLType{Float, 0, 0} st = SQLType{Float, 0, 0}
case reflect.Float64: case reflect.Float64:

View File

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

View File

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

View File

@ -16,6 +16,11 @@ import (
"xorm.io/xorm/schemas" "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 // Get retrieve one record from database, bean's non-empty fields
// will be as conditions // will be as conditions
func (session *Session) Get(bean interface{}) (bool, error) { 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") return false, errors.New("needs a pointer to a value")
} else if beanValue.Elem().Kind() == reflect.Ptr { } else if beanValue.Elem().Kind() == reflect.Ptr {
return false, errors.New("a pointer to a pointer is not allowed") 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 { if beanValue.Elem().Kind() == reflect.Struct {

View File

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

View File

@ -4,12 +4,6 @@
package xorm package xorm
import (
"time"
"xorm.io/xorm/log"
)
// Begin a transaction // Begin a transaction
func (session *Session) Begin() error { func (session *Session) Begin() error {
if session.isAutoCommit { if session.isAutoCommit {
@ -33,24 +27,7 @@ func (session *Session) Rollback() error {
session.isCommitedOrRollbacked = true session.isCommitedOrRollbacked = true
session.isAutoCommit = true session.isAutoCommit = true
start := time.Now() return session.tx.Rollback()
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 nil return nil
} }
@ -62,25 +39,7 @@ func (session *Session) Commit() error {
session.isCommitedOrRollbacked = true session.isCommitedOrRollbacked = true
session.isAutoCommit = true session.isAutoCommit = true
start := time.Now() if err := session.tx.Commit(); err != nil {
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 {
return err return err
} }
@ -125,3 +84,8 @@ func (session *Session) Commit() error {
} }
return nil 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)+" = ?") colNames = append(colNames, session.engine.Quote(table.Updated)+" = ?")
col := table.UpdatedColumn() col := table.UpdatedColumn()
val, t := session.engine.nowTime(col) val, t := session.engine.nowTime(col)
args = append(args, val) if session.engine.dialect.URI().DBType == schemas.ORACLE {
args = append(args, t)
} else {
args = append(args, val)
}
var colName = col.Name var colName = col.Name
if isStruct { if isStruct {
@ -269,8 +273,15 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
k = ct.Elem().Kind() k = ct.Elem().Kind()
} }
if k == reflect.Struct { 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 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 { if err != nil {
return 0, err return 0, err
} }

View File

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

View File

@ -42,3 +42,64 @@ func TestParseTableName(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, "p_parseTableName", table.Name) 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 // SQLTypeTagHandler describes SQL Type tag handler
func SQLTypeTagHandler(ctx *Context) error { func SQLTypeTagHandler(ctx *Context) error {
ctx.col.SQLType = schemas.SQLType{Name: ctx.tagName} ctx.col.SQLType = schemas.SQLType{Name: ctx.tagName}
if strings.EqualFold(ctx.tagName, "JSON") {
ctx.col.IsJSON = true
}
if len(ctx.params) > 0 { if len(ctx.params) > 0 {
if ctx.tagName == schemas.Enum { if ctx.tagName == schemas.Enum {
ctx.col.EnumOptions = make(map[string]int) ctx.col.EnumOptions = make(map[string]int)