diff --git a/.drone.yml b/.drone.yml index 7a18e0d6..05f06e99 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2,57 +2,288 @@ kind: pipeline name: testing steps: +- name: restore-cache + image: meltwater/drone-cache + pull: always + settings: + backend: "filesystem" + restore: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + - name: test-vet - image: golang:1.11 # The lowest golang requirement + image: golang:1.15 environment: GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' commands: - make vet - - make test + - make fmt-check + volumes: + - name: cache + path: /go when: event: - push - pull_request -- name: test-sqlite - image: golang:1.12 +- name: rebuild-cache + image: meltwater/drone-cache + pull: true + settings: + backend: "filesystem" + rebuild: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +volumes: + - name: cache + temp: {} + +--- +kind: pipeline +name: test-sqlite +depends_on: + - testing +steps: +- name: restore-cache + image: meltwater/drone-cache:dev + pull: always + settings: + backend: "filesystem" + restore: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +- name: test-sqlite3 + image: golang:1.15 environment: GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' + commands: + - make test-sqlite3 + - TEST_CACHE_ENABLE=true make test-sqlite3 + - TEST_QUOTE_POLICY=reserved make test-sqlite3 + volumes: + - name: cache + path: /go + +- name: test-sqlite + image: golang:1.15 + environment: + GO111MODULE: "on" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' commands: - make test-sqlite - TEST_CACHE_ENABLE=true make test-sqlite - TEST_QUOTE_POLICY=reserved make test-sqlite - when: - event: - - push - - pull_request + volumes: + - name: cache + path: /go + +- name: rebuild-cache + image: meltwater/drone-cache:dev + pull: true + settings: + backend: "filesystem" + rebuild: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +volumes: + - name: cache + temp: {} + +--- +kind: pipeline +name: test-mysql +depends_on: + - testing +steps: +- name: restore-cache + image: meltwater/drone-cache + pull: always + settings: + backend: "filesystem" + restore: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go - name: test-mysql - image: golang:1.12 + image: golang:1.15 environment: GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' TEST_MYSQL_HOST: mysql TEST_MYSQL_CHARSET: utf8 TEST_MYSQL_DBNAME: xorm_test TEST_MYSQL_USERNAME: root TEST_MYSQL_PASSWORD: commands: + - make test + - make test-mysql + - TEST_CACHE_ENABLE=true make test-mysql + - TEST_QUOTE_POLICY=reserved make test-mysql + volumes: + - name: cache + path: /go + +- name: test-mysql-utf8mb4 + image: golang:1.15 + depends_on: + - test-mysql + environment: + GO111MODULE: "on" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' + TEST_MYSQL_HOST: mysql + TEST_MYSQL_CHARSET: utf8mb4 + TEST_MYSQL_DBNAME: xorm_test + TEST_MYSQL_USERNAME: root + TEST_MYSQL_PASSWORD: + commands: - make test-mysql - TEST_CACHE_ENABLE=true make test-mysql - TEST_QUOTE_POLICY=reserved make test-mysql - when: - event: - - push - - pull_request + volumes: + - name: cache + path: /go -- name: test-mysql8 - image: golang:1.12 +- name: test-mymysql + pull: default + image: golang:1.15 + depends_on: + - test-mysql-utf8mb4 environment: GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' + TEST_MYSQL_HOST: mysql:3306 + TEST_MYSQL_DBNAME: xorm_test + TEST_MYSQL_USERNAME: root + TEST_MYSQL_PASSWORD: + commands: + - make test-mymysql + - TEST_CACHE_ENABLE=true make test-mymysql + - TEST_QUOTE_POLICY=reserved make test-mymysql + volumes: + - name: cache + path: /go + +- name: rebuild-cache + image: meltwater/drone-cache + depends_on: + - test-mysql + - test-mysql-utf8mb4 + - test-mymysql + pull: true + settings: + backend: "filesystem" + rebuild: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +volumes: + - name: cache + temp: {} + +services: +- name: mysql + pull: default + image: mysql:5.7 + environment: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: xorm_test + +--- +kind: pipeline +name: test-mysql8 +depends_on: + - test-mysql + - test-sqlite +steps: +- name: restore-cache + image: meltwater/drone-cache + pull: always + settings: + backend: "filesystem" + restore: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +- name: test-mysql8 + image: golang:1.15 + environment: + GO111MODULE: "on" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' TEST_MYSQL_HOST: mysql8 TEST_MYSQL_CHARSET: utf8mb4 TEST_MYSQL_DBNAME: xorm_test @@ -62,19 +293,71 @@ steps: - make test-mysql - TEST_CACHE_ENABLE=true make test-mysql - TEST_QUOTE_POLICY=reserved make test-mysql - when: - event: - - push - - pull_request + volumes: + - name: cache + path: /go -- name: test-mysql-utf8mb4 - image: golang:1.12 +- name: rebuild-cache + image: meltwater/drone-cache:dev + pull: true depends_on: - - test-mysql + - test-mysql8 + settings: + backend: "filesystem" + rebuild: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +volumes: + - name: cache + temp: {} + +services: +- name: mysql8 + pull: default + image: mysql:8.0 + environment: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: xorm_test + +--- +kind: pipeline +name: test-mariadb +depends_on: + - test-mysql8 +steps: +- name: restore-cache + image: meltwater/drone-cache + pull: always + settings: + backend: "filesystem" + restore: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +- name: test-mariadb + image: golang:1.15 environment: GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" - TEST_MYSQL_HOST: mysql + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' + TEST_MYSQL_HOST: mariadb TEST_MYSQL_CHARSET: utf8mb4 TEST_MYSQL_DBNAME: xorm_test TEST_MYSQL_USERNAME: root @@ -83,38 +366,71 @@ steps: - make test-mysql - TEST_CACHE_ENABLE=true make test-mysql - TEST_QUOTE_POLICY=reserved make test-mysql - when: - event: - - push - - pull_request + volumes: + - name: cache + path: /go -- name: test-mymysql - pull: default - image: golang:1.12 +- name: rebuild-cache + image: meltwater/drone-cache:dev depends_on: - - test-mysql-utf8mb4 + - test-mariadb + pull: true + settings: + backend: "filesystem" + rebuild: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +volumes: + - name: cache + temp: {} + +services: +- name: mariadb + pull: default + image: mariadb:10.4 environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" - TEST_MYSQL_HOST: mysql:3306 - TEST_MYSQL_DBNAME: xorm_test - TEST_MYSQL_USERNAME: root - TEST_MYSQL_PASSWORD: - commands: - - make test-mymysql - - TEST_CACHE_ENABLE=true make test-mymysql - - TEST_QUOTE_POLICY=reserved make test-mymysql - when: - event: - - push - - pull_request + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: xorm_test + +--- +kind: pipeline +name: test-postgres +depends_on: + - test-mariadb +steps: +- name: restore-cache + image: meltwater/drone-cache + pull: always + settings: + backend: "filesystem" + restore: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go - name: test-postgres pull: default - image: golang:1.12 + image: golang:1.15 environment: GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' TEST_PGSQL_HOST: pgsql TEST_PGSQL_DBNAME: xorm_test TEST_PGSQL_USERNAME: postgres @@ -123,19 +439,21 @@ steps: - make test-postgres - TEST_CACHE_ENABLE=true make test-postgres - TEST_QUOTE_POLICY=reserved make test-postgres - when: - event: - - push - - pull_request + volumes: + - name: cache + path: /go - name: test-postgres-schema pull: default - image: golang:1.12 + image: golang:1.15 depends_on: - test-postgres environment: GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' TEST_PGSQL_HOST: pgsql TEST_PGSQL_SCHEMA: xorm TEST_PGSQL_DBNAME: xorm_test @@ -145,17 +463,72 @@ steps: - make test-postgres - TEST_CACHE_ENABLE=true make test-postgres - TEST_QUOTE_POLICY=reserved make test-postgres - when: - event: - - push - - pull_request + volumes: + - name: cache + path: /go + +- name: rebuild-cache + image: meltwater/drone-cache:dev + pull: true + depends_on: + - test-postgres-schema + settings: + backend: "filesystem" + rebuild: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +volumes: + - name: cache + temp: {} + +services: +- name: pgsql + pull: default + image: postgres:9.5 + environment: + POSTGRES_DB: xorm_test + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + +--- +kind: pipeline +name: test-mssql +depends_on: + - test-postgres +steps: +- name: restore-cache + image: meltwater/drone-cache + pull: always + settings: + backend: "filesystem" + restore: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go - name: test-mssql pull: default - image: golang:1.12 + image: golang:1.15 environment: GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' TEST_MSSQL_HOST: mssql TEST_MSSQL_DBNAME: xorm_test TEST_MSSQL_USERNAME: sa @@ -164,17 +537,71 @@ steps: - make test-mssql - TEST_CACHE_ENABLE=true make test-mssql - TEST_QUOTE_POLICY=reserved make test-mssql - when: - event: - - push - - pull_request + - TEST_MSSQL_DEFAULT_VARCHAR=NVARCHAR TEST_MSSQL_DEFAULT_CHAR=NCHAR make test-mssql + volumes: + - name: cache + path: /go + +- name: rebuild-cache + image: meltwater/drone-cache:dev + pull: true + settings: + backend: "filesystem" + rebuild: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +volumes: + - name: cache + temp: {} + +services: +- name: mssql + pull: default + image: microsoft/mssql-server-linux:latest + environment: + ACCEPT_EULA: Y + SA_PASSWORD: yourStrong(!)Password + MSSQL_PID: Developer + +--- +kind: pipeline +name: test-tidb +depends_on: + - test-mssql +steps: +- name: restore-cache + image: meltwater/drone-cache + pull: always + settings: + backend: "filesystem" + restore: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go - name: test-tidb pull: default - image: golang:1.12 + image: golang:1.15 environment: GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' TEST_TIDB_HOST: "tidb:4000" TEST_TIDB_DBNAME: xorm_test TEST_TIDB_USERNAME: root @@ -183,17 +610,66 @@ steps: - make test-tidb - TEST_CACHE_ENABLE=true make test-tidb - TEST_QUOTE_POLICY=reserved make test-tidb - when: - event: - - push - - pull_request + volumes: + - name: cache + path: /go + +- name: rebuild-cache + image: meltwater/drone-cache:dev + pull: true + settings: + backend: "filesystem" + rebuild: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +volumes: + - name: cache + temp: {} + +services: +- name: tidb + pull: default + image: pingcap/tidb:v3.0.3 + +--- +kind: pipeline +name: test-cockroach +depends_on: + - test-tidb +steps: +- name: restore-cache + image: meltwater/drone-cache + pull: always + settings: + backend: "filesystem" + restore: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go - name: test-cockroach pull: default - image: golang:1.13 + image: golang:1.15 environment: GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" + GOPROXY: "https://goproxy.io" + CGO_ENABLED: 1 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' TEST_COCKROACH_HOST: "cockroach:26257" TEST_COCKROACH_DBNAME: xorm_test TEST_COCKROACH_USERNAME: root @@ -202,103 +678,62 @@ steps: - sleep 10 - make test-cockroach - TEST_CACHE_ENABLE=true make test-cockroach - when: - event: - - push - - pull_request + volumes: + - name: cache + path: /go -- name: merge_coverage - pull: default - image: golang:1.12 - environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.cn" - depends_on: - - test-vet - - test-sqlite - - test-mysql - - test-mysql8 - - test-mymysql - - test-postgres - - test-postgres-schema - - test-mssql - - test-tidb - - test-cockroach - commands: - - make coverage - when: - event: - - push - - pull_request +- name: rebuild-cache + image: meltwater/drone-cache:dev + pull: true + settings: + backend: "filesystem" + rebuild: true + cache_key: '{{ .Repo.Name }}_{{ checksum "go.mod" }}_{{ checksum "go.sum" }}_{{ arch }}_{{ os }}' + archive_format: "gzip" + filesystem_cache_root: "/go" + mount: + - pkg.mod + - pkg.build + volumes: + - name: cache + path: /go + +volumes: + - name: cache + temp: {} services: - -- name: mysql - pull: default - image: mysql:5.7 - environment: - MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: xorm_test - when: - event: - - push - - tag - - pull_request - -- name: mysql8 - pull: default - image: mysql:8.0 - environment: - MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: xorm_test - when: - event: - - push - - tag - - pull_request - -- name: pgsql - pull: default - image: postgres:9.5 - environment: - POSTGRES_DB: xorm_test - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - when: - event: - - push - - tag - - pull_request - -- name: mssql - pull: default - image: microsoft/mssql-server-linux:latest - environment: - ACCEPT_EULA: Y - SA_PASSWORD: yourStrong(!)Password - MSSQL_PID: Developer - when: - event: - - push - - tag - - pull_request - -- name: tidb - pull: default - image: pingcap/tidb:v3.0.3 - when: - event: - - push - - tag - - pull_request - - name: cockroach pull: default image: cockroachdb/cockroach:v19.2.4 commands: - /cockroach/cockroach start --insecure + +--- +kind: pipeline +name: merge_coverage +depends_on: + - testing + - test-sqlite + - test-mysql + - test-mysql8 + - test-mariadb + - test-postgres + - test-mssql + - test-tidb + - test-cockroach +steps: +- name: merge_coverage + pull: default + image: golang:1.15 + environment: + GO111MODULE: "on" + GOPROXY: "https://goproxy.io" + commands: + - make coverage when: + branch: + - master event: - - push - - tag - - pull_request + - push + - pull_request diff --git a/.gitignore b/.gitignore index 617d5da7..a3fbadd4 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ test.db.sql *coverage.out test.db integrations/*.sql +integrations/test_sqlite* \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cd081e0..3ac229c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,68 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log. +## [1.0.7](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1336) - 2021-01-21 + +* BUGFIXES + * Fix bug for mssql (#1854) +* MISC + * fix_bugs_for_mssql (#1852) + +## [1.0.6](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1308) - 2021-01-05 + +* BUGFIXES + * Fix bug when modify column on mssql (#1849) + * Fix find and count bug with cols (#1826) + * Fix update bug (#1823) + * Fix json tag with other type (#1822) +* ENHANCEMENTS + * prevent panic when struct with unexport field (#1839) + * Automatically convert datetime to int64 (#1715) +* MISC + * Fix index (#1841) + * Performance improvement for columnsbyName (#1788) + +## [1.0.5](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1299) - 2020-09-08 + +* BUGFIXES + * Fix bug of ToDB when update on a nil pointer (#1786) + * Fix warnings with schema Sync2 with default varchar as NVARCHAR (#1783) + * Do not ever quote asterisk symbol. Fixes #1780 (#1781) + * Fix bug on get columns for postgres (#1779) + +## [1.0.4](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1286) - 2020-09-02 + +* FEATURES + * Add params for mssql to allow redefine varchar as nvarchar or char as nchar (#1741) +* BUGFIXES + * Fix mysql dialect error from invalid db identifier in orderby clause (#1743) (#1751) +* ENHANCEMENTS + * Support get dataSourceName on ContextHook for monitor which DB executed SQL (#1740) +* MISC + * Correct default detection in MariaDB >= 10.2.7 (#1778) + +## [1.0.3](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1281) - 2020-07-10 + +* BUGFIXES + * Fix dump of sqlite (#1639) +* ENHANCEMENTS + * Fix index name parsing in SQLite dialect (#1737) + * add hooks for Commit and Rollback (#1733) + +## [1.0.2](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1261) - 2020-06-16 + +* FEATURES + * Add Hook (#1644) +* BUGFIXES + * Fix bug when ID used but no reference table given (#1709) + * Fix find and count bug (#1651) +* ENHANCEMENTS + * chore: improve snakeCasedName performance (#1688) + * Fix find with another struct (#1666) + * fix GetColumns missing ordinal position (#1660) +* MISC + * chore: improve titleCasedName performance (#1691) + ## [1.0.1](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1253) - 2020-03-25 * BUGFIXES diff --git a/Makefile b/Makefile index 4cccacd8..7305baa7 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,9 @@ GOFMT ?= gofmt -s TAGS ?= SED_INPLACE := sed -i -GOFILES := $(shell find . -name "*.go" -type f) +GO_DIRS := caches contexts integrations convert core dialects internal log migrate names schemas tags +GOFILES := $(wildcard *.go) +GOFILES += $(shell find $(GO_DIRS) -name "*.go" -type f) INTEGRATION_PACKAGES := xorm.io/xorm/integrations PACKAGES ?= $(filter-out $(INTEGRATION_PACKAGES),$(shell $(GO) list ./...)) @@ -20,6 +22,9 @@ TEST_MSSQL_HOST ?= mssql:1433 TEST_MSSQL_DBNAME ?= gitea TEST_MSSQL_USERNAME ?= sa TEST_MSSQL_PASSWORD ?= MwantsaSecurePassword1 +TEST_MSSQL_DEFAULT_VARCHAR ?= varchar +TEST_MSSQL_DEFAULT_CHAR ?= char +TEST_MSSQL_DO_NVARCHAR_OVERRIDE_TEST ?= true TEST_MYSQL_HOST ?= mysql:3306 TEST_MYSQL_CHARSET ?= utf8 @@ -96,7 +101,8 @@ help: @echo " - test-mysql run integration tests for mysql" @echo " - test-mssql run integration tests for mssql" @echo " - test-postgres run integration tests for postgres" - @echo " - test-sqlite run integration tests for sqlite" + @echo " - test-sqlite3 run integration tests for sqlite" + @echo " - test-sqlite run integration tests for pure go sqlite" @echo " - test-tidb run integration tests for tidb" @echo " - vet examines Go source code and reports suspicious constructs" @@ -144,12 +150,16 @@ test-cockroach\#%: go-check test-mssql: go-check $(GO) test $(INTEGRATION_PACKAGES) -v -race -db=mssql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ -conn_str="server=$(TEST_MSSQL_HOST);user id=$(TEST_MSSQL_USERNAME);password=$(TEST_MSSQL_PASSWORD);database=$(TEST_MSSQL_DBNAME)" \ + -default_varchar=$(TEST_MSSQL_DEFAULT_VARCHAR) -default_char=$(TEST_MSSQL_DEFAULT_CHAR) \ + -do_nvarchar_override_test=$(TEST_MSSQL_DO_NVARCHAR_OVERRIDE_TEST) \ -coverprofile=mssql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PNONY: test-mssql\#% test-mssql\#%: go-check $(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -db=mssql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \ -conn_str="server=$(TEST_MSSQL_HOST);user id=$(TEST_MSSQL_USERNAME);password=$(TEST_MSSQL_PASSWORD);database=$(TEST_MSSQL_DBNAME)" \ + -default_varchar=$(TEST_MSSQL_DEFAULT_VARCHAR) -default_char=$(TEST_MSSQL_DEFAULT_CHAR) \ + -do_nvarchar_override_test=$(TEST_MSSQL_DO_NVARCHAR_OVERRIDE_TEST) \ -coverprofile=mssql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PNONY: test-mymysql @@ -188,21 +198,37 @@ test-postgres\#%: go-check -conn_str="postgres://$(TEST_PGSQL_USERNAME):$(TEST_PGSQL_PASSWORD)@$(TEST_PGSQL_HOST)/$(TEST_PGSQL_DBNAME)?sslmode=disable" \ -quote=$(TEST_QUOTE_POLICY) -coverprofile=postgres.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic +.PHONY: test-sqlite3 +test-sqlite3: go-check + $(GO) test $(INTEGRATION_PACKAGES) -v -race -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ + -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite3.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + +.PHONY: test-sqlite3-schema +test-sqlite3-schema: go-check + $(GO) test $(INTEGRATION_PACKAGES) -v -race -schema=xorm -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ + -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite3.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + +.PHONY: test-sqlite3\#% +test-sqlite3\#%: go-check + $(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ + -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite3.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + .PHONY: test-sqlite test-sqlite: go-check - $(GO) test $(INTEGRATION_PACKAGES) -v -race -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ + $(GO) test $(INTEGRATION_PACKAGES) -v -race -cache=$(TEST_CACHE_ENABLE) -db=sqlite -conn_str="./test.db?cache=shared&mode=rwc" \ -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PHONY: test-sqlite-schema test-sqlite-schema: go-check - $(GO) test $(INTEGRATION_PACKAGES) -v -race -schema=xorm -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ + $(GO) test $(INTEGRATION_PACKAGES) -v -race -schema=xorm -cache=$(TEST_CACHE_ENABLE) -db=sqlite -conn_str="./test.db?cache=shared&mode=rwc" \ -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic .PHONY: test-sqlite\#% test-sqlite\#%: go-check - $(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \ + $(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -cache=$(TEST_CACHE_ENABLE) -db=sqlite -conn_str="./test.db?cache=shared&mode=rwc" \ -quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic + .PNONY: test-tidb test-tidb: go-check $(GO) test $(INTEGRATION_PACKAGES) -v -race -db=mysql -cache=$(TEST_CACHE_ENABLE) -ignore_select_update=true \ diff --git a/README.md b/README.md index a15be488..67380839 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,7 @@ Xorm is a simple and powerful ORM for Go. -[![Build Status](https://drone.gitea.com/api/badges/xorm/xorm/status.svg)](https://drone.gitea.com/xorm/xorm) [![](http://gocover.io/_badge/xorm.io/xorm)](https://gocover.io/xorm.io/xorm) -[![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm) -[![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3) +[![Build Status](https://drone.gitea.com/api/badges/xorm/xorm/status.svg)](https://drone.gitea.com/xorm/xorm) [![](http://gocover.io/_badge/xorm.io/xorm)](https://gocover.io/xorm.io/xorm) [![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm) [![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3) ## Notice @@ -315,7 +313,7 @@ err := engine.Where(builder.NotIn("a", 1, 2).And(builder.In("b", "c", "d", "e")) // SELECT id, name ... FROM user WHERE a NOT IN (?, ?) AND b IN (?, ?, ?) ``` -* Multiple operations in one go routine, no transation here but resue session memory +* Multiple operations in one go routine, no transaction here but resue session memory ```Go session := engine.NewSession() @@ -338,7 +336,7 @@ if _, err := session.Exec("delete from userinfo where username = ?", user2.Usern return nil ``` -* Transation should be on one go routine. There is transaction and resue session memory +* Transaction should be on one go routine. There is transaction and resue session memory ```Go session := engine.NewSession() diff --git a/README_CN.md b/README_CN.md index f6f88310..80245dd3 100644 --- a/README_CN.md +++ b/README_CN.md @@ -4,9 +4,7 @@ xorm 是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。 -[![Build Status](https://drone.gitea.com/api/badges/xorm/xorm/status.svg)](https://drone.gitea.com/xorm/xorm) [![](http://gocover.io/_badge/xorm.io/xorm)](https://gocover.io/xorm.io/xorm) -[![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm) -[![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3) +[![Build Status](https://drone.gitea.com/api/badges/xorm/xorm/status.svg)](https://drone.gitea.com/xorm/xorm) [![](http://gocover.io/_badge/xorm.io/xorm)](https://gocover.io/xorm.io/xorm) [![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm) [![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3) ## Notice diff --git a/caches/leveldb.go b/caches/leveldb.go index d1a177ad..f2f71d84 100644 --- a/caches/leveldb.go +++ b/caches/leveldb.go @@ -19,6 +19,7 @@ type LevelDBStore struct { var _ CacheStore = &LevelDBStore{} +// NewLevelDBStore creates a leveldb store func NewLevelDBStore(dbfile string) (*LevelDBStore, error) { db := &LevelDBStore{} h, err := leveldb.OpenFile(dbfile, nil) @@ -29,6 +30,7 @@ func NewLevelDBStore(dbfile string) (*LevelDBStore, error) { return db, nil } +// Put implements CacheStore func (s *LevelDBStore) Put(key string, value interface{}) error { val, err := Encode(value) if err != nil { @@ -50,6 +52,7 @@ func (s *LevelDBStore) Put(key string, value interface{}) error { return err } +// Get implements CacheStore func (s *LevelDBStore) Get(key string) (interface{}, error) { data, err := s.store.Get([]byte(key), nil) if err != nil { @@ -75,6 +78,7 @@ func (s *LevelDBStore) Get(key string) (interface{}, error) { return s.v, err } +// Del implements CacheStore func (s *LevelDBStore) Del(key string) error { err := s.store.Delete([]byte(key), nil) if err != nil { @@ -89,6 +93,7 @@ func (s *LevelDBStore) Del(key string) error { return err } +// Close implements CacheStore func (s *LevelDBStore) Close() { s.store.Close() } diff --git a/caches/manager.go b/caches/manager.go index 05045210..89a14106 100644 --- a/caches/manager.go +++ b/caches/manager.go @@ -6,6 +6,7 @@ package caches import "sync" +// Manager represents a cache manager type Manager struct { cacher Cacher disableGlobalCache bool @@ -14,6 +15,7 @@ type Manager struct { cacherLock sync.RWMutex } +// NewManager creates a cache manager func NewManager() *Manager { return &Manager{ cachers: make(map[string]Cacher), @@ -27,12 +29,14 @@ func (mgr *Manager) SetDisableGlobalCache(disable bool) { } } +// SetCacher set cacher of table func (mgr *Manager) SetCacher(tableName string, cacher Cacher) { mgr.cacherLock.Lock() mgr.cachers[tableName] = cacher mgr.cacherLock.Unlock() } +// GetCacher returns a cache of a table func (mgr *Manager) GetCacher(tableName string) Cacher { var cacher Cacher var ok bool diff --git a/contexts/hook.go b/contexts/hook.go index 71ad8e87..70f114dd 100644 --- a/contexts/hook.go +++ b/contexts/hook.go @@ -31,6 +31,7 @@ func NewContextHook(ctx context.Context, sql string, args []interface{}) *Contex } } +// End finish the hook invokation func (c *ContextHook) End(ctx context.Context, result sql.Result, err error) { c.Ctx = ctx c.Result = result @@ -38,19 +39,23 @@ func (c *ContextHook) End(ctx context.Context, result sql.Result, err error) { c.ExecuteTime = time.Now().Sub(c.start) } +// Hook represents a hook behaviour type Hook interface { BeforeProcess(c *ContextHook) (context.Context, error) AfterProcess(c *ContextHook) error } +// Hooks implements Hook interface but contains multiple Hook type Hooks struct { hooks []Hook } +// AddHook adds a Hook func (h *Hooks) AddHook(hooks ...Hook) { h.hooks = append(h.hooks, hooks...) } +// BeforeProcess invoked before execute the process func (h *Hooks) BeforeProcess(c *ContextHook) (context.Context, error) { ctx := c.Ctx for _, h := range h.hooks { @@ -63,6 +68,7 @@ func (h *Hooks) BeforeProcess(c *ContextHook) (context.Context, error) { return ctx, nil } +// AfterProcess invoked after exetue the process func (h *Hooks) AfterProcess(c *ContextHook) error { firstErr := c.Err for _, h := range h.hooks { diff --git a/core/db_test.go b/core/db_test.go index 777ab0ad..104c5b95 100644 --- a/core/db_test.go +++ b/core/db_test.go @@ -15,6 +15,7 @@ import ( _ "github.com/go-sql-driver/mysql" _ "github.com/mattn/go-sqlite3" + _ "modernc.org/sqlite" ) var ( @@ -27,7 +28,7 @@ func TestMain(m *testing.M) { flag.Parse() switch *dbtype { - case "sqlite3": + case "sqlite3", "sqlite": createTableSql = "CREATE TABLE IF NOT EXISTS `user` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NULL, " + "`title` TEXT NULL, `age` FLOAT NULL, `alias` TEXT NULL, `nick_name` TEXT NULL, `created` datetime);" case "mysql": @@ -45,8 +46,11 @@ func TestMain(m *testing.M) { func testOpen() (*DB, error) { switch *dbtype { case "sqlite3": - os.Remove("./test.db") + os.Remove("./test_sqlite3.db") return Open("sqlite3", "./test.db") + case "sqlite": + os.Remove("./test_sqlite.db") + return Open("sqlite", "./test.db") case "mysql": return Open("mysql", *dbConn) default: diff --git a/core/tx.go b/core/tx.go index 9b2988af..a85a6874 100644 --- a/core/tx.go +++ b/core/tx.go @@ -18,7 +18,8 @@ var ( // Tx represents a transaction type Tx struct { *sql.Tx - db *DB + db *DB + ctx context.Context } func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { @@ -32,13 +33,41 @@ func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { if err := db.afterProcess(hookCtx); err != nil { return nil, err } - return &Tx{tx, db}, nil + return &Tx{tx, db, ctx}, nil } func (db *DB) Begin() (*Tx, error) { return db.BeginTx(context.Background(), nil) } +func (tx *Tx) Commit() error { + hookCtx := contexts.NewContextHook(tx.ctx, "COMMIT", nil) + ctx, err := tx.db.beforeProcess(hookCtx) + if err != nil { + return err + } + err = tx.Tx.Commit() + hookCtx.End(ctx, nil, err) + if err := tx.db.afterProcess(hookCtx); err != nil { + return err + } + return nil +} + +func (tx *Tx) Rollback() error { + hookCtx := contexts.NewContextHook(tx.ctx, "ROLLBACK", nil) + ctx, err := tx.db.beforeProcess(hookCtx) + if err != nil { + return err + } + err = tx.Tx.Rollback() + hookCtx.End(ctx, nil, err) + if err := tx.db.afterProcess(hookCtx); err != nil { + return err + } + return nil +} + func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error) { names := make(map[string]int) var i int diff --git a/dialects/dialect.go b/dialects/dialect.go index dc96f73a..b02ec4ae 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -79,32 +79,34 @@ type Base struct { quoter schemas.Quoter } -func (b *Base) Quoter() schemas.Quoter { - return b.quoter +// Quoter returns the current database Quoter +func (db *Base) Quoter() schemas.Quoter { + return db.quoter } -func (b *Base) Init(dialect Dialect, uri *URI) error { - b.dialect, b.uri = dialect, uri +// Init initialize the dialect +func (db *Base) Init(dialect Dialect, uri *URI) error { + db.dialect, db.uri = dialect, uri return nil } -func (b *Base) URI() *URI { - return b.uri +// URI returns the uri of database +func (db *Base) URI() *URI { + return db.uri } -func (b *Base) DBType() schemas.DBType { - return b.uri.DBType -} - -func (b *Base) FormatBytes(bs []byte) string { +// FormatBytes formats bytes +func (db *Base) FormatBytes(bs []byte) string { return fmt.Sprintf("0x%x", bs) } +// DropTableSQL returns drop table SQL func (db *Base) DropTableSQL(tableName string) (string, bool) { quote := db.dialect.Quoter().Quote return fmt.Sprintf("DROP TABLE IF EXISTS %s", quote(tableName)), true } +// HasRecords returns true if the SQL has records returned func (db *Base) HasRecords(queryer core.Queryer, ctx context.Context, query string, args ...interface{}) (bool, error) { rows, err := queryer.QueryContext(ctx, query, args...) if err != nil { @@ -118,6 +120,7 @@ func (db *Base) HasRecords(queryer core.Queryer, ctx context.Context, query stri return false, nil } +// IsColumnExist returns true if the column of the table exist func (db *Base) IsColumnExist(queryer core.Queryer, ctx context.Context, tableName, colName string) (bool, error) { quote := db.dialect.Quoter().Quote query := fmt.Sprintf( @@ -132,11 +135,13 @@ func (db *Base) IsColumnExist(queryer core.Queryer, ctx context.Context, tableNa return db.HasRecords(queryer, ctx, query, db.uri.DBName, tableName, colName) } +// AddColumnSQL returns a SQL to add a column func (db *Base) AddColumnSQL(tableName string, col *schemas.Column) string { s, _ := ColumnString(db.dialect, col, true) return fmt.Sprintf("ALTER TABLE %v ADD %v", db.dialect.Quoter().Quote(tableName), s) } +// CreateIndexSQL returns a SQL to create index func (db *Base) CreateIndexSQL(tableName string, index *schemas.Index) string { quoter := db.dialect.Quoter() var unique string @@ -150,6 +155,7 @@ func (db *Base) CreateIndexSQL(tableName string, index *schemas.Index) string { quoter.Join(index.Cols, ",")) } +// DropIndexSQL returns a SQL to drop index func (db *Base) DropIndexSQL(tableName string, index *schemas.Index) string { quote := db.dialect.Quoter().Quote var name string @@ -161,16 +167,19 @@ func (db *Base) DropIndexSQL(tableName string, index *schemas.Index) string { return fmt.Sprintf("DROP INDEX %v ON %s", quote(name), quote(tableName)) } +// ModifyColumnSQL returns a SQL to modify SQL func (db *Base) ModifyColumnSQL(tableName string, col *schemas.Column) string { s, _ := ColumnString(db.dialect, col, false) - return fmt.Sprintf("alter table %s MODIFY COLUMN %s", tableName, s) + return fmt.Sprintf("ALTER TABLE %s MODIFY COLUMN %s", tableName, s) } -func (b *Base) ForUpdateSQL(query string) string { +// ForUpdateSQL returns for updateSQL +func (db *Base) ForUpdateSQL(query string) string { return query + " FOR UPDATE" } -func (b *Base) SetParams(params map[string]string) { +// SetParams set params +func (db *Base) SetParams(params map[string]string) { } var ( @@ -206,6 +215,7 @@ func regDrvsNDialects() bool { "postgres": {"postgres", func() Driver { return &pqDriver{} }, func() Dialect { return &postgres{} }}, "pgx": {"postgres", func() Driver { return &pqDriverPgx{} }, func() Dialect { return &postgres{} }}, "sqlite3": {"sqlite3", func() Driver { return &sqlite3Driver{} }, func() Dialect { return &sqlite3{} }}, + "sqlite": {"sqlite3", func() Driver { return &sqlite3Driver{} }, func() Dialect { return &sqlite3{} }}, "oci8": {"oracle", func() Driver { return &oci8Driver{} }, func() Dialect { return &oracle{} }}, "goracle": {"oracle", func() Driver { return &goracleDriver{} }, func() Dialect { return &oracle{} }}, } diff --git a/dialects/mssql.go b/dialects/mssql.go index 8ef924b8..15d1cd06 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -214,13 +214,45 @@ var ( type mssql struct { Base + defaultVarchar string + defaultChar string } func (db *mssql) Init(uri *URI) error { db.quoter = mssqlQuoter + db.defaultChar = "CHAR" + db.defaultVarchar = "VARCHAR" return db.Base.Init(db, uri) } +func (db *mssql) SetParams(params map[string]string) { + defaultVarchar, ok := params["DEFAULT_VARCHAR"] + if ok { + var t = strings.ToUpper(defaultVarchar) + switch t { + case "NVARCHAR", "VARCHAR": + db.defaultVarchar = t + default: + db.defaultVarchar = "VARCHAR" + } + } else { + db.defaultVarchar = "VARCHAR" + } + + defaultChar, ok := params["DEFAULT_CHAR"] + if ok { + var t = strings.ToUpper(defaultChar) + switch t { + case "NCHAR", "CHAR": + db.defaultChar = t + default: + db.defaultChar = "CHAR" + } + } else { + db.defaultChar = "CHAR" + } +} + func (db *mssql) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { @@ -231,6 +263,7 @@ func (db *mssql) SQLType(c *schemas.Column) string { } else if strings.EqualFold(c.Default, "false") { c.Default = "0" } + return res case schemas.Serial: c.IsAutoIncrement = true c.IsPrimaryKey = true @@ -251,10 +284,10 @@ func (db *mssql) SQLType(c *schemas.Column) string { case schemas.TimeStampz: res = "DATETIMEOFFSET" c.Length = 7 - case schemas.MediumInt: + case schemas.MediumInt, schemas.UnsignedInt: res = schemas.Int case schemas.Text, schemas.MediumText, schemas.TinyText, schemas.LongText, schemas.Json: - res = schemas.Varchar + "(MAX)" + res = db.defaultVarchar + "(MAX)" case schemas.Double: res = schemas.Real case schemas.Uuid: @@ -263,15 +296,35 @@ func (db *mssql) SQLType(c *schemas.Column) string { case schemas.TinyInt: res = schemas.TinyInt c.Length = 0 - case schemas.BigInt: + case schemas.BigInt, schemas.UnsignedBigInt: res = schemas.BigInt c.Length = 0 + case schemas.NVarchar: + res = t + if c.Length == -1 { + res += "(MAX)" + } + case schemas.Varchar: + res = db.defaultVarchar + if c.Length == -1 { + res += "(MAX)" + } + case schemas.Char: + res = db.defaultChar + if c.Length == -1 { + res += "(MAX)" + } + case schemas.NChar: + res = t + if c.Length == -1 { + res += "(MAX)" + } default: res = t } - if res == schemas.Int { - return schemas.Int + if res == schemas.Int || res == schemas.Bit || res == schemas.DateTime { + return res } hasLen1 := (c.Length > 0) @@ -317,6 +370,11 @@ func (db *mssql) DropTableSQL(tableName string) (string, bool) { "DROP TABLE \"%s\"", tableName, tableName), true } +func (db *mssql) ModifyColumnSQL(tableName string, col *schemas.Column) string { + s, _ := ColumnString(db.dialect, col, false) + return fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s", tableName, s) +} + func (db *mssql) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { args := []interface{}{idxName} sql := "select name from sysindexes where id=object_id('" + tableName + "') and name=?" @@ -389,8 +447,18 @@ func (db *mssql) GetColumns(queryer core.Queryer, ctx context.Context, tableName col.SQLType = schemas.SQLType{Name: schemas.TimeStampz, DefaultLength: 0, DefaultLength2: 0} case "NVARCHAR": col.SQLType = schemas.SQLType{Name: schemas.NVarchar, DefaultLength: 0, DefaultLength2: 0} + if col.Length > 0 { + col.Length /= 2 + col.Length2 /= 2 + } case "IMAGE": col.SQLType = schemas.SQLType{Name: schemas.VarBinary, DefaultLength: 0, DefaultLength2: 0} + case "NCHAR": + if col.Length > 0 { + col.Length /= 2 + col.Length2 /= 2 + } + fallthrough default: if _, ok := schemas.SqlTypes[ct]; ok { col.SQLType = schemas.SQLType{Name: ct, DefaultLength: 0, DefaultLength2: 0} @@ -472,7 +540,7 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? colName = strings.Trim(colName, "` ") var isRegular bool - if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { + if (strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName)) && len(indexName) > (5+len(tableName)) { indexName = indexName[5+len(tableName):] isRegular = true } diff --git a/dialects/mysql.go b/dialects/mysql.go index ac916c89..2b530daf 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -254,6 +254,10 @@ func (db *mysql) SQLType(c *schemas.Column) string { c.Length = 40 case schemas.Json: res = schemas.Text + case schemas.UnsignedInt: + res = schemas.Int + case schemas.UnsignedBigInt: + res = schemas.BigInt default: res = t } @@ -271,6 +275,11 @@ func (db *mysql) SQLType(c *schemas.Column) string { } else if hasLen1 { res += "(" + strconv.Itoa(c.Length) + ")" } + + if c.SQLType.Name == schemas.UnsignedBigInt || c.SQLType.Name == schemas.UnsignedInt { + res += " UNSIGNED" + } + return res } @@ -307,8 +316,17 @@ func (db *mysql) AddColumnSQL(tableName string, col *schemas.Column) string { func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{db.uri.DBName, tableName} + alreadyQuoted := "(INSTR(VERSION(), 'maria') > 0 && " + + "(SUBSTRING_INDEX(VERSION(), '.', 1) > 10 || " + + "(SUBSTRING_INDEX(VERSION(), '.', 1) = 10 && " + + "(SUBSTRING_INDEX(SUBSTRING(VERSION(), 4), '.', 1) > 2 || " + + "(SUBSTRING_INDEX(SUBSTRING(VERSION(), 4), '.', 1) = 2 && " + + "SUBSTRING_INDEX(SUBSTRING(VERSION(), 6), '-', 1) >= 7)))))" s := "SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`," + - " `COLUMN_KEY`, `EXTRA`,`COLUMN_COMMENT` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" + " `COLUMN_KEY`, `EXTRA`, `COLUMN_COMMENT`, " + + alreadyQuoted + " AS NEEDS_QUOTE " + + "FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" + + " ORDER BY `COLUMNS`.ORDINAL_POSITION" rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { @@ -322,27 +340,35 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName col := new(schemas.Column) col.Indexes = make(map[string]int) - var columnName, isNullable, colType, colKey, extra, comment string + var columnName, nullableStr, colType, colKey, extra, comment string + var alreadyQuoted, isUnsigned bool var colDefault *string - err = rows.Scan(&columnName, &isNullable, &colDefault, &colType, &colKey, &extra, &comment) + err = rows.Scan(&columnName, &nullableStr, &colDefault, &colType, &colKey, &extra, &comment, &alreadyQuoted) if err != nil { return nil, nil, err } col.Name = strings.Trim(columnName, "` ") col.Comment = comment - if "YES" == isNullable { + if nullableStr == "YES" { col.Nullable = true } - if colDefault != nil { + if colDefault != nil && (!alreadyQuoted || *colDefault != "NULL") { col.Default = *colDefault col.DefaultIsEmpty = false } else { col.DefaultIsEmpty = true } + fields := strings.Fields(colType) + if len(fields) == 2 && fields[1] == "unsigned" { + isUnsigned = true + } + colType = fields[0] cts := strings.Split(colType, "(") colName := cts[0] + // Remove the /* mariadb-5.3 */ suffix from coltypes + colName = strings.TrimSuffix(colName, "/* mariadb-5.3 */") colType = strings.ToUpper(colName) var len1, len2 int if len(cts) == 2 { @@ -377,11 +403,8 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName } } } - if colType == "FLOAT UNSIGNED" { - colType = "FLOAT" - } - if colType == "DOUBLE UNSIGNED" { - colType = "DOUBLE" + if isUnsigned { + colType = "UNSIGNED " + colType } col.Length = len1 col.Length2 = len2 @@ -403,9 +426,9 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName } if !col.DefaultIsEmpty { - if col.SQLType.IsText() { + if !alreadyQuoted && col.SQLType.IsText() { col.Default = "'" + col.Default + "'" - } else if col.SQLType.IsTime() && col.Default != "CURRENT_TIMESTAMP" { + } else if col.SQLType.IsTime() && !alreadyQuoted && col.Default != "CURRENT_TIMESTAMP" { col.Default = "'" + col.Default + "'" } } diff --git a/dialects/oracle.go b/dialects/oracle.go index 2620b0bb..91eed251 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -500,8 +500,8 @@ var ( } oracleQuoter = schemas.Quoter{ - Prefix: '[', - Suffix: ']', + Prefix: '"', + Suffix: '"', IsReserved: schemas.AlwaysReserve, } ) diff --git a/dialects/postgres.go b/dialects/postgres.go index 1996c49d..2b234c66 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -833,12 +833,12 @@ func (db *postgres) SQLType(c *schemas.Column) string { case schemas.Bit: res = schemas.Boolean return res - case schemas.MediumInt, schemas.Int, schemas.Integer: + case schemas.MediumInt, schemas.Int, schemas.Integer, schemas.UnsignedInt: if c.IsAutoIncrement { return schemas.Serial } return schemas.Integer - case schemas.BigInt: + case schemas.BigInt, schemas.UnsignedBigInt: if c.IsAutoIncrement { return schemas.BigSerial } @@ -857,6 +857,8 @@ func (db *postgres) SQLType(c *schemas.Column) string { res = schemas.Real case schemas.TinyText, schemas.MediumText, schemas.LongText: res = schemas.Text + case schemas.NChar: + res = schemas.Char case schemas.NVarchar: res = schemas.Varchar case schemas.Uuid: @@ -1015,7 +1017,7 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A schema := db.getSchema() if schema != "" { - s = fmt.Sprintf(s, "AND s.table_schema = $2") + s = fmt.Sprintf(s, " AND s.table_schema = $2") args = append(args, schema) } else { 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, `" `) if colDefault != nil { @@ -1086,8 +1092,10 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A col.Nullable = (isNullable == "YES") switch strings.ToLower(dataType) { - case "character varying", "character", "string": + case "character varying", "string": col.SQLType = schemas.SQLType{Name: schemas.Varchar, DefaultLength: 0, DefaultLength2: 0} + case "character": + col.SQLType = schemas.SQLType{Name: schemas.Char, DefaultLength: 0, DefaultLength2: 0} case "timestamp without time zone": col.SQLType = schemas.SQLType{Name: schemas.DateTime, DefaultLength: 0, DefaultLength2: 0} case "timestamp with time zone": diff --git a/dialects/sqlite3.go b/dialects/sqlite3.go index 0e910934..82683606 100644 --- a/dialects/sqlite3.go +++ b/dialects/sqlite3.go @@ -193,7 +193,8 @@ func (db *sqlite3) SQLType(c *schemas.Column) string { case schemas.Char, schemas.Varchar, schemas.NVarchar, schemas.TinyText, schemas.Text, schemas.MediumText, schemas.LongText, schemas.Json: return schemas.Text - case schemas.Bit, schemas.TinyInt, schemas.SmallInt, schemas.MediumInt, schemas.Int, schemas.Integer, schemas.BigInt: + case schemas.Bit, schemas.TinyInt, schemas.SmallInt, schemas.MediumInt, schemas.Int, schemas.Integer, schemas.BigInt, + schemas.UnsignedBigInt, schemas.UnsignedInt: return schemas.Integer case schemas.Float, schemas.Double, schemas.Real: return schemas.Real @@ -483,7 +484,7 @@ func (db *sqlite3) GetIndexes(queryer core.Queryer, ctx context.Context, tableNa continue } - indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []") + indexName := strings.Trim(strings.TrimSpace(sql[nNStart+6:nNEnd]), "`[]'\"") var isRegular bool if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { index.Name = indexName[5+len(tableName):] diff --git a/dialects/time.go b/dialects/time.go index b0394745..9a3c82a4 100644 --- a/dialects/time.go +++ b/dialects/time.go @@ -19,7 +19,11 @@ func FormatTime(dialect Dialect, sqlTypeName string, t time.Time) (v interface{} case schemas.Date: v = t.Format("2006-01-02") case schemas.DateTime, schemas.TimeStamp, schemas.Varchar: // !DarthPestilane! format time when sqlTypeName is schemas.Varchar. - 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: if dialect.URI().DBType == schemas.MSSQL { v = t.Format("2006-01-02T15:04:05.9999999Z07:00") diff --git a/engine.go b/engine.go index 7399f41a..384928d6 100644 --- a/engine.go +++ b/engine.go @@ -61,6 +61,10 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { return nil, err } + return newEngine(driverName, dataSourceName, dialect, db) +} + +func newEngine(driverName, dataSourceName string, dialect dialects.Dialect, db *core.DB) (*Engine, error) { cacherMgr := caches.NewManager() mapper := names.NewCacheMapper(new(names.SnakeMapper)) tagParser := tags.NewParser("xorm", dialect, mapper, mapper, cacherMgr) @@ -88,7 +92,7 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { engine.SetLogger(log.NewLoggerAdapter(logger)) runtime.SetFinalizer(engine, func(engine *Engine) { - engine.Close() + _ = engine.Close() }) return engine, nil @@ -101,6 +105,23 @@ func NewEngineWithParams(driverName string, dataSourceName string, params map[st return engine, err } +// NewEngineWithDB new a db manager with db. The params will be passed to db. +func NewEngineWithDB(driverName string, dataSourceName string, db *core.DB) (*Engine, error) { + dialect, err := dialects.OpenDialect(driverName, dataSourceName) + if err != nil { + return nil, err + } + return newEngine(driverName, dataSourceName, dialect, db) +} + +// NewEngineWithDialectAndDB new a db manager according to the parameter. +// If you do not want to use your own dialect or db, please use NewEngine. +// For creating dialect, you can call dialects.OpenDialect. And, for creating db, +// you can call core.Open or core.FromDB. +func NewEngineWithDialectAndDB(driverName, dataSourceName string, dialect dialects.Dialect, db *core.DB) (*Engine, error) { + return newEngine(driverName, dataSourceName, dialect, db) +} + // EnableSessionID if enable session id func (engine *Engine) EnableSessionID(enable bool) { engine.logSessionID = enable @@ -143,10 +164,12 @@ func (engine *Engine) Logger() log.ContextLogger { func (engine *Engine) SetLogger(logger interface{}) { var realLogger log.ContextLogger switch t := logger.(type) { - case log.Logger: - realLogger = log.NewLoggerAdapter(t) case log.ContextLogger: realLogger = t + case log.Logger: + realLogger = log.NewLoggerAdapter(t) + default: + panic("logger should implement either log.ContextLogger or log.Logger") } engine.logger = realLogger engine.DB().Logger = realLogger @@ -188,6 +211,11 @@ func (engine *Engine) SetColumnMapper(mapper names.Mapper) { engine.tagParser.SetColumnMapper(mapper) } +// SetTagIdentifier set the tag identifier +func (engine *Engine) SetTagIdentifier(tagIdentifier string) { + engine.tagParser.SetIdentifier(tagIdentifier) +} + // Quote Use QuoteStr quote the string sql func (engine *Engine) Quote(value string) string { value = strings.TrimSpace(value) @@ -347,13 +375,16 @@ func (engine *Engine) loadTableInfo(table *schemas.Table) error { var seq int for _, index := range indexes { for _, name := range index.Cols { - parts := strings.Split(name, " ") + parts := strings.Split(strings.TrimSpace(name), " ") if len(parts) > 1 { if parts[1] == "DESC" { seq = 1 + } else if parts[1] == "ASC" { + seq = 0 } } - if col := table.GetColumn(parts[0]); col != nil { + var colName = strings.Trim(parts[0], `"`) + if col := table.GetColumn(colName); col != nil { col.Indexes[index.Name] = index.Type } else { return fmt.Errorf("Unknown col %s seq %d, in index %v of table %v, columns %v", name, seq, index.Name, table.Name, table.ColumnsSeq()) @@ -412,6 +443,82 @@ func (engine *Engine) DumpTables(tables []*schemas.Table, w io.Writer, tp ...sch return engine.dumpTables(tables, w, tp...) } +func formatColumnValue(dstDialect dialects.Dialect, d interface{}, col *schemas.Column) string { + if d == nil { + return "NULL" + } + + if dq, ok := d.(bool); ok && (dstDialect.URI().DBType == schemas.SQLITE || + dstDialect.URI().DBType == schemas.MSSQL) { + if dq { + return "1" + } + return "0" + } + + if col.SQLType.IsText() { + var v = fmt.Sprintf("%s", d) + return "'" + strings.Replace(v, "'", "''", -1) + "'" + } else if col.SQLType.IsTime() { + var v = fmt.Sprintf("%s", d) + if strings.HasSuffix(v, " +0000 UTC") { + return fmt.Sprintf("'%s'", v[0:len(v)-len(" +0000 UTC")]) + } else if strings.HasSuffix(v, " +0000 +0000") { + return fmt.Sprintf("'%s'", v[0:len(v)-len(" +0000 +0000")]) + } + return "'" + strings.Replace(v, "'", "''", -1) + "'" + } else if col.SQLType.IsBlob() { + if reflect.TypeOf(d).Kind() == reflect.Slice { + return fmt.Sprintf("%s", dstDialect.FormatBytes(d.([]byte))) + } else if reflect.TypeOf(d).Kind() == reflect.String { + return fmt.Sprintf("'%s'", d.(string)) + } + } else if col.SQLType.IsNumeric() { + switch reflect.TypeOf(d).Kind() { + case reflect.Slice: + if col.SQLType.Name == schemas.Bool { + return fmt.Sprintf("%v", strconv.FormatBool(d.([]byte)[0] != byte('0'))) + } + return fmt.Sprintf("%s", string(d.([]byte))) + case reflect.Int16, reflect.Int8, reflect.Int32, reflect.Int64, reflect.Int: + if col.SQLType.Name == schemas.Bool { + v := reflect.ValueOf(d).Int() > 0 + if dstDialect.URI().DBType == schemas.SQLITE { + if v { + return "1" + } + return "0" + } + return fmt.Sprintf("%v", strconv.FormatBool(v)) + } + return fmt.Sprintf("%v", d) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if col.SQLType.Name == schemas.Bool { + v := reflect.ValueOf(d).Uint() > 0 + if dstDialect.URI().DBType == schemas.SQLITE { + if v { + return "1" + } + return "0" + } + return fmt.Sprintf("%v", strconv.FormatBool(v)) + } + return fmt.Sprintf("%v", d) + default: + return fmt.Sprintf("%v", d) + } + } + + s := fmt.Sprintf("%v", d) + if strings.Contains(s, ":") || strings.Contains(s, "-") { + if strings.HasSuffix(s, " +0000 UTC") { + return fmt.Sprintf("'%s'", s[0:len(s)-len(" +0000 UTC")]) + } + return fmt.Sprintf("'%s'", s) + } + return s +} + // dumpTables dump database all table structs and data to w with specify db type func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error { var dstDialect dialects.Dialect @@ -424,7 +531,10 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch } uri := engine.dialect.URI() - destURI := *uri + destURI := dialects.URI{ + DBType: tp[0], + DBName: uri.DBName, + } dstDialect.Init(&destURI) } @@ -495,59 +605,9 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch if col == nil { return errors.New("unknow column error") } - - if d == nil { - temp += ", NULL" - } else if col.SQLType.IsText() || col.SQLType.IsTime() { - var v = fmt.Sprintf("%s", d) - if strings.HasSuffix(v, " +0000 UTC") { - temp += fmt.Sprintf(", '%s'", v[0:len(v)-len(" +0000 UTC")]) - } else { - temp += ", '" + strings.Replace(v, "'", "''", -1) + "'" - } - } 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) - } - } + temp += "," + formatColumnValue(dstDialect, d, col) } - _, err = io.WriteString(w, temp[2:]+");\n") + _, err = io.WriteString(w, temp[1:]+");\n") if err != nil { return err } @@ -816,81 +876,11 @@ func (engine *Engine) IsTableExist(beanOrTableName interface{}) (bool, error) { return session.IsTableExist(beanOrTableName) } -// IDOf get id from one struct -func (engine *Engine) IDOf(bean interface{}) (schemas.PK, error) { - return engine.IDOfV(reflect.ValueOf(bean)) -} - // TableName returns table name with schema prefix if has func (engine *Engine) TableName(bean interface{}, includeSchema ...bool) string { return dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean, includeSchema...) } -// IDOfV get id from one value of struct -func (engine *Engine) IDOfV(rv reflect.Value) (schemas.PK, error) { - return engine.idOfV(rv) -} - -func (engine *Engine) idOfV(rv reflect.Value) (schemas.PK, error) { - v := reflect.Indirect(rv) - table, err := engine.tagParser.ParseWithCache(v) - if err != nil { - return nil, err - } - - pk := make([]interface{}, len(table.PrimaryKeys)) - for i, col := range table.PKColumns() { - var err error - - fieldName := col.FieldName - for { - parts := strings.SplitN(fieldName, ".", 2) - if len(parts) == 1 { - break - } - - v = v.FieldByName(parts[0]) - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - if v.Kind() != reflect.Struct { - return nil, ErrUnSupportedType - } - fieldName = parts[1] - } - - pkField := v.FieldByName(fieldName) - switch pkField.Kind() { - case reflect.String: - pk[i], err = engine.idTypeAssertion(col, pkField.String()) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - pk[i], err = engine.idTypeAssertion(col, strconv.FormatInt(pkField.Int(), 10)) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - // id of uint will be converted to int64 - pk[i], err = engine.idTypeAssertion(col, strconv.FormatUint(pkField.Uint(), 10)) - } - - if err != nil { - return nil, err - } - } - return schemas.PK(pk), nil -} - -func (engine *Engine) idTypeAssertion(col *schemas.Column, sid string) (interface{}, error) { - if col.SQLType.IsNumeric() { - n, err := strconv.ParseInt(sid, 10, 64) - if err != nil { - return nil, err - } - return n, nil - } else if col.SQLType.IsText() { - return sid, nil - } else { - return nil, errors.New("not supported") - } -} - // CreateIndexes create indexes func (engine *Engine) CreateIndexes(bean interface{}) error { session := engine.NewSession() @@ -1288,6 +1278,7 @@ func (engine *Engine) SetSchema(schema string) { engine.dialect.URI().SetSchema(schema) } +// AddHook adds a context Hook func (engine *Engine) AddHook(hook contexts.Hook) { engine.db.AddHook(hook) } @@ -1303,7 +1294,7 @@ func (engine *Engine) tbNameWithSchema(v string) string { return dialects.TableNameWithSchema(engine.dialect, v) } -// ContextHook creates a session with the context +// Context creates a session with the context func (engine *Engine) Context(ctx context.Context) *Session { session := engine.NewSession() session.isAutoClose = true @@ -1333,11 +1324,11 @@ func (engine *Engine) Transaction(f func(*Session) (interface{}, error)) (interf result, err := f(session) if err != nil { - return nil, err + return result, err } if err := session.Commit(); err != nil { - return nil, err + return result, err } return result, nil diff --git a/engine_group.go b/engine_group.go index cdd9dd44..3569690b 100644 --- a/engine_group.go +++ b/engine_group.go @@ -79,7 +79,7 @@ func (eg *EngineGroup) Close() error { return nil } -// ContextHook returned a group session +// Context returned a group session func (eg *EngineGroup) Context(ctx context.Context) *Session { sess := eg.NewSession() sess.isAutoClose = true @@ -144,6 +144,7 @@ func (eg *EngineGroup) SetLogger(logger interface{}) { } } +// AddHook adds Hook func (eg *EngineGroup) AddHook(hook contexts.Hook) { eg.Engine.AddHook(hook) for i := 0; i < len(eg.slaves); i++ { @@ -167,6 +168,14 @@ func (eg *EngineGroup) SetMapper(mapper names.Mapper) { } } +// SetTagIdentifier set the tag identifier +func (eg *EngineGroup) SetTagIdentifier(tagIdentifier string) { + eg.Engine.SetTagIdentifier(tagIdentifier) + for i := 0; i < len(eg.slaves); i++ { + eg.slaves[i].SetTagIdentifier(tagIdentifier) + } +} + // SetMaxIdleConns set the max idle connections on pool, default is 2 func (eg *EngineGroup) SetMaxIdleConns(conns int) { eg.Engine.DB().SetMaxIdleConns(conns) diff --git a/go.mod b/go.mod index ff42c0f7..5e073207 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,15 @@ module xorm.io/xorm -go 1.11 +go 1.13 require ( - github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4 - github.com/go-sql-driver/mysql v1.4.1 - github.com/kr/pretty v0.1.0 // indirect - github.com/lib/pq v1.0.0 - github.com/mattn/go-sqlite3 v1.10.0 + github.com/denisenkom/go-mssqldb v0.9.0 + github.com/go-sql-driver/mysql v1.5.0 + github.com/lib/pq v1.7.0 + github.com/mattn/go-sqlite3 v1.14.6 github.com/stretchr/testify v1.4.0 github.com/syndtr/goleveldb v1.0.0 github.com/ziutek/mymysql v1.5.4 - google.golang.org/appengine v1.6.0 // indirect - xorm.io/builder v0.3.7 + modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84 + xorm.io/builder v0.3.8 ) diff --git a/go.sum b/go.sum index 84f9126e..230c16aa 100644 --- a/go.sum +++ b/go.sum @@ -1,152 +1,89 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU= -cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4 h1:YcpmyvADGYw5LqMnHqSkyIELsHCGF6PkrmM31V8rF7o= -github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/denisenkom/go-mssqldb v0.9.0 h1:RSohk2RsiZqLZ0zCjtfn3S4Gp4exhpBWHyQ7D0yGjAk= +github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= -github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= -github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= +github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw= -google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= @@ -154,8 +91,32 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI= -xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= +modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009 h1:u0oCo5b9wyLr++HF3AN9JicGhkUxJhMz51+8TIZH9N0= +modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878= +modernc.org/ccgo/v3 v3.9.0 h1:JbcEIqjw4Agf+0g3Tc85YvfYqkkFOv6xBwS4zkfqSoA= +modernc.org/ccgo/v3 v3.9.0/go.mod h1:nQbgkn8mwzPdp4mm6BT6+p85ugQ7FrGgIcYaE7nSrpY= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= +modernc.org/libc v1.8.0 h1:Pp4uv9g0csgBMpGPABKtkieF6O5MGhfGo6ZiOdlYfR8= +modernc.org/libc v1.8.0/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= +modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM= +modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= +modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84 h1:rgEUzE849tFlHSoeCrKyS9cZAljC+DY7MdMHKq6R6sY= +modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84/go.mod h1:PGzq6qlhyYjL6uVbSgS6WoF7ZopTW/sI7+7p+mb4ZVU= +modernc.org/strutil v1.1.0 h1:+1/yCzZxY2pZwwrsbH+4T7BQMoLQ9QiBshRC9eicYsc= +modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/tcl v1.5.0 h1:euZSUNfE0Fd4W8VqXI1Ly1v7fqDJoBuAV88Ea+SnaSs= +modernc.org/tcl v1.5.0/go.mod h1:gb57hj4pO8fRrK54zveIfFXBaMHK3SKJNWcmRw1cRzc= +modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= +modernc.org/z v1.0.1 h1:WyIDpEpAIx4Hel6q/Pcgj/VhaQV5XPJ2I6ryIYbjnpc= +modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= +xorm.io/builder v0.3.8 h1:P/wPgRqa9kX5uE0aA1/ukJ23u9KH0aSRpHLwDKXigSE= +xorm.io/builder v0.3.8/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= diff --git a/integrations/engine_test.go b/integrations/engine_test.go index 19c5285d..3b843f16 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -20,6 +20,7 @@ import ( _ "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/assert" _ "github.com/ziutek/mymysql/godrv" + _ "modernc.org/sqlite" ) func TestPing(t *testing.T) { @@ -93,19 +94,23 @@ func TestDump(t *testing.T) { assert.NoError(t, PrepareEngine()) type TestDumpStruct struct { - Id int64 - Name string + Id int64 + Name string + IsMan bool + Created time.Time `xorm:"created"` } assertSync(t, new(TestDumpStruct)) - testEngine.Insert([]TestDumpStruct{ - {Name: "1"}, + cnt, err := testEngine.Insert([]TestDumpStruct{ + {Name: "1", IsMan: true}, {Name: "2\n"}, {Name: "3;"}, {Name: "4\n;\n''"}, {Name: "5'\n"}, }) + assert.NoError(t, err) + assert.EqualValues(t, 5, cnt) fp := fmt.Sprintf("%v.sql", testEngine.Dialect().URI().DBType) os.Remove(fp) @@ -116,7 +121,7 @@ func TestDump(t *testing.T) { sess := testEngine.NewSession() defer sess.Close() assert.NoError(t, sess.Begin()) - _, err := sess.ImportFile(fp) + _, err = sess.ImportFile(fp) assert.NoError(t, err) assert.NoError(t, sess.Commit()) @@ -128,6 +133,49 @@ func TestDump(t *testing.T) { } } +func TestDumpTables(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + type TestDumpTableStruct struct { + Id int64 + Name string + IsMan bool + Created time.Time `xorm:"created"` + } + + assertSync(t, new(TestDumpTableStruct)) + + testEngine.Insert([]TestDumpTableStruct{ + {Name: "1", IsMan: true}, + {Name: "2\n"}, + {Name: "3;"}, + {Name: "4\n;\n''"}, + {Name: "5'\n"}, + }) + + fp := fmt.Sprintf("%v-table.sql", testEngine.Dialect().URI().DBType) + os.Remove(fp) + tb, err := testEngine.TableInfo(new(TestDumpTableStruct)) + assert.NoError(t, err) + assert.NoError(t, testEngine.(*xorm.Engine).DumpTablesToFile([]*schemas.Table{tb}, fp)) + + assert.NoError(t, PrepareEngine()) + + sess := testEngine.NewSession() + defer sess.Close() + assert.NoError(t, sess.Begin()) + _, err = sess.ImportFile(fp) + assert.NoError(t, err) + assert.NoError(t, sess.Commit()) + + for _, tp := range []schemas.DBType{schemas.SQLITE, schemas.MYSQL, schemas.POSTGRES, schemas.MSSQL} { + name := fmt.Sprintf("dump_%v-table.sql", tp) + t.Run(name, func(t *testing.T) { + assert.NoError(t, testEngine.(*xorm.Engine).DumpTablesToFile([]*schemas.Table{tb}, name, tp)) + }) + } +} + func TestSetSchema(t *testing.T) { assert.NoError(t, PrepareEngine()) @@ -139,3 +187,16 @@ func TestSetSchema(t *testing.T) { assert.EqualValues(t, oldSchema, testEngine.Dialect().URI().Schema) } } + +func TestImport(t *testing.T) { + if testEngine.Dialect().URI().DBType != schemas.MYSQL { + t.Skip() + return + } + sess := testEngine.NewSession() + defer sess.Close() + assert.NoError(t, sess.Begin()) + _, err := sess.ImportFile("./testdata/import1.sql") + assert.NoError(t, err) + assert.NoError(t, sess.Commit()) +} diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index 00477235..c3e99183 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -502,13 +502,58 @@ func TestFindAndCountOneFunc(t *testing.T) { assert.EqualValues(t, 1, cnt) results = make([]FindAndCountStruct, 0, 1) - cnt, err = testEngine.Where("msg = ?", true).Limit(1).FindAndCount(&results) + cnt, err = testEngine.Where("1=1").Limit(1).FindAndCount(&results) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(results)) + assert.EqualValues(t, 2, cnt) + assert.EqualValues(t, FindAndCountStruct{ + Id: 1, + Content: "111", + Msg: false, + }, results[0]) + + results = make([]FindAndCountStruct, 0, 1) + cnt, err = testEngine.Where("1=1").Limit(1).FindAndCount(&results) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(results)) + assert.EqualValues(t, 2, cnt) + assert.EqualValues(t, FindAndCountStruct{ + Id: 1, + Content: "111", + Msg: false, + }, results[0]) + + results = make([]FindAndCountStruct, 0, 1) + cnt, err = testEngine.Where("1=1").Limit(1, 1).FindAndCount(&results) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(results)) + assert.EqualValues(t, 2, cnt) + assert.EqualValues(t, FindAndCountStruct{ + Id: 2, + Content: "222", + Msg: true, + }, results[0]) + + results = make([]FindAndCountStruct, 0, 1) + cnt, err = testEngine.Where("1=1").Limit(1, 1).FindAndCount(&results) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(results)) + assert.EqualValues(t, 2, cnt) + assert.EqualValues(t, FindAndCountStruct{ + Id: 2, + Content: "222", + Msg: true, + }, results[0]) + + results = make([]FindAndCountStruct, 0, 1) + cnt, err = testEngine.Where("msg = ?", true).Select("id, content, msg"). + Limit(1).FindAndCount(&results) assert.NoError(t, err) assert.EqualValues(t, 1, len(results)) assert.EqualValues(t, 1, cnt) results = make([]FindAndCountStruct, 0, 1) - cnt, err = testEngine.Where("msg = ?", true).Select("id, content, msg"). + cnt, err = testEngine.Where("msg = ?", true).Cols("id", "content", "msg"). Limit(1).FindAndCount(&results) assert.NoError(t, err) assert.EqualValues(t, 1, len(results)) @@ -708,6 +753,13 @@ func TestFindExtends(t *testing.T) { err = testEngine.Find(&results) assert.NoError(t, err) assert.EqualValues(t, 2, len(results)) + + results = make([]FindExtendsA, 0, 2) + err = testEngine.Find(&results, &FindExtendsB{ + ID: 1, + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(results)) } func TestFindExtends3(t *testing.T) { diff --git a/integrations/session_get_test.go b/integrations/session_get_test.go index 4e50f9ab..99db98fc 100644 --- a/integrations/session_get_test.go +++ b/integrations/session_get_test.go @@ -6,11 +6,13 @@ package integrations import ( "database/sql" + "errors" "fmt" "strconv" "testing" "time" + "xorm.io/xorm" "xorm.io/xorm/contexts" "xorm.io/xorm/schemas" @@ -394,6 +396,60 @@ func TestJSONString(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(jss)) assert.True(t, `["1","2"]` == jss[0].Content || `["1", "2"]` == jss[0].Content) + + type JsonAnonymousStruct struct { + Id int64 + JsonString `xorm:"'json_string' JSON LONGTEXT"` + } + + assertSync(t, new(JsonAnonymousStruct)) + + _, err = testEngine.Insert(&JsonAnonymousStruct{ + JsonString: JsonString{ + Content: "1", + }, + }) + assert.NoError(t, err) + + var jas JsonAnonymousStruct + has, err = testEngine.Get(&jas) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, 1, jas.Id) + assert.EqualValues(t, "1", jas.Content) + + var jass []JsonAnonymousStruct + err = testEngine.Find(&jass) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(jass)) + assert.EqualValues(t, "1", jass[0].Content) + + type JsonStruct struct { + Id int64 + JSON JsonString `xorm:"'json_string' JSON LONGTEXT"` + } + + assertSync(t, new(JsonStruct)) + + _, err = testEngine.Insert(&JsonStruct{ + JSON: JsonString{ + Content: "2", + }, + }) + assert.NoError(t, err) + + var jst JsonStruct + has, err = testEngine.Get(&jst) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, 1, jst.Id) + assert.EqualValues(t, "2", jst.JSON.Content) + + var jsts []JsonStruct + err = testEngine.Find(&jsts) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(jsts)) + assert.EqualValues(t, "2", jsts[0].JSON.Content) } func TestGetActionMapping(t *testing.T) { @@ -696,3 +752,17 @@ func TestGetViaMapCond(t *testing.T) { assert.NoError(t, err) assert.False(t, has) } + +func TestGetNil(t *testing.T) { + type GetNil struct { + Id int64 + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(GetNil)) + + var gn *GetNil + has, err := testEngine.Get(gn) + assert.True(t, errors.Is(err, xorm.ErrObjectIsNil)) + assert.False(t, has) +} diff --git a/integrations/session_schema_test.go b/integrations/session_schema_test.go index 005b6619..1ef653ef 100644 --- a/integrations/session_schema_test.go +++ b/integrations/session_schema_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "xorm.io/xorm/schemas" ) func TestStoreEngine(t *testing.T) { @@ -102,6 +103,9 @@ func TestSyncTable(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(tables)) assert.EqualValues(t, "sync_table1", tables[0].Name) + tableInfo, err := testEngine.TableInfo(new(SyncTable1)) + assert.NoError(t, err) + assert.EqualValues(t, testEngine.Dialect().SQLType(tables[0].GetColumn("name")), testEngine.Dialect().SQLType(tableInfo.GetColumn("name"))) assert.NoError(t, testEngine.Sync2(new(SyncTable2))) @@ -109,6 +113,9 @@ func TestSyncTable(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(tables)) assert.EqualValues(t, "sync_table1", tables[0].Name) + tableInfo, err = testEngine.TableInfo(new(SyncTable2)) + assert.NoError(t, err) + assert.EqualValues(t, testEngine.Dialect().SQLType(tables[0].GetColumn("name")), testEngine.Dialect().SQLType(tableInfo.GetColumn("name"))) assert.NoError(t, testEngine.Sync2(new(SyncTable3))) @@ -116,6 +123,9 @@ func TestSyncTable(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(tables)) assert.EqualValues(t, "sync_table1", tables[0].Name) + tableInfo, err = testEngine.TableInfo(new(SyncTable3)) + assert.NoError(t, err) + assert.EqualValues(t, testEngine.Dialect().SQLType(tables[0].GetColumn("name")), testEngine.Dialect().SQLType(tableInfo.GetColumn("name"))) } func TestSyncTable2(t *testing.T) { @@ -143,6 +153,63 @@ func TestSyncTable2(t *testing.T) { assert.EqualValues(t, colMapper.Obj2Table("NewCol"), tables[0].Columns()[3].Name) } +func TestSyncTable3(t *testing.T) { + type SyncTable5 struct { + Id int64 + Name string + Text string `xorm:"TEXT"` + Char byte `xorm:"CHAR(1)"` + TenChar [10]byte `xorm:"CHAR(10)"` + TenVarChar string `xorm:"VARCHAR(10)"` + } + + assert.NoError(t, PrepareEngine()) + + assert.NoError(t, testEngine.Sync2(new(SyncTable5))) + + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + tableInfo, err := testEngine.TableInfo(new(SyncTable5)) + assert.NoError(t, err) + assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("name")), testEngine.Dialect().SQLType(tables[0].GetColumn("name"))) + assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("text")), testEngine.Dialect().SQLType(tables[0].GetColumn("text"))) + assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("char")), testEngine.Dialect().SQLType(tables[0].GetColumn("char"))) + assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("ten_char")), testEngine.Dialect().SQLType(tables[0].GetColumn("ten_char"))) + assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("ten_var_char")), testEngine.Dialect().SQLType(tables[0].GetColumn("ten_var_char"))) + + if *doNVarcharTest { + var oldDefaultVarchar string + var oldDefaultChar string + oldDefaultVarchar, *defaultVarchar = *defaultVarchar, "nvarchar" + oldDefaultChar, *defaultChar = *defaultChar, "nchar" + testEngine.Dialect().SetParams(map[string]string{ + "DEFAULT_VARCHAR": *defaultVarchar, + "DEFAULT_CHAR": *defaultChar, + }) + defer func() { + *defaultVarchar = oldDefaultVarchar + *defaultChar = oldDefaultChar + testEngine.Dialect().SetParams(map[string]string{ + "DEFAULT_VARCHAR": *defaultVarchar, + "DEFAULT_CHAR": *defaultChar, + }) + }() + assert.NoError(t, PrepareEngine()) + + assert.NoError(t, testEngine.Sync2(new(SyncTable5))) + + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + tableInfo, err := testEngine.TableInfo(new(SyncTable5)) + assert.NoError(t, err) + assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("name")), testEngine.Dialect().SQLType(tables[0].GetColumn("name"))) + assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("text")), testEngine.Dialect().SQLType(tables[0].GetColumn("text"))) + assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("char")), testEngine.Dialect().SQLType(tables[0].GetColumn("char"))) + assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("ten_char")), testEngine.Dialect().SQLType(tables[0].GetColumn("ten_char"))) + assert.EqualValues(t, testEngine.Dialect().SQLType(tableInfo.GetColumn("ten_var_char")), testEngine.Dialect().SQLType(tables[0].GetColumn("ten_var_char"))) + } +} + func TestIsTableExist(t *testing.T) { assert.NoError(t, PrepareEngine()) @@ -330,3 +397,30 @@ func TestSync2_Default(t *testing.T) { assertSync(t, new(TestSync2Default)) assert.NoError(t, testEngine.Sync2(new(TestSync2Default))) } + +func TestModifyColum(t *testing.T) { + // Since SQLITE don't support modify column SQL, currrently just ignore + if testEngine.Dialect().URI().DBType == schemas.SQLITE { + return + } + type TestModifyColumn struct { + Id int64 + UserId int64 `xorm:"default(1)"` + IsMember bool `xorm:"default(true)"` + Name string `xorm:"char(10)"` + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(TestModifyColumn)) + + alterSQL := testEngine.Dialect().ModifyColumnSQL("test_modify_column", &schemas.Column{ + Name: "name", + SQLType: schemas.SQLType{ + Name: "VARCHAR", + }, + Length: 16, + Nullable: false, + }) + _, err := testEngine.Exec(alterSQL) + assert.NoError(t, err) +} diff --git a/integrations/session_test.go b/integrations/session_test.go index bdf3278d..c3ef0513 100644 --- a/integrations/session_test.go +++ b/integrations/session_test.go @@ -54,3 +54,11 @@ func TestMustLogSQL(t *testing.T) { _, err := testEngine.Table("userinfo").MustLogSQL(true).Get(new(Userinfo)) assert.NoError(t, err) } + +func TestEnableSessionId(t *testing.T) { + assert.NoError(t, PrepareEngine()) + testEngine.EnableSessionID(true) + assertSync(t, new(Userinfo)) + _, err := testEngine.Table("userinfo").MustLogSQL(true).Get(new(Userinfo)) + assert.NoError(t, err) +} diff --git a/integrations/session_update_test.go b/integrations/session_update_test.go index 8800246c..df0631c4 100644 --- a/integrations/session_update_test.go +++ b/integrations/session_update_test.go @@ -12,8 +12,10 @@ import ( "github.com/stretchr/testify/assert" "xorm.io/xorm" + "xorm.io/xorm/internal/statements" "xorm.io/xorm/internal/utils" "xorm.io/xorm/names" + "xorm.io/xorm/schemas" ) func TestUpdateMap(t *testing.T) { @@ -39,6 +41,27 @@ func TestUpdateMap(t *testing.T) { }) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) + + cnt, err = testEngine.Table("update_table").ID(tb.Id).Update(map[string]interface{}{ + "name": "test2", + "age": 36, + }) + assert.Error(t, err) + assert.True(t, statements.IsIDConditionWithNoTableErr(err)) + assert.EqualValues(t, 0, cnt) + + cnt, err = testEngine.Table("update_table").Update(map[string]interface{}{ + "name": "test2", + "age": 36, + }, &UpdateTable{ + Id: tb.Id, + }) + assert.NoError(t, err) + if testEngine.Dialect().URI().DBType == schemas.MYSQL { + assert.EqualValues(t, 0, cnt) + } else { + assert.EqualValues(t, 1, cnt) + } } func TestUpdateLimit(t *testing.T) { @@ -179,6 +202,9 @@ func TestForUpdate(t *testing.T) { // lock is NOT used wg.Add(1) + + wg2 := &sync.WaitGroup{} + wg2.Add(1) go func() { f3 := new(ForUpdate) session3.Where("id = ?", 1) @@ -192,10 +218,10 @@ func TestForUpdate(t *testing.T) { t.Errorf("read lock failed") } wg.Done() + wg2.Done() }() - // wait for go rountines - time.Sleep(50 * time.Millisecond) + wg2.Wait() f := new(ForUpdate) f.Name = "updated by session1" @@ -988,7 +1014,7 @@ func TestUpdateMapContent(t *testing.T) { assert.EqualValues(t, false, c2.IsMan) assert.EqualValues(t, 2, c2.Gender) - cnt, err = testEngine.Table(testEngine.TableName(new(UpdateMapContent))).ID(c.Id).Update(map[string]interface{}{ + cnt, err = testEngine.Table(new(UpdateMapContent)).ID(c.Id).Update(map[string]interface{}{ "age": 15, "is_man": true, "gender": 1, @@ -1341,3 +1367,50 @@ func TestUpdateMultiplePK(t *testing.T) { _, err = testEngine.ID(&MySlice{test.Id, test.Name}).Update(test) assert.NoError(t, err) } + +type TestFieldType1 struct { + cb []byte +} + +func (a *TestFieldType1) FromDB(src []byte) error { + a.cb = src + return nil +} + +func (a TestFieldType1) ToDB() ([]byte, error) { + return a.cb, nil +} + +type TestTable1 struct { + Id int64 + Field1 *TestFieldType1 `xorm:"text"` + UpdateTime time.Time +} + +func TestNilFromDB(t *testing.T) { + assert.NoError(t, PrepareEngine()) + assertSync(t, new(TestTable1)) + + cnt, err := testEngine.Insert(&TestTable1{ + Field1: &TestFieldType1{ + cb: []byte("string"), + }, + UpdateTime: time.Now(), + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + cnt, err = testEngine.Update(TestTable1{ + UpdateTime: time.Now().Add(time.Second), + }, TestTable1{ + Id: 1, + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + cnt, err = testEngine.Insert(&TestTable1{ + UpdateTime: time.Now(), + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) +} diff --git a/integrations/testdata/import1.sql b/integrations/testdata/import1.sql new file mode 100644 index 00000000..e004f41c --- /dev/null +++ b/integrations/testdata/import1.sql @@ -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='网站配置'; \ No newline at end of file diff --git a/integrations/tests.go b/integrations/tests.go index c8219935..512f3962 100644 --- a/integrations/tests.go +++ b/integrations/tests.go @@ -8,6 +8,7 @@ import ( "database/sql" "flag" "fmt" + "net/url" "os" "strings" "testing" @@ -35,7 +36,10 @@ var ( schema = flag.String("schema", "", "specify the schema") ignoreSelectUpdate = flag.Bool("ignore_select_update", false, "ignore select update if implementation difference, only for tidb") ingoreUpdateLimit = flag.Bool("ignore_update_limit", false, "ignore update limit if implementation difference, only for cockroach") + doNVarcharTest = flag.Bool("do_nvarchar_override_test", false, "do nvarchar override test in sync table, only for mssql") quotePolicyStr = flag.String("quote", "always", "quote could be always, none, reversed") + defaultVarchar = flag.String("default_varchar", "varchar", "default varchar type, mssql only, could be varchar or nvarchar, default is varchar") + defaultChar = flag.String("default_char", "char", "default char type, mssql only, could be char or nchar, default is char") tableMapper names.Mapper colMapper names.Mapper ) @@ -94,6 +98,13 @@ func createEngine(dbType, connStr string) error { return fmt.Errorf("db.Exec: %v", err) } db.Close() + case schemas.SQLITE, "sqlite": + u, err := url.Parse(connStr) + if err != nil { + return err + } + connStr = u.Path + *ignoreSelectUpdate = true default: *ignoreSelectUpdate = true } @@ -137,6 +148,11 @@ func createEngine(dbType, connStr string) error { } else { testEngine.SetQuotePolicy(dialects.QuotePolicyAlways) } + + testEngine.Dialect().SetParams(map[string]string{ + "DEFAULT_VARCHAR": *defaultVarchar, + "DEFAULT_CHAR": *defaultChar, + }) } tableMapper = testEngine.GetTableMapper() @@ -156,17 +172,25 @@ func createEngine(dbType, connStr string) error { return nil } +// PrepareEngine prepare tests ORM engine func PrepareEngine() error { return createEngine(dbType, connString) } +// MainTest the tests entrance func MainTest(m *testing.M) { flag.Parse() dbType = *db if *db == "sqlite3" { if ptrConnStr == nil { - connString = "./test.db?cache=shared&mode=rwc" + connString = "./test_sqlite3.db?cache=shared&mode=rwc" + } else { + connString = *ptrConnStr + } + } else if *db == "sqlite" { + if ptrConnStr == nil { + connString = "./test_sqlite.db?cache=shared&mode=rwc" } else { connString = *ptrConnStr } diff --git a/integrations/types_test.go b/integrations/types_test.go index 112308f3..d0357d6b 100644 --- a/integrations/types_test.go +++ b/integrations/types_test.go @@ -375,3 +375,30 @@ func TestCustomType2(t *testing.T) { fmt.Println(users) } + +func TestUnsigned(t *testing.T) { + type MyUnsignedStruct struct { + Id uint64 + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(MyUnsignedStruct)) + + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + assert.EqualValues(t, 1, len(tables)) + assert.EqualValues(t, 1, len(tables[0].Columns())) + + switch testEngine.Dialect().URI().DBType { + case schemas.SQLITE: + assert.EqualValues(t, "INTEGER", tables[0].Columns()[0].SQLType.Name) + case schemas.MYSQL: + assert.EqualValues(t, "UNSIGNED BIGINT", tables[0].Columns()[0].SQLType.Name) + case schemas.POSTGRES: + assert.EqualValues(t, "BIGINT", tables[0].Columns()[0].SQLType.Name) + case schemas.MSSQL: + assert.EqualValues(t, "BIGINT", tables[0].Columns()[0].SQLType.Name) + default: + assert.False(t, true, "Unsigned is not implemented") + } +} diff --git a/interface.go b/interface.go index 6aac4ae8..55162c8c 100644 --- a/interface.go +++ b/interface.go @@ -101,6 +101,7 @@ type EngineInterface interface { SetCacher(string, caches.Cacher) SetConnMaxLifetime(time.Duration) SetColumnMapper(names.Mapper) + SetTagIdentifier(string) SetDefaultCacher(caches.Cacher) SetLogger(logger interface{}) SetLogLevel(log.LogLevel) @@ -120,6 +121,7 @@ type EngineInterface interface { TableInfo(bean interface{}) (*schemas.Table, error) TableName(interface{}, ...bool) string UnMapType(reflect.Type) + EnableSessionID(bool) } var ( diff --git a/internal/json/json.go b/internal/json/json.go index c9a2eb4e..ef52f51f 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -6,15 +6,15 @@ package json import "encoding/json" -// JSONInterface represents an interface to handle json data -type JSONInterface interface { +// Interface represents an interface to handle json data +type Interface interface { Marshal(v interface{}) ([]byte, error) Unmarshal(data []byte, v interface{}) error } var ( // DefaultJSONHandler default json handler - DefaultJSONHandler JSONInterface = StdJSON{} + DefaultJSONHandler Interface = StdJSON{} ) // StdJSON implements JSONInterface via encoding/json diff --git a/internal/statements/pk.go b/internal/statements/pk.go index b6ae0f23..59da89c0 100644 --- a/internal/statements/pk.go +++ b/internal/statements/pk.go @@ -20,6 +20,21 @@ var ( uintType = reflect.TypeOf(uint64(0)) ) +// ErrIDConditionWithNoTable represents an error there is no reference table with an ID condition +type ErrIDConditionWithNoTable struct { + ID schemas.PK +} + +func (err ErrIDConditionWithNoTable) Error() string { + return fmt.Sprintf("ID condition %#v need reference table", err.ID) +} + +// IsIDConditionWithNoTableErr return true if the err is ErrIDConditionWithNoTable +func IsIDConditionWithNoTableErr(err error) bool { + _, ok := err.(ErrIDConditionWithNoTable) + return ok +} + // ID generate "where id = ? " statement or for composite key "where key1 = ? and key2 = ?" func (statement *Statement) ID(id interface{}) *Statement { switch t := id.(type) { @@ -58,13 +73,17 @@ func (statement *Statement) ID(id interface{}) *Statement { return statement } +// ProcessIDParam handles the process of id condition func (statement *Statement) ProcessIDParam() error { - if statement.idParam == nil || statement.RefTable == nil { + if statement.idParam == nil { return nil } + if statement.RefTable == nil { + return ErrIDConditionWithNoTable{statement.idParam} + } + if len(statement.RefTable.PrimaryKeys) != len(statement.idParam) { - fmt.Println("=====", statement.RefTable.PrimaryKeys, statement.idParam) return fmt.Errorf("ID condition is error, expect %d primarykeys, there are %d", len(statement.RefTable.PrimaryKeys), len(statement.idParam), diff --git a/internal/statements/statement.go b/internal/statements/statement.go index ed7bdaeb..a4294bec 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -704,7 +704,7 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{}, col.SQLType.IsBlob() || col.SQLType.Name == schemas.TimeStampz) { continue } - if col.SQLType.IsJson() { + if col.IsJSON { continue } @@ -813,7 +813,7 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{}, continue } } else { - if col.SQLType.IsJson() { + if col.IsJSON { if col.SQLType.IsText() { bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface()) if err != nil { diff --git a/internal/statements/update.go b/internal/statements/update.go index b6ae118e..251880b2 100644 --- a/internal/statements/update.go +++ b/internal/statements/update.go @@ -130,7 +130,7 @@ func (statement *Statement) BuildUpdates(tableValue reflect.Value, } } - if structConvert, ok := fieldValue.Interface().(convert.Conversion); ok { + if structConvert, ok := fieldValue.Interface().(convert.Conversion); ok && !fieldValue.IsNil() { data, err := structConvert.ToDB() if err != nil { return nil, nil, err @@ -204,7 +204,7 @@ func (statement *Statement) BuildUpdates(tableValue reflect.Value, continue } } else { - if !col.SQLType.IsJson() { + if !col.IsJSON { table, err := statement.tagParser.ParseWithCache(fieldValue) if err != nil { val = fieldValue.Interface() diff --git a/internal/statements/values.go b/internal/statements/values.go index a1102c54..71327c55 100644 --- a/internal/statements/values.go +++ b/internal/statements/values.go @@ -36,18 +36,21 @@ func (statement *Statement) Value2Interface(col *schemas.Column, fieldValue refl } } - if fieldConvert, ok := fieldValue.Interface().(convert.Conversion); ok { - data, err := fieldConvert.ToDB() - if err != nil { - return nil, err + isNil := fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() + if !isNil { + if fieldConvert, ok := fieldValue.Interface().(convert.Conversion); ok { + 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() @@ -83,7 +86,7 @@ func (statement *Statement) Value2Interface(col *schemas.Column, fieldValue refl return t.Float64, nil } - if !col.SQLType.IsJson() { + if !col.IsJSON { // !! 增加支持driver.Valuer接口的结构,如sql.NullString if v, ok := fieldValue.Interface().(driver.Valuer); ok { return v.Value() diff --git a/internal/utils/name.go b/internal/utils/name.go index f5fc3ff7..840dd9e8 100644 --- a/internal/utils/name.go +++ b/internal/utils/name.go @@ -8,6 +8,7 @@ import ( "fmt" ) +// IndexName returns index name func IndexName(tableName, idxName string) string { return fmt.Sprintf("IDX_%v_%v", tableName, idxName) } diff --git a/internal/utils/reflect.go b/internal/utils/reflect.go index 3dad6bfe..7973d4d3 100644 --- a/internal/utils/reflect.go +++ b/internal/utils/reflect.go @@ -8,6 +8,7 @@ import ( "reflect" ) +// ReflectValue returns value of a bean func ReflectValue(bean interface{}) reflect.Value { return reflect.Indirect(reflect.ValueOf(bean)) } diff --git a/internal/utils/sql.go b/internal/utils/sql.go index 5e68c4a4..369ca2b8 100644 --- a/internal/utils/sql.go +++ b/internal/utils/sql.go @@ -8,6 +8,7 @@ import ( "strings" ) +// IsSubQuery returns true if it contains a sub query func IsSubQuery(tbName string) bool { const selStr = "select" if len(tbName) <= len(selStr)+1 { diff --git a/internal/utils/strings.go b/internal/utils/strings.go index b5dc37b7..86469c0f 100644 --- a/internal/utils/strings.go +++ b/internal/utils/strings.go @@ -8,10 +8,12 @@ import ( "strings" ) +// IndexNoCase index a string in a string with no care of capitalize func IndexNoCase(s, sep string) int { return strings.Index(strings.ToLower(s), strings.ToLower(sep)) } +// SplitNoCase split a string by a seperator with no care of capitalize func SplitNoCase(s, sep string) []string { idx := IndexNoCase(s, sep) if idx < 0 { @@ -20,6 +22,7 @@ func SplitNoCase(s, sep string) []string { return strings.Split(s, s[idx:idx+len(sep)]) } +// SplitNNoCase split n by a seperator with no care of capitalize func SplitNNoCase(s, sep string, n int) []string { idx := IndexNoCase(s, sep) if idx < 0 { @@ -27,4 +30,3 @@ func SplitNNoCase(s, sep string, n int) []string { } return strings.SplitN(s, s[idx:idx+len(sep)], n) } - diff --git a/internal/utils/zero.go b/internal/utils/zero.go index 8f033c60..007e3c33 100644 --- a/internal/utils/zero.go +++ b/internal/utils/zero.go @@ -9,6 +9,7 @@ import ( "time" ) +// Zeroable represents an interface which could know if it's a zero value type Zeroable interface { IsZero() bool } @@ -21,39 +22,39 @@ func IsZero(k interface{}) bool { return true } - switch k.(type) { + switch t := k.(type) { case int: - return k.(int) == 0 + return t == 0 case int8: - return k.(int8) == 0 + return t == 0 case int16: - return k.(int16) == 0 + return t == 0 case int32: - return k.(int32) == 0 + return t == 0 case int64: - return k.(int64) == 0 + return t == 0 case uint: - return k.(uint) == 0 + return t == 0 case uint8: - return k.(uint8) == 0 + return t == 0 case uint16: - return k.(uint16) == 0 + return t == 0 case uint32: - return k.(uint32) == 0 + return t == 0 case uint64: - return k.(uint64) == 0 + return t == 0 case float32: - return k.(float32) == 0 + return t == 0 case float64: - return k.(float64) == 0 + return t == 0 case bool: - return k.(bool) == false + return !t case string: - return k.(string) == "" + return t == "" case *time.Time: - return k.(*time.Time) == nilTime || IsTimeZero(*k.(*time.Time)) + return t == nilTime || IsTimeZero(*t) case time.Time: - return IsTimeZero(k.(time.Time)) + return IsTimeZero(t) case Zeroable: return k.(Zeroable) == nil || k.(Zeroable).IsZero() case reflect.Value: // for go version less than 1.13 because reflect.Value has no method IsZero @@ -65,6 +66,7 @@ func IsZero(k interface{}) bool { var zeroType = reflect.TypeOf((*Zeroable)(nil)).Elem() +// IsValueZero returns true if the reflect Value is a zero func IsValueZero(v reflect.Value) bool { switch v.Kind() { case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Slice: @@ -88,6 +90,7 @@ func IsValueZero(v reflect.Value) bool { return false } +// IsStructZero returns true if the Value is a struct and all fields is zero func IsStructZero(v reflect.Value) bool { if !v.IsValid() || v.NumField() == 0 { return true @@ -120,6 +123,7 @@ func IsStructZero(v reflect.Value) bool { return true } +// IsArrayZero returns true is a slice of array is zero func IsArrayZero(v reflect.Value) bool { if !v.IsValid() || v.Len() == 0 { return true @@ -134,11 +138,13 @@ func IsArrayZero(v reflect.Value) bool { return true } +// represents all zero times const ( ZeroTime0 = "0000-00-00 00:00:00" ZeroTime1 = "0001-01-01 00:00:00" ) +// IsTimeZero return true if a time is zero func IsTimeZero(t time.Time) bool { return t.IsZero() || t.Format("2006-01-02 15:04:05") == ZeroTime0 || t.Format("2006-01-02 15:04:05") == ZeroTime1 diff --git a/log/logger_context.go b/log/logger_context.go index 6b7252ef..46802576 100644 --- a/log/logger_context.go +++ b/log/logger_context.go @@ -42,6 +42,7 @@ var ( // enumerate all the context keys var ( SessionIDKey = "__xorm_session_id" + SessionKey = "__xorm_session_key" SessionShowSQLKey = "__xorm_show_sql" ) diff --git a/migrate/migrate.go b/migrate/migrate.go index 82c58f90..19b4afe0 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -110,10 +110,7 @@ func (m *Migrate) RollbackLast() error { return err } - if err := m.RollbackMigration(lastRunnedMigration); err != nil { - return err - } - return nil + return m.RollbackMigration(lastRunnedMigration) } func (m *Migrate) getLastRunnedMigration() (*Migration, error) { diff --git a/migrate/migrate_test.go b/migrate/migrate_test.go index 19554f7e..3d7aa189 100644 --- a/migrate/migrate_test.go +++ b/migrate/migrate_test.go @@ -106,10 +106,7 @@ func TestInitSchema(t *testing.T) { if err := tx.Sync2(&Person{}); err != nil { return err } - if err := tx.Sync2(&Pet{}); err != nil { - return err - } - return nil + return tx.Sync2(&Pet{}) }) err = m.Migrate() diff --git a/names/mapper.go b/names/mapper.go index 4aaf0844..79add76e 100644 --- a/names/mapper.go +++ b/names/mapper.go @@ -7,6 +7,7 @@ package names import ( "strings" "sync" + "unsafe" ) // Mapper represents a name convertation between struct's fields name and table's column name @@ -77,19 +78,24 @@ func (m SameMapper) Table2Obj(t string) string { type SnakeMapper struct { } +func b2s(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + func snakeCasedName(name string) string { - newstr := make([]rune, 0) - for idx, chr := range name { - if isUpper := 'A' <= chr && chr <= 'Z'; isUpper { - if idx > 0 { + newstr := make([]byte, 0, len(name)+1) + for i := 0; i < len(name); i++ { + c := name[i] + if isUpper := 'A' <= c && c <= 'Z'; isUpper { + if i > 0 { newstr = append(newstr, '_') } - 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 { @@ -97,27 +103,28 @@ func (mapper SnakeMapper) Obj2Table(name string) string { } func titleCasedName(name string) string { - newstr := make([]rune, 0) + newstr := make([]byte, 0, len(name)) upNextChar := true name = strings.ToLower(name) - for _, chr := range name { + for i := 0; i < len(name); i++ { + c := name[i] switch { case upNextChar: upNextChar = false - if 'a' <= chr && chr <= 'z' { - chr -= ('a' - 'A') + if 'a' <= c && c <= 'z' { + c -= 'a' - 'A' } - case chr == '_': + case c == '_': upNextChar = true continue } - newstr = append(newstr, chr) + newstr = append(newstr, c) } - return string(newstr) + return b2s(newstr) } func (mapper SnakeMapper) Table2Obj(name string) string { diff --git a/names/mapper_test.go b/names/mapper_test.go index 0edfd2a8..a39cb569 100644 --- a/names/mapper_test.go +++ b/names/mapper_test.go @@ -5,6 +5,7 @@ package names import ( + "strings" "testing" ) @@ -47,3 +48,23 @@ func TestGonicMapperToObj(t *testing.T) { } } } + +func BenchmarkSnakeCasedName(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + s := strings.Repeat("FooBar", 32) + for i := 0; i < b.N; i++ { + _ = snakeCasedName(s) + } +} + +func BenchmarkTitleCasedName(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + s := strings.Repeat("foo_bar", 32) + for i := 0; i < b.N; i++ { + _ = titleCasedName(s) + } +} diff --git a/schemas/column.go b/schemas/column.go index 418629ac..4f32afab 100644 --- a/schemas/column.go +++ b/schemas/column.go @@ -5,8 +5,10 @@ package schemas import ( + "errors" "fmt" "reflect" + "strconv" "strings" "time" ) @@ -49,6 +51,7 @@ type Column struct { func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable bool) *Column { return &Column{ Name: name, + IsJSON: sqlType.IsJson(), TableName: "", FieldName: fieldName, SQLType: sqlType, @@ -115,3 +118,17 @@ func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) { return &fieldValue, nil } + +// ConvertID converts id content to suitable type according column type +func (col *Column) ConvertID(sid string) (interface{}, error) { + if col.SQLType.IsNumeric() { + n, err := strconv.ParseInt(sid, 10, 64) + if err != nil { + return nil, err + } + return n, nil + } else if col.SQLType.IsText() { + return sid, nil + } + return nil, errors.New("not supported") +} diff --git a/schemas/quote.go b/schemas/quote.go index c44abe25..a0070048 100644 --- a/schemas/quote.go +++ b/schemas/quote.go @@ -82,9 +82,7 @@ func (q Quoter) JoinWrite(b *strings.Builder, a []string, sep string) error { return err } } - if s != "*" { - q.QuoteTo(b, strings.TrimSpace(s)) - } + q.QuoteTo(b, strings.TrimSpace(s)) } return nil } @@ -143,7 +141,7 @@ func (q Quoter) quoteWordTo(buf *strings.Builder, word string) error { } isReserved := q.IsReserved(realWord) - if isReserved { + if isReserved && realWord != "*" { if err := buf.WriteByte(q.Prefix); err != nil { return err } @@ -151,7 +149,7 @@ func (q Quoter) quoteWordTo(buf *strings.Builder, word string) error { if _, err := buf.WriteString(realWord); err != nil { return err } - if isReserved { + if isReserved && realWord != "*" { return buf.WriteByte(q.Suffix) } diff --git a/schemas/quote_test.go b/schemas/quote_test.go index 708b450e..8e351dc0 100644 --- a/schemas/quote_test.go +++ b/schemas/quote_test.go @@ -22,6 +22,7 @@ func TestAlwaysQuoteTo(t *testing.T) { {"[mytable]", "`mytable`"}, {"[mytable]", `[mytable]`}, {`["mytable"]`, `"mytable"`}, + {`[mytable].*`, `[mytable].*`}, {"[myschema].[mytable]", "myschema.mytable"}, {"[myschema].[mytable]", "`myschema`.mytable"}, {"[myschema].[mytable]", "myschema.`mytable`"}, @@ -65,6 +66,7 @@ func TestReversedQuoteTo(t *testing.T) { {"[mytable]", "mytable"}, {"[mytable]", "`mytable`"}, {"[mytable]", `[mytable]`}, + {"[mytable].*", `[mytable].*`}, {`"mytable"`, `"mytable"`}, {"myschema.[mytable]", "myschema.mytable"}, {"myschema.[mytable]", "`myschema`.mytable"}, @@ -98,6 +100,7 @@ func TestNoQuoteTo(t *testing.T) { {"mytable", "mytable"}, {"mytable", "`mytable`"}, {"mytable", `[mytable]`}, + {"mytable.*", `[mytable].*`}, {`"mytable"`, `"mytable"`}, {"myschema.mytable", "myschema.mytable"}, {"myschema.mytable", "`myschema`.mytable"}, @@ -127,6 +130,8 @@ func TestJoin(t *testing.T) { assert.EqualValues(t, "[a],[b]", quoter.Join([]string{"a", " b"}, ",")) + assert.EqualValues(t, "[a].*,[b].[c]", quoter.Join([]string{"a.*", " b.c"}, ",")) + assert.EqualValues(t, "[f1], [f2], [f3]", quoter.Join(cols, ", ")) quoter.IsReserved = AlwaysNoReserve @@ -134,11 +139,11 @@ func TestJoin(t *testing.T) { } func TestStrings(t *testing.T) { - cols := []string{"f1", "f2", "t3.f3"} + cols := []string{"f1", "f2", "t3.f3", "t4.*"} quoter := Quoter{'[', ']', AlwaysReserve} quotedCols := quoter.Strings(cols) - assert.EqualValues(t, []string{"[f1]", "[f2]", "[t3].[f3]"}, quotedCols) + assert.EqualValues(t, []string{"[f1]", "[f2]", "[t3].[f3]", "[t4].*"}, quotedCols) } func TestTrim(t *testing.T) { diff --git a/schemas/table.go b/schemas/table.go index 38596991..7ca9531f 100644 --- a/schemas/table.go +++ b/schemas/table.go @@ -5,7 +5,9 @@ package schemas import ( + "fmt" "reflect" + "strconv" "strings" ) @@ -28,6 +30,7 @@ type Table struct { Comment string } +// NewEmptyTable creates an empty table func NewEmptyTable() *Table { return NewTable("", nil) } @@ -44,23 +47,21 @@ func NewTable(name string, t reflect.Type) *Table { } } +// Columns returns table's columns func (table *Table) Columns() []*Column { return table.columns } +// ColumnsSeq returns table's column names according sequence func (table *Table) ColumnsSeq() []string { return table.columnsSeq } func (table *Table) columnsByName(name string) []*Column { - for k, cols := range table.columnsMap { - if strings.EqualFold(k, name) { - return cols - } - } - return nil + return table.columnsMap[strings.ToLower(name)] } +// GetColumn returns column according column name, if column not found, return nil func (table *Table) GetColumn(name string) *Column { cols := table.columnsByName(name) if cols != nil { @@ -70,6 +71,7 @@ func (table *Table) GetColumn(name string) *Column { return nil } +// GetColumnIdx returns column according name and idx func (table *Table) GetColumnIdx(name string, idx int) *Column { cols := table.columnsByName(name) if cols != nil && idx < len(cols) { @@ -144,3 +146,45 @@ func (table *Table) AddColumn(col *Column) { func (table *Table) AddIndex(index *Index) { table.Indexes[index.Name] = index } + +// IDOfV get id from one value of struct +func (table *Table) IDOfV(rv reflect.Value) (PK, error) { + v := reflect.Indirect(rv) + pk := make([]interface{}, len(table.PrimaryKeys)) + for i, col := range table.PKColumns() { + var err error + + fieldName := col.FieldName + for { + parts := strings.SplitN(fieldName, ".", 2) + if len(parts) == 1 { + break + } + + v = v.FieldByName(parts[0]) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + if v.Kind() != reflect.Struct { + return nil, fmt.Errorf("Unsupported read value of column %s from field %s", col.Name, col.FieldName) + } + fieldName = parts[1] + } + + pkField := v.FieldByName(fieldName) + switch pkField.Kind() { + case reflect.String: + pk[i], err = col.ConvertID(pkField.String()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + pk[i], err = col.ConvertID(strconv.FormatInt(pkField.Int(), 10)) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + // id of uint will be converted to int64 + pk[i], err = col.ConvertID(strconv.FormatUint(pkField.Uint(), 10)) + } + + if err != nil { + return nil, err + } + } + return PK(pk), nil +} diff --git a/schemas/type.go b/schemas/type.go index 89459a4d..6b50d184 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -68,14 +68,21 @@ func (s *SQLType) IsJson() bool { return s.Name == Json || s.Name == Jsonb } +func (s *SQLType) IsXML() bool { + return s.Name == XML +} + var ( - Bit = "BIT" - TinyInt = "TINYINT" - SmallInt = "SMALLINT" - MediumInt = "MEDIUMINT" - Int = "INT" - Integer = "INTEGER" - BigInt = "BIGINT" + Bit = "BIT" + UnsignedBit = "UNSIGNED BIT" + TinyInt = "TINYINT" + SmallInt = "SMALLINT" + MediumInt = "MEDIUMINT" + Int = "INT" + UnsignedInt = "UNSIGNED INT" + Integer = "INTEGER" + BigInt = "BIGINT" + UnsignedBigInt = "UNSIGNED BIGINT" Enum = "ENUM" Set = "SET" @@ -128,22 +135,28 @@ var ( Json = "JSON" Jsonb = "JSONB" + XML = "XML" Array = "ARRAY" SqlTypes = map[string]int{ - Bit: NUMERIC_TYPE, - TinyInt: NUMERIC_TYPE, - SmallInt: NUMERIC_TYPE, - MediumInt: NUMERIC_TYPE, - Int: NUMERIC_TYPE, - Integer: NUMERIC_TYPE, - BigInt: NUMERIC_TYPE, + Bit: NUMERIC_TYPE, + UnsignedBit: NUMERIC_TYPE, + TinyInt: NUMERIC_TYPE, + SmallInt: NUMERIC_TYPE, + MediumInt: NUMERIC_TYPE, + Int: NUMERIC_TYPE, + UnsignedInt: NUMERIC_TYPE, + Integer: NUMERIC_TYPE, + BigInt: NUMERIC_TYPE, + UnsignedBigInt: NUMERIC_TYPE, Enum: TEXT_TYPE, Set: TEXT_TYPE, Json: TEXT_TYPE, Jsonb: TEXT_TYPE, + XML: TEXT_TYPE, + Char: TEXT_TYPE, NChar: TEXT_TYPE, Varchar: TEXT_TYPE, @@ -273,10 +286,14 @@ var ( // Type2SQLType generate SQLType acorrding Go's type func Type2SQLType(t reflect.Type) (st SQLType) { switch k := t.Kind(); k { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: st = SQLType{Int, 0, 0} - case reflect.Int64, reflect.Uint64: + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: + st = SQLType{UnsignedInt, 0, 0} + case reflect.Int64: st = SQLType{BigInt, 0, 0} + case reflect.Uint64: + st = SQLType{UnsignedBigInt, 0, 0} case reflect.Float32: st = SQLType{Float, 0, 0} case reflect.Float64: diff --git a/session.go b/session.go index 761b1415..d5ccb6dc 100644 --- a/session.go +++ b/session.go @@ -107,7 +107,7 @@ func newSession(engine *Engine) *Session { ctx = engine.defaultContext } - return &Session{ + session := &Session{ ctx: ctx, engine: engine, tx: nil, @@ -136,6 +136,10 @@ func newSession(engine *Engine) *Session { sessionType: engineSession, } + if engine.logSessionID { + session.ctx = context.WithValue(session.ctx, log.SessionKey, session) + } + return session } // Close release the connection from pool @@ -165,6 +169,11 @@ func (session *Session) db() *core.DB { return session.engine.db } +// Engine returns session Engine +func (session *Session) Engine() *Engine { + return session.engine +} + func (session *Session) getQueryer() core.Queryer { if session.tx != nil { return session.tx @@ -495,7 +504,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b fieldType := fieldValue.Type() hasAssigned := false - if col.SQLType.IsJson() { + if col.IsJSON { var bs []byte if rawValueType.Kind() == reflect.String { bs = []byte(vv.String()) @@ -675,7 +684,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b session.engine.logger.Errorf("sql.Sanner error: %v", err) hasAssigned = false } - } else if col.SQLType.IsJson() { + } else if col.IsJSON { if rawValueType.Kind() == reflect.String { hasAssigned = true x := reflect.New(fieldType) @@ -887,7 +896,7 @@ func (session *Session) incrVersionFieldValue(fieldValue *reflect.Value) { } } -// ContextHook sets the context on this session +// Context sets the context on this session func (session *Session) Context(ctx context.Context) *Session { session.ctx = ctx return session diff --git a/session_find.go b/session_find.go index 6fbca695..0daea005 100644 --- a/session_find.go +++ b/session_find.go @@ -57,9 +57,18 @@ func (session *Session) FindAndCount(rowsSlicePtr interface{}, condiBean ...inte if session.statement.SelectStr != "" { session.statement.SelectStr = "" } + if len(session.statement.ColumnMap) > 0 { + session.statement.ColumnMap = []string{} + } if session.statement.OrderStr != "" { session.statement.OrderStr = "" } + if session.statement.LimitN != nil { + session.statement.LimitN = nil + } + if session.statement.Start > 0 { + session.statement.Start = 0 + } // session has stored the conditions so we use `unscoped` to avoid duplicated condition. return session.Unscoped().Count(reflect.New(sliceElementType).Interface()) @@ -108,8 +117,11 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) ) if tp == tpStruct { if !session.statement.NoAutoCondition && len(condiBean) > 0 { - var err error - autoCond, err = session.statement.BuildConds(table, condiBean[0], true, true, false, true, addedTableName) + condTable, err := session.engine.tagParser.Parse(reflect.ValueOf(condiBean[0])) + if err != nil { + return err + } + autoCond, err = session.statement.BuildConds(condTable, condiBean[0], true, true, false, true, addedTableName) if err != nil { return err } @@ -317,7 +329,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in } var pk schemas.PK = make([]interface{}, len(table.PrimaryKeys)) for i, col := range table.PKColumns() { - pk[i], err = session.engine.idTypeAssertion(col, res[i]) + pk[i], err = col.ConvertID(res[i]) if err != nil { return err } @@ -367,7 +379,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in } else { session.engine.logger.Debugf("[cache] cache hit bean: %v, %v, %v", tableName, id, bean) - pk, err := session.engine.IDOf(bean) + pk, err := table.IDOfV(reflect.ValueOf(bean)) if err != nil { return err } @@ -416,7 +428,6 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in if err != nil { return err } - session.statement = statement vs := reflect.Indirect(reflect.ValueOf(beans)) @@ -425,7 +436,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in if rv.Kind() != reflect.Ptr { rv = rv.Addr() } - id, err := session.engine.idOfV(rv) + id, err := table.IDOfV(rv) if err != nil { return err } diff --git a/session_get.go b/session_get.go index afedcd1f..e303176d 100644 --- a/session_get.go +++ b/session_get.go @@ -16,6 +16,11 @@ import ( "xorm.io/xorm/schemas" ) +var ( + // ErrObjectIsNil return error of object is nil + ErrObjectIsNil = errors.New("object should not be nil") +) + // Get retrieve one record from database, bean's non-empty fields // will be as conditions func (session *Session) Get(bean interface{}) (bool, error) { @@ -37,6 +42,8 @@ func (session *Session) get(bean interface{}) (bool, error) { return false, errors.New("needs a pointer to a value") } else if beanValue.Elem().Kind() == reflect.Ptr { return false, errors.New("a pointer to a pointer is not allowed") + } else if beanValue.IsNil() { + return false, ErrObjectIsNil } if beanValue.Elem().Kind() == reflect.Struct { diff --git a/session_schema.go b/session_schema.go index 9ccf8abe..7d36ae7f 100644 --- a/session_schema.go +++ b/session_schema.go @@ -448,27 +448,43 @@ func (session *Session) ImportFile(ddlPath string) ([]sql.Result, error) { // Import SQL DDL from io.Reader func (session *Session) Import(r io.Reader) ([]sql.Result, error) { - var results []sql.Result - var lastError error - scanner := bufio.NewScanner(r) + var ( + results []sql.Result + lastError error + inSingleQuote bool + startComment bool + ) - var inSingleQuote bool + scanner := bufio.NewScanner(r) semiColSpliter := func(data []byte, atEOF bool) (advance int, token []byte, err error) { if atEOF && len(data) == 0 { return 0, nil, nil } + var oriInSingleQuote = inSingleQuote for i, b := range data { - if b == '\'' { - inSingleQuote = !inSingleQuote - } - if !inSingleQuote && b == ';' { - return i + 1, data[0:i], nil + if startComment { + if b == '\n' { + startComment = false + } + } 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 atEOF { return len(data), data, nil } + inSingleQuote = oriInSingleQuote // Request more data. return 0, nil, nil } @@ -479,10 +495,10 @@ func (session *Session) Import(r io.Reader) ([]sql.Result, error) { query := strings.Trim(scanner.Text(), " \t\n\r") if len(query) > 0 { result, err := session.Exec(query) - results = append(results, result) if err != nil { return nil, err } + results = append(results, result) } } diff --git a/session_tx.go b/session_tx.go index cd23cf89..8763784c 100644 --- a/session_tx.go +++ b/session_tx.go @@ -4,12 +4,6 @@ package xorm -import ( - "time" - - "xorm.io/xorm/log" -) - // Begin a transaction func (session *Session) Begin() error { if session.isAutoCommit { @@ -33,24 +27,7 @@ func (session *Session) Rollback() error { session.isCommitedOrRollbacked = true session.isAutoCommit = true - start := time.Now() - needSQL := session.DB().NeedLogSQL(session.ctx) - if needSQL { - session.engine.logger.BeforeSQL(log.LogContext{ - Ctx: session.ctx, - SQL: "ROLL BACK", - }) - } - err := session.tx.Rollback() - if needSQL { - session.engine.logger.AfterSQL(log.LogContext{ - Ctx: session.ctx, - SQL: "ROLL BACK", - ExecuteTime: time.Now().Sub(start), - Err: err, - }) - } - return err + return session.tx.Rollback() } return nil } @@ -62,25 +39,7 @@ func (session *Session) Commit() error { session.isCommitedOrRollbacked = true session.isAutoCommit = true - start := time.Now() - needSQL := session.DB().NeedLogSQL(session.ctx) - if needSQL { - session.engine.logger.BeforeSQL(log.LogContext{ - Ctx: session.ctx, - SQL: "COMMIT", - }) - } - err := session.tx.Commit() - if needSQL { - session.engine.logger.AfterSQL(log.LogContext{ - Ctx: session.ctx, - SQL: "COMMIT", - ExecuteTime: time.Now().Sub(start), - Err: err, - }) - } - - if err != nil { + if err := session.tx.Commit(); err != nil { return err } @@ -125,3 +84,8 @@ func (session *Session) Commit() error { } return nil } + +// IsInTx if current session is in a transaction +func (session *Session) IsInTx() bool { + return !session.isAutoCommit +} diff --git a/session_update.go b/session_update.go index 62116c47..0adac25e 100644 --- a/session_update.go +++ b/session_update.go @@ -206,7 +206,11 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 colNames = append(colNames, session.engine.Quote(table.Updated)+" = ?") col := table.UpdatedColumn() val, t := session.engine.nowTime(col) - 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 if isStruct { @@ -269,8 +273,15 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 k = ct.Elem().Kind() } if k == reflect.Struct { + var refTable = session.statement.RefTable + if refTable == nil { + refTable, err = session.engine.TableInfo(condiBean[0]) + if err != nil { + return 0, err + } + } var err error - autoCond, err = session.statement.BuildConds(session.statement.RefTable, condiBean[0], true, true, false, true, false) + autoCond, err = session.statement.BuildConds(refTable, condiBean[0], true, true, false, true, false) if err != nil { return 0, err } diff --git a/tags/parser.go b/tags/parser.go index 236d2d46..45dd6d9d 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -63,6 +63,11 @@ func (parser *Parser) SetColumnMapper(mapper names.Mapper) { parser.columnMapper = mapper } +func (parser *Parser) SetIdentifier(identifier string) { + parser.ClearCaches() + parser.identifier = identifier +} + func (parser *Parser) ParseWithCache(v reflect.Value) (*schemas.Table, error) { t := v.Type() tableI, ok := parser.tableCache.Load(t) @@ -115,6 +120,7 @@ func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) { t := v.Type() if t.Kind() == reflect.Ptr { t = t.Elem() + v = v.Elem() } if t.Kind() != reflect.Struct { return nil, ErrUnsupportedType @@ -252,7 +258,7 @@ func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) { addIndex(indexName, table, col, indexType) } } - } else { + } else if fieldValue.CanSet() { var sqlType schemas.SQLType if fieldValue.CanAddr() { if _, ok := fieldValue.Addr().Interface().(convert.Conversion); ok { @@ -271,6 +277,8 @@ func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) { if fieldType.Kind() == reflect.Int64 && (strings.ToUpper(col.FieldName) == "ID" || strings.HasSuffix(strings.ToUpper(col.FieldName), ".ID")) { idFieldColName = col.Name } + } else { + continue } if col.IsAutoIncrement { col.Nullable = false diff --git a/tags/parser_test.go b/tags/parser_test.go index 6065bf2e..c3bf8051 100644 --- a/tags/parser_test.go +++ b/tags/parser_test.go @@ -42,3 +42,64 @@ func TestParseTableName(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, "p_parseTableName", table.Name) } + +func TestUnexportField(t *testing.T) { + parser := NewParser( + "xorm", + dialects.QueryDialect("mysql"), + names.SnakeMapper{}, + names.SnakeMapper{}, + caches.NewManager(), + ) + + type VanilaStruct struct { + private int + Public int + } + table, err := parser.Parse(reflect.ValueOf(new(VanilaStruct))) + assert.NoError(t, err) + assert.EqualValues(t, "vanila_struct", table.Name) + assert.EqualValues(t, 1, len(table.Columns())) + + for _, col := range table.Columns() { + assert.EqualValues(t, "public", col.Name) + assert.NotEqual(t, "private", col.Name) + } + + type TaggedStruct struct { + private int `xorm:"private"` + Public int `xorm:"-"` + } + table, err = parser.Parse(reflect.ValueOf(new(TaggedStruct))) + assert.NoError(t, err) + assert.EqualValues(t, "tagged_struct", table.Name) + assert.EqualValues(t, 1, len(table.Columns())) + + for _, col := range table.Columns() { + assert.EqualValues(t, "private", col.Name) + assert.NotEqual(t, "public", col.Name) + } +} + +func TestParseWithOtherIdentifier(t *testing.T) { + parser := NewParser( + "xorm", + dialects.QueryDialect("mysql"), + names.GonicMapper{}, + names.SnakeMapper{}, + caches.NewManager(), + ) + + type StructWithDBTag struct { + FieldFoo string `db:"foo"` + } + parser.SetIdentifier("db") + table, err := parser.Parse(reflect.ValueOf(new(StructWithDBTag))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_db_tag", table.Name) + assert.EqualValues(t, 1, len(table.Columns())) + + for _, col := range table.Columns() { + assert.EqualValues(t, "foo", col.Name) + } +} diff --git a/tags/tag.go b/tags/tag.go index ee3f1e82..bb5b5838 100644 --- a/tags/tag.go +++ b/tags/tag.go @@ -226,6 +226,9 @@ func CommentTagHandler(ctx *Context) error { // SQLTypeTagHandler describes SQL Type tag handler func SQLTypeTagHandler(ctx *Context) error { ctx.col.SQLType = schemas.SQLType{Name: ctx.tagName} + if strings.EqualFold(ctx.tagName, "JSON") { + ctx.col.IsJSON = true + } if len(ctx.params) > 0 { if ctx.tagName == schemas.Enum { ctx.col.EnumOptions = make(map[string]int)