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/.revive.toml b/.revive.toml index 64e223bb..6dec7465 100644 --- a/.revive.toml +++ b/.revive.toml @@ -15,6 +15,7 @@ warningCode = 1 [rule.if-return] [rule.increment-decrement] [rule.var-naming] + arguments = [["ID", "UID", "UUID", "URL", "JSON"], []] [rule.var-declaration] [rule.package-comments] [rule.range] @@ -22,4 +23,5 @@ warningCode = 1 [rule.time-naming] [rule.unexported-return] [rule.indent-error-flow] -[rule.errorf] \ No newline at end of file +[rule.errorf] +[rule.struct-tag] \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cd081e0..13e721ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,83 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log. +## [1.1.0](https://gitea.com/xorm/xorm/releases/tag/1.1.0) - 2021-05-14 + +* FEATURES + * Unsigned Support for mysql (#1889) + * Support modernc.org/sqlite (#1850) +* TESTING + * More tests (#1890) +* MISC + * Byte strings in postgres aren't 0x... (#1906) + * Fix another bug with #1872 (#1905) + * Fix two issues with dumptables (#1903) + * Fix comments (#1896) + * Fix comments (#1893) + * MariaDB 10.5 adds a suffix on old datatypes (#1885) + +## [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..bf71b0f4 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 \ @@ -217,4 +243,4 @@ test-tidb\#%: go-check .PHONY: vet vet: - $(GO) vet $(shell $(GO) list ./...) \ No newline at end of file + $(GO) vet $(shell $(GO) list ./...) 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/encode.go b/caches/encode.go index 4ba39924..95536d7e 100644 --- a/caches/encode.go +++ b/caches/encode.go @@ -13,22 +13,26 @@ import ( "io" ) -// md5 hash string +// Md5 return md5 hash string func Md5(str string) string { m := md5.New() io.WriteString(m, str) return fmt.Sprintf("%x", m.Sum(nil)) } + +// Encode Encode data func Encode(data interface{}) ([]byte, error) { //return JsonEncode(data) return GobEncode(data) } +// Decode decode data func Decode(data []byte, to interface{}) error { //return JsonDecode(data, to) return GobDecode(data, to) } +// GobEncode encode data with gob func GobEncode(data interface{}) ([]byte, error) { var buf bytes.Buffer enc := gob.NewEncoder(&buf) @@ -39,12 +43,14 @@ func GobEncode(data interface{}) ([]byte, error) { return buf.Bytes(), nil } +// GobDecode decode data with gob func GobDecode(data []byte, to interface{}) error { buf := bytes.NewBuffer(data) dec := gob.NewDecoder(buf) return dec.Decode(to) } +// JsonEncode encode data with json func JsonEncode(data interface{}) ([]byte, error) { val, err := json.Marshal(data) if err != nil { @@ -53,6 +59,7 @@ func JsonEncode(data interface{}) ([]byte, error) { return val, nil } +// JsonDecode decode data with json func JsonDecode(data []byte, to interface{}) error { return json.Unmarshal(data, to) } 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.go b/core/db.go index 50c64c6f..ef5ab227 100644 --- a/core/db.go +++ b/core/db.go @@ -23,6 +23,7 @@ var ( DefaultCacheSize = 200 ) +// MapToSlice map query and struct as sql and args func MapToSlice(query string, mp interface{}) (string, []interface{}, error) { vv := reflect.ValueOf(mp) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { @@ -44,6 +45,7 @@ func MapToSlice(query string, mp interface{}) (string, []interface{}, error) { return query, args, err } +// StructToSlice converts a query and struct as sql and args func StructToSlice(query string, st interface{}) (string, []interface{}, error) { vv := reflect.ValueOf(st) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { @@ -176,6 +178,7 @@ func (db *DB) QueryMap(query string, mp interface{}) (*Rows, error) { return db.QueryMapContext(context.Background(), query, mp) } +// QueryStructContext query rows with struct func (db *DB) QueryStructContext(ctx context.Context, query string, st interface{}) (*Rows, error) { query, args, err := StructToSlice(query, st) if err != nil { @@ -184,10 +187,12 @@ func (db *DB) QueryStructContext(ctx context.Context, query string, st interface return db.QueryContext(ctx, query, args...) } +// QueryStruct query rows with struct func (db *DB) QueryStruct(query string, st interface{}) (*Rows, error) { return db.QueryStructContext(context.Background(), query, st) } +// QueryRowContext query row with args func (db *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row { rows, err := db.QueryContext(ctx, query, args...) if err != nil { @@ -196,10 +201,12 @@ func (db *DB) QueryRowContext(ctx context.Context, query string, args ...interfa return &Row{rows, nil} } +// QueryRow query row with args func (db *DB) QueryRow(query string, args ...interface{}) *Row { return db.QueryRowContext(context.Background(), query, args...) } +// QueryRowMapContext query row with map func (db *DB) QueryRowMapContext(ctx context.Context, query string, mp interface{}) *Row { query, args, err := MapToSlice(query, mp) if err != nil { @@ -208,10 +215,12 @@ func (db *DB) QueryRowMapContext(ctx context.Context, query string, mp interface return db.QueryRowContext(ctx, query, args...) } +// QueryRowMap query row with map func (db *DB) QueryRowMap(query string, mp interface{}) *Row { return db.QueryRowMapContext(context.Background(), query, mp) } +// QueryRowStructContext query row with struct func (db *DB) QueryRowStructContext(ctx context.Context, query string, st interface{}) *Row { query, args, err := StructToSlice(query, st) if err != nil { @@ -220,6 +229,7 @@ func (db *DB) QueryRowStructContext(ctx context.Context, query string, st interf return db.QueryRowContext(ctx, query, args...) } +// QueryRowStruct query row with struct func (db *DB) QueryRowStruct(query string, st interface{}) *Row { return db.QueryRowStructContext(context.Background(), query, st) } @@ -239,10 +249,12 @@ func (db *DB) ExecMapContext(ctx context.Context, query string, mp interface{}) return db.ExecContext(ctx, query, args...) } +// ExecMap exec query with map func (db *DB) ExecMap(query string, mp interface{}) (sql.Result, error) { return db.ExecMapContext(context.Background(), query, mp) } +// ExecStructContext exec query with map func (db *DB) ExecStructContext(ctx context.Context, query string, st interface{}) (sql.Result, error) { query, args, err := StructToSlice(query, st) if err != nil { @@ -251,6 +263,7 @@ func (db *DB) ExecStructContext(ctx context.Context, query string, st interface{ return db.ExecContext(ctx, query, args...) } +// ExecContext exec query with args func (db *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { hookCtx := contexts.NewContextHook(ctx, query, args) ctx, err := db.beforeProcess(hookCtx) @@ -265,6 +278,7 @@ func (db *DB) ExecContext(ctx context.Context, query string, args ...interface{} return res, nil } +// ExecStruct exec query with struct func (db *DB) ExecStruct(query string, st interface{}) (sql.Result, error) { return db.ExecStructContext(context.Background(), query, st) } @@ -288,6 +302,7 @@ func (db *DB) afterProcess(c *contexts.ContextHook) error { return err } +// AddHook adds hook func (db *DB) AddHook(h ...contexts.Hook) { db.hooks.AddHook(h...) } diff --git a/core/db_test.go b/core/db_test.go index 777ab0ad..e9c2d82d 100644 --- a/core/db_test.go +++ b/core/db_test.go @@ -15,25 +15,26 @@ import ( _ "github.com/go-sql-driver/mysql" _ "github.com/mattn/go-sqlite3" + _ "modernc.org/sqlite" ) var ( dbtype = flag.String("dbtype", "sqlite3", "database type") dbConn = flag.String("dbConn", "./db_test.db", "database connect string") - createTableSql string + createTableSQL string ) func TestMain(m *testing.M) { flag.Parse() switch *dbtype { - case "sqlite3": - createTableSql = "CREATE TABLE IF NOT EXISTS `user` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NULL, " + + 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": fallthrough default: - createTableSql = "CREATE TABLE IF NOT EXISTS `user` (`id` INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, `name` TEXT NULL, " + + createTableSQL = "CREATE TABLE IF NOT EXISTS `user` (`id` INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, `name` TEXT NULL, " + "`title` TEXT NULL, `age` FLOAT NULL, `alias` TEXT NULL, `nick_name` TEXT NULL, `created` datetime);" } @@ -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: @@ -62,7 +66,7 @@ func BenchmarkOriQuery(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -117,7 +121,7 @@ func BenchmarkStructQuery(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -162,7 +166,7 @@ func BenchmarkStruct2Query(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -208,7 +212,7 @@ func BenchmarkSliceInterfaceQuery(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -266,7 +270,7 @@ func BenchmarkSliceInterfaceQuery(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -317,7 +321,7 @@ func BenchmarkSliceStringQuery(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -368,7 +372,7 @@ func BenchmarkMapInterfaceQuery(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -422,7 +426,7 @@ func BenchmarkMapInterfaceQuery(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -469,7 +473,7 @@ func BenchmarkMapStringQuery(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -515,7 +519,7 @@ func BenchmarkExec(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -540,7 +544,7 @@ func BenchmarkExecMap(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } @@ -573,7 +577,7 @@ func TestExecMap(t *testing.T) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { t.Error(err) } @@ -616,7 +620,7 @@ func TestExecStruct(t *testing.T) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { t.Error(err) } @@ -659,7 +663,7 @@ func BenchmarkExecStruct(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSql) + _, err = db.Exec(createTableSQL) if err != nil { b.Error(err) } diff --git a/core/rows.go b/core/rows.go index a1e8bfbc..c15a59a3 100644 --- a/core/rows.go +++ b/core/rows.go @@ -11,11 +11,13 @@ import ( "sync" ) +// Rows represents rows of table type Rows struct { *sql.Rows db *DB } +// ToMapString returns all records func (rs *Rows) ToMapString() ([]map[string]string, error) { cols, err := rs.Columns() if err != nil { @@ -34,7 +36,7 @@ func (rs *Rows) ToMapString() ([]map[string]string, error) { return results, nil } -// scan data to a struct's pointer according field index +// ScanStructByIndex scan data to a struct's pointer according field index func (rs *Rows) ScanStructByIndex(dest ...interface{}) error { if len(dest) == 0 { return errors.New("at least one struct") @@ -94,7 +96,7 @@ func fieldByName(v reflect.Value, name string) reflect.Value { return reflect.Zero(t) } -// scan data to a struct's pointer according field name +// ScanStructByName scan data to a struct's pointer according field name func (rs *Rows) ScanStructByName(dest interface{}) error { vv := reflect.ValueOf(dest) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { @@ -120,7 +122,7 @@ func (rs *Rows) ScanStructByName(dest interface{}) error { return rs.Rows.Scan(newDest...) } -// scan data to a slice's pointer, slice's length should equal to columns' number +// ScanSlice scan data to a slice's pointer, slice's length should equal to columns' number func (rs *Rows) ScanSlice(dest interface{}) error { vv := reflect.ValueOf(dest) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Slice { @@ -155,7 +157,7 @@ func (rs *Rows) ScanSlice(dest interface{}) error { return nil } -// scan data to a map's pointer +// ScanMap scan data to a map's pointer func (rs *Rows) ScanMap(dest interface{}) error { vv := reflect.ValueOf(dest) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { @@ -187,6 +189,7 @@ func (rs *Rows) ScanMap(dest interface{}) error { return nil } +// Row reprents a row of a tab type Row struct { rows *Rows // One of these two will be non-nil: @@ -205,6 +208,7 @@ func NewRow(rows *Rows, err error) *Row { return &Row{rows, err} } +// Columns returns all columns of the row func (row *Row) Columns() ([]string, error) { if row.err != nil { return nil, row.err @@ -212,6 +216,7 @@ func (row *Row) Columns() ([]string, error) { return row.rows.Columns() } +// Scan retrieves all row column values func (row *Row) Scan(dest ...interface{}) error { if row.err != nil { return row.err @@ -238,6 +243,7 @@ func (row *Row) Scan(dest ...interface{}) error { return row.rows.Close() } +// ScanStructByName retrieves all row column values into a struct func (row *Row) ScanStructByName(dest interface{}) error { if row.err != nil { return row.err @@ -258,6 +264,7 @@ func (row *Row) ScanStructByName(dest interface{}) error { return row.rows.Close() } +// ScanStructByIndex retrieves all row column values into a struct func (row *Row) ScanStructByIndex(dest interface{}) error { if row.err != nil { return row.err @@ -278,7 +285,7 @@ func (row *Row) ScanStructByIndex(dest interface{}) error { return row.rows.Close() } -// scan data to a slice's pointer, slice's length should equal to columns' number +// ScanSlice scan data to a slice's pointer, slice's length should equal to columns' number func (row *Row) ScanSlice(dest interface{}) error { if row.err != nil { return row.err @@ -300,7 +307,7 @@ func (row *Row) ScanSlice(dest interface{}) error { return row.rows.Close() } -// scan data to a map's pointer +// ScanMap scan data to a map's pointer func (row *Row) ScanMap(dest interface{}) error { if row.err != nil { return row.err @@ -322,6 +329,7 @@ func (row *Row) ScanMap(dest interface{}) error { return row.rows.Close() } +// ToMapString returns all clumns of this record func (row *Row) ToMapString() (map[string]string, error) { cols, err := row.Columns() if err != nil { diff --git a/core/scan.go b/core/scan.go index 897b5341..1e7e4525 100644 --- a/core/scan.go +++ b/core/scan.go @@ -10,12 +10,14 @@ import ( "time" ) +// NullTime defines a customize type NullTime type NullTime time.Time var ( _ driver.Valuer = NullTime{} ) +// Scan implements driver.Valuer func (ns *NullTime) Scan(value interface{}) error { if value == nil { return nil @@ -58,9 +60,11 @@ func convertTime(dest *NullTime, src interface{}) error { return nil } +// EmptyScanner represents an empty scanner type EmptyScanner struct { } +// Scan implements func (EmptyScanner) Scan(src interface{}) error { return nil } diff --git a/core/stmt.go b/core/stmt.go index d46ac9c6..260843d5 100644 --- a/core/stmt.go +++ b/core/stmt.go @@ -21,6 +21,7 @@ type Stmt struct { query string } +// PrepareContext creates a prepare statement func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error) { names := make(map[string]int) var i int @@ -42,10 +43,12 @@ func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error) { return &Stmt{stmt, db, names, query}, nil } +// Prepare creates a prepare statement func (db *DB) Prepare(query string) (*Stmt, error) { return db.PrepareContext(context.Background(), query) } +// ExecMapContext execute with map func (s *Stmt) ExecMapContext(ctx context.Context, mp interface{}) (sql.Result, error) { vv := reflect.ValueOf(mp) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { @@ -59,10 +62,12 @@ func (s *Stmt) ExecMapContext(ctx context.Context, mp interface{}) (sql.Result, return s.ExecContext(ctx, args...) } +// ExecMap executes with map func (s *Stmt) ExecMap(mp interface{}) (sql.Result, error) { return s.ExecMapContext(context.Background(), mp) } +// ExecStructContext executes with struct func (s *Stmt) ExecStructContext(ctx context.Context, st interface{}) (sql.Result, error) { vv := reflect.ValueOf(st) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { @@ -76,10 +81,12 @@ func (s *Stmt) ExecStructContext(ctx context.Context, st interface{}) (sql.Resul return s.ExecContext(ctx, args...) } +// ExecStruct executes with struct func (s *Stmt) ExecStruct(st interface{}) (sql.Result, error) { return s.ExecStructContext(context.Background(), st) } +// ExecContext with args func (s *Stmt) ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error) { hookCtx := contexts.NewContextHook(ctx, s.query, args) ctx, err := s.db.beforeProcess(hookCtx) @@ -94,6 +101,7 @@ func (s *Stmt) ExecContext(ctx context.Context, args ...interface{}) (sql.Result return res, nil } +// QueryContext query with args func (s *Stmt) QueryContext(ctx context.Context, args ...interface{}) (*Rows, error) { hookCtx := contexts.NewContextHook(ctx, s.query, args) ctx, err := s.db.beforeProcess(hookCtx) @@ -108,10 +116,12 @@ func (s *Stmt) QueryContext(ctx context.Context, args ...interface{}) (*Rows, er return &Rows{rows, s.db}, nil } +// Query query with args func (s *Stmt) Query(args ...interface{}) (*Rows, error) { return s.QueryContext(context.Background(), args...) } +// QueryMapContext query with map func (s *Stmt) QueryMapContext(ctx context.Context, mp interface{}) (*Rows, error) { vv := reflect.ValueOf(mp) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { @@ -126,10 +136,12 @@ func (s *Stmt) QueryMapContext(ctx context.Context, mp interface{}) (*Rows, erro return s.QueryContext(ctx, args...) } +// QueryMap query with map func (s *Stmt) QueryMap(mp interface{}) (*Rows, error) { return s.QueryMapContext(context.Background(), mp) } +// QueryStructContext query with struct func (s *Stmt) QueryStructContext(ctx context.Context, st interface{}) (*Rows, error) { vv := reflect.ValueOf(st) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { @@ -144,19 +156,23 @@ func (s *Stmt) QueryStructContext(ctx context.Context, st interface{}) (*Rows, e return s.QueryContext(ctx, args...) } +// QueryStruct query with struct func (s *Stmt) QueryStruct(st interface{}) (*Rows, error) { return s.QueryStructContext(context.Background(), st) } +// QueryRowContext query row with args func (s *Stmt) QueryRowContext(ctx context.Context, args ...interface{}) *Row { rows, err := s.QueryContext(ctx, args...) return &Row{rows, err} } +// QueryRow query row with args func (s *Stmt) QueryRow(args ...interface{}) *Row { return s.QueryRowContext(context.Background(), args...) } +// QueryRowMapContext query row with map func (s *Stmt) QueryRowMapContext(ctx context.Context, mp interface{}) *Row { vv := reflect.ValueOf(mp) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { @@ -171,10 +187,12 @@ func (s *Stmt) QueryRowMapContext(ctx context.Context, mp interface{}) *Row { return s.QueryRowContext(ctx, args...) } +// QueryRowMap query row with map func (s *Stmt) QueryRowMap(mp interface{}) *Row { return s.QueryRowMapContext(context.Background(), mp) } +// QueryRowStructContext query row with struct func (s *Stmt) QueryRowStructContext(ctx context.Context, st interface{}) *Row { vv := reflect.ValueOf(st) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { @@ -189,6 +207,7 @@ func (s *Stmt) QueryRowStructContext(ctx context.Context, st interface{}) *Row { return s.QueryRowContext(ctx, args...) } +// QueryRowStruct query row with struct func (s *Stmt) QueryRowStruct(st interface{}) *Row { return s.QueryRowStructContext(context.Background(), st) } diff --git a/core/tx.go b/core/tx.go index 9b2988af..a2f745f8 100644 --- a/core/tx.go +++ b/core/tx.go @@ -18,9 +18,11 @@ var ( // Tx represents a transaction type Tx struct { *sql.Tx - db *DB + db *DB + ctx context.Context } +// BeginTx begin a transaction with option func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { hookCtx := contexts.NewContextHook(ctx, "BEGIN TRANSACTION", nil) ctx, err := db.beforeProcess(hookCtx) @@ -32,13 +34,39 @@ 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 } +// Begin begins a transaction func (db *DB) Begin() (*Tx, error) { return db.BeginTx(context.Background(), nil) } +// Commit submit the transaction +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) + return tx.db.afterProcess(hookCtx) +} + +// Rollback rollback the transaction +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) + return tx.db.afterProcess(hookCtx) +} + +// PrepareContext prepare the query func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error) { names := make(map[string]int) var i int @@ -60,19 +88,23 @@ func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error) { return &Stmt{stmt, tx.db, names, query}, nil } +// Prepare prepare the query func (tx *Tx) Prepare(query string) (*Stmt, error) { return tx.PrepareContext(context.Background(), query) } +// StmtContext creates Stmt with context func (tx *Tx) StmtContext(ctx context.Context, stmt *Stmt) *Stmt { stmt.Stmt = tx.Tx.StmtContext(ctx, stmt.Stmt) return stmt } +// Stmt creates Stmt func (tx *Tx) Stmt(stmt *Stmt) *Stmt { return tx.StmtContext(context.Background(), stmt) } +// ExecMapContext executes query with args in a map func (tx *Tx) ExecMapContext(ctx context.Context, query string, mp interface{}) (sql.Result, error) { query, args, err := MapToSlice(query, mp) if err != nil { @@ -81,10 +113,12 @@ func (tx *Tx) ExecMapContext(ctx context.Context, query string, mp interface{}) return tx.ExecContext(ctx, query, args...) } +// ExecMap executes query with args in a map func (tx *Tx) ExecMap(query string, mp interface{}) (sql.Result, error) { return tx.ExecMapContext(context.Background(), query, mp) } +// ExecStructContext executes query with args in a struct func (tx *Tx) ExecStructContext(ctx context.Context, query string, st interface{}) (sql.Result, error) { query, args, err := StructToSlice(query, st) if err != nil { @@ -93,6 +127,7 @@ func (tx *Tx) ExecStructContext(ctx context.Context, query string, st interface{ return tx.ExecContext(ctx, query, args...) } +// ExecContext executes a query with args func (tx *Tx) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { hookCtx := contexts.NewContextHook(ctx, query, args) ctx, err := tx.db.beforeProcess(hookCtx) @@ -107,10 +142,12 @@ func (tx *Tx) ExecContext(ctx context.Context, query string, args ...interface{} return res, err } +// ExecStruct executes query with args in a struct func (tx *Tx) ExecStruct(query string, st interface{}) (sql.Result, error) { return tx.ExecStructContext(context.Background(), query, st) } +// QueryContext query with args func (tx *Tx) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) { hookCtx := contexts.NewContextHook(ctx, query, args) ctx, err := tx.db.beforeProcess(hookCtx) @@ -128,10 +165,12 @@ func (tx *Tx) QueryContext(ctx context.Context, query string, args ...interface{ return &Rows{rows, tx.db}, nil } +// Query query with args func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error) { return tx.QueryContext(context.Background(), query, args...) } +// QueryMapContext query with args in a map func (tx *Tx) QueryMapContext(ctx context.Context, query string, mp interface{}) (*Rows, error) { query, args, err := MapToSlice(query, mp) if err != nil { @@ -140,10 +179,12 @@ func (tx *Tx) QueryMapContext(ctx context.Context, query string, mp interface{}) return tx.QueryContext(ctx, query, args...) } +// QueryMap query with args in a map func (tx *Tx) QueryMap(query string, mp interface{}) (*Rows, error) { return tx.QueryMapContext(context.Background(), query, mp) } +// QueryStructContext query with args in struct func (tx *Tx) QueryStructContext(ctx context.Context, query string, st interface{}) (*Rows, error) { query, args, err := StructToSlice(query, st) if err != nil { @@ -152,19 +193,23 @@ func (tx *Tx) QueryStructContext(ctx context.Context, query string, st interface return tx.QueryContext(ctx, query, args...) } +// QueryStruct query with args in struct func (tx *Tx) QueryStruct(query string, st interface{}) (*Rows, error) { return tx.QueryStructContext(context.Background(), query, st) } +// QueryRowContext query one row with args func (tx *Tx) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row { rows, err := tx.QueryContext(ctx, query, args...) return &Row{rows, err} } +// QueryRow query one row with args func (tx *Tx) QueryRow(query string, args ...interface{}) *Row { return tx.QueryRowContext(context.Background(), query, args...) } +// QueryRowMapContext query one row with args in a map func (tx *Tx) QueryRowMapContext(ctx context.Context, query string, mp interface{}) *Row { query, args, err := MapToSlice(query, mp) if err != nil { @@ -173,10 +218,12 @@ func (tx *Tx) QueryRowMapContext(ctx context.Context, query string, mp interface return tx.QueryRowContext(ctx, query, args...) } +// QueryRowMap query one row with args in a map func (tx *Tx) QueryRowMap(query string, mp interface{}) *Row { return tx.QueryRowMapContext(context.Background(), query, mp) } +// QueryRowStructContext query one row with args in struct func (tx *Tx) QueryRowStructContext(ctx context.Context, query string, st interface{}) *Row { query, args, err := StructToSlice(query, st) if err != nil { @@ -185,6 +232,7 @@ func (tx *Tx) QueryRowStructContext(ctx context.Context, query string, st interf return tx.QueryRowContext(ctx, query, args...) } +// QueryRowStruct query one row with args in struct func (tx *Tx) QueryRowStruct(query string, st interface{}) *Row { return tx.QueryRowStructContext(context.Background(), query, st) } 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/driver.go b/dialects/driver.go index ae3afe42..bb46a936 100644 --- a/dialects/driver.go +++ b/dialects/driver.go @@ -8,6 +8,7 @@ import ( "fmt" ) +// Driver represents a database driver type Driver interface { Parse(string, string) (*URI, error) } @@ -16,6 +17,7 @@ var ( drivers = map[string]Driver{} ) +// RegisterDriver register a driver func RegisterDriver(driverName string, driver Driver) { if driver == nil { panic("core: Register driver is nil") @@ -26,10 +28,12 @@ func RegisterDriver(driverName string, driver Driver) { drivers[driverName] = driver } +// QueryDriver query a driver with name func QueryDriver(driverName string) Driver { return drivers[driverName] } +// RegisteredDriverSize returned all drivers's length func RegisteredDriverSize() int { return len(drivers) } @@ -38,7 +42,7 @@ func RegisteredDriverSize() int { func OpenDialect(driverName, connstr string) (Dialect, error) { driver := QueryDriver(driverName) if driver == nil { - return nil, fmt.Errorf("Unsupported driver name: %v", driverName) + return nil, fmt.Errorf("unsupported driver name: %v", driverName) } uri, err := driver.Parse(driverName, connstr) @@ -48,7 +52,7 @@ func OpenDialect(driverName, connstr string) (Dialect, error) { dialect := QueryDialect(uri.DBType) if dialect == nil { - return nil, fmt.Errorf("Unsupported dialect type: %v", uri.DBType) + return nil, fmt.Errorf("unsupported dialect type: %v", uri.DBType) } dialect.Init(uri) diff --git a/dialects/filter.go b/dialects/filter.go index 6968b6ce..2a36a731 100644 --- a/dialects/filter.go +++ b/dialects/filter.go @@ -38,6 +38,7 @@ func convertQuestionMark(sql, prefix string, start int) string { return buf.String() } +// Do implements Filter func (s *SeqFilter) Do(sql string) string { return convertQuestionMark(sql, s.Prefix, s.Start) } 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 f9a2e943..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,9 +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` = ?" + - " ORDER BY `INFORMATION_SCHEMA`.`COLUMNS`.ORDINAL_POSITION" + " `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 { @@ -323,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 { @@ -378,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 @@ -404,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/postgres.go b/dialects/postgres.go index 1996c49d..e76e5b7e 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -824,6 +824,11 @@ func (db *postgres) SetQuotePolicy(quotePolicy QuotePolicy) { } } +// FormatBytes formats bytes +func (db *postgres) FormatBytes(bs []byte) string { + return fmt.Sprintf("E'\\x%x'", bs) +} + func (db *postgres) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { @@ -833,12 +838,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 +862,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 +1022,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 +1057,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 +1097,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..5aee0c10 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") @@ -34,6 +38,7 @@ func FormatTime(dialect Dialect, sqlTypeName string, t time.Time) (v interface{} return } +// FormatColumnTime format column time func FormatColumnTime(dialect Dialect, defaultTimeZone *time.Location, col *schemas.Column, t time.Time) (v interface{}) { if t.IsZero() { if col.Nullable { diff --git a/engine.go b/engine.go index 7399f41a..d49eea9a 100644 --- a/engine.go +++ b/engine.go @@ -21,6 +21,7 @@ import ( "xorm.io/xorm/contexts" "xorm.io/xorm/core" "xorm.io/xorm/dialects" + "xorm.io/xorm/internal/json" "xorm.io/xorm/internal/utils" "xorm.io/xorm/log" "xorm.io/xorm/names" @@ -61,6 +62,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 +93,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 +106,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 +165,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 +212,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 +376,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 +444,99 @@ 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 string + switch reflect.TypeOf(d).Kind() { + case reflect.Struct, reflect.Array, reflect.Slice, reflect.Map: + bytes, err := json.DefaultJSONHandler.Marshal(d) + if err != nil { + v = fmt.Sprintf("%s", d) + } else { + v = string(bytes) + } + default: + v = fmt.Sprintf("%s", d) + } + + return "'" + strings.Replace(v, "'", "''", -1) + "'" + } else if col.SQLType.IsTime() { + if dstDialect.URI().DBType == schemas.MSSQL && col.SQLType.Name == schemas.DateTime { + if t, ok := d.(time.Time); ok { + return "'" + t.UTC().Format("2006-01-02 15:04:05") + "'" + } + } + 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("%d", 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("%d", 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,9 +549,14 @@ 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) } + cacherMgr := caches.NewManager() + dstTableCache := tags.NewParser("xorm", dstDialect, engine.GetTableMapper(), engine.GetColumnMapper(), cacherMgr) _, err := io.WriteString(w, fmt.Sprintf("/*Generated by xorm %s, from %s to %s*/\n\n", time.Now().In(engine.TZLocation).Format("2006-01-02 15:04:05"), engine.dialect.URI().DBType, dstDialect.URI().DBType)) @@ -435,9 +565,18 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch } for i, table := range tables { - tableName := table.Name + dstTable := table + if table.Type != nil { + dstTable, err = dstTableCache.Parse(reflect.New(table.Type).Elem()) + if err != nil { + engine.logger.Errorf("Unable to infer table for %s in new dialect. Error: %v", table.Name) + dstTable = table + } + } + + dstTableName := dstTable.Name if dstDialect.URI().Schema != "" { - tableName = fmt.Sprintf("%s.%s", dstDialect.URI().Schema, table.Name) + dstTableName = fmt.Sprintf("%s.%s", dstDialect.URI().Schema, dstTable.Name) } originalTableName := table.Name if engine.dialect.URI().Schema != "" { @@ -449,27 +588,30 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch return err } } - sqls, _ := dstDialect.CreateTableSQL(table, tableName) + + sqls, _ := dstDialect.CreateTableSQL(dstTable, dstTableName) for _, s := range sqls { _, err = io.WriteString(w, s+";\n") if err != nil { return err } } - if len(table.PKColumns()) > 0 && dstDialect.URI().DBType == schemas.MSSQL { - fmt.Fprintf(w, "SET IDENTITY_INSERT [%s] ON;\n", table.Name) + if len(dstTable.PKColumns()) > 0 && dstDialect.URI().DBType == schemas.MSSQL { + fmt.Fprintf(w, "SET IDENTITY_INSERT [%s] ON;\n", dstTable.Name) } - for _, index := range table.Indexes { - _, err = io.WriteString(w, dstDialect.CreateIndexSQL(table.Name, index)+";\n") + for _, index := range dstTable.Indexes { + _, err = io.WriteString(w, dstDialect.CreateIndexSQL(dstTable.Name, index)+";\n") if err != nil { return err } } cols := table.ColumnsSeq() + dstCols := dstTable.ColumnsSeq() + colNames := engine.dialect.Quoter().Join(cols, ", ") - destColNames := dstDialect.Quoter().Join(cols, ", ") + destColNames := dstDialect.Quoter().Join(dstCols, ", ") rows, err := engine.DB().QueryContext(engine.defaultContext, "SELECT "+colNames+" FROM "+engine.Quote(originalTableName)) if err != nil { @@ -477,85 +619,83 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch } defer rows.Close() - for rows.Next() { - dest := make([]interface{}, len(cols)) - err = rows.ScanSlice(&dest) - if err != nil { - return err - } - - _, err = io.WriteString(w, "INSERT INTO "+dstDialect.Quoter().Quote(tableName)+" ("+destColNames+") VALUES (") - if err != nil { - return err - } - - var temp string - for i, d := range dest { - col := table.GetColumn(cols[i]) - if col == nil { - return errors.New("unknow column error") + if table.Type != nil { + sess := engine.NewSession() + defer sess.Close() + for rows.Next() { + beanValue := reflect.New(table.Type) + bean := beanValue.Interface() + fields, err := rows.Columns() + if err != nil { + return err + } + scanResults, err := sess.row2Slice(rows, fields, bean) + if err != nil { + return err } - 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) + "'" + dataStruct := utils.ReflectValue(bean) + _, err = sess.slice2Bean(scanResults, fields, bean, &dataStruct, table) + if err != nil { + return err + } + + _, err = io.WriteString(w, "INSERT INTO "+dstDialect.Quoter().Quote(dstTableName)+" ("+destColNames+") VALUES (") + if err != nil { + return err + } + + var temp string + for _, d := range dstCols { + col := table.GetColumn(d) + if col == nil { + return errors.New("unknown column error") } - } 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) + + fields := strings.Split(col.FieldName, ".") + field := dataStruct + for _, fieldName := range fields { + field = field.FieldByName(fieldName) } + temp += "," + formatColumnValue(dstDialect, field.Interface(), col) + } + _, err = io.WriteString(w, temp[1:]+");\n") + if err != nil { + return err } } - _, err = io.WriteString(w, temp[2:]+");\n") - if err != nil { - return err + } else { + for rows.Next() { + dest := make([]interface{}, len(cols)) + err = rows.ScanSlice(&dest) + if err != nil { + return err + } + + _, err = io.WriteString(w, "INSERT INTO "+dstDialect.Quoter().Quote(dstTableName)+" ("+destColNames+") VALUES (") + if err != nil { + return err + } + + var temp string + for i, d := range dest { + col := table.GetColumn(cols[i]) + if col == nil { + return errors.New("unknow column error") + } + + temp += "," + formatColumnValue(dstDialect, d, col) + } + _, err = io.WriteString(w, temp[1:]+");\n") + if err != nil { + return err + } } } // FIXME: Hack for postgres if dstDialect.URI().DBType == schemas.POSTGRES && table.AutoIncrColumn() != nil { - _, err = io.WriteString(w, "SELECT setval('"+tableName+"_id_seq', COALESCE((SELECT MAX("+table.AutoIncrColumn().Name+") + 1 FROM "+dstDialect.Quoter().Quote(tableName)+"), 1), false);\n") + _, err = io.WriteString(w, "SELECT setval('"+dstTableName+"_id_seq', COALESCE((SELECT MAX("+table.AutoIncrColumn().Name+") + 1 FROM "+dstDialect.Quoter().Quote(dstTableName)+"), 1), false);\n") if err != nil { return err } @@ -816,81 +956,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 +1358,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 +1374,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 +1404,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..f6e4af90 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,16 @@ 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/json-iterator/go v1.1.11 + 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..3c79850c 100644 --- a/go.sum +++ b/go.sum @@ -1,152 +1,97 @@ -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/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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 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 +99,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_delete_test.go b/integrations/session_delete_test.go index f3565963..cc7e861d 100644 --- a/integrations/session_delete_test.go +++ b/integrations/session_delete_test.go @@ -97,6 +97,7 @@ func TestDeleted(t *testing.T) { // Test normal Find() var records1 []Deleted err = testEngine.Where("`"+testEngine.GetColumnMapper().Obj2Table("Id")+"` > 0").Find(&records1, &Deleted{}) + assert.NoError(t, err) assert.EqualValues(t, 3, len(records1)) // Test normal Get() @@ -132,6 +133,7 @@ func TestDeleted(t *testing.T) { record2 := &Deleted{} has, err = testEngine.ID(2).Get(record2) assert.NoError(t, err) + assert.True(t, has) assert.True(t, record2.DeletedAt.IsZero()) // Test find all records whatever `deleted`. diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index b9d722ba..f90b5317 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)) @@ -608,6 +653,31 @@ func TestFindAndCount2(t *testing.T) { assert.EqualValues(t, 0, cnt) } +type FindAndCountWithTableName struct { + Id int64 + Name string +} + +func (FindAndCountWithTableName) TableName() string { + return "find_and_count_with_table_name1" +} + +func TestFindAndCountWithTableName(t *testing.T) { + assert.NoError(t, PrepareEngine()) + assertSync(t, new(FindAndCountWithTableName)) + + cnt, err := testEngine.Insert(&FindAndCountWithTableName{ + Name: "1", + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var res []FindAndCountWithTableName + cnt, err = testEngine.FindAndCount(&res) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) +} + type FindMapDevice struct { Deviceid string `xorm:"pk"` Status int 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/tags_test.go b/integrations/tags_test.go index f787fffe..fc7b505e 100644 --- a/integrations/tags_test.go +++ b/integrations/tags_test.go @@ -757,6 +757,8 @@ func TestAutoIncrTag(t *testing.T) { assert.True(t, cols[0].IsAutoIncrement) assert.True(t, cols[0].IsPrimaryKey) assert.Equal(t, "id", cols[0].Name) + assert.True(t, cols[0].DefaultIsEmpty) + assert.EqualValues(t, "", cols[0].Default) type TestAutoIncr2 struct { Id int64 `xorm:"id"` @@ -770,6 +772,8 @@ func TestAutoIncrTag(t *testing.T) { assert.False(t, cols[0].IsAutoIncrement) assert.False(t, cols[0].IsPrimaryKey) assert.Equal(t, "id", cols[0].Name) + assert.True(t, cols[0].DefaultIsEmpty) + assert.EqualValues(t, "", cols[0].Default) type TestAutoIncr3 struct { Id int64 `xorm:"'ID'"` @@ -783,6 +787,8 @@ func TestAutoIncrTag(t *testing.T) { assert.False(t, cols[0].IsAutoIncrement) assert.False(t, cols[0].IsPrimaryKey) assert.Equal(t, "ID", cols[0].Name) + assert.True(t, cols[0].DefaultIsEmpty) + assert.EqualValues(t, "", cols[0].Default) type TestAutoIncr4 struct { Id int64 `xorm:"pk"` @@ -796,6 +802,8 @@ func TestAutoIncrTag(t *testing.T) { assert.False(t, cols[0].IsAutoIncrement) assert.True(t, cols[0].IsPrimaryKey) assert.Equal(t, "id", cols[0].Name) + assert.True(t, cols[0].DefaultIsEmpty) + assert.EqualValues(t, "", cols[0].Default) } func TestTagComment(t *testing.T) { @@ -809,6 +817,16 @@ func TestTagComment(t *testing.T) { Id int64 `xorm:"comment(主键)"` } + tb, err := testEngine.TableInfo(new(TestComment1)) + assert.NoError(t, err) + cols := tb.Columns() + assert.EqualValues(t, 1, len(cols)) + assert.False(t, cols[0].IsAutoIncrement) + assert.False(t, cols[0].IsPrimaryKey) + assert.Equal(t, "id", cols[0].Name) + assert.True(t, cols[0].DefaultIsEmpty) + assert.EqualValues(t, "", cols[0].Default) + assert.NoError(t, testEngine.Sync2(new(TestComment1))) tables, err := testEngine.DBMetas() @@ -823,6 +841,16 @@ func TestTagComment(t *testing.T) { Id int64 `xorm:"comment('主键')"` } + tb, err = testEngine.TableInfo(new(TestComment2)) + assert.NoError(t, err) + cols = tb.Columns() + assert.EqualValues(t, 1, len(cols)) + assert.False(t, cols[0].IsAutoIncrement) + assert.False(t, cols[0].IsPrimaryKey) + assert.Equal(t, "id", cols[0].Name) + assert.True(t, cols[0].DefaultIsEmpty) + assert.EqualValues(t, "", cols[0].Default) + assert.NoError(t, testEngine.Sync2(new(TestComment2))) tables, err = testEngine.DBMetas() @@ -841,6 +869,28 @@ func TestTagDefault(t *testing.T) { Age int `xorm:"default(10)"` } + tb, err := testEngine.TableInfo(new(DefaultStruct)) + assert.NoError(t, err) + cols := tb.Columns() + assert.EqualValues(t, 3, len(cols)) + assert.True(t, cols[0].IsAutoIncrement) + assert.True(t, cols[0].IsPrimaryKey) + assert.Equal(t, "id", cols[0].Name) + assert.True(t, cols[0].DefaultIsEmpty) + assert.EqualValues(t, "", cols[0].Default) + + assert.False(t, cols[1].IsAutoIncrement) + assert.False(t, cols[1].IsPrimaryKey) + assert.Equal(t, "name", cols[1].Name) + assert.True(t, cols[1].DefaultIsEmpty) + assert.EqualValues(t, "", cols[1].Default) + + assert.False(t, cols[2].IsAutoIncrement) + assert.False(t, cols[2].IsPrimaryKey) + assert.Equal(t, "age", cols[2].Name) + assert.False(t, cols[2].DefaultIsEmpty) + assert.EqualValues(t, "10", cols[2].Default) + assertSync(t, new(DefaultStruct)) tables, err := testEngine.DBMetas() @@ -880,10 +930,33 @@ func TestTagDefault2(t *testing.T) { assert.NoError(t, PrepareEngine()) type DefaultStruct2 struct { - Id int64 - Name string + Id int64 + Name string + NullDefault string `xorm:"default('NULL')"` } + tb, err := testEngine.TableInfo(new(DefaultStruct2)) + assert.NoError(t, err) + cols := tb.Columns() + assert.EqualValues(t, 3, len(cols)) + assert.True(t, cols[0].IsAutoIncrement) + assert.True(t, cols[0].IsPrimaryKey) + assert.Equal(t, "id", cols[0].Name) + assert.True(t, cols[0].DefaultIsEmpty) + assert.EqualValues(t, "", cols[0].Default) + + assert.False(t, cols[1].IsAutoIncrement) + assert.False(t, cols[1].IsPrimaryKey) + assert.Equal(t, "name", cols[1].Name) + assert.True(t, cols[1].DefaultIsEmpty) + assert.EqualValues(t, "", cols[1].Default) + + assert.False(t, cols[2].IsAutoIncrement) + assert.False(t, cols[2].IsPrimaryKey) + assert.Equal(t, "null_default", cols[2].Name) + assert.False(t, cols[2].DefaultIsEmpty) + assert.EqualValues(t, "'NULL'", cols[2].Default) + assertSync(t, new(DefaultStruct2)) tables, err := testEngine.DBMetas() 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..8b14b0f4 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() @@ -150,23 +166,28 @@ func createEngine(dbType, connStr string) error { for _, table := range tables { tableNames = append(tableNames, table.Name) } - if err = testEngine.DropTables(tableNames...); err != nil { - return err - } - return nil + return testEngine.DropTables(tableNames...) } +// 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/json/jsoniter.go b/internal/json/jsoniter.go new file mode 100644 index 00000000..cfe7a19e --- /dev/null +++ b/internal/json/jsoniter.go @@ -0,0 +1,28 @@ +// Copyright 2021 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build jsoniter + +package json + +import ( + jsoniter "github.com/json-iterator/go" +) + +func init() { + DefaultJSONHandler = JSONiter{} +} + +// JSONiter implements JSONInterface via jsoniter +type JSONiter struct{} + +// Marshal implements JSONInterface +func (JSONiter) Marshal(v interface{}) ([]byte, error) { + return jsoniter.Marshal(v) +} + +// Unmarshal implements JSONInterface +func (JSONiter) Unmarshal(data []byte, v interface{}) error { + return jsoniter.Unmarshal(data, v) +} diff --git a/internal/statements/cache.go b/internal/statements/cache.go index cb33df08..669cd018 100644 --- a/internal/statements/cache.go +++ b/internal/statements/cache.go @@ -12,6 +12,7 @@ import ( "xorm.io/xorm/schemas" ) +// ConvertIDSQL converts SQL with id func (statement *Statement) ConvertIDSQL(sqlStr string) string { if statement.RefTable != nil { cols := statement.RefTable.PKColumns() @@ -37,6 +38,7 @@ func (statement *Statement) ConvertIDSQL(sqlStr string) string { return "" } +// ConvertUpdateSQL converts update SQL func (statement *Statement) ConvertUpdateSQL(sqlStr string) (string, string) { if statement.RefTable == nil || len(statement.RefTable.PrimaryKeys) != 1 { return "", "" diff --git a/internal/statements/expr_param.go b/internal/statements/expr_param.go index 6657408e..d0c355d3 100644 --- a/internal/statements/expr_param.go +++ b/internal/statements/expr_param.go @@ -12,6 +12,7 @@ import ( "xorm.io/xorm/schemas" ) +// ErrUnsupportedExprType represents an error with unsupported express type type ErrUnsupportedExprType struct { tp string } diff --git a/internal/statements/pk.go b/internal/statements/pk.go index d0787719..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,11 +73,16 @@ 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) { return fmt.Errorf("ID condition is error, expect %d primarykeys, there are %d", len(statement.RefTable.PrimaryKeys), diff --git a/internal/statements/query.go b/internal/statements/query.go index ab3021bf..f1b36770 100644 --- a/internal/statements/query.go +++ b/internal/statements/query.go @@ -14,6 +14,7 @@ import ( "xorm.io/xorm/schemas" ) +// GenQuerySQL generate query SQL func (statement *Statement) GenQuerySQL(sqlOrArgs ...interface{}) (string, []interface{}, error) { if len(sqlOrArgs) > 0 { return statement.ConvertSQLOrArgs(sqlOrArgs...) @@ -72,6 +73,7 @@ func (statement *Statement) GenQuerySQL(sqlOrArgs ...interface{}) (string, []int return sqlStr, args, nil } +// GenSumSQL generates sum SQL func (statement *Statement) GenSumSQL(bean interface{}, columns ...string) (string, []interface{}, error) { if statement.RawSQL != "" { return statement.GenRawSQL(), statement.RawParams, nil @@ -102,6 +104,7 @@ func (statement *Statement) GenSumSQL(bean interface{}, columns ...string) (stri return sqlStr, append(statement.joinArgs, condArgs...), nil } +// GenGetSQL generates Get SQL func (statement *Statement) GenGetSQL(bean interface{}) (string, []interface{}, error) { v := rValue(bean) isStruct := v.Kind() == reflect.Struct @@ -316,6 +319,7 @@ func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderB return buf.String(), condArgs, nil } +// GenExistSQL generates Exist SQL func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interface{}, error) { if statement.RawSQL != "" { return statement.GenRawSQL(), statement.RawParams, nil @@ -385,6 +389,7 @@ func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interfac return sqlStr, args, nil } +// GenFindSQL generates Find SQL func (statement *Statement) GenFindSQL(autoCond builder.Cond) (string, []interface{}, error) { if statement.RawSQL != "" { return statement.GenRawSQL(), statement.RawParams, nil diff --git a/internal/statements/statement.go b/internal/statements/statement.go index ed7bdaeb..3dd036a6 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -90,19 +90,17 @@ func NewStatement(dialect dialects.Dialect, tagParser *tags.Parser, defaultTimeZ return statement } +// SetTableName set table name func (statement *Statement) SetTableName(tableName string) { statement.tableName = tableName } -func (statement *Statement) omitStr() string { - return statement.dialect.Quoter().Join(statement.OmitColumnMap, " ,") -} - // GenRawSQL generates correct raw sql func (statement *Statement) GenRawSQL() string { return statement.ReplaceQuote(statement.RawSQL) } +// GenCondSQL generates condition SQL func (statement *Statement) GenCondSQL(condOrBuilder interface{}) (string, []interface{}, error) { condSQL, condArgs, err := builder.ToSQL(condOrBuilder) if err != nil { @@ -111,6 +109,7 @@ func (statement *Statement) GenCondSQL(condOrBuilder interface{}) (string, []int return statement.ReplaceQuote(condSQL), condArgs, nil } +// ReplaceQuote replace sql key words with quote func (statement *Statement) ReplaceQuote(sql string) string { if sql == "" || statement.dialect.URI().DBType == schemas.MYSQL || statement.dialect.URI().DBType == schemas.SQLITE { @@ -119,11 +118,12 @@ func (statement *Statement) ReplaceQuote(sql string) string { return statement.dialect.Quoter().Replace(sql) } +// SetContextCache sets context cache func (statement *Statement) SetContextCache(ctxCache contexts.ContextCache) { statement.Context = ctxCache } -// Init reset all the statement's fields +// Reset reset all the statement's fields func (statement *Statement) Reset() { statement.RefTable = nil statement.Start = 0 @@ -163,7 +163,7 @@ func (statement *Statement) Reset() { statement.LastError = nil } -// NoAutoCondition if you do not want convert bean's field as query condition, then use this function +// SetNoAutoCondition if you do not want convert bean's field as query condition, then use this function func (statement *Statement) SetNoAutoCondition(no ...bool) *Statement { statement.NoAutoCondition = true if len(no) > 0 { @@ -271,6 +271,7 @@ func (statement *Statement) NotIn(column string, args ...interface{}) *Statement return statement } +// SetRefValue set ref value func (statement *Statement) SetRefValue(v reflect.Value) error { var err error statement.RefTable, err = statement.tagParser.ParseWithCache(reflect.Indirect(v)) @@ -285,6 +286,7 @@ func rValue(bean interface{}) reflect.Value { return reflect.Indirect(reflect.ValueOf(bean)) } +// SetRefBean set ref bean func (statement *Statement) SetRefBean(bean interface{}) error { var err error statement.RefTable, err = statement.tagParser.ParseWithCache(rValue(bean)) @@ -390,6 +392,7 @@ func (statement *Statement) Cols(columns ...string) *Statement { return statement } +// ColumnStr returns column string func (statement *Statement) ColumnStr() string { return statement.dialect.Quoter().Join(statement.ColumnMap, ", ") } @@ -493,11 +496,12 @@ func (statement *Statement) Asc(colNames ...string) *Statement { return statement } +// Conds returns condtions func (statement *Statement) Conds() builder.Cond { return statement.cond } -// Table tempororily set table name, the parameter could be a string or a pointer of struct +// SetTable tempororily set table name, the parameter could be a string or a pointer of struct func (statement *Statement) SetTable(tableNameOrBean interface{}) error { v := rValue(tableNameOrBean) t := v.Type() @@ -564,7 +568,7 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition return statement } -// tbName get some table's table name +// tbNameNoSchema get some table's table name func (statement *Statement) tbNameNoSchema(table *schemas.Table) string { if len(statement.AltTableName) > 0 { return statement.AltTableName @@ -585,12 +589,13 @@ func (statement *Statement) Having(conditions string) *Statement { return statement } -// Unscoped always disable struct tag "deleted" +// SetUnscoped always disable struct tag "deleted" func (statement *Statement) SetUnscoped() *Statement { statement.unscoped = true return statement } +// GetUnscoped return true if it's unscoped func (statement *Statement) GetUnscoped() bool { return statement.unscoped } @@ -636,6 +641,7 @@ func (statement *Statement) genColumnStr() string { return buf.String() } +// GenCreateTableSQL generated create table SQL func (statement *Statement) GenCreateTableSQL() []string { statement.RefTable.StoreEngine = statement.StoreEngine statement.RefTable.Charset = statement.Charset @@ -643,6 +649,7 @@ func (statement *Statement) GenCreateTableSQL() []string { return s } +// GenIndexSQL generated create index SQL func (statement *Statement) GenIndexSQL() []string { var sqls []string tbName := statement.TableName() @@ -659,6 +666,7 @@ func uniqueName(tableName, uqeName string) string { return fmt.Sprintf("UQE_%v_%v", tableName, uqeName) } +// GenUniqueSQL generates unique SQL func (statement *Statement) GenUniqueSQL() []string { var sqls []string tbName := statement.TableName() @@ -671,6 +679,7 @@ func (statement *Statement) GenUniqueSQL() []string { return sqls } +// GenDelIndexSQL generate delete index SQL func (statement *Statement) GenDelIndexSQL() []string { var sqls []string tbName := statement.TableName() @@ -704,7 +713,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 +822,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 { @@ -896,6 +905,7 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{}, return builder.And(conds...), nil } +// BuildConds builds condition func (statement *Statement) BuildConds(table *schemas.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, addedTableName bool) (builder.Cond, error) { return statement.buildConds2(table, bean, includeVersion, includeUpdated, includeNil, includeAutoIncr, statement.allUseBool, statement.useAllCols, statement.unscoped, statement.MustColumnMap, statement.TableName(), statement.TableAlias, addedTableName) @@ -911,12 +921,10 @@ func (statement *Statement) mergeConds(bean interface{}) error { statement.cond = statement.cond.And(autoCond) } - if err := statement.ProcessIDParam(); err != nil { - return err - } - return nil + return statement.ProcessIDParam() } +// GenConds generates conditions func (statement *Statement) GenConds(bean interface{}) (string, []interface{}, error) { if err := statement.mergeConds(bean); err != nil { return "", nil, err @@ -930,6 +938,7 @@ func (statement *Statement) quoteColumnStr(columnStr string) string { return statement.dialect.Quoter().Join(columns, ",") } +// ConvertSQLOrArgs converts sql or args func (statement *Statement) ConvertSQLOrArgs(sqlOrArgs ...interface{}) (string, []interface{}, error) { sql, args, err := convertSQLOrArgs(sqlOrArgs...) if err != nil { diff --git a/internal/statements/statement_args.go b/internal/statements/statement_args.go index dc14467d..64089c1e 100644 --- a/internal/statements/statement_args.go +++ b/internal/statements/statement_args.go @@ -77,6 +77,7 @@ func convertArg(arg interface{}, convertFunc func(string) string) string { const insertSelectPlaceHolder = true +// WriteArg writes an arg func (statement *Statement) WriteArg(w *builder.BytesWriter, arg interface{}) error { switch argv := arg.(type) { case *builder.Builder: @@ -116,6 +117,7 @@ func (statement *Statement) WriteArg(w *builder.BytesWriter, arg interface{}) er return nil } +// WriteArgs writes args func (statement *Statement) WriteArgs(w *builder.BytesWriter, args []interface{}) error { for i, arg := range args { if err := statement.WriteArg(w, arg); 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..b0ce8076 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 @@ -15,6 +16,7 @@ type Mapper interface { Table2Obj(string) string } +// CacheMapper represents a cache mapper type CacheMapper struct { oriMapper Mapper obj2tableCache map[string]string @@ -23,12 +25,14 @@ type CacheMapper struct { table2objMutex sync.RWMutex } +// NewCacheMapper creates a cache mapper func NewCacheMapper(mapper Mapper) *CacheMapper { return &CacheMapper{oriMapper: mapper, obj2tableCache: make(map[string]string), table2objCache: make(map[string]string), } } +// Obj2Table implements Mapper func (m *CacheMapper) Obj2Table(o string) string { m.obj2tableMutex.RLock() t, ok := m.obj2tableCache[o] @@ -44,6 +48,7 @@ func (m *CacheMapper) Obj2Table(o string) string { return t } +// Table2Obj implements Mapper func (m *CacheMapper) Table2Obj(t string) string { m.table2objMutex.RLock() o, ok := m.table2objCache[t] @@ -59,15 +64,17 @@ func (m *CacheMapper) Table2Obj(t string) string { return o } -// SameMapper implements IMapper and provides same name between struct and +// SameMapper implements Mapper and provides same name between struct and // database table type SameMapper struct { } +// Obj2Table implements Mapper func (m SameMapper) Obj2Table(o string) string { return o } +// Table2Obj implements Mapper func (m SameMapper) Table2Obj(t string) string { return t } @@ -77,49 +84,57 @@ func (m SameMapper) Table2Obj(t string) string { type SnakeMapper struct { } -func snakeCasedName(name string) string { - newstr := make([]rune, 0) - for idx, chr := range name { - if isUpper := 'A' <= chr && chr <= 'Z'; isUpper { - if idx > 0 { - newstr = append(newstr, '_') - } - chr -= ('A' - 'a') - } - newstr = append(newstr, chr) - } - - return string(newstr) +func b2s(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) } +func snakeCasedName(name string) string { + newstr := make([]byte, 0, len(name)+1) + for i := 0; i < len(name); i++ { + c := name[i] + if isUpper := 'A' <= c && c <= 'Z'; isUpper { + if i > 0 { + newstr = append(newstr, '_') + } + c += 'a' - 'A' + } + newstr = append(newstr, c) + } + + return b2s(newstr) +} + +// Obj2Table implements Mapper func (mapper SnakeMapper) Obj2Table(name string) string { return snakeCasedName(name) } 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) } +// Table2Obj implements Mapper func (mapper SnakeMapper) Table2Obj(name string) string { return titleCasedName(name) } @@ -161,10 +176,12 @@ func gonicCasedName(name string) string { return strings.ToLower(string(newstr)) } +// Obj2Table implements Mapper func (mapper GonicMapper) Obj2Table(name string) string { return gonicCasedName(name) } +// Table2Obj implements Mapper func (mapper GonicMapper) Table2Obj(name string) string { newstr := make([]rune, 0) @@ -227,14 +244,17 @@ type PrefixMapper struct { Prefix string } +// Obj2Table implements Mapper func (mapper PrefixMapper) Obj2Table(name string) string { return mapper.Prefix + mapper.Mapper.Obj2Table(name) } +// Table2Obj implements Mapper func (mapper PrefixMapper) Table2Obj(name string) string { return mapper.Mapper.Table2Obj(name[len(mapper.Prefix):]) } +// NewPrefixMapper creates a prefix mapper func NewPrefixMapper(mapper Mapper, prefix string) PrefixMapper { return PrefixMapper{mapper, prefix} } @@ -245,14 +265,17 @@ type SuffixMapper struct { Suffix string } +// Obj2Table implements Mapper func (mapper SuffixMapper) Obj2Table(name string) string { return mapper.Mapper.Obj2Table(name) + mapper.Suffix } +// Table2Obj implements Mapper func (mapper SuffixMapper) Table2Obj(name string) string { return mapper.Mapper.Table2Obj(name[:len(name)-len(mapper.Suffix)]) } +// NewSuffixMapper creates a suffix mapper func NewSuffixMapper(mapper Mapper, suffix string) SuffixMapper { return SuffixMapper{mapper, suffix} } 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/names/table_name.go b/names/table_name.go index 0afb1ae3..cc0e9274 100644 --- a/names/table_name.go +++ b/names/table_name.go @@ -19,6 +19,7 @@ var ( tvCache sync.Map ) +// GetTableName returns table name func GetTableName(mapper Mapper, v reflect.Value) string { if v.Type().Implements(tpTableName) { return v.Interface().(TableName).TableName() diff --git a/schemas/column.go b/schemas/column.go index 418629ac..5808b84d 100644 --- a/schemas/column.go +++ b/schemas/column.go @@ -5,12 +5,15 @@ package schemas import ( + "errors" "fmt" "reflect" + "strconv" "strings" "time" ) +// enumerates all database mapping way const ( TWOSIDES = iota + 1 ONLYTODB @@ -49,6 +52,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 +119,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/index.go b/schemas/index.go index 9541250f..8f31af52 100644 --- a/schemas/index.go +++ b/schemas/index.go @@ -28,6 +28,7 @@ func NewIndex(name string, indexType int) *Index { return &Index{true, name, indexType, make([]string, 0)} } +// XName returns the special index name for the table func (index *Index) XName(tableName string) string { if !strings.HasPrefix(index.Name, "UQE_") && !strings.HasPrefix(index.Name, "IDX_") { @@ -43,11 +44,10 @@ func (index *Index) XName(tableName string) string { // AddColumn add columns which will be composite index func (index *Index) AddColumn(cols ...string) { - for _, col := range cols { - index.Cols = append(index.Cols, col) - } + index.Cols = append(index.Cols, cols...) } +// Equal return true if the two Index is equal func (index *Index) Equal(dst *Index) bool { if index.Type != dst.Type { return false diff --git a/schemas/pk.go b/schemas/pk.go index 03916b44..da3c7899 100644 --- a/schemas/pk.go +++ b/schemas/pk.go @@ -11,13 +11,16 @@ import ( "xorm.io/xorm/internal/utils" ) +// PK represents primary key values type PK []interface{} +// NewPK creates primay keys func NewPK(pks ...interface{}) *PK { p := PK(pks) return &p } +// IsZero return true if primay keys are zero func (p *PK) IsZero() bool { for _, k := range *p { if utils.IsZero(k) { @@ -27,6 +30,7 @@ func (p *PK) IsZero() bool { return false } +// ToString convert to SQL string func (p *PK) ToString() (string, error) { buf := new(bytes.Buffer) enc := gob.NewEncoder(buf) @@ -34,6 +38,7 @@ func (p *PK) ToString() (string, error) { return buf.String(), err } +// FromString reads content to load primary keys func (p *PK) FromString(content string) error { dec := gob.NewDecoder(bytes.NewBufferString(content)) err := dec.Decode(p) diff --git a/schemas/quote.go b/schemas/quote.go index c44abe25..71040ad9 100644 --- a/schemas/quote.go +++ b/schemas/quote.go @@ -16,10 +16,10 @@ type Quoter struct { } var ( - // AlwaysFalseReverse always think it's not a reverse word + // AlwaysNoReserve always think it's not a reverse word AlwaysNoReserve = func(string) bool { return false } - // AlwaysReverse always reverse the word + // AlwaysReserve always reverse the word AlwaysReserve = func(string) bool { return true } // CommanQuoteMark represnets the common quote mark @@ -29,10 +29,12 @@ var ( CommonQuoter = Quoter{CommanQuoteMark, CommanQuoteMark, AlwaysReserve} ) +// IsEmpty return true if no prefix and suffix func (q Quoter) IsEmpty() bool { return q.Prefix == 0 && q.Suffix == 0 } +// Quote quote a string func (q Quoter) Quote(s string) string { var buf strings.Builder q.QuoteTo(&buf, s) @@ -59,12 +61,14 @@ func (q Quoter) Trim(s string) string { return buf.String() } +// Join joins a slice with quoters func (q Quoter) Join(a []string, sep string) string { var b strings.Builder q.JoinWrite(&b, a, sep) return b.String() } +// JoinWrite writes quoted content to a builder func (q Quoter) JoinWrite(b *strings.Builder, a []string, sep string) error { if len(a) == 0 { return nil @@ -82,9 +86,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 +145,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 +153,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..bfa517aa 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) { @@ -88,23 +90,28 @@ func (table *Table) PKColumns() []*Column { return columns } +// ColumnType returns a column's type func (table *Table) ColumnType(name string) reflect.Type { t, _ := table.Type.FieldByName(name) return t.Type } +// AutoIncrColumn returns autoincrement column func (table *Table) AutoIncrColumn() *Column { return table.GetColumn(table.AutoIncrement) } +// VersionColumn returns version column's information func (table *Table) VersionColumn() *Column { return table.GetColumn(table.Version) } +// UpdatedColumn returns updated column's information func (table *Table) UpdatedColumn() *Column { return table.GetColumn(table.Updated) } +// DeletedColumn returns deleted column's information func (table *Table) DeletedColumn() *Column { return table.GetColumn(table.Deleted) } @@ -144,3 +151,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..fc02f015 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -11,8 +11,10 @@ import ( "time" ) +// DBType represents a database type type DBType string +// enumerates all database types const ( POSTGRES DBType = "postgres" SQLITE DBType = "sqlite3" @@ -28,6 +30,7 @@ type SQLType struct { DefaultLength2 int } +// enumerates all columns types const ( UNKNOW_TYPE = iota TEXT_TYPE @@ -37,6 +40,7 @@ const ( ARRAY_TYPE ) +// IsType reutrns ture if the column type is the same as the parameter func (s *SQLType) IsType(st int) bool { if t, ok := SqlTypes[s.Name]; ok && t == st { return true @@ -44,38 +48,53 @@ func (s *SQLType) IsType(st int) bool { return false } +// IsText returns true if column is a text type func (s *SQLType) IsText() bool { return s.IsType(TEXT_TYPE) } +// IsBlob returns true if column is a binary type func (s *SQLType) IsBlob() bool { return s.IsType(BLOB_TYPE) } +// IsTime returns true if column is a time type func (s *SQLType) IsTime() bool { return s.IsType(TIME_TYPE) } +// IsNumeric returns true if column is a numeric type func (s *SQLType) IsNumeric() bool { return s.IsType(NUMERIC_TYPE) } +// IsArray returns true if column is an array type func (s *SQLType) IsArray() bool { return s.IsType(ARRAY_TYPE) } +// IsJson returns true if column is an array type func (s *SQLType) IsJson() bool { return s.Name == Json || s.Name == Jsonb } +// IsXML returns true if column is an xml type +func (s *SQLType) IsXML() bool { + return s.Name == XML +} + +// enumerates all the database column types 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 +147,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, @@ -197,53 +222,55 @@ var ( // !nashtsai! treat following var as interal const values, these are used for reflect.TypeOf comparison var ( - c_EMPTY_STRING string - c_BOOL_DEFAULT bool - c_BYTE_DEFAULT byte - c_COMPLEX64_DEFAULT complex64 - c_COMPLEX128_DEFAULT complex128 - c_FLOAT32_DEFAULT float32 - c_FLOAT64_DEFAULT float64 - c_INT64_DEFAULT int64 - c_UINT64_DEFAULT uint64 - c_INT32_DEFAULT int32 - c_UINT32_DEFAULT uint32 - c_INT16_DEFAULT int16 - c_UINT16_DEFAULT uint16 - c_INT8_DEFAULT int8 - c_UINT8_DEFAULT uint8 - c_INT_DEFAULT int - c_UINT_DEFAULT uint - c_TIME_DEFAULT time.Time + emptyString string + boolDefault bool + byteDefault byte + complex64Default complex64 + complex128Default complex128 + float32Default float32 + float64Default float64 + int64Default int64 + uint64Default uint64 + int32Default int32 + uint32Default uint32 + int16Default int16 + uint16Default uint16 + int8Default int8 + uint8Default uint8 + intDefault int + uintDefault uint + timeDefault time.Time ) +// enumerates all types var ( - IntType = reflect.TypeOf(c_INT_DEFAULT) - Int8Type = reflect.TypeOf(c_INT8_DEFAULT) - Int16Type = reflect.TypeOf(c_INT16_DEFAULT) - Int32Type = reflect.TypeOf(c_INT32_DEFAULT) - Int64Type = reflect.TypeOf(c_INT64_DEFAULT) + IntType = reflect.TypeOf(intDefault) + Int8Type = reflect.TypeOf(int8Default) + Int16Type = reflect.TypeOf(int16Default) + Int32Type = reflect.TypeOf(int32Default) + Int64Type = reflect.TypeOf(int64Default) - UintType = reflect.TypeOf(c_UINT_DEFAULT) - Uint8Type = reflect.TypeOf(c_UINT8_DEFAULT) - Uint16Type = reflect.TypeOf(c_UINT16_DEFAULT) - Uint32Type = reflect.TypeOf(c_UINT32_DEFAULT) - Uint64Type = reflect.TypeOf(c_UINT64_DEFAULT) + UintType = reflect.TypeOf(uintDefault) + Uint8Type = reflect.TypeOf(uint8Default) + Uint16Type = reflect.TypeOf(uint16Default) + Uint32Type = reflect.TypeOf(uint32Default) + Uint64Type = reflect.TypeOf(uint64Default) - Float32Type = reflect.TypeOf(c_FLOAT32_DEFAULT) - Float64Type = reflect.TypeOf(c_FLOAT64_DEFAULT) + Float32Type = reflect.TypeOf(float32Default) + Float64Type = reflect.TypeOf(float64Default) - Complex64Type = reflect.TypeOf(c_COMPLEX64_DEFAULT) - Complex128Type = reflect.TypeOf(c_COMPLEX128_DEFAULT) + Complex64Type = reflect.TypeOf(complex64Default) + Complex128Type = reflect.TypeOf(complex128Default) - StringType = reflect.TypeOf(c_EMPTY_STRING) - BoolType = reflect.TypeOf(c_BOOL_DEFAULT) - ByteType = reflect.TypeOf(c_BYTE_DEFAULT) + StringType = reflect.TypeOf(emptyString) + BoolType = reflect.TypeOf(boolDefault) + ByteType = reflect.TypeOf(byteDefault) BytesType = reflect.SliceOf(ByteType) - TimeType = reflect.TypeOf(c_TIME_DEFAULT) + TimeType = reflect.TypeOf(timeDefault) ) +// enumerates all types var ( PtrIntType = reflect.PtrTo(IntType) PtrInt8Type = reflect.PtrTo(Int8Type) @@ -273,10 +300,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: @@ -284,7 +315,7 @@ func Type2SQLType(t reflect.Type) (st SQLType) { case reflect.Complex64, reflect.Complex128: st = SQLType{Varchar, 64, 0} case reflect.Array, reflect.Slice, reflect.Map: - if t.Elem() == reflect.TypeOf(c_BYTE_DEFAULT) { + if t.Elem() == reflect.TypeOf(byteDefault) { st = SQLType{Blob, 0, 0} } else { st = SQLType{Text, 0, 0} @@ -308,7 +339,7 @@ func Type2SQLType(t reflect.Type) (st SQLType) { return } -// default sql type change to go types +// SQLType2Type convert default sql type change to go types func SQLType2Type(st SQLType) reflect.Type { name := strings.ToUpper(st.Name) switch name { @@ -327,7 +358,7 @@ func SQLType2Type(st SQLType) reflect.Type { case Bool: return reflect.TypeOf(true) case DateTime, Date, Time, TimeStamp, TimeStampz, SmallDateTime, Year: - return reflect.TypeOf(c_TIME_DEFAULT) + return reflect.TypeOf(timeDefault) case Decimal, Numeric, Money, SmallMoney: return reflect.TypeOf("") default: 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 3bc6a642..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()) @@ -320,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 } @@ -370,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 } @@ -419,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)) @@ -428,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 add30a13..5ad67b53 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -21,9 +21,11 @@ import ( ) var ( + // ErrUnsupportedType represents an unsupported type error ErrUnsupportedType = errors.New("Unsupported type") ) +// Parser represents a parser for xorm tag type Parser struct { identifier string dialect dialects.Dialect @@ -34,6 +36,7 @@ type Parser struct { tableCache sync.Map // map[reflect.Type]*schemas.Table } +// NewParser creates a tag parser func NewParser(identifier string, dialect dialects.Dialect, tableMapper, columnMapper names.Mapper, cacherMgr *caches.Manager) *Parser { return &Parser{ identifier: identifier, @@ -45,24 +48,35 @@ func NewParser(identifier string, dialect dialects.Dialect, tableMapper, columnM } } +// GetTableMapper returns table mapper func (parser *Parser) GetTableMapper() names.Mapper { return parser.tableMapper } +// SetTableMapper sets table mapper func (parser *Parser) SetTableMapper(mapper names.Mapper) { parser.ClearCaches() parser.tableMapper = mapper } +// GetColumnMapper returns column mapper func (parser *Parser) GetColumnMapper() names.Mapper { return parser.columnMapper } +// SetColumnMapper sets column mapper func (parser *Parser) SetColumnMapper(mapper names.Mapper) { parser.ClearCaches() parser.columnMapper = mapper } +// SetIdentifier sets tag identifier +func (parser *Parser) SetIdentifier(identifier string) { + parser.ClearCaches() + parser.identifier = identifier +} + +// ParseWithCache parse a struct with cache func (parser *Parser) ParseWithCache(v reflect.Value) (*schemas.Table, error) { t := v.Type() tableI, ok := parser.tableCache.Load(t) @@ -253,7 +267,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 { @@ -272,6 +286,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)