From bfe223c907645c8123b8d66b552aefd54994ac64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=8A=91=E6=89=AC?= <1341524317@qq.com> Date: Thu, 15 Sep 2022 06:12:18 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20ogx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ogx/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 ogx/.keep diff --git a/ogx/.keep b/ogx/.keep new file mode 100644 index 00000000..e69de29b -- Gitee From 7c198d748421640b4cef71012868daad59dc27ea Mon Sep 17 00:00:00 2001 From: pablo <1341524317@qq.com> Date: Thu, 15 Sep 2022 14:19:50 +0800 Subject: [PATCH 2/2] ogx --- ogx/.gitignore | 4 + ogx/Makefile | 30 + ogx/README.md | 6 + ogx/db.go | 696 +++++++ ogx/dbfixture/encoder.go | 46 + ogx/dbfixture/fixture.go | 445 +++++ ogx/dbfixture/go.mod | 19 + ogx/dbfixture/go.sum | 26 + ogx/dialect/append.go | 88 + ogx/dialect/dialect.go | 26 + ogx/dialect/feature/feature.go | 35 + ogx/dialect/ogdialect/append.go | 364 ++++ ogx/dialect/ogdialect/array.go | 65 + ogx/dialect/ogdialect/array_parser.go | 133 ++ ogx/dialect/ogdialect/array_parser_test.go | 55 + ogx/dialect/ogdialect/array_scan.go | 302 +++ ogx/dialect/ogdialect/dialect.go | 110 + ogx/dialect/ogdialect/go.mod | 15 + ogx/dialect/ogdialect/go.sum | 20 + ogx/dialect/ogdialect/hstore.go | 73 + ogx/dialect/ogdialect/hstore_parser.go | 142 ++ ogx/dialect/ogdialect/hstore_parser_test.go | 62 + ogx/dialect/ogdialect/hstore_scan.go | 82 + ogx/dialect/ogdialect/safe.go | 12 + ogx/dialect/ogdialect/scan.go | 11 + ogx/dialect/ogdialect/sqltype.go | 105 + ogx/dialect/ogdialect/stream_parser.go | 60 + ogx/dialect/ogdialect/unsafe.go | 19 + ogx/dialect/ogdialect/version.go | 6 + ogx/dialect/sqltype/sqltype.go | 16 + ogx/docs/.keep | 0 ...67\344\276\213\346\226\207\346\241\243.md" | 1774 +++++++++++++++++ ogx/docs/Ogx_logo.png | Bin 0 -> 79479 bytes ogx/example/basic/go.mod | 34 + ogx/example/basic/go.sum | 125 ++ ogx/example/basic/main.go | 148 ++ ogx/example/create-table-index/go.mod | 32 + ogx/example/create-table-index/go.sum | 125 ++ ogx/example/create-table-index/main.go | 60 + ogx/example/cursor-pagination/go.mod | 34 + ogx/example/cursor-pagination/go.sum | 125 ++ ogx/example/cursor-pagination/main.go | 145 ++ ogx/example/custom-type/go.mod | 34 + ogx/example/custom-type/go.sum | 125 ++ ogx/example/custom-type/main.go | 88 + ogx/example/fixture/fixture.yml | 19 + ogx/example/fixture/go.mod | 35 + ogx/example/fixture/go.sum | 128 ++ ogx/example/fixture/main.go | 95 + ogx/example/get-where-fields/go.mod | 30 + ogx/example/get-where-fields/go.sum | 112 ++ ogx/example/get-where-fields/main.go | 52 + ogx/example/migrate/README.md | 55 + ogx/example/migrate/go.mod | 37 + ogx/example/migrate/go.sum | 132 ++ ogx/example/migrate/main.go | 165 ++ .../migrate/migrations/20210505110026_foo.go | 18 + .../migrations/20210505110847_bar.down.sql | 1 + .../migrations/20210505110847_bar.up.sql | 7 + ogx/example/migrate/migrations/main.go | 11 + ogx/example/model-hooks/go.mod | 35 + ogx/example/model-hooks/go.sum | 125 ++ ogx/example/model-hooks/main.go | 101 + ogx/example/multi-tenant/fixture.yml | 8 + ogx/example/multi-tenant/go.mod | 36 + ogx/example/multi-tenant/go.sum | 128 ++ ogx/example/multi-tenant/main.go | 84 + ogx/example/placeholders/go.mod | 32 + ogx/example/placeholders/go.sum | 125 ++ ogx/example/placeholders/main.go | 83 + ogx/example/rel-belongs-to/go.mod | 31 + ogx/example/rel-belongs-to/go.sum | 124 ++ ogx/example/rel-belongs-to/main.go | 98 + .../rel-has-many-polymorphic/fixture.yml | 41 + ogx/example/rel-has-many-polymorphic/go.mod | 36 + ogx/example/rel-has-many-polymorphic/go.sum | 128 ++ ogx/example/rel-has-many-polymorphic/main.go | 103 + ogx/example/rel-has-many/go.mod | 31 + ogx/example/rel-has-many/go.sum | 124 ++ ogx/example/rel-has-many/main.go | 99 + ogx/example/rel-has-one/go.mod | 31 + ogx/example/rel-has-one/go.sum | 124 ++ ogx/example/rel-has-one/main.go | 98 + ogx/example/rel-join-condition/go.mod | 31 + ogx/example/rel-join-condition/go.sum | 124 ++ ogx/example/rel-join-condition/main.go | 107 + ogx/example/rel-many-to-many-self/go.mod | 31 + ogx/example/rel-many-to-many-self/go.sum | 124 ++ ogx/example/rel-many-to-many-self/main.go | 108 + ogx/example/rel-many-to-many/go.mod | 31 + ogx/example/rel-many-to-many/go.sum | 124 ++ ogx/example/rel-many-to-many/main.go | 126 ++ ogx/example/string-representation/go.mod | 27 + ogx/example/string-representation/go.sum | 111 ++ ogx/example/string-representation/main.go | 29 + ogx/example/trivial/go.mod | 32 + ogx/example/trivial/go.sum | 125 ++ ogx/example/trivial/main.go | 37 + ogx/example/tx-composition/go.mod | 34 + ogx/example/tx-composition/go.sum | 125 ++ ogx/example/tx-composition/main.go | 92 + ogx/extra/ogxbig/README.md | 98 + ogx/extra/ogxbig/float.go | 102 + ogx/extra/ogxbig/go.mod | 15 + ogx/extra/ogxbig/go.sum | 13 + ogx/extra/ogxbig/int.go | 140 ++ ogx/extra/ogxbig/int_test.go | 233 +++ ogx/extra/ogxbig/ogxbig.go | 22 + ogx/extra/ogxdebug/debug.go | 136 ++ ogx/extra/ogxdebug/go.mod | 20 + ogx/extra/ogxdebug/go.sum | 33 + ogx/extra/ogxjson/json.go | 26 + ogx/extra/ogxjson/provider.go | 43 + ogx/extra/ogxrelic/go.mod | 24 + ogx/extra/ogxrelic/go.sum | 136 ++ ogx/extra/ogxrelic/option.go | 33 + ogx/extra/ogxrelic/relic.go | 52 + ogx/go.mod | 20 + ogx/go.sum | 29 + ogx/hook.go | 109 + ogx/internal/flag.go | 16 + ogx/internal/hex.go | 43 + ogx/internal/logger.go | 27 + ogx/internal/map_key.go | 67 + ogx/internal/parser/parser.go | 141 ++ ogx/internal/safe.go | 12 + ogx/internal/tagparser/parser.go | 184 ++ ogx/internal/tagparser/parser_test.go | 45 + ogx/internal/time.go | 61 + ogx/internal/underscore.go | 67 + ogx/internal/unsafe.go | 21 + ogx/internal/util.go | 57 + ogx/migrate/migration.go | 286 +++ ogx/migrate/migrations.go | 168 ++ ogx/migrate/migrator.go | 402 ++++ ogx/model.go | 201 ++ ogx/model_map.go | 183 ++ ogx/model_map_slice.go | 162 ++ ogx/model_scan.go | 56 + ogx/model_slice.go | 82 + ogx/model_table_has_many.go | 149 ++ ogx/model_table_m2m.go | 138 ++ ogx/model_table_slice.go | 122 ++ ogx/model_table_struct.go | 355 ++++ ogx/ogx.go | 79 + ogx/query_base.go | 1337 +++++++++++++ ogx/query_column_add.go | 116 ++ ogx/query_column_drop.go | 118 ++ ogx/query_delete.go | 349 ++++ ogx/query_index_create.go | 249 +++ ogx/query_index_drop.go | 116 ++ ogx/query_insert.go | 652 ++++++ ogx/query_raw.go | 65 + ogx/query_select.go | 1202 +++++++++++ ogx/query_table_create.go | 344 ++++ ogx/query_table_drop.go | 148 ++ ogx/query_table_truncate.go | 132 ++ ogx/query_update.go | 585 ++++++ ogx/query_values.go | 222 +++ ogx/relation_join.go | 398 ++++ ogx/schema/append.go | 101 + ogx/schema/append_value.go | 318 +++ ogx/schema/dialect.go | 165 ++ ogx/schema/field.go | 138 ++ ogx/schema/formatter.go | 246 +++ ogx/schema/hook.go | 59 + ogx/schema/reflect.go | 72 + ogx/schema/relation.go | 35 + ogx/schema/scan.go | 516 +++++ ogx/schema/sqlfmt.go | 87 + ogx/schema/sqltype.go | 141 ++ ogx/schema/table.go | 1029 ++++++++++ ogx/schema/tables.go | 151 ++ ogx/schema/zerochecker.go | 122 ++ ogx/util.go | 114 ++ ogx/version.go | 5 + 176 files changed, 23802 insertions(+) create mode 100644 ogx/.gitignore create mode 100644 ogx/Makefile create mode 100644 ogx/README.md create mode 100644 ogx/db.go create mode 100644 ogx/dbfixture/encoder.go create mode 100644 ogx/dbfixture/fixture.go create mode 100644 ogx/dbfixture/go.mod create mode 100644 ogx/dbfixture/go.sum create mode 100644 ogx/dialect/append.go create mode 100644 ogx/dialect/dialect.go create mode 100644 ogx/dialect/feature/feature.go create mode 100644 ogx/dialect/ogdialect/append.go create mode 100644 ogx/dialect/ogdialect/array.go create mode 100644 ogx/dialect/ogdialect/array_parser.go create mode 100644 ogx/dialect/ogdialect/array_parser_test.go create mode 100644 ogx/dialect/ogdialect/array_scan.go create mode 100644 ogx/dialect/ogdialect/dialect.go create mode 100644 ogx/dialect/ogdialect/go.mod create mode 100644 ogx/dialect/ogdialect/go.sum create mode 100644 ogx/dialect/ogdialect/hstore.go create mode 100644 ogx/dialect/ogdialect/hstore_parser.go create mode 100644 ogx/dialect/ogdialect/hstore_parser_test.go create mode 100644 ogx/dialect/ogdialect/hstore_scan.go create mode 100644 ogx/dialect/ogdialect/safe.go create mode 100644 ogx/dialect/ogdialect/scan.go create mode 100644 ogx/dialect/ogdialect/sqltype.go create mode 100644 ogx/dialect/ogdialect/stream_parser.go create mode 100644 ogx/dialect/ogdialect/unsafe.go create mode 100644 ogx/dialect/ogdialect/version.go create mode 100644 ogx/dialect/sqltype/sqltype.go create mode 100644 ogx/docs/.keep create mode 100644 "ogx/docs/Ogx \344\275\277\347\224\250\346\240\267\344\276\213\346\226\207\346\241\243.md" create mode 100644 ogx/docs/Ogx_logo.png create mode 100644 ogx/example/basic/go.mod create mode 100644 ogx/example/basic/go.sum create mode 100644 ogx/example/basic/main.go create mode 100644 ogx/example/create-table-index/go.mod create mode 100644 ogx/example/create-table-index/go.sum create mode 100644 ogx/example/create-table-index/main.go create mode 100644 ogx/example/cursor-pagination/go.mod create mode 100644 ogx/example/cursor-pagination/go.sum create mode 100644 ogx/example/cursor-pagination/main.go create mode 100644 ogx/example/custom-type/go.mod create mode 100644 ogx/example/custom-type/go.sum create mode 100644 ogx/example/custom-type/main.go create mode 100644 ogx/example/fixture/fixture.yml create mode 100644 ogx/example/fixture/go.mod create mode 100644 ogx/example/fixture/go.sum create mode 100644 ogx/example/fixture/main.go create mode 100644 ogx/example/get-where-fields/go.mod create mode 100644 ogx/example/get-where-fields/go.sum create mode 100644 ogx/example/get-where-fields/main.go create mode 100644 ogx/example/migrate/README.md create mode 100644 ogx/example/migrate/go.mod create mode 100644 ogx/example/migrate/go.sum create mode 100644 ogx/example/migrate/main.go create mode 100644 ogx/example/migrate/migrations/20210505110026_foo.go create mode 100644 ogx/example/migrate/migrations/20210505110847_bar.down.sql create mode 100644 ogx/example/migrate/migrations/20210505110847_bar.up.sql create mode 100644 ogx/example/migrate/migrations/main.go create mode 100644 ogx/example/model-hooks/go.mod create mode 100644 ogx/example/model-hooks/go.sum create mode 100644 ogx/example/model-hooks/main.go create mode 100644 ogx/example/multi-tenant/fixture.yml create mode 100644 ogx/example/multi-tenant/go.mod create mode 100644 ogx/example/multi-tenant/go.sum create mode 100644 ogx/example/multi-tenant/main.go create mode 100644 ogx/example/placeholders/go.mod create mode 100644 ogx/example/placeholders/go.sum create mode 100644 ogx/example/placeholders/main.go create mode 100644 ogx/example/rel-belongs-to/go.mod create mode 100644 ogx/example/rel-belongs-to/go.sum create mode 100644 ogx/example/rel-belongs-to/main.go create mode 100644 ogx/example/rel-has-many-polymorphic/fixture.yml create mode 100644 ogx/example/rel-has-many-polymorphic/go.mod create mode 100644 ogx/example/rel-has-many-polymorphic/go.sum create mode 100644 ogx/example/rel-has-many-polymorphic/main.go create mode 100644 ogx/example/rel-has-many/go.mod create mode 100644 ogx/example/rel-has-many/go.sum create mode 100644 ogx/example/rel-has-many/main.go create mode 100644 ogx/example/rel-has-one/go.mod create mode 100644 ogx/example/rel-has-one/go.sum create mode 100644 ogx/example/rel-has-one/main.go create mode 100644 ogx/example/rel-join-condition/go.mod create mode 100644 ogx/example/rel-join-condition/go.sum create mode 100644 ogx/example/rel-join-condition/main.go create mode 100644 ogx/example/rel-many-to-many-self/go.mod create mode 100644 ogx/example/rel-many-to-many-self/go.sum create mode 100644 ogx/example/rel-many-to-many-self/main.go create mode 100644 ogx/example/rel-many-to-many/go.mod create mode 100644 ogx/example/rel-many-to-many/go.sum create mode 100644 ogx/example/rel-many-to-many/main.go create mode 100644 ogx/example/string-representation/go.mod create mode 100644 ogx/example/string-representation/go.sum create mode 100644 ogx/example/string-representation/main.go create mode 100644 ogx/example/trivial/go.mod create mode 100644 ogx/example/trivial/go.sum create mode 100644 ogx/example/trivial/main.go create mode 100644 ogx/example/tx-composition/go.mod create mode 100644 ogx/example/tx-composition/go.sum create mode 100644 ogx/example/tx-composition/main.go create mode 100644 ogx/extra/ogxbig/README.md create mode 100644 ogx/extra/ogxbig/float.go create mode 100644 ogx/extra/ogxbig/go.mod create mode 100644 ogx/extra/ogxbig/go.sum create mode 100644 ogx/extra/ogxbig/int.go create mode 100644 ogx/extra/ogxbig/int_test.go create mode 100644 ogx/extra/ogxbig/ogxbig.go create mode 100644 ogx/extra/ogxdebug/debug.go create mode 100644 ogx/extra/ogxdebug/go.mod create mode 100644 ogx/extra/ogxdebug/go.sum create mode 100644 ogx/extra/ogxjson/json.go create mode 100644 ogx/extra/ogxjson/provider.go create mode 100644 ogx/extra/ogxrelic/go.mod create mode 100644 ogx/extra/ogxrelic/go.sum create mode 100644 ogx/extra/ogxrelic/option.go create mode 100644 ogx/extra/ogxrelic/relic.go create mode 100644 ogx/go.mod create mode 100644 ogx/go.sum create mode 100644 ogx/hook.go create mode 100644 ogx/internal/flag.go create mode 100644 ogx/internal/hex.go create mode 100644 ogx/internal/logger.go create mode 100644 ogx/internal/map_key.go create mode 100644 ogx/internal/parser/parser.go create mode 100644 ogx/internal/safe.go create mode 100644 ogx/internal/tagparser/parser.go create mode 100644 ogx/internal/tagparser/parser_test.go create mode 100644 ogx/internal/time.go create mode 100644 ogx/internal/underscore.go create mode 100644 ogx/internal/unsafe.go create mode 100644 ogx/internal/util.go create mode 100644 ogx/migrate/migration.go create mode 100644 ogx/migrate/migrations.go create mode 100644 ogx/migrate/migrator.go create mode 100644 ogx/model.go create mode 100644 ogx/model_map.go create mode 100644 ogx/model_map_slice.go create mode 100644 ogx/model_scan.go create mode 100644 ogx/model_slice.go create mode 100644 ogx/model_table_has_many.go create mode 100644 ogx/model_table_m2m.go create mode 100644 ogx/model_table_slice.go create mode 100644 ogx/model_table_struct.go create mode 100644 ogx/ogx.go create mode 100644 ogx/query_base.go create mode 100644 ogx/query_column_add.go create mode 100644 ogx/query_column_drop.go create mode 100644 ogx/query_delete.go create mode 100644 ogx/query_index_create.go create mode 100644 ogx/query_index_drop.go create mode 100644 ogx/query_insert.go create mode 100644 ogx/query_raw.go create mode 100644 ogx/query_select.go create mode 100644 ogx/query_table_create.go create mode 100644 ogx/query_table_drop.go create mode 100644 ogx/query_table_truncate.go create mode 100644 ogx/query_update.go create mode 100644 ogx/query_values.go create mode 100644 ogx/relation_join.go create mode 100644 ogx/schema/append.go create mode 100644 ogx/schema/append_value.go create mode 100644 ogx/schema/dialect.go create mode 100644 ogx/schema/field.go create mode 100644 ogx/schema/formatter.go create mode 100644 ogx/schema/hook.go create mode 100644 ogx/schema/reflect.go create mode 100644 ogx/schema/relation.go create mode 100644 ogx/schema/scan.go create mode 100644 ogx/schema/sqlfmt.go create mode 100644 ogx/schema/sqltype.go create mode 100644 ogx/schema/table.go create mode 100644 ogx/schema/tables.go create mode 100644 ogx/schema/zerochecker.go create mode 100644 ogx/util.go create mode 100644 ogx/version.go diff --git a/ogx/.gitignore b/ogx/.gitignore new file mode 100644 index 00000000..e606bf0a --- /dev/null +++ b/ogx/.gitignore @@ -0,0 +1,4 @@ +# Patterns for files created by this project. +# For other files, use global gitignore. +.vscode/ +.idea/ diff --git a/ogx/Makefile b/ogx/Makefile new file mode 100644 index 00000000..f6165fdf --- /dev/null +++ b/ogx/Makefile @@ -0,0 +1,30 @@ +ALL_GO_MOD_DIRS := $(shell find . -type f -name 'go.mod' -exec dirname {} \; | sort) +EXAMPLE_GO_MOD_DIRS := $(shell find ./example/ -type f -name 'go.mod' -exec dirname {} \; | sort) + +test: + set -e; for dir in $(ALL_GO_MOD_DIRS); do \ + echo "go test in $${dir}"; \ + (cd "$${dir}" && \ + go test && \ + env GOOS=linux GOARCH=386 go test && \ + go vet); \ + done + +go_mod_tidy: + go get -u && go mod tidy -go=1.18 + set -e; for dir in $(ALL_GO_MOD_DIRS); do \ + echo "go mod tidy in $${dir}"; \ + (cd "$${dir}" && \ + go get -u ./... && \ + go mod tidy -go=1.18); \ + done + +fmt: + gofmt -w -s ./ + goimports -w -local gitee.com/chentanyang/ogx ./ + +run-examples: + set -e; for dir in $(EXAMPLE_GO_MOD_DIRS); do \ + echo "go run . in $${dir}"; \ + (cd "$${dir}" && go run .); \ + done diff --git a/ogx/README.md b/ogx/README.md new file mode 100644 index 00000000..daf5cd03 --- /dev/null +++ b/ogx/README.md @@ -0,0 +1,6 @@ +# ogx +SQL-first Golang ORM for openGauss + +## DOCUMENTATION +[使用样例文档](https://gitee.com/chentanyang/ogx/blob/master/docs/Ogx%20%E4%BD%BF%E7%94%A8%E6%A0%B7%E4%BE%8B%E6%96%87%E6%A1%A3.md) + diff --git a/ogx/db.go b/ogx/db.go new file mode 100644 index 00000000..8f5531d4 --- /dev/null +++ b/ogx/db.go @@ -0,0 +1,696 @@ +package ogx + +import ( + "context" + "crypto/rand" + "database/sql" + "encoding/hex" + "fmt" + "reflect" + "strings" + "sync/atomic" + + "gitee.com/chentanyang/ogx/dialect/feature" + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/schema" +) + +const ( + discardUnknownColumns internal.Flag = 1 << iota +) + +type DBStats struct { + Queries uint32 + Errors uint32 +} + +type DBOption func(db *DB) + +func WithDiscardUnknownColumns() DBOption { + return func(db *DB) { + db.flags = db.flags.Set(discardUnknownColumns) + } +} + +type DB struct { + *sql.DB + + dialect schema.Dialect + features feature.Feature + + queryHooks []QueryHook + + fmter schema.Formatter + flags internal.Flag + + stats DBStats +} + +func NewDB(sqldb *sql.DB, dialect schema.Dialect, opts ...DBOption) *DB { + dialect.Init(sqldb) + + db := &DB{ + DB: sqldb, + dialect: dialect, + features: dialect.Features(), + fmter: schema.NewFormatter(dialect), + } + + for _, opt := range opts { + opt(db) + } + + return db +} + +func (db *DB) String() string { + var b strings.Builder + b.WriteString("DB") + return b.String() +} + +func (db *DB) DBStats() DBStats { + return DBStats{ + Queries: atomic.LoadUint32(&db.stats.Queries), + Errors: atomic.LoadUint32(&db.stats.Errors), + } +} + +func (db *DB) NewValues(model interface{}) *ValuesQuery { + return NewValuesQuery(db, model) +} + +func (db *DB) NewSelect() *SelectQuery { + return NewSelectQuery(db) +} + +func (db *DB) NewInsert() *InsertQuery { + return NewInsertQuery(db) +} + +func (db *DB) NewUpdate() *UpdateQuery { + return NewUpdateQuery(db) +} + +func (db *DB) NewDelete() *DeleteQuery { + return NewDeleteQuery(db) +} + +func (db *DB) NewRaw(query string, args ...interface{}) *RawQuery { + return NewRawQuery(db, query, args...) +} + +func (db *DB) NewCreateTable() *CreateTableQuery { + return NewCreateTableQuery(db) +} + +func (db *DB) NewDropTable() *DropTableQuery { + return NewDropTableQuery(db) +} + +func (db *DB) NewCreateIndex() *CreateIndexQuery { + return NewCreateIndexQuery(db) +} + +func (db *DB) NewDropIndex() *DropIndexQuery { + return NewDropIndexQuery(db) +} + +func (db *DB) NewTruncateTable() *TruncateTableQuery { + return NewTruncateTableQuery(db) +} + +func (db *DB) NewAddColumn() *AddColumnQuery { + return NewAddColumnQuery(db) +} + +func (db *DB) NewDropColumn() *DropColumnQuery { + return NewDropColumnQuery(db) +} + +func (db *DB) ResetModel(ctx context.Context, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewDropTable().Model(model).IfExists().Cascade().Exec(ctx); err != nil { + return err + } + if _, err := db.NewCreateTable().Model(model).Exec(ctx); err != nil { + return err + } + } + return nil +} + +func (db *DB) Dialect() schema.Dialect { + return db.dialect +} + +func (db *DB) ScanRows(ctx context.Context, rows *sql.Rows, dest ...interface{}) error { + defer rows.Close() + + model, err := newModel(db, dest) + if err != nil { + return err + } + + _, err = model.ScanRows(ctx, rows) + if err != nil { + return err + } + + return rows.Err() +} + +func (db *DB) ScanRow(ctx context.Context, rows *sql.Rows, dest ...interface{}) error { + model, err := newModel(db, dest) + if err != nil { + return err + } + + rs, ok := model.(rowScanner) + if !ok { + return fmt.Errorf("ogx: %T does not support ScanRow", model) + } + + return rs.ScanRow(ctx, rows) +} + +type queryHookIniter interface { + Init(db *DB) +} + +func (db *DB) AddQueryHook(hook QueryHook) { + if initer, ok := hook.(queryHookIniter); ok { + initer.Init(db) + } + db.queryHooks = append(db.queryHooks, hook) +} + +func (db *DB) Table(typ reflect.Type) *schema.Table { + return db.dialect.Tables().Get(typ) +} + +// RegisterModel registers models by name so they can be referenced in table relations +// and fixtures. +func (db *DB) RegisterModel(models ...interface{}) { + db.dialect.Tables().Register(models...) +} + +func (db *DB) clone() *DB { + clone := *db + + l := len(clone.queryHooks) + clone.queryHooks = clone.queryHooks[:l:l] + + return &clone +} + +func (db *DB) WithNamedArg(name string, value interface{}) *DB { + clone := db.clone() + clone.fmter = clone.fmter.WithNamedArg(name, value) + return clone +} + +func (db *DB) Formatter() schema.Formatter { + return db.fmter +} + +// UpdateFQN returns a fully qualified column name. For MySQL, it returns the column name with +// the table alias. For other RDBMS, it returns just the column name. +func (db *DB) UpdateFQN(alias, column string) Ident { + if db.HasFeature(feature.UpdateMultiTable) { + return Ident(alias + "." + column) + } + return Ident(column) +} + +// HasFeature uses feature package to report whether the underlying DBMS supports this feature. +func (db *DB) HasFeature(feat feature.Feature) bool { + return db.fmter.HasFeature(feat) +} + +//------------------------------------------------------------------------------ + +func (db *DB) Exec(query string, args ...interface{}) (sql.Result, error) { + return db.ExecContext(context.Background(), query, args...) +} + +func (db *DB) ExecContext( + ctx context.Context, query string, args ...interface{}, +) (sql.Result, error) { + formattedQuery := db.format(query, args) + ctx, event := db.beforeQuery(ctx, nil, query, args, formattedQuery, nil) + res, err := db.DB.ExecContext(ctx, formattedQuery) + db.afterQuery(ctx, event, res, err) + return res, err +} + +func (db *DB) Query(query string, args ...interface{}) (*sql.Rows, error) { + return db.QueryContext(context.Background(), query, args...) +} + +func (db *DB) QueryContext( + ctx context.Context, query string, args ...interface{}, +) (*sql.Rows, error) { + formattedQuery := db.format(query, args) + ctx, event := db.beforeQuery(ctx, nil, query, args, formattedQuery, nil) + rows, err := db.DB.QueryContext(ctx, formattedQuery) + db.afterQuery(ctx, event, nil, err) + return rows, err +} + +func (db *DB) QueryRow(query string, args ...interface{}) *sql.Row { + return db.QueryRowContext(context.Background(), query, args...) +} + +func (db *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { + formattedQuery := db.format(query, args) + ctx, event := db.beforeQuery(ctx, nil, query, args, formattedQuery, nil) + row := db.DB.QueryRowContext(ctx, formattedQuery) + db.afterQuery(ctx, event, nil, row.Err()) + return row +} + +func (db *DB) format(query string, args []interface{}) string { + return db.fmter.FormatQuery(query, args...) +} + +//------------------------------------------------------------------------------ + +type Conn struct { + db *DB + *sql.Conn +} + +func (db *DB) Conn(ctx context.Context) (Conn, error) { + conn, err := db.DB.Conn(ctx) + if err != nil { + return Conn{}, err + } + return Conn{ + db: db, + Conn: conn, + }, nil +} + +func (c Conn) ExecContext( + ctx context.Context, query string, args ...interface{}, +) (sql.Result, error) { + formattedQuery := c.db.format(query, args) + ctx, event := c.db.beforeQuery(ctx, nil, query, args, formattedQuery, nil) + res, err := c.Conn.ExecContext(ctx, formattedQuery) + c.db.afterQuery(ctx, event, res, err) + return res, err +} + +func (c Conn) QueryContext( + ctx context.Context, query string, args ...interface{}, +) (*sql.Rows, error) { + formattedQuery := c.db.format(query, args) + ctx, event := c.db.beforeQuery(ctx, nil, query, args, formattedQuery, nil) + rows, err := c.Conn.QueryContext(ctx, formattedQuery) + c.db.afterQuery(ctx, event, nil, err) + return rows, err +} + +func (c Conn) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { + formattedQuery := c.db.format(query, args) + ctx, event := c.db.beforeQuery(ctx, nil, query, args, formattedQuery, nil) + row := c.Conn.QueryRowContext(ctx, formattedQuery) + c.db.afterQuery(ctx, event, nil, row.Err()) + return row +} + +func (c Conn) Dialect() schema.Dialect { + return c.db.Dialect() +} + +func (c Conn) NewValues(model interface{}) *ValuesQuery { + return NewValuesQuery(c.db, model).Conn(c) +} + +func (c Conn) NewSelect() *SelectQuery { + return NewSelectQuery(c.db).Conn(c) +} + +func (c Conn) NewInsert() *InsertQuery { + return NewInsertQuery(c.db).Conn(c) +} + +func (c Conn) NewUpdate() *UpdateQuery { + return NewUpdateQuery(c.db).Conn(c) +} + +func (c Conn) NewDelete() *DeleteQuery { + return NewDeleteQuery(c.db).Conn(c) +} + +func (c Conn) NewRaw(query string, args ...interface{}) *RawQuery { + return NewRawQuery(c.db, query, args...).Conn(c) +} + +func (c Conn) NewCreateTable() *CreateTableQuery { + return NewCreateTableQuery(c.db).Conn(c) +} + +func (c Conn) NewDropTable() *DropTableQuery { + return NewDropTableQuery(c.db).Conn(c) +} + +func (c Conn) NewCreateIndex() *CreateIndexQuery { + return NewCreateIndexQuery(c.db).Conn(c) +} + +func (c Conn) NewDropIndex() *DropIndexQuery { + return NewDropIndexQuery(c.db).Conn(c) +} + +func (c Conn) NewTruncateTable() *TruncateTableQuery { + return NewTruncateTableQuery(c.db).Conn(c) +} + +func (c Conn) NewAddColumn() *AddColumnQuery { + return NewAddColumnQuery(c.db).Conn(c) +} + +func (c Conn) NewDropColumn() *DropColumnQuery { + return NewDropColumnQuery(c.db).Conn(c) +} + +// RunInTx runs the function in a transaction. If the function returns an error, +// the transaction is rolled back. Otherwise, the transaction is committed. +func (c Conn) RunInTx( + ctx context.Context, opts *sql.TxOptions, fn func(ctx context.Context, tx Tx) error, +) error { + tx, err := c.BeginTx(ctx, opts) + if err != nil { + return err + } + + var done bool + + defer func() { + if !done { + _ = tx.Rollback() + } + }() + + if err := fn(ctx, tx); err != nil { + return err + } + + done = true + return tx.Commit() +} + +func (c Conn) BeginTx(ctx context.Context, opts *sql.TxOptions) (Tx, error) { + ctx, event := c.db.beforeQuery(ctx, nil, "BEGIN", nil, "BEGIN", nil) + tx, err := c.Conn.BeginTx(ctx, opts) + c.db.afterQuery(ctx, event, nil, err) + if err != nil { + return Tx{}, err + } + return Tx{ + ctx: ctx, + db: c.db, + Tx: tx, + }, nil +} + +//------------------------------------------------------------------------------ + +type Stmt struct { + *sql.Stmt +} + +func (db *DB) Prepare(query string) (Stmt, error) { + return db.PrepareContext(context.Background(), query) +} + +func (db *DB) PrepareContext(ctx context.Context, query string) (Stmt, error) { + stmt, err := db.DB.PrepareContext(ctx, query) + if err != nil { + return Stmt{}, err + } + return Stmt{Stmt: stmt}, nil +} + +//------------------------------------------------------------------------------ + +type Tx struct { + ctx context.Context + db *DB + // name is the name of a savepoint + name string + *sql.Tx +} + +// RunInTx runs the function in a transaction. If the function returns an error, +// the transaction is rolled back. Otherwise, the transaction is committed. +func (db *DB) RunInTx( + ctx context.Context, opts *sql.TxOptions, fn func(ctx context.Context, tx Tx) error, +) error { + tx, err := db.BeginTx(ctx, opts) + if err != nil { + return err + } + + var done bool + + defer func() { + if !done { + _ = tx.Rollback() + } + }() + + if err := fn(ctx, tx); err != nil { + return err + } + + done = true + return tx.Commit() +} + +func (db *DB) Begin() (Tx, error) { + return db.BeginTx(context.Background(), nil) +} + +func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (Tx, error) { + ctx, event := db.beforeQuery(ctx, nil, "BEGIN", nil, "BEGIN", nil) + tx, err := db.DB.BeginTx(ctx, opts) + db.afterQuery(ctx, event, nil, err) + if err != nil { + return Tx{}, err + } + return Tx{ + ctx: ctx, + db: db, + Tx: tx, + }, nil +} + +func (tx Tx) Commit() error { + if tx.name == "" { + return tx.commitTX() + } + return tx.commitSP() +} + +func (tx Tx) commitTX() error { + ctx, event := tx.db.beforeQuery(tx.ctx, nil, "COMMIT", nil, "COMMIT", nil) + err := tx.Tx.Commit() + tx.db.afterQuery(ctx, event, nil, err) + return err +} + +func (tx Tx) commitSP() error { + if tx.Dialect().Features().Has(feature.MSSavepoint) { + return nil + } + query := "RELEASE SAVEPOINT " + tx.name + _, err := tx.ExecContext(tx.ctx, query) + return err +} + +func (tx Tx) Rollback() error { + if tx.name == "" { + return tx.rollbackTX() + } + return tx.rollbackSP() +} + +func (tx Tx) rollbackTX() error { + ctx, event := tx.db.beforeQuery(tx.ctx, nil, "ROLLBACK", nil, "ROLLBACK", nil) + err := tx.Tx.Rollback() + tx.db.afterQuery(ctx, event, nil, err) + return err +} + +func (tx Tx) rollbackSP() error { + query := "ROLLBACK TO SAVEPOINT " + tx.name + if tx.Dialect().Features().Has(feature.MSSavepoint) { + query = "ROLLBACK TRANSACTION " + tx.name + } + _, err := tx.ExecContext(tx.ctx, query) + return err +} + +func (tx Tx) Exec(query string, args ...interface{}) (sql.Result, error) { + return tx.ExecContext(context.TODO(), query, args...) +} + +func (tx Tx) ExecContext( + ctx context.Context, query string, args ...interface{}, +) (sql.Result, error) { + formattedQuery := tx.db.format(query, args) + ctx, event := tx.db.beforeQuery(ctx, nil, query, args, formattedQuery, nil) + res, err := tx.Tx.ExecContext(ctx, formattedQuery) + tx.db.afterQuery(ctx, event, res, err) + return res, err +} + +func (tx Tx) Query(query string, args ...interface{}) (*sql.Rows, error) { + return tx.QueryContext(context.TODO(), query, args...) +} + +func (tx Tx) QueryContext( + ctx context.Context, query string, args ...interface{}, +) (*sql.Rows, error) { + formattedQuery := tx.db.format(query, args) + ctx, event := tx.db.beforeQuery(ctx, nil, query, args, formattedQuery, nil) + rows, err := tx.Tx.QueryContext(ctx, formattedQuery) + tx.db.afterQuery(ctx, event, nil, err) + return rows, err +} + +func (tx Tx) QueryRow(query string, args ...interface{}) *sql.Row { + return tx.QueryRowContext(context.TODO(), query, args...) +} + +func (tx Tx) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { + formattedQuery := tx.db.format(query, args) + ctx, event := tx.db.beforeQuery(ctx, nil, query, args, formattedQuery, nil) + row := tx.Tx.QueryRowContext(ctx, formattedQuery) + tx.db.afterQuery(ctx, event, nil, row.Err()) + return row +} + +//------------------------------------------------------------------------------ + +func (tx Tx) Begin() (Tx, error) { + return tx.BeginTx(tx.ctx, nil) +} + +// BeginTx will save a point in the running transaction. +func (tx Tx) BeginTx(ctx context.Context, _ *sql.TxOptions) (Tx, error) { + // mssql savepoint names are limited to 32 characters + sp := make([]byte, 14) + _, err := rand.Read(sp) + if err != nil { + return Tx{}, err + } + + qName := "SP_" + hex.EncodeToString(sp) + query := "SAVEPOINT " + qName + if tx.Dialect().Features().Has(feature.MSSavepoint) { + query = "SAVE TRANSACTION " + qName + } + _, err = tx.ExecContext(ctx, query) + if err != nil { + return Tx{}, err + } + return Tx{ + ctx: ctx, + db: tx.db, + Tx: tx.Tx, + name: qName, + }, nil +} + +func (tx Tx) RunInTx( + ctx context.Context, _ *sql.TxOptions, fn func(ctx context.Context, tx Tx) error, +) error { + sp, err := tx.BeginTx(ctx, nil) + if err != nil { + return err + } + + var done bool + + defer func() { + if !done { + _ = sp.Rollback() + } + }() + + if err := fn(ctx, sp); err != nil { + return err + } + + done = true + return sp.Commit() +} + +func (tx Tx) Dialect() schema.Dialect { + return tx.db.Dialect() +} + +func (tx Tx) NewValues(model interface{}) *ValuesQuery { + return NewValuesQuery(tx.db, model).Conn(tx) +} + +func (tx Tx) NewSelect() *SelectQuery { + return NewSelectQuery(tx.db).Conn(tx) +} + +func (tx Tx) NewInsert() *InsertQuery { + return NewInsertQuery(tx.db).Conn(tx) +} + +func (tx Tx) NewUpdate() *UpdateQuery { + return NewUpdateQuery(tx.db).Conn(tx) +} + +func (tx Tx) NewDelete() *DeleteQuery { + return NewDeleteQuery(tx.db).Conn(tx) +} + +func (tx Tx) NewRaw(query string, args ...interface{}) *RawQuery { + return NewRawQuery(tx.db, query, args...).Conn(tx) +} + +func (tx Tx) NewCreateTable() *CreateTableQuery { + return NewCreateTableQuery(tx.db).Conn(tx) +} + +func (tx Tx) NewDropTable() *DropTableQuery { + return NewDropTableQuery(tx.db).Conn(tx) +} + +func (tx Tx) NewCreateIndex() *CreateIndexQuery { + return NewCreateIndexQuery(tx.db).Conn(tx) +} + +func (tx Tx) NewDropIndex() *DropIndexQuery { + return NewDropIndexQuery(tx.db).Conn(tx) +} + +func (tx Tx) NewTruncateTable() *TruncateTableQuery { + return NewTruncateTableQuery(tx.db).Conn(tx) +} + +func (tx Tx) NewAddColumn() *AddColumnQuery { + return NewAddColumnQuery(tx.db).Conn(tx) +} + +func (tx Tx) NewDropColumn() *DropColumnQuery { + return NewDropColumnQuery(tx.db).Conn(tx) +} + +//------------------------------------------------------------------------------ + +func (db *DB) makeQueryBytes() []byte { + // TODO: make this configurable? + return make([]byte, 0, 4096) +} diff --git a/ogx/dbfixture/encoder.go b/ogx/dbfixture/encoder.go new file mode 100644 index 00000000..dd5c3f4f --- /dev/null +++ b/ogx/dbfixture/encoder.go @@ -0,0 +1,46 @@ +package dbfixture + +import ( + "fmt" + "io" + "reflect" + + "gitee.com/chentanyang/ogx" + "gopkg.in/yaml.v3" +) + +type fixtureRows struct { + Model string `yaml:"model"` + Rows interface{} `yaml:"rows"` +} + +type Encoder struct { + db *ogx.DB + enc *yaml.Encoder +} + +func NewEncoder(db *ogx.DB, w io.Writer) *Encoder { + return &Encoder{ + db: db, + enc: yaml.NewEncoder(w), + } +} + +func (e *Encoder) Encode(multiRows ...interface{}) error { + fixtures := make([]fixtureRows, len(multiRows)) + + for i, rows := range multiRows { + v := reflect.ValueOf(rows) + if v.Kind() != reflect.Slice { + return fmt.Errorf("dbfixture: got %T, wanted a slice", rows) + } + + table := e.db.Dialect().Tables().Get(v.Type().Elem()) + fixtures[i] = fixtureRows{ + Model: table.TypeName, + Rows: rows, + } + } + + return e.enc.Encode(fixtures) +} diff --git a/ogx/dbfixture/fixture.go b/ogx/dbfixture/fixture.go new file mode 100644 index 00000000..6f055b74 --- /dev/null +++ b/ogx/dbfixture/fixture.go @@ -0,0 +1,445 @@ +package dbfixture + +import ( + "bytes" + "context" + "database/sql" + "fmt" + "io/fs" + "reflect" + "regexp" + "strconv" + "strings" + "text/template" + "text/template/parse" + "time" + + "gopkg.in/yaml.v3" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/schema" +) + +var ( + funcNameRE = regexp.MustCompile(`^\{\{ (\w+) \}\}$`) + tplRE = regexp.MustCompile(`\{\{ .+ \}\}`) +) + +type FixtureOption func(l *Fixture) + +func WithRecreateTables() FixtureOption { + return func(l *Fixture) { + if l.truncateTables { + panic("don't use WithDropTables together with WithTruncateTables") + } + l.recreateTables = true + l.seenTables = make(map[string]struct{}) + } +} + +func WithTruncateTables() FixtureOption { + return func(l *Fixture) { + if l.truncateTables { + panic("don't use WithTruncateTables together with WithRecreateTables") + } + l.truncateTables = true + l.seenTables = make(map[string]struct{}) + } +} + +func WithTemplateFuncs(funcMap template.FuncMap) FixtureOption { + return func(l *Fixture) { + for k, v := range funcMap { + l.funcMap[k] = v + } + } +} + +type BeforeInsertData struct { + Query *ogx.InsertQuery + Model interface{} +} + +type BeforeInsertFunc func(ctx context.Context, data *BeforeInsertData) error + +func WithBeforeInsert(fn BeforeInsertFunc) FixtureOption { + return func(f *Fixture) { + f.beforeInsert = append(f.beforeInsert, fn) + } +} + +type Fixture struct { + db *ogx.DB + + recreateTables bool + truncateTables bool + beforeInsert []BeforeInsertFunc + + seenTables map[string]struct{} + + funcMap template.FuncMap + modelRows map[string]map[string]interface{} +} + +func New(db *ogx.DB, opts ...FixtureOption) *Fixture { + f := &Fixture{ + db: db, + + funcMap: defaultFuncs(), + modelRows: make(map[string]map[string]interface{}), + } + for _, opt := range opts { + opt(f) + } + return f +} + +func (f *Fixture) Row(id string) (interface{}, error) { + ss := strings.Split(id, ".") + if len(ss) != 2 { + return nil, fmt.Errorf("fixture: invalid row id: %q", id) + } + model, rowID := ss[0], ss[1] + + rows, ok := f.modelRows[model] + if !ok { + return nil, fmt.Errorf("fixture: unknown model=%q", model) + } + + row, ok := rows[rowID] + if !ok { + return nil, fmt.Errorf("fixture: can't find row=%q for model=%q", rowID, model) + } + + return row, nil +} + +func (f *Fixture) MustRow(id string) interface{} { + row, err := f.Row(id) + if err != nil { + panic(err) + } + return row +} + +func (f *Fixture) Load(ctx context.Context, fsys fs.FS, names ...string) error { + for _, name := range names { + if err := f.load(ctx, fsys, name); err != nil { + return err + } + } + return nil +} + +func (f *Fixture) load(ctx context.Context, fsys fs.FS, name string) error { + fh, err := fsys.Open(name) + if err != nil { + return err + } + + var fixtures []fixtureData + + dec := yaml.NewDecoder(fh) + if err := dec.Decode(&fixtures); err != nil { + return err + } + + for i := range fixtures { + if err := f.addFixture(ctx, &fixtures[i]); err != nil { + return err + } + } + + return nil +} + +func (f *Fixture) addFixture(ctx context.Context, data *fixtureData) error { + table := f.db.Dialect().Tables().ByModel(data.Model) + if table == nil { + return fmt.Errorf("fixture: can't find model=%q (use db.RegisterModel)", data.Model) + } + + if f.recreateTables { + if err := f.dropTable(ctx, table); err != nil { + return err + } + } else if f.truncateTables { + if err := f.truncateTable(ctx, table); err != nil { + return err + } + } + + for _, row := range data.Rows { + if err := f.addRow(ctx, table, row); err != nil { + return err + } + } + + return nil +} + +func (f *Fixture) addRow(ctx context.Context, table *schema.Table, row row) error { + var rowID string + strct := reflect.New(table.Type).Elem() + + for key, value := range row { + if key == "_id" { + if err := value.Decode(&rowID); err != nil { + return err + } + continue + } + + field, err := table.Field(key) + if err != nil { + return err + } + + if err := f.decodeField(strct, field, &value); err != nil { + return fmt.Errorf("dbfixture: decoding %s failed: %w", key, err) + } + } + + model := strct.Addr().Interface() + q := f.db.NewInsert().Model(model) + + data := &BeforeInsertData{ + Query: q, + Model: model, + } + for _, fn := range f.beforeInsert { + if err := fn(ctx, data); err != nil { + return err + } + } + + if _, err := q.Exec(ctx); err != nil { + return err + } + + if rowID == "" && len(table.PKs) == 1 { + pk := table.PKs[0] + fv := pk.Value(strct) + rowID = "pk" + asString(fv) + } + + if rowID != "" { + rows, ok := f.modelRows[table.TypeName] + if !ok { + rows = make(map[string]interface{}) + f.modelRows[table.TypeName] = rows + } + rows[rowID] = model + } + + return nil +} + +func (f *Fixture) decodeField(strct reflect.Value, field *schema.Field, value *yaml.Node) error { + fv := field.Value(strct) + iface := fv.Addr().Interface() + + if value.Tag != "!!str" { + return value.Decode(iface) + } + + if ss := funcNameRE.FindStringSubmatch(value.Value); len(ss) > 0 { + if fn, ok := f.funcMap[ss[1]].(func() interface{}); ok { + return scanFieldValue(strct, field, fn()) + } + } + + if tplRE.MatchString(value.Value) { + src, err := f.eval(value.Value) + if err != nil { + return err + } + return scanFieldValue(strct, field, src) + } + + if v, ok := iface.(yaml.Unmarshaler); ok { + return v.UnmarshalYAML(value) + } + + if _, ok := iface.(sql.Scanner); ok { + var str string + if err := value.Decode(&str); err != nil { + return err + } + return field.ScanValue(strct, str) + } + + return value.Decode(iface) +} + +func (f *Fixture) dropTable(ctx context.Context, table *schema.Table) error { + if _, ok := f.seenTables[table.Name]; ok { + return nil + } + f.seenTables[table.Name] = struct{}{} + + if _, err := f.db.NewDropTable(). + Model(table.ZeroIface). + IfExists(). + Cascade(). + Exec(ctx); err != nil { + return err + } + + if _, err := f.db.NewCreateTable(). + Model(table.ZeroIface). + Exec(ctx); err != nil { + return err + } + + return nil +} + +func (f *Fixture) truncateTable(ctx context.Context, table *schema.Table) error { + if _, ok := f.seenTables[table.Name]; ok { + return nil + } + f.seenTables[table.Name] = struct{}{} + + if _, err := f.db.NewTruncateTable(). + Model(table.ZeroIface). + Cascade(). + Exec(ctx); err != nil { + return err + } + + return nil +} + +func (f *Fixture) eval(templ string) (interface{}, error) { + if v, ok := f.evalFuncCall(templ); ok { + return v, nil + } + + tpl, err := template.New("").Funcs(f.funcMap).Parse(templ) + if err != nil { + return nil, err + } + + var buf bytes.Buffer + + if err := tpl.Execute(&buf, f.modelRows); err != nil { + return nil, err + } + + return buf.String(), nil +} + +func (f *Fixture) evalFuncCall(templ string) (interface{}, bool) { + tree, err := parse.Parse("", templ, "{{", "}}", f.funcMap) + if err != nil { + return nil, false + } + + root := tree[""].Root + if len(root.Nodes) != 1 { + return nil, false + } + + action, ok := root.Nodes[0].(*parse.ActionNode) + if !ok { + return nil, false + } + + if len(action.Pipe.Cmds) != 1 { + return nil, false + } + + args := action.Pipe.Cmds[0].Args + if len(args) == 0 { + return nil, false + } + + funcName, ok := args[0].(*parse.IdentifierNode) + if !ok { + return nil, false + } + + fn, ok := f.funcMap[funcName.Ident] + if !ok { + return nil, false + } + + fnValue := reflect.ValueOf(fn) + fnType := fnValue.Type() + if fnType.NumOut() != 1 { + return nil, false + } + + args = args[1:] + if len(args) != fnType.NumIn() { + return nil, false + } + argValues := make([]reflect.Value, len(args)) + + for i, node := range args { + switch node := node.(type) { + case *parse.StringNode: + argValues[i] = reflect.ValueOf(node.Text) + case *parse.NumberNode: + switch { + case node.IsInt: + argValues[i] = reflect.ValueOf(node.Int64) + case node.IsUint: + argValues[i] = reflect.ValueOf(node.Uint64) + case node.IsFloat: + argValues[i] = reflect.ValueOf(node.Float64) + case node.IsComplex: + argValues[i] = reflect.ValueOf(node.Complex128) + default: + argValues[i] = reflect.ValueOf(node.Text) + } + case *parse.BoolNode: + argValues[i] = reflect.ValueOf(node.True) + default: + return nil, false + } + } + + out := fnValue.Call(argValues) + return out[0].Interface(), true +} + +type fixtureData struct { + Model string `yaml:"model"` + Rows []row `yaml:"rows"` +} + +type row map[string]yaml.Node + +func asString(rv reflect.Value) string { + switch rv.Kind() { + case reflect.Bool: + return strconv.FormatBool(rv.Bool()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.FormatInt(rv.Int(), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return strconv.FormatUint(rv.Uint(), 10) + case reflect.Float32: + return strconv.FormatFloat(rv.Float(), 'g', -1, 32) + case reflect.Float64: + return strconv.FormatFloat(rv.Float(), 'g', -1, 64) + } + return fmt.Sprintf("%v", rv.Interface()) +} + +func defaultFuncs() template.FuncMap { + return template.FuncMap{ + "now": func() interface{} { + return time.Now() + }, + } +} + +func scanFieldValue(strct reflect.Value, field *schema.Field, value interface{}) error { + if v := reflect.ValueOf(value); v.CanConvert(field.StructField.Type) { + field.Value(strct).Set(v.Convert(field.StructField.Type)) + return nil + } + return field.ScanValue(strct, value) +} diff --git a/ogx/dbfixture/go.mod b/ogx/dbfixture/go.mod new file mode 100644 index 00000000..9d3a3d01 --- /dev/null +++ b/ogx/dbfixture/go.mod @@ -0,0 +1,19 @@ +module gitee.com/chentanyang/ogx/dbfixture + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../ + +require ( + gitee.com/chentanyang/ogx v1.0.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect +) diff --git a/ogx/dbfixture/go.sum b/ogx/dbfixture/go.sum new file mode 100644 index 00000000..7b87e315 --- /dev/null +++ b/ogx/dbfixture/go.sum @@ -0,0 +1,26 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ogx/dialect/append.go b/ogx/dialect/append.go new file mode 100644 index 00000000..162ef035 --- /dev/null +++ b/ogx/dialect/append.go @@ -0,0 +1,88 @@ +package dialect + +import ( + "math" + "strconv" + + "gitee.com/chentanyang/ogx/internal" +) + +func AppendError(b []byte, err error) []byte { + b = append(b, "?!("...) + b = append(b, err.Error()...) + b = append(b, ')') + return b +} + +func AppendNull(b []byte) []byte { + return append(b, "NULL"...) +} + +func AppendBool(b []byte, v bool) []byte { + if v { + return append(b, "TRUE"...) + } + return append(b, "FALSE"...) +} + +func AppendFloat32(b []byte, v float32) []byte { + return appendFloat(b, float64(v), 32) +} + +func AppendFloat64(b []byte, v float64) []byte { + return appendFloat(b, v, 64) +} + +func appendFloat(b []byte, v float64, bitSize int) []byte { + switch { + case math.IsNaN(v): + return append(b, "'NaN'"...) + case math.IsInf(v, 1): + return append(b, "'Infinity'"...) + case math.IsInf(v, -1): + return append(b, "'-Infinity'"...) + default: + return strconv.AppendFloat(b, v, 'f', -1, bitSize) + } +} + +//------------------------------------------------------------------------------ + +func AppendIdent(b []byte, field string, quote byte) []byte { + return appendIdent(b, internal.Bytes(field), quote) +} + +func appendIdent(b, src []byte, quote byte) []byte { + var quoted bool +loop: + for _, c := range src { + switch c { + case '*': + if !quoted { + b = append(b, '*') + continue loop + } + case '.': + if quoted { + b = append(b, quote) + quoted = false + } + b = append(b, '.') + continue loop + } + + if !quoted { + b = append(b, quote) + quoted = true + } + if c == quote { + b = append(b, quote, quote) + } else { + b = append(b, c) + } + } + if quoted { + b = append(b, quote) + } + return b +} diff --git a/ogx/dialect/dialect.go b/ogx/dialect/dialect.go new file mode 100644 index 00000000..03b81fbb --- /dev/null +++ b/ogx/dialect/dialect.go @@ -0,0 +1,26 @@ +package dialect + +type Name int + +func (n Name) String() string { + switch n { + case PG: + return "pg" + case SQLite: + return "sqlite" + case MySQL: + return "mysql" + case MSSQL: + return "mssql" + default: + return "invalid" + } +} + +const ( + Invalid Name = iota + PG + SQLite + MySQL + MSSQL +) diff --git a/ogx/dialect/feature/feature.go b/ogx/dialect/feature/feature.go new file mode 100644 index 00000000..fd3a72b2 --- /dev/null +++ b/ogx/dialect/feature/feature.go @@ -0,0 +1,35 @@ +package feature + +import "gitee.com/chentanyang/ogx/internal" + +type Feature = internal.Flag + +const ( + CTE Feature = 1 << iota + WithValues + Returning + InsertReturning + Output // mssql + DefaultPlaceholder + DoubleColonCast + ValuesRow + UpdateMultiTable + InsertTableAlias + UpdateTableAlias + DeleteTableAlias + AutoIncrement + Identity + TableCascade + TableIdentity + TableTruncate + InsertOnConflict // INSERT ... ON CONFLICT + InsertOnDuplicateKey // INSERT ... ON DUPLICATE KEY + InsertIgnore // INSERT IGNORE ... + TableNotExists + OffsetFetch + SelectExists + UpdateFromTable + MSSavepoint + GeneratedIdentity + CompositeIn // ... WHERE (A,B) IN ((N, NN), (N, NN)...) +) diff --git a/ogx/dialect/ogdialect/append.go b/ogx/dialect/ogdialect/append.go new file mode 100644 index 00000000..f1918492 --- /dev/null +++ b/ogx/dialect/ogdialect/append.go @@ -0,0 +1,364 @@ +package ogdialect + +import ( + "database/sql/driver" + "encoding/hex" + "fmt" + "reflect" + "strconv" + "time" + "unicode/utf8" + + "gitee.com/chentanyang/ogx/dialect" + "gitee.com/chentanyang/ogx/schema" +) + +var ( + driverValuerType = reflect.TypeOf((*driver.Valuer)(nil)).Elem() + + stringType = reflect.TypeOf((*string)(nil)).Elem() + sliceStringType = reflect.TypeOf([]string(nil)) + + intType = reflect.TypeOf((*int)(nil)).Elem() + sliceIntType = reflect.TypeOf([]int(nil)) + + int64Type = reflect.TypeOf((*int64)(nil)).Elem() + sliceInt64Type = reflect.TypeOf([]int64(nil)) + + float64Type = reflect.TypeOf((*float64)(nil)).Elem() + sliceFloat64Type = reflect.TypeOf([]float64(nil)) +) + +func arrayAppend(fmter schema.Formatter, b []byte, v interface{}) []byte { + switch v := v.(type) { + case int64: + return strconv.AppendInt(b, v, 10) + case float64: + return dialect.AppendFloat64(b, v) + case bool: + return dialect.AppendBool(b, v) + case []byte: + return arrayAppendBytes(b, v) + case string: + return arrayAppendString(b, v) + case time.Time: + return fmter.Dialect().AppendTime(b, v) + default: + err := fmt.Errorf("ogdialect: can't append %T", v) + return dialect.AppendError(b, err) + } +} + +func arrayAppendStringValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte { + return arrayAppendString(b, v.String()) +} + +func arrayAppendBytesValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte { + return arrayAppendBytes(b, v.Bytes()) +} + +func arrayAppendDriverValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte { + iface, err := v.Interface().(driver.Valuer).Value() + if err != nil { + return dialect.AppendError(b, err) + } + return arrayAppend(fmter, b, iface) +} + +//------------------------------------------------------------------------------ + +func (d *Dialect) arrayAppender(typ reflect.Type) schema.AppenderFunc { + kind := typ.Kind() + + switch kind { + case reflect.Ptr: + if fn := d.arrayAppender(typ.Elem()); fn != nil { + return schema.PtrAppender(fn) + } + case reflect.Slice, reflect.Array: + // ok: + default: + return nil + } + + elemType := typ.Elem() + + if kind == reflect.Slice { + switch elemType { + case stringType: + return appendStringSliceValue + case intType: + return appendIntSliceValue + case int64Type: + return appendInt64SliceValue + case float64Type: + return appendFloat64SliceValue + } + } + + appendElem := d.arrayElemAppender(elemType) + if appendElem == nil { + panic(fmt.Errorf("ogdialect: %s is not supported", typ)) + } + + return func(fmter schema.Formatter, b []byte, v reflect.Value) []byte { + kind := v.Kind() + switch kind { + case reflect.Ptr, reflect.Slice: + if v.IsNil() { + return dialect.AppendNull(b) + } + } + + if kind == reflect.Ptr { + v = v.Elem() + } + + b = append(b, '\'') + + b = append(b, '{') + for i := 0; i < v.Len(); i++ { + elem := v.Index(i) + b = appendElem(fmter, b, elem) + b = append(b, ',') + } + if v.Len() > 0 { + b[len(b)-1] = '}' // Replace trailing comma. + } else { + b = append(b, '}') + } + + b = append(b, '\'') + + return b + } +} + +func (d *Dialect) arrayElemAppender(typ reflect.Type) schema.AppenderFunc { + if typ.Implements(driverValuerType) { + return arrayAppendDriverValue + } + switch typ.Kind() { + case reflect.String: + return arrayAppendStringValue + case reflect.Slice: + if typ.Elem().Kind() == reflect.Uint8 { + return arrayAppendBytesValue + } + } + return schema.Appender(d, typ) +} + +func appendStringSliceValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte { + ss := v.Convert(sliceStringType).Interface().([]string) + return appendStringSlice(b, ss) +} + +func appendStringSlice(b []byte, ss []string) []byte { + if ss == nil { + return dialect.AppendNull(b) + } + + b = append(b, '\'') + + b = append(b, '{') + for _, s := range ss { + b = arrayAppendString(b, s) + b = append(b, ',') + } + if len(ss) > 0 { + b[len(b)-1] = '}' // Replace trailing comma. + } else { + b = append(b, '}') + } + + b = append(b, '\'') + + return b +} + +func appendIntSliceValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte { + ints := v.Convert(sliceIntType).Interface().([]int) + return appendIntSlice(b, ints) +} + +func appendIntSlice(b []byte, ints []int) []byte { + if ints == nil { + return dialect.AppendNull(b) + } + + b = append(b, '\'') + + b = append(b, '{') + for _, n := range ints { + b = strconv.AppendInt(b, int64(n), 10) + b = append(b, ',') + } + if len(ints) > 0 { + b[len(b)-1] = '}' // Replace trailing comma. + } else { + b = append(b, '}') + } + + b = append(b, '\'') + + return b +} + +func appendInt64SliceValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte { + ints := v.Convert(sliceInt64Type).Interface().([]int64) + return appendInt64Slice(b, ints) +} + +func appendInt64Slice(b []byte, ints []int64) []byte { + if ints == nil { + return dialect.AppendNull(b) + } + + b = append(b, '\'') + + b = append(b, '{') + for _, n := range ints { + b = strconv.AppendInt(b, n, 10) + b = append(b, ',') + } + if len(ints) > 0 { + b[len(b)-1] = '}' // Replace trailing comma. + } else { + b = append(b, '}') + } + + b = append(b, '\'') + + return b +} + +func appendFloat64SliceValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte { + floats := v.Convert(sliceFloat64Type).Interface().([]float64) + return appendFloat64Slice(b, floats) +} + +func appendFloat64Slice(b []byte, floats []float64) []byte { + if floats == nil { + return dialect.AppendNull(b) + } + + b = append(b, '\'') + + b = append(b, '{') + for _, n := range floats { + b = dialect.AppendFloat64(b, n) + b = append(b, ',') + } + if len(floats) > 0 { + b[len(b)-1] = '}' // Replace trailing comma. + } else { + b = append(b, '}') + } + + b = append(b, '\'') + + return b +} + +//------------------------------------------------------------------------------ + +func arrayAppendBytes(b []byte, bs []byte) []byte { + if bs == nil { + return dialect.AppendNull(b) + } + + b = append(b, `"\\x`...) + + s := len(b) + b = append(b, make([]byte, hex.EncodedLen(len(bs)))...) + hex.Encode(b[s:], bs) + + b = append(b, '"') + + return b +} + +func arrayAppendString(b []byte, s string) []byte { + b = append(b, '"') + for _, r := range s { + switch r { + case 0: + // ignore + case '\'': + b = append(b, "''"...) + case '"': + b = append(b, '\\', '"') + case '\\': + b = append(b, '\\', '\\') + default: + if r < utf8.RuneSelf { + b = append(b, byte(r)) + break + } + l := len(b) + if cap(b)-l < utf8.UTFMax { + b = append(b, make([]byte, utf8.UTFMax)...) + } + n := utf8.EncodeRune(b[l:l+utf8.UTFMax], r) + b = b[:l+n] + } + } + b = append(b, '"') + return b +} + +//------------------------------------------------------------------------------ + +var mapStringStringType = reflect.TypeOf(map[string]string(nil)) + +func (d *Dialect) hstoreAppender(typ reflect.Type) schema.AppenderFunc { + kind := typ.Kind() + + switch kind { + case reflect.Ptr: + if fn := d.hstoreAppender(typ.Elem()); fn != nil { + return schema.PtrAppender(fn) + } + case reflect.Map: + // ok: + default: + return nil + } + + if typ.Key() == stringType && typ.Elem() == stringType { + return appendMapStringStringValue + } + + return func(fmter schema.Formatter, b []byte, v reflect.Value) []byte { + err := fmt.Errorf("ogx: Hstore(unsupported %s)", v.Type()) + return dialect.AppendError(b, err) + } +} + +func appendMapStringString(b []byte, m map[string]string) []byte { + if m == nil { + return dialect.AppendNull(b) + } + + b = append(b, '\'') + + for key, value := range m { + b = arrayAppendString(b, key) + b = append(b, '=', '>') + b = arrayAppendString(b, value) + b = append(b, ',') + } + if len(m) > 0 { + b = b[:len(b)-1] // Strip trailing comma. + } + + b = append(b, '\'') + + return b +} + +func appendMapStringStringValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte { + m := v.Convert(mapStringStringType).Interface().(map[string]string) + return appendMapStringString(b, m) +} diff --git a/ogx/dialect/ogdialect/array.go b/ogx/dialect/ogdialect/array.go new file mode 100644 index 00000000..d453e344 --- /dev/null +++ b/ogx/dialect/ogdialect/array.go @@ -0,0 +1,65 @@ +package ogdialect + +import ( + "database/sql" + "fmt" + "reflect" + + "gitee.com/chentanyang/ogx/schema" +) + +type ArrayValue struct { + v reflect.Value + + append schema.AppenderFunc + scan schema.ScannerFunc +} + +// Array accepts a slice and returns a wrapper for working with PostgreSQL +// array data type. +// +// For struct fields you can use array tag: +// +// Emails []string `ogx:",array"` +func Array(vi interface{}) *ArrayValue { + v := reflect.ValueOf(vi) + if !v.IsValid() { + panic(fmt.Errorf("ogx: Array(nil)")) + } + + return &ArrayValue{ + v: v, + + append: ogdialect.arrayAppender(v.Type()), + scan: arrayScanner(v.Type()), + } +} + +var ( + _ schema.QueryAppender = (*ArrayValue)(nil) + _ sql.Scanner = (*ArrayValue)(nil) +) + +func (a *ArrayValue) AppendQuery(fmter schema.Formatter, b []byte) ([]byte, error) { + if a.append == nil { + panic(fmt.Errorf("ogx: Array(unsupported %s)", a.v.Type())) + } + return a.append(fmter, b, a.v), nil +} + +func (a *ArrayValue) Scan(src interface{}) error { + if a.scan == nil { + return fmt.Errorf("ogx: Array(unsupported %s)", a.v.Type()) + } + if a.v.Kind() != reflect.Ptr { + return fmt.Errorf("ogx: Array(non-pointer %s)", a.v.Type()) + } + return a.scan(a.v, src) +} + +func (a *ArrayValue) Value() interface{} { + if a.v.IsValid() { + return a.v.Interface() + } + return nil +} diff --git a/ogx/dialect/ogdialect/array_parser.go b/ogx/dialect/ogdialect/array_parser.go new file mode 100644 index 00000000..a25c18ec --- /dev/null +++ b/ogx/dialect/ogdialect/array_parser.go @@ -0,0 +1,133 @@ +package ogdialect + +import ( + "bytes" + "encoding/hex" + "fmt" + "io" +) + +type arrayParser struct { + *streamParser + err error +} + +func newArrayParser(b []byte) *arrayParser { + p := &arrayParser{ + streamParser: newStreamParser(b, 1), + } + if len(b) < 2 || b[0] != '{' || b[len(b)-1] != '}' { + p.err = fmt.Errorf("ogx: can't parse array: %q", b) + } + return p +} + +func (p *arrayParser) NextElem() ([]byte, error) { + if p.err != nil { + return nil, p.err + } + + c, err := p.readByte() + if err != nil { + return nil, err + } + + switch c { + case '}': + return nil, io.EOF + case '"': + b, err := p.readSubstring() + if err != nil { + return nil, err + } + + if p.peek() == ',' { + p.skipNext() + } + + return b, nil + default: + b := p.readSimple() + if bytes.Equal(b, []byte("NULL")) { + b = nil + } + + if p.peek() == ',' { + p.skipNext() + } + + return b, nil + } +} + +func (p *arrayParser) readSimple() []byte { + p.unreadByte() + + if i := bytes.IndexByte(p.b[p.i:], ','); i >= 0 { + b := p.b[p.i : p.i+i] + p.i += i + return b + } + + b := p.b[p.i : len(p.b)-1] + p.i = len(p.b) - 1 + return b +} + +func (p *arrayParser) readSubstring() ([]byte, error) { + c, err := p.readByte() + if err != nil { + return nil, err + } + + p.buf = p.buf[:0] + for { + if c == '"' { + break + } + + next, err := p.readByte() + if err != nil { + return nil, err + } + + if c == '\\' { + switch next { + case '\\', '"': + p.buf = append(p.buf, next) + + c, err = p.readByte() + if err != nil { + return nil, err + } + default: + p.buf = append(p.buf, '\\') + c = next + } + continue + } + if c == '\'' && next == '\'' { + p.buf = append(p.buf, next) + c, err = p.readByte() + if err != nil { + return nil, err + } + continue + } + + p.buf = append(p.buf, c) + c = next + } + + if bytes.HasPrefix(p.buf, []byte("\\x")) && len(p.buf)%2 == 0 { + data := p.buf[2:] + buf := make([]byte, hex.DecodedLen(len(data))) + n, err := hex.Decode(buf, data) + if err != nil { + return nil, err + } + return buf[:n], nil + } + + return p.buf, nil +} diff --git a/ogx/dialect/ogdialect/array_parser_test.go b/ogx/dialect/ogdialect/array_parser_test.go new file mode 100644 index 00000000..d4b64f94 --- /dev/null +++ b/ogx/dialect/ogdialect/array_parser_test.go @@ -0,0 +1,55 @@ +package ogdialect + +import ( + "io" + "testing" +) + +func TestArrayParser(t *testing.T) { + tests := []struct { + s string + els []string + }{ + {`{}`, []string{}}, + {`{""}`, []string{""}}, + {`{"\\"}`, []string{`\`}}, + {`{"''"}`, []string{"'"}}, + {`{{"'\"{}"}}`, []string{`{"'\"{}"}`}}, + {`{"'\"{}"}`, []string{`'"{}`}}, + + {"{1,2}", []string{"1", "2"}}, + {"{1,NULL}", []string{"1", ""}}, + {`{"1","2"}`, []string{"1", "2"}}, + {`{"{1}","{2}"}`, []string{"{1}", "{2}"}}, + } + + for testi, test := range tests { + p := newArrayParser([]byte(test.s)) + + var got []string + for { + s, err := p.NextElem() + if err != nil { + if err == io.EOF { + break + } + t.Fatal(err) + } + got = append(got, string(s)) + } + + if len(got) != len(test.els) { + t.Fatalf( + "test #%d got %d elements, wanted %d (got=%#v wanted=%#v)", + testi, len(got), len(test.els), got, test.els) + } + + for i, el := range got { + if el != test.els[i] { + t.Fatalf( + "test #%d el #%d does not match: %s != %s (got=%#v wanted=%#v)", + testi, i, el, test.els[i], got, test.els) + } + } + } +} diff --git a/ogx/dialect/ogdialect/array_scan.go b/ogx/dialect/ogdialect/array_scan.go new file mode 100644 index 00000000..c7032e2b --- /dev/null +++ b/ogx/dialect/ogdialect/array_scan.go @@ -0,0 +1,302 @@ +package ogdialect + +import ( + "fmt" + "io" + "reflect" + "strconv" + + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/schema" +) + +func arrayScanner(typ reflect.Type) schema.ScannerFunc { + kind := typ.Kind() + + switch kind { + case reflect.Ptr: + if fn := arrayScanner(typ.Elem()); fn != nil { + return schema.PtrScanner(fn) + } + case reflect.Slice, reflect.Array: + // ok: + default: + return nil + } + + elemType := typ.Elem() + + if kind == reflect.Slice { + switch elemType { + case stringType: + return scanStringSliceValue + case intType: + return scanIntSliceValue + case int64Type: + return scanInt64SliceValue + case float64Type: + return scanFloat64SliceValue + } + } + + scanElem := schema.Scanner(elemType) + return func(dest reflect.Value, src interface{}) error { + dest = reflect.Indirect(dest) + if !dest.CanSet() { + return fmt.Errorf("ogx: Scan(non-settable %s)", dest.Type()) + } + + kind := dest.Kind() + + if src == nil { + if kind != reflect.Slice || !dest.IsNil() { + dest.Set(reflect.Zero(dest.Type())) + } + return nil + } + + if kind == reflect.Slice { + if dest.IsNil() { + dest.Set(reflect.MakeSlice(dest.Type(), 0, 0)) + } else if dest.Len() > 0 { + dest.Set(dest.Slice(0, 0)) + } + } + + b, err := toBytes(src) + if err != nil { + return err + } + + p := newArrayParser(b) + nextValue := internal.MakeSliceNextElemFunc(dest) + for { + elem, err := p.NextElem() + if err != nil { + if err == io.EOF { + break + } + return err + } + + elemValue := nextValue() + if err := scanElem(elemValue, elem); err != nil { + return err + } + } + + return nil + } +} + +func scanStringSliceValue(dest reflect.Value, src interface{}) error { + dest = reflect.Indirect(dest) + if !dest.CanSet() { + return fmt.Errorf("ogx: Scan(non-settable %s)", dest.Type()) + } + + slice, err := decodeStringSlice(src) + if err != nil { + return err + } + + dest.Set(reflect.ValueOf(slice)) + return nil +} + +func decodeStringSlice(src interface{}) ([]string, error) { + if src == nil { + return nil, nil + } + + b, err := toBytes(src) + if err != nil { + return nil, err + } + + slice := make([]string, 0) + + p := newArrayParser(b) + for { + elem, err := p.NextElem() + if err != nil { + if err == io.EOF { + break + } + return nil, err + } + slice = append(slice, string(elem)) + } + + return slice, nil +} + +func scanIntSliceValue(dest reflect.Value, src interface{}) error { + dest = reflect.Indirect(dest) + if !dest.CanSet() { + return fmt.Errorf("ogx: Scan(non-settable %s)", dest.Type()) + } + + slice, err := decodeIntSlice(src) + if err != nil { + return err + } + + dest.Set(reflect.ValueOf(slice)) + return nil +} + +func decodeIntSlice(src interface{}) ([]int, error) { + if src == nil { + return nil, nil + } + + b, err := toBytes(src) + if err != nil { + return nil, err + } + + slice := make([]int, 0) + + p := newArrayParser(b) + for { + elem, err := p.NextElem() + if err != nil { + if err == io.EOF { + break + } + return nil, err + } + + if elem == nil { + slice = append(slice, 0) + continue + } + + n, err := strconv.Atoi(bytesToString(elem)) + if err != nil { + return nil, err + } + + slice = append(slice, n) + } + + return slice, nil +} + +func scanInt64SliceValue(dest reflect.Value, src interface{}) error { + dest = reflect.Indirect(dest) + if !dest.CanSet() { + return fmt.Errorf("ogx: Scan(non-settable %s)", dest.Type()) + } + + slice, err := decodeInt64Slice(src) + if err != nil { + return err + } + + dest.Set(reflect.ValueOf(slice)) + return nil +} + +func decodeInt64Slice(src interface{}) ([]int64, error) { + if src == nil { + return nil, nil + } + + b, err := toBytes(src) + if err != nil { + return nil, err + } + + slice := make([]int64, 0) + + p := newArrayParser(b) + for { + elem, err := p.NextElem() + if err != nil { + if err == io.EOF { + break + } + return nil, err + } + + if elem == nil { + slice = append(slice, 0) + continue + } + + n, err := strconv.ParseInt(bytesToString(elem), 10, 64) + if err != nil { + return nil, err + } + + slice = append(slice, n) + } + + return slice, nil +} + +func scanFloat64SliceValue(dest reflect.Value, src interface{}) error { + dest = reflect.Indirect(dest) + if !dest.CanSet() { + return fmt.Errorf("ogx: Scan(non-settable %s)", dest.Type()) + } + + slice, err := scanFloat64Slice(src) + if err != nil { + return err + } + + dest.Set(reflect.ValueOf(slice)) + return nil +} + +func scanFloat64Slice(src interface{}) ([]float64, error) { + if src == -1 { + return nil, nil + } + + b, err := toBytes(src) + if err != nil { + return nil, err + } + + slice := make([]float64, 0) + + p := newArrayParser(b) + for { + elem, err := p.NextElem() + if err != nil { + if err == io.EOF { + break + } + return nil, err + } + + if elem == nil { + slice = append(slice, 0) + continue + } + + n, err := strconv.ParseFloat(bytesToString(elem), 64) + if err != nil { + return nil, err + } + + slice = append(slice, n) + } + + return slice, nil +} + +func toBytes(src interface{}) ([]byte, error) { + switch src := src.(type) { + case string: + return stringToBytes(src), nil + case []byte: + return src, nil + default: + return nil, fmt.Errorf("ogx: got %T, wanted []byte or string", src) + } +} diff --git a/ogx/dialect/ogdialect/dialect.go b/ogx/dialect/ogdialect/dialect.go new file mode 100644 index 00000000..e95ebb5d --- /dev/null +++ b/ogx/dialect/ogdialect/dialect.go @@ -0,0 +1,110 @@ +package ogdialect + +import ( + "database/sql" + "fmt" + "strconv" + "strings" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/dialect" + "gitee.com/chentanyang/ogx/dialect/feature" + "gitee.com/chentanyang/ogx/dialect/sqltype" + "gitee.com/chentanyang/ogx/schema" +) + +var ogdialect = New() + +func init() { + if Version() != ogx.Version() { + panic(fmt.Errorf("ogdialect and Ogx must have the same version: v%s != v%s", + Version(), ogx.Version())) + } +} + +type Dialect struct { + schema.BaseDialect + + tables *schema.Tables + features feature.Feature +} + +func New() *Dialect { + d := new(Dialect) + d.tables = schema.NewTables(d) + d.features = feature.CTE | + feature.WithValues | + feature.Returning | + feature.InsertReturning | + feature.DefaultPlaceholder | + feature.DoubleColonCast | + feature.InsertTableAlias | + feature.UpdateTableAlias | + feature.DeleteTableAlias | + feature.TableCascade | + feature.TableIdentity | + feature.TableTruncate | + feature.TableNotExists | + feature.InsertOnConflict | + feature.SelectExists | + feature.GeneratedIdentity | + feature.CompositeIn + return d +} + +func (d *Dialect) Init(*sql.DB) {} + +func (d *Dialect) Name() dialect.Name { + return dialect.PG +} + +func (d *Dialect) Features() feature.Feature { + return d.features +} + +func (d *Dialect) Tables() *schema.Tables { + return d.tables +} + +func (d *Dialect) OnTable(table *schema.Table) { + for _, field := range table.FieldMap { + d.onField(field) + } +} + +func (d *Dialect) onField(field *schema.Field) { + field.DiscoveredSQLType = fieldSQLType(field) + + if field.AutoIncrement && !field.Identity { + switch field.DiscoveredSQLType { + case sqltype.SmallInt: + field.CreateTableSQLType = pgTypeSmallSerial + case sqltype.Integer: + field.CreateTableSQLType = pgTypeSerial + case sqltype.BigInt: + field.CreateTableSQLType = pgTypeBigSerial + } + } + + if field.Tag.HasOption("array") || strings.HasSuffix(field.UserSQLType, "[]") { + field.Append = d.arrayAppender(field.StructField.Type) + field.Scan = arrayScanner(field.StructField.Type) + } + + if field.DiscoveredSQLType == sqltype.HSTORE { + field.Append = d.hstoreAppender(field.StructField.Type) + field.Scan = hstoreScanner(field.StructField.Type) + } +} + +func (d *Dialect) IdentQuote() byte { + return '"' +} + +func (d *Dialect) AppendUint32(b []byte, n uint32) []byte { + return strconv.AppendInt(b, int64(int32(n)), 10) +} + +func (d *Dialect) AppendUint64(b []byte, n uint64) []byte { + return strconv.AppendInt(b, int64(n), 10) +} diff --git a/ogx/dialect/ogdialect/go.mod b/ogx/dialect/ogdialect/go.mod new file mode 100644 index 00000000..4c47233d --- /dev/null +++ b/ogx/dialect/ogdialect/go.mod @@ -0,0 +1,15 @@ +module gitee.com/chentanyang/ogx/dialect/ogdialect + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +require gitee.com/chentanyang/ogx v1.0.0 + +require ( + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect +) diff --git a/ogx/dialect/ogdialect/go.sum b/ogx/dialect/ogdialect/go.sum new file mode 100644 index 00000000..56ac8caa --- /dev/null +++ b/ogx/dialect/ogdialect/go.sum @@ -0,0 +1,20 @@ +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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ogx/dialect/ogdialect/hstore.go b/ogx/dialect/ogdialect/hstore.go new file mode 100644 index 00000000..341efb57 --- /dev/null +++ b/ogx/dialect/ogdialect/hstore.go @@ -0,0 +1,73 @@ +package ogdialect + +import ( + "database/sql" + "fmt" + "reflect" + + "gitee.com/chentanyang/ogx/schema" +) + +type HStoreValue struct { + v reflect.Value + + append schema.AppenderFunc + scan schema.ScannerFunc +} + +// HStore accepts a map[string]string and returns a wrapper for working with PostgreSQL +// hstore data type. +// +// For struct fields you can use hstore tag: +// +// Attrs map[string]string `ogx:",hstore"` +func HStore(vi interface{}) *HStoreValue { + v := reflect.ValueOf(vi) + if !v.IsValid() { + panic(fmt.Errorf("ogx: HStore(nil)")) + } + + typ := v.Type() + if typ.Kind() == reflect.Ptr { + typ = typ.Elem() + } + if typ.Kind() != reflect.Map { + panic(fmt.Errorf("ogx: Hstore(unsupported %s)", typ)) + } + + return &HStoreValue{ + v: v, + + append: ogdialect.hstoreAppender(v.Type()), + scan: hstoreScanner(v.Type()), + } +} + +var ( + _ schema.QueryAppender = (*HStoreValue)(nil) + _ sql.Scanner = (*HStoreValue)(nil) +) + +func (h *HStoreValue) AppendQuery(fmter schema.Formatter, b []byte) ([]byte, error) { + if h.append == nil { + panic(fmt.Errorf("ogx: HStore(unsupported %s)", h.v.Type())) + } + return h.append(fmter, b, h.v), nil +} + +func (h *HStoreValue) Scan(src interface{}) error { + if h.scan == nil { + return fmt.Errorf("ogx: HStore(unsupported %s)", h.v.Type()) + } + if h.v.Kind() != reflect.Ptr { + return fmt.Errorf("ogx: HStore(non-pointer %s)", h.v.Type()) + } + return h.scan(h.v.Elem(), src) +} + +func (h *HStoreValue) Value() interface{} { + if h.v.IsValid() { + return h.v.Interface() + } + return nil +} diff --git a/ogx/dialect/ogdialect/hstore_parser.go b/ogx/dialect/ogdialect/hstore_parser.go new file mode 100644 index 00000000..d12c337c --- /dev/null +++ b/ogx/dialect/ogdialect/hstore_parser.go @@ -0,0 +1,142 @@ +package ogdialect + +import ( + "bytes" + "fmt" +) + +type hstoreParser struct { + *streamParser + err error +} + +func newHStoreParser(b []byte) *hstoreParser { + p := &hstoreParser{ + streamParser: newStreamParser(b, 0), + } + if len(b) < 6 || b[0] != '"' { + p.err = fmt.Errorf("ogx: can't parse hstore: %q", b) + } + return p +} + +func (p *hstoreParser) NextKey() (string, error) { + if p.err != nil { + return "", p.err + } + + err := p.skipByte('"') + if err != nil { + return "", err + } + + key, err := p.readSubstring() + if err != nil { + return "", err + } + + const separator = "=>" + + for i := range separator { + err = p.skipByte(separator[i]) + if err != nil { + return "", err + } + } + + return string(key), nil +} + +func (p *hstoreParser) NextValue() (string, error) { + if p.err != nil { + return "", p.err + } + + c, err := p.readByte() + if err != nil { + return "", err + } + + switch c { + case '"': + value, err := p.readSubstring() + if err != nil { + return "", err + } + + if p.peek() == ',' { + p.skipNext() + } + + if p.peek() == ' ' { + p.skipNext() + } + + return string(value), nil + default: + value := p.readSimple() + if bytes.Equal(value, []byte("NULL")) { + value = nil + } + + if p.peek() == ',' { + p.skipNext() + } + + return string(value), nil + } +} + +func (p *hstoreParser) readSimple() []byte { + p.unreadByte() + + if i := bytes.IndexByte(p.b[p.i:], ','); i >= 0 { + b := p.b[p.i : p.i+i] + p.i += i + return b + } + + b := p.b[p.i:len(p.b)] + p.i = len(p.b) + return b +} + +func (p *hstoreParser) readSubstring() ([]byte, error) { + c, err := p.readByte() + if err != nil { + return nil, err + } + + p.buf = p.buf[:0] + for { + if c == '"' { + break + } + + next, err := p.readByte() + if err != nil { + return nil, err + } + + if c == '\\' { + switch next { + case '\\', '"': + p.buf = append(p.buf, next) + + c, err = p.readByte() + if err != nil { + return nil, err + } + default: + p.buf = append(p.buf, '\\') + c = next + } + continue + } + + p.buf = append(p.buf, c) + c = next + } + + return p.buf, nil +} diff --git a/ogx/dialect/ogdialect/hstore_parser_test.go b/ogx/dialect/ogdialect/hstore_parser_test.go new file mode 100644 index 00000000..70623661 --- /dev/null +++ b/ogx/dialect/ogdialect/hstore_parser_test.go @@ -0,0 +1,62 @@ +package ogdialect + +import ( + "io" + "testing" +) + +func TestHStoreParser(t *testing.T) { + tests := []struct { + s string + m map[string]string + }{ + {`""=>""`, map[string]string{"": ""}}, + {`"\\"=>"\\"`, map[string]string{`\`: `\`}}, + {`"'"=>"'"`, map[string]string{"'": "'"}}, + {`"'\"{}"=>"'\"{}"`, map[string]string{`'"{}`: `'"{}`}}, + + {`"1"=>"2", "3"=>"4"`, map[string]string{"1": "2", "3": "4"}}, + {`"1"=>NULL`, map[string]string{"1": ""}}, + {`"1"=>"NULL"`, map[string]string{"1": "NULL"}}, + {`"{1}"=>"{2}", "{3}"=>"{4}"`, map[string]string{"{1}": "{2}", "{3}": "{4}"}}, + } + + for testi, test := range tests { + p := newHStoreParser([]byte(test.s)) + + got := make(map[string]string) + for { + key, err := p.NextKey() + if err != nil { + if err == io.EOF { + break + } + t.Fatal(err) + } + + value, err := p.NextValue() + if err != nil { + if err == io.EOF { + break + } + t.Fatal(err) + } + + got[key] = value + } + + if len(got) != len(test.m) { + t.Fatalf( + "test #%d got %d elements, wanted %d (got=%#v wanted=%#v)", + testi, len(got), len(test.m), got, test.m) + } + + for k, v := range got { + if v != test.m[k] { + t.Fatalf( + "test #%d key #%s does not match: %s != %s (got=%#v wanted=%#v)", + testi, k, v, test.m[k], got, test.m) + } + } + } +} diff --git a/ogx/dialect/ogdialect/hstore_scan.go b/ogx/dialect/ogdialect/hstore_scan.go new file mode 100644 index 00000000..45b7999d --- /dev/null +++ b/ogx/dialect/ogdialect/hstore_scan.go @@ -0,0 +1,82 @@ +package ogdialect + +import ( + "fmt" + "io" + "reflect" + + "gitee.com/chentanyang/ogx/schema" +) + +func hstoreScanner(typ reflect.Type) schema.ScannerFunc { + kind := typ.Kind() + + switch kind { + case reflect.Ptr: + if fn := hstoreScanner(typ.Elem()); fn != nil { + return schema.PtrScanner(fn) + } + case reflect.Map: + // ok: + default: + return nil + } + + if typ.Key() == stringType && typ.Elem() == stringType { + return scanMapStringStringValue + } + return func(dest reflect.Value, src interface{}) error { + return fmt.Errorf("ogx: Hstore(unsupported %s)", dest.Type()) + } +} + +func scanMapStringStringValue(dest reflect.Value, src interface{}) error { + dest = reflect.Indirect(dest) + if !dest.CanSet() { + return fmt.Errorf("ogx: Scan(non-settable %s)", dest.Type()) + } + + m, err := decodeMapStringString(src) + if err != nil { + return err + } + + dest.Set(reflect.ValueOf(m)) + return nil +} + +func decodeMapStringString(src interface{}) (map[string]string, error) { + if src == nil { + return nil, nil + } + + b, err := toBytes(src) + if err != nil { + return nil, err + } + + m := make(map[string]string) + + p := newHStoreParser(b) + for { + key, err := p.NextKey() + if err != nil { + if err == io.EOF { + break + } + return nil, err + } + + value, err := p.NextValue() + if err != nil { + if err == io.EOF { + break + } + return nil, err + } + + m[key] = value + } + + return m, nil +} diff --git a/ogx/dialect/ogdialect/safe.go b/ogx/dialect/ogdialect/safe.go new file mode 100644 index 00000000..4ec9db7f --- /dev/null +++ b/ogx/dialect/ogdialect/safe.go @@ -0,0 +1,12 @@ +//go:build appengine +// +build appengine + +package ogdialect + +func bytesToString(b []byte) string { + return string(b) +} + +func stringToBytes(s string) []byte { + return []byte(s) +} diff --git a/ogx/dialect/ogdialect/scan.go b/ogx/dialect/ogdialect/scan.go new file mode 100644 index 00000000..fd2b3e3a --- /dev/null +++ b/ogx/dialect/ogdialect/scan.go @@ -0,0 +1,11 @@ +package ogdialect + +import ( + "reflect" + + "gitee.com/chentanyang/ogx/schema" +) + +func scanner(typ reflect.Type) schema.ScannerFunc { + return schema.Scanner(typ) +} diff --git a/ogx/dialect/ogdialect/sqltype.go b/ogx/dialect/ogdialect/sqltype.go new file mode 100644 index 00000000..5fb1e479 --- /dev/null +++ b/ogx/dialect/ogdialect/sqltype.go @@ -0,0 +1,105 @@ +package ogdialect + +import ( + "encoding/json" + "net" + "reflect" + + "gitee.com/chentanyang/ogx/dialect/sqltype" + "gitee.com/chentanyang/ogx/schema" +) + +const ( + // Date / Time + pgTypeTimestampTz = "TIMESTAMPTZ" // Timestamp with a time zone + pgTypeDate = "DATE" // Date + pgTypeTime = "TIME" // Time without a time zone + pgTypeTimeTz = "TIME WITH TIME ZONE" // Time with a time zone + pgTypeInterval = "INTERVAL" // Time Interval + + // Network Addresses + pgTypeInet = "INET" // IPv4 or IPv6 hosts and networks + pgTypeCidr = "CIDR" // IPv4 or IPv6 networks + pgTypeMacaddr = "MACADDR" // MAC addresses + + // Serial Types + pgTypeSmallSerial = "SMALLSERIAL" // 2 byte autoincrementing integer + pgTypeSerial = "SERIAL" // 4 byte autoincrementing integer + pgTypeBigSerial = "BIGSERIAL" // 8 byte autoincrementing integer + + // Character Types + pgTypeChar = "CHAR" // fixed length string (blank padded) + pgTypeText = "TEXT" // variable length string without limit + + // JSON Types + pgTypeJSON = "JSON" // text representation of json data + pgTypeJSONB = "JSONB" // binary representation of json data + + // Binary Data Types + pgTypeBytea = "BYTEA" // binary string +) + +var ( + ipType = reflect.TypeOf((*net.IP)(nil)).Elem() + ipNetType = reflect.TypeOf((*net.IPNet)(nil)).Elem() + jsonRawMessageType = reflect.TypeOf((*json.RawMessage)(nil)).Elem() +) + +func fieldSQLType(field *schema.Field) string { + if field.UserSQLType != "" { + return field.UserSQLType + } + + if v, ok := field.Tag.Option("composite"); ok { + return v + } + if field.Tag.HasOption("hstore") { + return sqltype.HSTORE + } + + if field.Tag.HasOption("array") { + switch field.IndirectType.Kind() { + case reflect.Slice, reflect.Array: + sqlType := sqlType(field.IndirectType.Elem()) + return sqlType + "[]" + } + } + + if field.DiscoveredSQLType == sqltype.Blob { + return pgTypeBytea + } + + return sqlType(field.IndirectType) +} + +func sqlType(typ reflect.Type) string { + switch typ { + case ipType: + return pgTypeInet + case ipNetType: + return pgTypeCidr + case jsonRawMessageType: + return pgTypeJSONB + } + + sqlType := schema.DiscoverSQLType(typ) + switch sqlType { + case sqltype.Timestamp: + sqlType = pgTypeTimestampTz + } + + switch typ.Kind() { + case reflect.Map, reflect.Struct: + if sqlType == sqltype.VarChar { + return pgTypeJSONB + } + return sqlType + case reflect.Array, reflect.Slice: + if typ.Elem().Kind() == reflect.Uint8 { + return pgTypeBytea + } + return pgTypeJSONB + } + + return sqlType +} diff --git a/ogx/dialect/ogdialect/stream_parser.go b/ogx/dialect/ogdialect/stream_parser.go new file mode 100644 index 00000000..cd138ab0 --- /dev/null +++ b/ogx/dialect/ogdialect/stream_parser.go @@ -0,0 +1,60 @@ +package ogdialect + +import ( + "fmt" + "io" +) + +type streamParser struct { + b []byte + i int + + buf []byte +} + +func newStreamParser(b []byte, start int) *streamParser { + return &streamParser{ + b: b, + i: start, + } +} + +func (p *streamParser) valid() bool { + return p.i < len(p.b) +} + +func (p *streamParser) skipByte(skip byte) error { + c, err := p.readByte() + if err != nil { + return err + } + if c == skip { + return nil + } + p.unreadByte() + return fmt.Errorf("got %q, wanted %q", c, skip) +} + +func (p *streamParser) readByte() (byte, error) { + if p.valid() { + c := p.b[p.i] + p.i++ + return c, nil + } + return 0, io.EOF +} + +func (p *streamParser) unreadByte() { + p.i-- +} + +func (p *streamParser) peek() byte { + if p.valid() { + return p.b[p.i] + } + return 0 +} + +func (p *streamParser) skipNext() { + p.i++ +} diff --git a/ogx/dialect/ogdialect/unsafe.go b/ogx/dialect/ogdialect/unsafe.go new file mode 100644 index 00000000..215d3c90 --- /dev/null +++ b/ogx/dialect/ogdialect/unsafe.go @@ -0,0 +1,19 @@ +//go:build !appengine +// +build !appengine + +package ogdialect + +import "unsafe" + +func bytesToString(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + +func stringToBytes(s string) []byte { + return *(*[]byte)(unsafe.Pointer( + &struct { + string + Cap int + }{s, len(s)}, + )) +} diff --git a/ogx/dialect/ogdialect/version.go b/ogx/dialect/ogdialect/version.go new file mode 100644 index 00000000..0317dbae --- /dev/null +++ b/ogx/dialect/ogdialect/version.go @@ -0,0 +1,6 @@ +package ogdialect + +// Version is the current release version. +func Version() string { + return "1.0.0" +} diff --git a/ogx/dialect/sqltype/sqltype.go b/ogx/dialect/sqltype/sqltype.go new file mode 100644 index 00000000..1031fd35 --- /dev/null +++ b/ogx/dialect/sqltype/sqltype.go @@ -0,0 +1,16 @@ +package sqltype + +const ( + Boolean = "BOOLEAN" + SmallInt = "SMALLINT" + Integer = "INTEGER" + BigInt = "BIGINT" + Real = "REAL" + DoublePrecision = "DOUBLE PRECISION" + VarChar = "VARCHAR" + Blob = "BLOB" + Timestamp = "TIMESTAMP" + JSON = "JSON" + JSONB = "JSONB" + HSTORE = "HSTORE" +) diff --git a/ogx/docs/.keep b/ogx/docs/.keep new file mode 100644 index 00000000..e69de29b diff --git "a/ogx/docs/Ogx \344\275\277\347\224\250\346\240\267\344\276\213\346\226\207\346\241\243.md" "b/ogx/docs/Ogx \344\275\277\347\224\250\346\240\267\344\276\213\346\226\207\346\241\243.md" new file mode 100644 index 00000000..f9150c6d --- /dev/null +++ "b/ogx/docs/Ogx \344\275\277\347\224\250\346\240\267\344\276\213\346\226\207\346\241\243.md" @@ -0,0 +1,1774 @@ +![](./Ogx_logo.png) +# 简介 +Ogx是一个openGauss数据库的GO实现的ORM。使用Ogx编写代码仿佛直接构造、操作SQL语句,相比于其他ORM更加SQL-like。除此之外,Ogx对原有代码较小的破坏性,可以方便的集成到原有项目当中。 +# 教程 +## Declaring Models 声明模型 +### 表到go结构体的映射 +对于每个表,需要定义一个相应的 Go struct(即模型)。ogx 将暴露的结构字段映射到表列并忽略未暴露的字段。 +``` +type User struct { + ogx.BaseModel `ogx:"table:users,alias:u"` + + ID int64 `ogx:"id,pk,autoincrement"` + Name string `ogx:"name,notnull"` + email string // unexported fields are ignored +} +``` +### struct tags +ogx会使用默认值来生成和限制,但是您可以使用以下tag标记来覆盖默认值。 + +| TAG | 作用 | +| --- | --- | +| ogx.BaseModel `ogx:"table:table_name" | 覆盖默认表名。 | +| ogx.BaseModel `ogx:"alias:table_alias"` | 覆盖默认表别名。 | +| ogx.BaseModel `ogx:"select:view_name"` | 覆盖 SELECT 查询的表名。 | +| ogx:"-" | 忽略字段。 | +| ogx:"column_name" | 覆盖默认列名。 | +| ogx:"alt:alt_name" | 替代列名。Migrations时使用。 | +| ogx:",pk" | 将列标记为主键并应用notnull选项。支持多个/复合主键。 | +| ogx:",autoincrement" | 标记为自动增量 | +| ogx:"type:uuid" | 覆盖默认 SQL 类型。 | +| ogx:"default:gen_random_uuid()" | 使CreateTable设置DEFAULT表达式。 | +| ogx:",notnull" | 使CreateTable添加NOT NULL约束。 | +| ogx:",unique" | 使CreateTable添加一个唯一的约束。 | +| ogx:",unique:group_name" | 一组列的唯一约束。 | +| ogx:",nullzero" | Marshal Go 零值作为 SQLNULL或DEFAULT(如果支持)。 | +| ogx:",scanonly" | 仅使用该字段扫描查询结果并在 SELECT/INSERT/UPDATE/DELETE 中忽略。 | +| ogx:",array" | 使用 PostgreSQL 数组。 | +| ogx:",json_use_number" | 用json.Decoder.UseNumber解码 JSON。 | +| ogx:",msgpack" | 使用 MessagePack 编码/解码数据。 | +| DeletedAt time.Time `ogx:",soft_delete"` | 在Model上启用软删除 | + +### 表名 + +ogx 通过下划线从结构名称生成表名和别名。ogx会将表名变成复数,例如, struct`ArticleCategory`获取表名`article_categories`和别名`article_category`。 +要覆盖生成的名称和别名: +```cpp +type User struct { + ogx.BaseModel `ogx:"table:myusers,alias:u"` +} +``` +要为查询指定不同的表名`SELECT`: +```cpp +type User struct { + ogx.BaseModel `ogx:"select:users_view,alias:u"` +} +``` +#### ModelTableExpr() +使用该`ModelTableExpr()`方法,您可以覆盖结构表名称,但不能覆盖别名。`ModelTableExpr()`应始终使用相同的表别名,例如: +```cpp +type User struct { + ogx.BaseModel `ogx:"table:myusers,alias:u"` +} + +// Good. +db.NewSelect().Model(&User{}).ModelTableExpr("all_users AS u") +db.NewSelect().Model(&User{}).ModelTableExpr("deleted_users AS u") + +// Bad. +db.NewSelect().Model(&User{}).ModelTableExpr("all_users AS user") +db.NewSelect().Model(&User{}).ModelTableExpr("deleted_users AS deleted") +``` +### 列名 +ogx通过下划线从结构字段名称生成列名称。例如, struct fieldUserID获取列名user_id。 +要覆盖生成的列名: +```cpp +type User struct { + Name string `ogx:"myname"` +} +``` +### 列类型 +Ogx 从结构字段类型生成列类型。例如, Go 类型 `string`被翻译为 SQL 类型`varchar`。 +要覆盖生成的列类型: +```cpp +type User struct { + ID int64 `ogx:"type:integer"` +} +``` +### NULLS +要表示 SQL NULL,您可以使用指针或sql.Null*类型: +```cpp +type Item struct { + Active *bool + // or + Active sql.NullBool +} +``` +例如: + +- `(*bool)(nil)`并`sql.NullBool{}`表示`NULL`。 +- `(*bool)(false)`并`sql.NullBool{Valid: true}`表示`FALSE`。 +- `(*bool)(true)`并`sql.NullBool{Valid: true, Value: true}`表示`TRUE`。 + +要将Go 零值编组为`NULL`,请使用`nullzero`标记 +```cpp +type User struct { + Name string `ogx:",nullzero"` +} +``` +### DEFAULT +要指定默认 SQL 表达式,请使用`nullzero`、`notnull`和`default`标记的组合: +```cpp +type User struct { + Name string `ogx:",nullzero,notnull,default:'unknown'"` +} + +err := db.NewCreateTable().Model((*User)(nil)).Exec(ctx) +``` +```cpp +CREATE TABLE users ( + name text NOT NULL DEFAULT 'unknown' +); +``` +### timestamps +使用以下代码自动设置创建和更新时间`INSERT`: +```cpp +type User struct { + CreatedAt time.Time `ogx:",nullzero,notnull,default:current_timestamp"` + UpdatedAt time.Time `ogx:",nullzero,notnull,default:current_timestamp"` +} +``` +如果您不想设置更新时间,请使用`ogx.NullTime`: +```cpp +type User struct { + CreatedAt time.Time `ogx:",nullzero,notnull,default:current_timestamp"` + UpdatedAt ogx.NullTime +} +``` +您还可以使用hook来设置结构字段: +```cpp +var _ ogx.BeforeAppendModelHook = (*User)(nil) + +func (u *User) BeforeAppendModel(ctx context.Context, query ogx.Query) error { + switch query.(type) { + case *ogx.InsertQuery: + u.CreatedAt = time.Now() + case *ogx.UpdateQuery: + u.UpdatedAt = time.Now() + } + return nil +} +``` +### extend +extend您可以使用标签选项在现有模型中添加/删除字段。新模型将从原始模型继承表名和别名。 +```cpp +type UserWithCount struct { + User `ogx:",extend"` + + Name string `ogx:"-"` // remove this field + AvatarCount int // add a new field +} +``` +### 嵌入结构 +ogx 允许使用前缀将模型嵌入到另一个模型中,例如: +```cpp +type Role struct { + Name string + Users Permissions `ogx:"embed:users_"` + Profiles Permissions `ogx:"embed:profiles_"` + Roles Permissions `ogx:"embed:roles_"` +} + +type Permissions struct { + View bool + Create bool + Update bool + Delete bool +} +``` +上面的代码生成下表: +```cpp +CREATE TABLE roles ( + name TEXT, + + users_view BOOLEAN, + users_create BOOLEAN, + users_update BOOLEAN, + users_delete BOOLEAN, + + profiles_view BOOLEAN, + profiles_create BOOLEAN, + profiles_update BOOLEAN, + profiles_delete BOOLEAN, + + roles_view BOOLEAN, + roles_create BOOLEAN, + roles_update BOOLEAN, + roles_delete BOOLEAN +); +``` +## CRUD接口 +### 编写查询 +#### 查询的设计 +建议您首先使用数据库的命令行工具编写查询并测试,然后使用ogx构建对应的查询。 +ogx查询语句的特点: + +- 将一个长查询按逻辑分成一个一个块 +- 使用转义值替换占位符(ogx.Ident和ogx.Safe) + +比如下面的查询代码 +```go +err := db.NewSelect(). + Model(book). + ColumnExpr("lower(name)"). + Where("? = ?", ogx.Ident("id"), "some-id"). + Scan(ctx) +``` +对应的SQL查询语句 +```go +SELECT lower(name) +FROM "books" +WHERE "id" = 'some-id' +``` +#### Scan与Exec +组建了一个查询后,需要Exec()执行 +```go +result, err := db.NewInsert().Model(&user).Exec(ctx) +``` +对于select语句还可以使用Scan() +```go +err := db.NewSelect().Model(&user).Where("id = 1").Scan(ctx) +``` +默认情况下Exec()将列扫描到模型中,但您也可以指定不同的目标: +```go +err := db.NewSelect().Model((*User)(nil)).Where("id = 1").Scan(ctx, &user) +``` +可以将结果扫描至: + +- struct +- map[string]interface{} +- 普通变量 +- 由以上几种组成的slices +```go +// Scan into a map. +m := make(map[string]interface{}) +err := db.NewSelect().Model(&user).Where("id = 1").Scan(ctx, &m) + +// Scan into a slice of maps. +ms := make([]map[string]interface{}, 0) +err := db.NewSelect().Model(&user).Limit(100).Scan(ctx, &ms) + +// Scan into a var. +var name string +err := db.NewSelect().Model(&user).Column("name").Where("id = 1").Scan(ctx, &name) + +// Scan columns into separate slices. +var ids []int64 +var names []string +err := db.NewSelect().Model(&user).Column("id", "name").Limit(100).Scan(ctx, &ids, &names) +``` +#### ogx.IDB +igx 提供igx.IDB接口,您可以使用它来接受igx.DB、igx.Tx和igx.Conn: +```go +func InsertUser(ctx context.Context, db igx.IDB, user *User) error { + _, err := db.NewInsert().Model(user).Exec(ctx) + return err +} + +err := InsertUser(ctx, db, user) + +err := db.RunInTx(ctx, nil, func(ctx context.Context, tx ogx.Tx) error { + return InsertUser(ctx, tx, user) +}) +``` +#### 扫描行 +执行查询语句并并扫描所有行: +```go +rows, err := db.QueryContext(ctx, "SELECT * FROM users") +if err != nil { + panic(err) +} + +err = db.ScanRows(ctx, rows, &users) +``` +逐行扫描 +```go +rows, err := db.NewSelect().Model((*User)(nil)).Rows(ctx) +if err != nil { + panic(err) +} +defer rows.Close() + +for rows.Next() { + user := new(User) + if err := db.ScanRow(ctx, rows, user); err != nil { + panic(err) + } +} + +if err := rows.Err(); err != nil { + panic(err) +} +``` +#### Scanonly +Scanonly可以使有些字段无法被插入和更新,但是还是可以被扫描。 +```go +type Model struct { + Foo string +- Bar string `"ogx:"-"` ++ Bar string `"ogx:",scanonly"` +} +``` +#### WithDiscardUnknownColumns() +WitchDiscardUnknownColunms()可以丢弃未知的SQL列 +```go +db := ogx.NewDB(sqldb, ogdialect.New(), ogx.WithDiscardUnknownColumns()) +``` +如果要忽略单个列,只需加下划线即可: +```go +err := db.NewSelect(). + ColumnExpr("1 AS _rank"). // ignore the column when scanning + OrderExpr("_rank DESC"). // but use it for sorting + Scan(ctx) +``` +### SQL placeholders +ogx在查询中识别`?`占位符并用提供的参数替换它们。ogx 引用和转义字符串值并删除空字节。 +#### 基本占位符 +基本占位符: +```go +// SELECT 'foo', 'bar' +db.ColumnExpr("?, ?", 'foo', 'bar') +``` +```go +// SELECT 'foo', 'bar', 'foo' +db.ColumnExpr("?0, ?1, ?0", 'foo', 'bar') +``` +#### ogx.Ident +引用SQL标识符(比如列名或者表名)时,使用ogx.Ident: +```go +q.ColumnExpr("? = ?", ogx.Ident("foo"), "bar") +``` +```go +"foo" = 'bar' -- openGauss +``` +#### ogx.Safe +使用ogx.Safe可以禁用双引号 +```go +q.TableExpr("(?) AS foo", ogx.Safe("generate_series(0, 10)")) +``` +```go +FROM (generate_series(0, 10)) AS foo +``` +#### IN 语句 +使用ogx.In生成IN(...)查询 +```go +// WHERE foo IN ('hello', 'world') +q.Where("foo IN (?)", ogx.In([]string{"hello", "world"})) +``` +对于复合(多个)键,您可以使用嵌套切片: +```go +// WHERE (foo, bar) IN (('hello', 'world'), ('hell', 'yeah')) +q.Where("(foo, bar) IN (?)", ogx.In([][]string{ + {"hello", "world"}, + {"hell", "yeah"}, +})) +``` +#### 模型占位符 +ogx 还支持以下模型占位符: + +- `?TableName`- 模型表名称,例如"`users`". +- `?TableAlias`- 模型表别名,例如"`user`". +- `?PKs`- 表主键,例如,"`id`" +- `?TablePKs`- 具有别名的表主键,例如,"`user`"."`id`" +- `?Columns`- 表格列,例如"`id`", "`name`", "`emails`". +- `?TableColumns`- 具有别名的表列,例如"`user`"."`id`", "`user`"."`name`", "`user`"."`emails`" +#### 全局占位符 +ogx还支持全局占位符: +```go +// db1 and db2 share the same *sql.DB, but have different named args. +db1 := db.WithNamedArg("SCHEMA", ogx.Ident("foo")) +db2 := db.WithNamedArg("SCHEMA", ogx.Ident("bar")) + +// FROM foo.table +db1.NewSelect().TableExpr("?SCHEMA.table") + +// FROM bar.table +db2.NewSelect().TableExpr("?SCHEMA.table") +``` +### SELECT +#### 常见API +```go +db.NewSelect(). + With("cte_name", subquery). + + Model(&strct). + Model(&slice). + + Column("col1", "col2"). // quotes column names + ColumnExpr("col1, col2"). // arbitrary unsafe expression + ColumnExpr("count(*)"). + ColumnExpr("count(?)", ogx.Ident("id")). + ColumnExpr("(?) AS alias", subquery). + ExcludeColumn("col1"). // all columns except col1 + ExcludeColumn("*"). // exclude all columns + + Table("table1", "table2"). // quotes table names + TableExpr("table1 AS t1"). // arbitrary unsafe expression + TableExpr("(?) AS alias", subquery). + ModelTableExpr("table1 AS t1"). // overrides model table name + + Join("JOIN table2 AS t2 ON t2.id = t1.id"). + Join("LEFT JOIN table2 AS t2").JoinOn("t2.id = t1.id"). + + WherePK(). // where using primary keys + Where("id = ?", 123). + Where("name LIKE ?", "my%"). + Where("? = 123", ogx.Ident("id")). + Where("id IN (?)", ogx.In([]int64{1, 2, 3})). + Where("id IN (?)", subquery). + Where("FALSE").WhereOr("TRUE"). + WhereGroup(" AND ", func(q *ogx.SelectQuery) *ogx.SelectQuery { + return q.WhereOr("id = 1"). + WhereOr("id = 2") + }). + + Group("col1", "col2"). // quotes column names + GroupExpr("lower(col1)"). // arbitrary unsafe expression + + Order("col1 ASC", "col2 DESC"). // quotes column names + OrderExpr("col1 ASC NULLS FIRST"). // arbitrary unsafe expression + + Having("column_name > ?", 123). + + Limit(100). + Offset(100). + + For("UPDATE"). + For("SHARE"). + + Scan(ctx) +``` +#### 示例 + +1. 定义模型并使用Select方法查询 +```go +book := new(Book) +err := db.NewSelect().Model(book).Where("id = ?", 123).Scan(ctx) +``` + +2. Count + +Ogx提供Count方法来构建count(*)查询 +```go +count, err := db.NewSelect().Model((*User)(nil)).Count(ctx) +``` +因为scan和count都是很常见的操作,ogx提供ScanAndCount函数 +```go +var users []User +count, err := db.NewSelect().Model(&users).Limit(20).ScanAndCount(ctx) +if err != nil { + panic(err) +} +fmt.Println(users, count) +``` +#### Exists +可以使用Exists方法来构建Exists表达式 +```go +exists, err := db.NewSelect().Model((*User)(nil)).Where("name LIKE '%foo%'").Exists(ctx) +if err != nil { + panic(err) +} +if !exists { + fmt.Println("such user does not exist") +} + +// SQL +// SELECT EXISTS (SELECT * FROM users WHERE name LIKE '%foo%') +``` +#### Joins +使用Join方法可以构建Join表达式 +```go +book := new(Book) +err := db.NewSelect(). + Model(book). + ColumnExpr("book.*"). + ColumnExpr("a.id AS author__id, a.name AS author__name"). + Join("JOIN authors AS a ON a.id = book.author_id"). + OrderExpr("book.id ASC"). + Limit(1). + Scan(ctx) + +// SQL +// SELECT book.*, a.id AS author__id, a.name AS author__name +// FROM books +// JOIN authors AS a ON a.id = book.author_id +// ORDER BY book.id ASC +// LIMIT 1 +``` +JoinOn方法用于构建复杂Join语句 +```go +q = q. + Join("JOIN authors AS a"). + JoinOn("a.id = book.author_id"). + JoinOn("a.deleted_at IS NULL") + +// SQL +// JOIN authors AS a ON a.id = book.author_id AND a.deleted_at IS NULL +``` +#### Subqueries +Ogx支持构建子查询 +```go +subq := db.NewSelect().Model((*Book)(nil)).Where("author_id = ?", 1) + +err := db.NewSelect().Model().TableExpr("(?) AS book", subq).Scan(ctx, &books) + +// SQL +// SELECT * FROM ( +// SELECT "book"."id", "book"."title", "book"."text" +// FROM "books" AS "book" WHERE (author_id = 1) +// ) AS book +``` +#### Raw queries +Ogx可以方便的构建原始查询 +```go +type User struct { + ID int64 + Name string +} + +users := make([]User, 0) + +err := db.NewRawQuery( + "SELECT id, name FROM ? LIMIT ?", + ogx.Ident("users"), 100, +).Scan(ctx, &users) + +// SQL +// SELECT id, name FROM "users" LIMIT 100 +``` +### WHERE +#### Basics +Where方法基本用法 +```go +q = q.Where("column LIKE 'hello%'") +``` +但是这是不安全的,您可以使用placeholders和ogx.Ident来更安全的构建Where语句 +```go +q = q.Where("? LIKE ?", ogx.Ident("mycolumn"), "hello%") +``` +#### QueryBuilder +Ogx提供QueryBuilder接口来支持构建查询所需要的常见方法 +```go +func addWhere(q ogx.QueryBuilder) ogx.QueryBuilder { + return q.Where("id = ?", 123) +} + +qb := db.NewSelect().QueryBuilder() +addWhere(qb) + +qb := db.NewUpdate().QueryBuilder() +addWhere(qb) + +qb := db.NewDelete().QueryBuilder() +addWhere(qb) + +// Alternatively. + +db.NewSelect().ApplyQueryBuilder(addWhere) +db.NewUpdate().ApplyQueryBuilder(addWhere) +db.NewDelete().ApplyQueryBuilder(addWhere) +``` +#### Where In +可以使用ogx.In来进行Where In查询 +```go +q = q.Where("user_id IN (?)", ogx.In([]int64{1, 2, 3})) +``` +#### WherePk +WherePk支持自动生成Where语句 +```go +users := []User{ + {ID: 1}, + {ID: 2}, + {ID: 3}, +} +err := db.NewSelect().Model(&users).WherePK().Scan(ctx) + +// SQL +// SELECT * FROM users WHERE id IN (1, 2, 3) +``` +WherePk也支持指定列名进行查找 +```go +users := []User{ + {Email: "one@my.com"}, + {Email: "two@my.com"}, + {Email: "three@my.com"}, +} +err := db.NewSelect().Model(&users).WherePK("email").Scan(ctx) + +// SQL +// SELECT * FROM users WHERE email IN ('one@my.com', 'two@my.com', 'three@my.com') +``` +#### Grouping +您可以使用WhereOr来实现OR的效果: +``` +q = q.Where("id = 1").WhereOr("id = 2").WhereOr("id = 3") +``` +可以用括号将条件分组: +``` +q = q. + WhereGroup(" AND ", func(q *ogx.SelectQuery) *ogx.SelectQuery { + return q.Where("id = 1").WhereOr("id = 2").WhereOr("id = 3") + }). + WhereGroup(" AND NOT ", func(q *ogx.SelectQuery) *ogx.SelectQuery { + return q.Where("active").WhereOr("archived") + }) + +// SQL: +// WHERE (id = 1 OR id = 2 OR id = 3) AND NOT (active OR archived) +``` +#### + +### CTE and VALUES +#### With +大多数ogx queries 通过With方法支持CTEs: +``` +q1 := db.NewSelect() +q2 := db.NewSelect() + +q := db.NewInsert(). + With("q1", q1). + With("q2", q2). + Table("q1", "q2") +``` +使用With方法在两个表之间拷贝数据: +``` +src := db.NewSelect().Model((*Comment)(nil)) + +res, err := db.NewInsert(). + With("src", src). + Table("comments_backup", "src"). + Exec(ctx) + +// SQL: +// WITH src AS (SELECT * FROM comments) +// INSERT INTO comments_backups SELECT * FROM src +``` +#### Values +Ogx提供ValuesQuery来帮助构建CTEs +``` +values := db.NewValues(&[]*Book{book1, book2}) + +res, err := db.NewUpdate(). + With("_data", values). + Model((*Book)(nil)). + Table("_data"). + Set("title = _data.title"). + Set("text = _data.text"). + Where("book.id = _data.id"). + Exec(ctx) + +// SQL: +// WITH _data (id, title, text) AS (VALUES (1, 'title1', 'text1'), (2, 'title2', 'text2')) +// UPDATE books AS book +// SET title = _data.title, text = _data.text +// FROM _data +// WHERE book.id = _data.id +``` +#### WithOrder +您也可以使用WithOrder在值中插入行排名 +``` +users := []User{ + {ID: 1, "one@my.com"}, + {ID: 2, "two@my.com"} +} + +err := db.NewSelect(). + With("data", db.NewValues(&users).WithOrder()). + Model(&users). + Where("user.id = data.id"). + OrderExpr("data._order"). + Scan(ctx) + +// SQL: +// WITH “data” ("id", "email", _order) AS ( +// VALUES +// (42::BIGINT, 'one@my.com'::VARCHAR, 0), +// (43::BIGINT, 'two@my.com'::VARCHAR, 1) +// ) +// SELECT "user"."id", "user"."email" +// FROM "users" AS "user" +// WHERE (user.id = data.id) +// ORDER BY data._order +``` +### INSERT +#### 常见API +``` +db.NewInsert(). + With("cte_name", subquery). + + Model(&strct). + Model(&slice). + Model(&map). // only map[string]interface{} + + Column("col1", "col2"). // list of columns to insert + ExcludeColumn("col1"). // all columns except col1 + ExcludeColumn("*"). // exclude all columns + + Table("table1", "table2"). // quotes table names + TableExpr("table1 AS t1"). // arbitrary unsafe expression + TableExpr("(?) AS subq", subquery). + ModelTableExpr("table1 AS t1"). // overrides model table name + + Value("col1", "expr1", arg1, arg2). // overrides column value + + On("CONFLICT (id) DO UPDATE"). + Set("col1 = EXCLUDED.col1"). + + WherePK(). // where using primary keys + Where("id = ?", 123). + Where("name LIKE ?", "my%"). + Where("? = 123", ogx.Ident("id")). + Where("id IN (?)", ogx.In([]int64{1, 2, 3})). + Where("id IN (?)", subquery). + Where("FALSE").WhereOr("TRUE"). + WhereGroup(" AND ", func(q *ogx.SelectQuery) *ogx.SelectQuery { + return q.WhereOr("id = 1"). + WhereOr("id = 2") + }). + + Returning("*"). + Returning("col1, col2"). + Returning("NULL"). // don't return anything + + Exec(ctx) +``` +#### 示例 + +1. 定义Model并插入数据 +``` +book := &Book{Title: "hello"} + +res, err := db.NewInsert().Model(book).Exec(ctx) +``` + +2. 使用slice进行批量数据插入 +``` +books := []Book{book1, book2} +res, err := db.NewInsert().Model(&books).Exec(ctx) +if err != nil { + panic(err) +} + +for _, book := range books { + fmt.Println(book.ID) // book id is scanned automatically +} +``` + +3. 插入并且更新数据 +``` +_, err := db.NewInsert(). + Model(&book). + On("CONFLICT (id) DO UPDATE"). + Set("title = EXCLUDED.title"). + Exec(ctx) + +// SQL +// INSERT INTO "books" ("id", "title") VALUES (100, 'my title') +// ON CONFLICT (id) DO UPDATE SET title = EXCLUDED.title +``` + +4. 忽略重复 +``` +_, err := db.NewInsert(). + Model(&book). + Ignore(). + Exec(ctx) + +// SQL +// INSERT INTO `books` (`id`, `title`) VALUES (100, 'my title') +// ON CONFLICT DO NOTHING; +``` + +5. 使用Map[string]interface{}进行数据插入 +``` +values := map[string]interface{}{ + "title": "title1", + "text": "text1", +} +_, err := db.NewInsert().Model(&values).TableExpr("books").Exec() + +// SQL +// INSERT INTO "books" ("title", "text") VALUES ('title1', 'text2') +``` + +6. Insert...select + +从另一个表拷贝数据 +``` +_, err := db.NewInsert(). + Table("books_backup"). + Table("books"). + Exec(ctx) + +// SQL: +// INSERT INTO "books_backup" SELECT * FROM "books" +``` +您还可以指定要复制的列 +``` +_, err := db.NewInsert(). + ColumnExpr("id, name"). + Table("dest"). + Table("src"). + Exec(ctx) + +// SQL: +// INSERT INTO "dest" (id, name) SELECT id, name FROM "src" +``` +### UPDATE +[@OmariPablo(emotional-da66l)](/emotional-da66l) +#### 常见API +```go +db.NewUpdate(). +With("cte_name", subquery). + +Model(&strct). +Model(&slice). +Model(&map). // only map[string]interface{} + +Column("col1", "col2"). // list of columns to insert +ExcludeColumn("col1"). // all columns except col1 +ExcludeColumn("*"). // exclude all columns + +Table("table1", "table2"). // quotes table names +TableExpr("table1 AS t1"). // arbitrary unsafe expression +TableExpr("(?) AS alias", subquery). +ModelTableExpr("table1 AS t1"). // overrides model table name + +Value("col1", "expr1", arg1, arg2). // overrides column value + +// Generates `SET col1 = 'value1'` +Set("col1 = ?", "value1"). +SetColumn("col1", "?", "value1"). + +WherePK(). // where using primary keys +Where("id = ?", 123). +Where("name LIKE ?", "my%"). +Where("? = 123", ogx.Ident("id")). +Where("id IN (?)", ogx.In([]int64{1, 2, 3})). +Where("id IN (?)", subquery). +Where("FALSE").WhereOr("TRUE"). +WhereGroup(" AND ", func(q *ogx.SelectQuery) *ogx.SelectQuery { + return q.WhereOr("id = 1"). + WhereOr("id = 2") + }). + +Returning("*"). +Returning("col1, col2"). +Returning("NULL"). // don't return anything + +Exec(ctx) +``` +#### 示例 +更新一行:通过定义model和使用UpdateQuery实现 +```go +book := &Book{ID: 123, Title: "hello"} + +res, err := db.NewUpdate().Model(book).WherePK().Exec(ctx) +``` +更新一列: +```go +book.Title = "hello" + +res, err := db.NewUpdate(). + Model(book). + Column("title"). + Where("id = ?", 123). + Exec(ctx) +``` +```go +UPDATE books SET title = 'my title' WHERE id = 1 +``` +或者: +```go +res, err := db.NewUpdate(). + Model(book). + Set("title = ?", "hello"). + Where("id = ?", 123). + Exec(ctx) +``` +#### 批量更新 +可以通过通用表表达式(CTE)进行批量更新 +```go +values := db.NewValues(&[]*Book{book1, book2}) + +res, err := db.NewUpdate(). + With("_data", values). + Model((*Book)(nil)). + TableExpr("_data"). + Set("title = _data.title"). + Set("text = _data.text"). + Where("book.id = _data.id"). + Exec(ctx) +``` +```go +WITH _data (id, title, text) AS ( + VALUES + (1, 'title1', 'text1'), + (2, 'title2', 'text2') +) +UPDATE books AS book +SET title = _data.title, text = _data.text +FROM _data +WHERE book.id = _data.id +``` +或者可以使用`Bulk()`创建CTE +```go +res, err := db.NewUpdate(). + Model(&books). + Column("title", "text"). + Bulk(). + Exec(ctx) +``` +#### 通过map更新 +使用`map[string]interface{}`: +```go +value := map[string]interface{}{ + "title": "title1", + "text": "text1", +} +res, err := db.NewUpdate(). + Model(&value). + TableExpr("books"). + Where("id = ?", 1). + Exec(ctx) +``` +```go +UPDATE books +SET title = 'title1', text = 'text2' +WHERE id = 1 +``` +#### OmitZero() +ogx可以使用`OmitZero()`方法来忽略空值的字段,比如下面的例子就因为`email`字段是空值而没有更新。 +```go +type User struct { + ID int64 + Name string + Email string +} + +res, err := db.NewUpdate(). + Model(&User{ID: 1, Name: "John Doe"}). + OmitZero(). + WherePK(). + Exec(ctx) +``` +```go +UPDATE users +SET name = "John Doe" +WHERE id = 1 +``` +### DELETE +[@OmariPablo(emotional-da66l)](/emotional-da66l) +#### 常见API +```go +db.NewDelete(). + With("cte_name", subquery). + + Model(&strct). + Model(&slice). + + Table("table1", "table2"). // quotes table names + TableExpr("table1 AS t1"). // arbitrary unsafe expression + TableExpr("(?) AS alias", subquery). + ModelTableExpr("table1 AS t1"). // overrides model table name + + WherePK(). // where using primary keys + Where("id = ?", 123). + Where("name LIKE ?", "my%"). + Where("? = 123", ogx.Ident("id")). + Where("id IN (?)", ogx.In([]int64{1, 2, 3})). + Where("id IN (?)", subquery). + Where("FALSE").WhereOr("TRUE"). + WhereGroup(" AND ", func(q *ogx.SelectQuery) *ogx.SelectQuery { + return q.WhereOr("id = 1"). + WhereOr("id = 2") + }). + + Returning("*"). + Returning("col1, col2"). + Returning("NULL"). // don't return anything + + Exec(ctx) +``` +#### 示例 +需要先定义一个model然后使用delete语句: +```go +res, err := db.NewDelete().Where("id = ?", 123).Exec(ctx) +``` +#### 批量删除 +通过主键批量删除: +```go +books := []*Book{book1, book2} // slice of books with ids +res, err := db.NewDelete().Model(&books).WherePK().Exec(ctx) +``` +```go +DELETE FROM "books" WHERE id IN (1, 2) +``` +#### USING +使用别的表删除行: +```go +res, err := db.NewDelete(). + Model((*Book)(nil)). + TableExpr("archived_books AS src"). + Where("book.id = src.id"). + Exec(ctx) +``` +```go +DELETE FROM "books" AS book +USING archived_books AS src +WHERE book.id = src.id +``` + +### Truncate Table +#### 常见API +``` +db.NewTruncateTable(). + + Model(&strct). + + Table("table1"). // quotes table names + TableExpr("table1"). // arbitrary unsafe expression + ModelTableExpr("table1"). // overrides model table name + + ContinueIdentity(). + Cascade(). + Restrict(). + + Exec(ctx) +``` +#### 示例 +``` +_, err := db.NewTruncateTable().Model((*Book)(nil)).Exec(ctx) +if err != nil { + panic(err) +} +``` +### Create Table +#### 常见API +```go +db.NewCreateTable(). + + Model(&strct). + + Table("table1"). // quotes table names + TableExpr("table1"). // arbitrary unsafe expression + ModelTableExpr("table1"). // overrides model table name + + Temp(). + IfNotExists(). + Varchar(100). // turns VARCHAR into VARCHAR(100) + + WithForeignKeys(). + ForeignKey(`(fkey) REFERENCES table1 (pkey) ON DELETE CASCADE`). + PartitionBy("HASH (id)"). + TableSpace("fasttablespace"). + + Exec(ctx) +``` +#### 示例 + +1. 创建table +```go +_, err := db.NewCreateTable(). + Model((*Book)(nil)). + ForeignKey(`("author_id") REFERENCES "users" ("id") ON DELETE CASCADE`). + Exec(ctx) +if err != nil { + panic(err) +} +``` + +2. 重置table +```go +err := db.ResetModel(ctx, (*Model1)(nil), (*Model2)(nil)) + +// SQL: +// DROP TABLE IF EXISTS model1 CASCADE; +// CREATE TABLE model1 (...); +// DROP TABLE IF EXISTS model2 CASCADE; +// CREATE TABLE model2 (...); +``` +#### Hooks + +1. ogx.BeforeCreateTableHook +```go +var _ ogx.BeforeCreateTableHook = (*Book)(nil) + +func (*Book) BeforeCreateTable(ctx context.Context, query *ogx.CreateTableQuery) error { + query.ForeignKey(`("author_id") REFERENCES "users" ("id") ON DELETE CASCADE`) + return nil +} + +if _, err := db.NewCreateTable().Model((*Book)(nil)).Exec(ctx); err != nil { + panic(err) +} +``` + +2. ogx.AfterCreateTableHook +```go +var _ ogx.AfterCreateTableHook = (*Book)(nil) + +func (*Book) AfterCreateTable(ctx context.Context, query *ogx.CreateTableQuery) error { + _, err := query.DB().NewCreateIndex(). + Model((*Book)(nil)). + Index("category_id_idx"). + Column("category_id"). + Exec(ctx) + return err +} +``` +### Drop Table +#### API +常用的删除表API: +```go +db.NewDropTable(). + + Model(&strct). + + Table("table1"). // quotes table names + TableExpr("table1"). // arbitrary unsafe expression + ModelTableExpr("table1"). // overrides model table name + + IfExists(). + + Cascade(). + Restrict(). + + Exec(ctx) +``` +#### 示例 +删除openGauss表: +```go +_, err := db.NewDropTable().Model((*Book)(nil)).IfExists().Exec(ctx) +if err != nil { + panic(err) +} +``` + +## Relations 关联 +ogx支持以下四种关系:has-one、belongs-to、has-many、polymorphic-has-many、many-to-many。 +### has-one +has one与另一个模型建立一对一的关联,这种关联表示一个模型的每个实例都包含另一个模型的一个实例。例如,`User`模型和`Profile`模型,`User`模型 has one `Profile`模型。为了在模型之上定义has-one关系,需要将`ogx:"rel:has-one"` tag 添加到模型域上: +```go +// Profile belongs to User. +type Profile struct { + ID int64 `ogx:",pk"` + Lang string + UserID int64 +} + +type User struct { + ID int64 `ogx:",pk"` + Name string + Profile *Profile `ogx:"rel:has-one,join:id=user_id"` +} +``` +您也可以指定join多个列,例如:`join:id=user_id,join:vendor_id=vendor_id.` +### belongs-to +为了在模型之上定义belongs-to关系,需要将`ogx:"rel:belongs-to"` tag 添加到模型域上: +```go +type Profile struct { + ID int64 `ogx:",pk"` + Lang string +} + +// User has one profile. +type User struct { + ID int64 `ogx:",pk"` + Name string + ProfileID int64 + Profile *Profile `ogx:"rel:belongs-to,join:profile_id=id"` +} +``` +### has-many +通过在模型域上添加`ogx:"rel:has-many"` tag,可以实现has-many关系: +```go +type Profile struct { + ID int64 `ogx:",pk"` + Lang string + Active bool + UserID int64 +} + +// User has many profiles. +type User struct { + ID int64 `ogx:",pk"` + Name string + Profiles []*Profile `ogx:"rel:has-many,join:id=user_id"` +} +``` +### many-to-many +为了定义many-to-many关系,您需要添加`ogx:"m2m:order_to_items"`到模型域。您还需要在中间模型上定义两个has-one关系并且使用`db.RegisterModel`手动注册模型。 +下面这个例子,Order模型和Item模型是has-many关系,我们使用`OrderToItem`模型作为中间模型联接`Order`模型和`Items`模型。 +```go +func init() { + // Register many to many model so ogx can better recognize m2m relation. + // This should be done before you use the model for the first time. + db.RegisterModel((*OrderToItem)(nil)) +} + +type Order struct { + ID int64 `ogx:",pk"` + // Order and Item in join:Order=Item are fields in OrderToItem model + Items []Item `ogx:"m2m:order_to_items,join:Order=Item"` +} + +type Item struct { + ID int64 `ogx:",pk"` +} + +type OrderToItem struct { + OrderID int64 `ogx:",pk"` + Order *Order `ogx:"rel:belongs-to,join:order_id=id"` + ItemID int64 `ogx:",pk"` + Item *Item `ogx:"rel:belongs-to,join:item_id=id"` +} +``` +## Migrations 迁移 +您可以编写Migrations以更改数据库schema或者数据。Migrations可以是常规的 Go 函数或 SQL 命令组文件。 +#### Migrations names +每次Migration放入一个独立的文件,迁移文件名由唯一的迁移名称和注释组合成。eg:`20210707110026_add_foo_column.go` +#### Migrations 状态 +ogx将完成的Migrations names存储在ogx_migrations表中,以决定运行哪些迁移。ogx还使用该信息来回滚。 +当迁移失败时,ogx仍会将迁移标记为已实施,以便您可以回滚部分应用的迁移并尝试再次运行迁移。 +#### 组迁移 +当有多个Migrations要运行时,ogx将这些migrations作为一个group一起运行。在回滚期间,ogx会还原最后一个迁移组(而不是单个迁移)。 +要回滚单个迁移,您需要回滚最后一个组,删除要跳过的迁移,然后再次运行迁移。或者,您可以进行有所需要的更改的新迁移。 +#### Go-based Migrations +Go-based Migrations使用常规 Go 函数来进行迁移。每个这类函数都必须在`main.go`文件创建的Migrations集合中注册: +```go +var Migrations = migrate.NewMigrations() + +func init() { + if err := Migrations.DiscoverCaller(); err != nil { + panic(err) + } +} +``` +然后,在一个单独的文件中,您应该使用`MustRegister`方法来定义和注册Migrations,例如,在`20220707110026_test_migration.go`: +```go +func init() { + Migrations.MustRegister(func(ctx context.Context, db *ogx.DB) error { + fmt.Print(" [up migration] ") + return nil + }, func(ctx context.Context, db *ogx.DB) error { + fmt.Print(" [down migration] ") + return nil + }) +} +``` +#### SQL-based Migrations +SQL-based Migrations使用一个以.up.sql为文件名结尾的包含SQL命令的文件进行Miragtion。您可以使用`--migration:splitline `作为分隔符来创建包含多个语句的迁移。 +```go +SELECT 1 + +--migration:split + +SELECT 2 +``` +您可以使用`Discover()`方法注册此类迁移: +```go +var sqlMigrations embed.FS + +func init() { + if err := Migrations.Discover(sqlMigrations); err != nil { + panic(err) + } +} +``` +要创建事务迁移,请使用.tx.up.sql作为文件名后缀。 +## Fixtures 数据库初始化 +您可以使用fixtures将初始数据加载到数据库中以进行测试或演示。您可以以 YAML 格式编写fixtures,并根据需要从测试或者Migrations中加载它们。 +### 创建Fixtures +Fixtures是一个够使用text/template表达式来生成值的 YAML 文件。ogx通过yaml.v3将YAML数据解析到Go models中然后将改models保存进数据库中。 +### 加载Fixtures +假设Fixture储存在testdata/fixture.yml,您可以通过以下代码来加载: +``` +//在db注册该Model +db.RegisterModel((*User)(nil), (*Org)(nil)) + +fixture := dbfixture.New(db) +err := fixture.Load(ctx, os.DirFS("testdata"), "fixture.yml") +``` +#### fixture.WithRecreateTables() +使用WithRecreateTables()函数,您可以删除现存的表然后替换为一个新表。 +``` +fixture := dbfixture.New(db, dbfixture.WithRecreateTables()) +``` +#### dbfixture.WithTemplateFuncs() +您还可以在Fixture中注册和使用自定义模板函数 +``` +funcMap := template.FuncMap{ + "now": func() string { + return time.Now().Format(time.RFC3339Nano) + }, +} + +fixture := dbfixture.New(db, dbfixture.WithTemplateFuncs(funcMap)) +``` +### 检索Fixtures数据 +您可以使用Row和MustRow方法检索已经加载过的模型。 +``` +fmt.Println("Smith", fixture.MustRow("User.smith").(*User)) +``` +### 字段名称 +ogx使用 SQL 列名查找匹配的 struct 字段,然后调用yaml.v3来解析数据。因此,当解析到结构字段时,您可能需要使用yaml tag来覆盖默认的 YAML 字段名称。 +``` +type User struct { + ID int64 `ogx:",pk,autoincrement"` + Params UserParams `ogx:"type:jsonb"` +} + +type UserParams struct { + Param1 string `yaml:"param1"` + Param2 string `yaml:"param2"` +} +``` +## Transactions 事务 +`ogx.Tx`是`sql.Tx`的轻量wrapper,除了sql.Tx的常用接口,ogx.Tx还提供了query hooks和构建query的builder。 +```go +// 开始一个事务 +tx, err := db.BeginTx(ctx, &sql.TxOptions{}) + +// 提交/回滚一个事务 +err := tx.Commit() +err := tx.Rollback() +``` +### 在事务中执行查询 +和`ogx.DB`一样,您可以在`ogx.Tx`执行查询 +```go +res, err := tx.NewInsert().Model(&models).Exec(ctx) + +res, err := tx.NewUpdate().Model(&models).Column("col1", "col2").Exec(ctx) + +err := tx.NewSelect().Model(&models).Limit(100).Scan(ctx) +``` +### 使用`RunInTx`在事务中运行函数 +Ogx提供RunIntx函数来帮助在事务中运行特定函数。如果函数返回错误,则事务将回滚;否则,事务已经被提交。 +```go +err := db.RunInTx(ctx, &sql.TxOptions{}, func(ctx context.Context, tx ogx.Tx) error { + _, err := tx.Exec(...) + return err +}) +``` +## Hooks 钩子 +Hook是用户创建的,在指定操作之前、之后调用的函数。例如,您可以编写在每个query处理前执行的函数。在编写Hook时,常用compile time check技巧确保 +您的函数实现了Hook接口。Ogx中的Hook可以分为Model Hook、Model query hooks、Query Hook三种。 +### Model hooks +#### BeforeAppendModel +当需要在插入或者更新前进行某种操作时,可以使用该Hook +##### 示例 +```go +type Model struct { + ID int64 + CreatedAt time.Time + UpdatedAt time.Time +} + +var _ ogx.BeforeAppendModelHook = (*Model)(nil) + +func (m *Model) BeforeAppendModel(ctx context.Context, query ogx.Query) error { + switch query.(type) { + case *ogx.InsertQuery: + m.CreatedAt = time.Now() + case *ogx.UpdateQuery: + m.UpdatedAt = time.Now() + } + return nil +} +``` +#### Before/AfterScanRow +该Hook在scan row value之前或者之后被调用 +```go +type UserModel struct{} + +var _ ogx.BeforeScanRowHook = (*UserModel)(nil) +func (m *Model) BeforeScanRow(ctx context.Context) error { return nil } + +var _ ogx.AfterScanRowHook = (*UserModel)(nil) +func (m *Model) AfterScanRow(ctx context.Context) error { return nil } +``` +### Model query hooks +该Hook定义在Model之上,每次Model在执行特定操作前后会调用该Hook。Ogx支持的Model query hook: +```go +type UserModel struct{} // user model + +var _ ogx.BeforeSelectHook = (*UserModel)(nil) // compile time checks +func (*Model) BeforeSelect(ctx context.Context, query *ogx.SelectQuery) error { return nil } +var _ ogx.AfterSelectHook = (*UserModel)(nil) +func (*Model) AfterSelect(ctx context.Context, query *ogx.SelectQuery) error { return nil } + +var _ ogx.BeforeInsertHook = (*UserModel)(nil) +func (*Model) BeforeInsert(ctx context.Context, query *ogx.InsertQuery) error { nil } +var _ ogx.AfterInsertHook = (*UserModel)(nil) +func (*Model) AfterInsert(ctx context.Context, query *ogx.InsertQuery) error { return nil } + +var _ ogx.BeforeUpdateHook = (*UserModel)(nil) +func (*Model) BeforeUpdate(ctx context.Context, query *ogx.UpdateQuery) error { return nil } +var _ ogx.AfterUpdateHook = (*UserModel)(nil) +func (*Model) AfterUpdate(ctx context.Context, query *ogx.UpdateQuery) error { return nil } + +var _ ogx.BeforeDeleteHook = (*UserModel)(nil) +func (*Model) BeforeDelete(ctx context.Context, query *ogx.DeleteQuery) error { return nil } +var _ ogx.AfterDeleteHook = (*UserModel)(nil) +func (*Model) AfterDelete(ctx context.Context, query *ogx.DeleteQuery) error { return nil } + +var _ ogx.BeforeCreateTableHook = (*UserModel)(nil) +func (*Model) BeforeCreateTable(ctx context.Context, query *CreateTableQuery) error { return nil } +var _ ogx.AfterCreateTableHook = (*UserModel)(nil) +func (*Model) AfterCreateTable(ctx context.Context, query *CreateTableQuery) error { return nil } + +var _ ogx.BeforeDropTableHook = (*UserModel)(nil) +func (*Model) BeforeDropTable(ctx context.Context, query *DropTableQuery) error { return nil } +var _ ogx.AfterDropTableHook = (*UserModel)(nil) +func (*Model) AfterDropTable(ctx context.Context, query *DropTableQuery) error { return nil } +``` +### Query Hooks +OGX支持在执行查询之前和之后调用的Query Hook。OGX内部使用Query Hook实现Logging Query的功能。 +实现自定义QueryHook需要实现QueryHook接口 +```go +BeforeQuery(context.Context, *QueryEvent) context.Context +AfterQuery(context.Context, *QueryEvent) +``` +#### 示例 +```go +type MyQueryHook struct{} + +func (h *MyQueryHook) BeforeQuery(ctx context.Context, event *ogx.QueryEvent) context.Context { + return ctx +} + +func (h *MyQueryHook) AfterQuery(ctx context.Context, event *ogx.QueryEvent) { + fmt.Println(time.Since(event.StartTime), string(event.Query)) +} + +db.AddQueryHook(&MyQueryHook{}) +``` +## ogx软删除 +#### 软删除 +软删除可以将某一行标记为已删除,而无需从数据库中实际删除它们。您可以通过使用辅助标志列并修改查询来检查是否有软删除标志来实现软删除。 +例如,使用`deleted_at timestamptz`列作为软删除某一行的标志。 +```cpp +UPDATE users SET deleted_at = now() WHERE id = 1 +``` +要选择未删除的(实时)行: +```cpp +SELECT * FROM users WHERE deleted_at IS NULL +``` +#### 使用ogx模型 +ogx 通过使用`time.Timecolumn` 作为行是否被删除的标识来支持软删除。ogx 自动调整查询以检查标志。 +要在模型上启用软删除,请添加`DeletedAt`带有`soft_delete`标签的字段: +```cpp +type User struct { + ID int64 + CreatedAt time.Time `ogx:",nullzero,notnull,default:current_timestamp"` + DeletedAt time.Time `ogx:",soft_delete,nullzero"` +} +``` +对于此类模型,ogx只会更新行而不是删除: +```cpp +_, err := db.NewDelete().Model(user).Where("id = ?", 123).Exec(ctx) +``` +```cpp +UPDATE users SET deleted_at = current_timestamp WHERE id = 123 +``` +``ogx 还会自动从查询结果中排除软删除的行: +```cpp +err := db.NewSelect().Model(&users).Scan(ctx) +``` +```cpp +SELECT * FROM users WHERE deleted_at IS NULL +``` +要选择软删除的行 +```cpp +err := db.NewSelect().Model(&users).WhereDeleted().Scan(ctx) +``` +```cpp +SELECT * FROM users WHERE deleted_at IS NOT NULL +``` +要选择包括软删除行在内的所有行: +```cpp +err := db.NewSelect().Model(&users).WhereAllWithDeleted().Scan(ctx) +``` +```cpp +SELECT * FROM users +``` +最后,要从数据库中实际删除软删除的行: +```cpp +db.NewDelete().Model(user).Where("id = ?", 123).ForceDelete().Exec(ctx) +``` +```cpp +DELETE FROM users WHERE id = 123 AND deleted_at IS NOT NULL +``` +#### 使用表的视图 +您还可以使用表视图实现软删除。给定下表架构 +```cpp +CREATE TABLE all_users ( + id int8 PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, + name varchar(500), + + created_at timestamptz NOT NULL DEFAULT now(), + deleted_at timestamptz +); +``` +您可以创建忽略已删除用户的视图: +```cpp +CREATE VIEW users AS +SELECT * FROM all_users +WHERE deleted_at IS NULL +``` +openGauss视图支持插入和删除,因此您可以在模型中使用它们: +```cpp +type User struct { + ogx.BaseModel `ogx:"users"` + + ID uint64 + Name string +} +``` +要查询已删除的行,请使用`ModelTableExpr()`更改表: +```cpp +var deletedUsers []User +err := db.NewSelect(). + Model(&deletedUsers). + ModelTableExpr("all_users"). + Where("deleted_at IS NOT NULL"). + Scan(ctx) +``` +#### Unique indexs +使用具有唯一索引的软删除可能会导致插入查询发生冲突,因为软删除的行与普通行一样包含在唯一索引中。 +使用某些 DBMS,您可以从索引中排除软删除的行: +```cpp +CREATE UNIQUE INDEX index_name ON table (column1) WHERE deleted_at IS NULL; +``` +或者,您可以`deleted_at`使用函数将列包含到索引列`coalesce`以转换`NULL`时间,因为`NULL`它不等于包括自身在内的任何其他值: +```cpp +CREATE UNIQUE INDEX index_name ON table (column1, coalesce(deleted_at, '1970-01-01 00:00:00')) +``` +如果您的 DBMS 不允许在索引列中使用表达式,您可以通过删除 `nullzero` 选项为ogx 配置零时间1970-01-01 00:00:00+00:00: +```cpp +type User struct { + ID int64 + CreatedAt time.Time `ogx:",nullzero,notnull,default:current_timestamp"` +- DeletedAt time.Time `ogx:",soft_delete,nullzero"` ++ DeletedAt time.Time `ogx:",soft_delete"` +} +``` +## Debugger +为了方便查看执行的SQL命令,可以使用ogxdebug包来打印这些命令到stdout。 +首先,需要安装ogxdebug包 +```go +go get gitee.com/chentanyang/ogx/extra/ogxdebug +``` +然后添加query hook,默认情况下ogx只会打印失败的queries +```go +import "gitee.com/chentanyang/ogx/extra/ogxdebug" + +db := ogx.NewDB(sqldb, dialect) +db.AddQueryHook(ogxdebug.NewQueryHook()) +``` +ogxdebug.NewQueryHook可以通过传入参数进行配置,目前全部的可配置项如下: +```go + db.AddQueryHook(ogxdebug.NewQueryHook( + ogxdebug.WithEnabled(true), + ogxdebug.WithVerbose(true), + ogxdebug.WithWriter(), + ogxdebug.FromEnv("ogxDEBUG"), + )) +``` +ogxdebug.WithEnabled(on bool)函数决定是否启动该hook +ogxdebug.WithVerbose(on bool)函数决定是否打印所有queries +ogxdebug.WithWriter(w io.Writer)函数设置debug打印日志的输出 +ogxdebug.FromEnv("OGXDEBUG")函数通过环境变量来设置hook, + +- OGXDEBUG=0 表示关闭此hook +- OGXDEBUG=1 表示打开此hook +- OGXDEBUG=2 表示打开hook,并且打开verbose,打印每条query + +还可以默认情况下禁用钩子,并使用环境变量在需要时启用它: +```go +ogxdebug.NewQueryHook( + // disable the hook + ogxdebug.WithEnabled(false), + + // OGXDEBUG=1 logs failed queries + // OGXDEBUG=2 logs all queries + ogxdebug.FromEnv("OGXDEBUG"), +) +``` +### 示例 +```go +func main() { + ctx := context.Background() + + connStr := "host=192.168.20.40 port=26000 user=test password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + if err != nil { + panic(err) + } + + db := ogx.NewDB(opengaussdb, ogdialect.New()) + db.AddQueryHook(ogxdebug.NewQueryHook(ogxdebug.WithVerbose(true))) + + if _, err := db.NewCreateTable().Model((*Book)(nil)).Exec(ctx); err != nil { + panic(err) + } + defer func() { + _ = dropSchema(ctx, db, (*Book)(nil)) + }() +} + +// Output: +// [ogx] 11:26:30.389 CREATE TABLE 329.644ms CREATE TABLE "books" ("id" BIGSERIAL NOT NULL, "name" VARCHAR, "category_id" BIGINT, PRIMARY KEY ("id")) +// [ogx] 11:26:30.456 CREATE INDEX 66.762ms CREATE INDEX "category_id_idx" ON "books" ("category_id") +// [ogx] 11:26:30.507 DROP TABLE 50.403ms DROP TABLE IF EXISTS "books" CASCADE +``` +## Custom Types 自定义数据类型 +Ogx使用database/sql接口访问openGauss,所以可以通过实现sql.Scanner和driver.Valuer实现用户自定义类型的支持。 +### 示例 +sql.Scanner的值可以为下列类型: + +- int64 +- float64 +- bool +- []byte +- string +- time.Time +- nil - for NULL values + +以自定义Time结构体为例子: +```go +type Time struct { + time.Time +} +``` +为Time实现sql.Scanner接口: +```go +var _ sql.Scanner = (*Time)(nil) + +// Scan scans the time parsing it if necessary using timeFormat. +func (tm *Time) Scan(src interface{}) (err error) { + switch src := src.(type) { + case time.Time: + *tm = NewTime(src) + return nil + case string: + tm.Time, err = time.ParseInLocation(timeFormat, src, time.UTC) + return err + case []byte: + tm.Time, err = time.ParseInLocation(timeFormat, string(src), time.UTC) + return err + case nil: + tm.Time = time.Time{} + return nil + default: + return fmt.Errorf("unsupported data type: %T", src) + } +} +``` +driver.Valuer将golang类型返回到driver中,其值必须为下列类型之一: + +- int64 +- float64 +- bool +- []byte +- string +- time.Time +- nil - for NULL values + +为Time实现driver.Valuer接口 +```go +var _ driver.Valuer = (*Time)(nil) + +// Scan scans the time parsing it if necessary using timeFormat. +func (tm Time) Value() (driver.Value, error) { + return tm.UTC().Format(timeFormat), nil +} +``` +用户测试代码: +```go +src := Now() +var dest Time +if err := db.NewSelect().ColumnExpr("?", src).Scan(ctx, &dest); err != nil { + panic(err) +} + +// Output: +// [ogx] 09:37:55.619 SELECT 216.927ms SELECT '01:37:55.4027804' +// src 0000-01-01 01:37:55.4027804 +0000 UTC +// dest 0000-01-01 01:37:55.4027804 +0000 UT +``` +## Cursor pagination 使用游标进行数据库分页 +#### 常规分页 +通常,您可以使用以下命令对结果进行分页`LIMIT X OFFSET Y`: +``` +SELECT * FROM entries ORDER BY id ASC LIMIT 10 OFFSET 0; -- first page +SELECT * FROM entries ORDER BY id ASC LIMIT 10 OFFSET 10; -- second page +SELECT * FROM entries ORDER BY id ASC LIMIT 10 OFFSET 20; -- third page +``` +但是当offset很大时这种分页方法使得查询非常缓慢。 +#### 游标分页 +基于游标的分页通过向客户端返回指向页面上最后一项的指针(游标)来工作。为了获得下一页,客户端将游标传递给服务器,服务器在给定游标后返回结果。这种方法的主要限制是客户端不能跳转到特定页面并且不知道页面的总数。请注意:基于游标的分页比常规分页体验更差,因此只在必须时使用。 +因为游标必须明确地标识行,所以只能在具有唯一约束的主键或列上使用基于游标的分页。这也确保了查询使用索引并且可以快速跳过已经分页的行 +#### 示例 +例如,我们在Ogx里使用主键作为指针对以下模型进行分页: +``` +type Entry struct { + ID int64 + Text string +} +``` +我们的辅助`Cursor`结构可能如下所示 +``` +type Cursor struct { + Start int64 // pointer to the first item for the previous page + End int64 // pointer to the last item for the next page +} +``` +要检索下一页,我们需要从游标指向最后一项继续: +``` +func selectNextPage(ctx context.Context, db *ogx.DB, cursor int64) ([]Entry, Cursor, error) { + var entries []Entry + if err := db.NewSelect(). + Model(&entries). + Where("id > ?", cursor). + OrderExpr("id ASC"). + Limit(10). + Scan(ctx); err != nil { + return nil, Cursor{}, err + } + return entries, NewCursor(entries), nil +} +``` +要检索上一页,我们需要从指向第一项的游标开始向后迭代: +``` +func selectPrevPage(ctx context.Context, db *ogx.DB, cursor int64) ([]Entry, Cursor, error) { + var entries []Entry + if err := db.NewSelect(). + Model(&entries). + Where("id < ?", cursor). + OrderExpr("id DESC"). + Limit(10). + Scan(ctx); err != nil { + return nil, Cursor{}, err + } + return entries, NewCursor(entries), nil +} +``` +我们可以使用这些方法: +``` +page1, cursor, err := selectNextPage(ctx, db, 0) +if err != nil { + panic(err) +} + +page2, cursor, err := selectNextPage(ctx, db, cursor.End) +if err != nil { + panic(err) +} + +prevPage, _, err := selectPrevPage(ctx, db, cursor.Start) +if err != nil { + panic(err) +} +``` diff --git a/ogx/docs/Ogx_logo.png b/ogx/docs/Ogx_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a4069feaa09787a06ff958639e8559fe3d5983cd GIT binary patch literal 79479 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 z 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 literal 0 HcmV?d00001 diff --git a/ogx/example/basic/go.mod b/ogx/example/basic/go.mod new file mode 100644 index 00000000..e7c25c05 --- /dev/null +++ b/ogx/example/basic/go.mod @@ -0,0 +1,34 @@ +module gitee.com/chentanyang/ogx/example/basic + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +replace gitee.com/chentanyang/ogx/dbfixture => ../../dbfixture + +replace gitee.com/chentanyang/ogx/extra/ogxdebug => ../../extra/ogxdebug + +replace gitee.com/chentanyang/ogx/dialect/ogdialect => ../../dialect/ogdialect + +require ( + gitee.com/chentanyang/ogx v1.1.7 + gitee.com/chentanyang/ogx/dialect/ogdialect v0.0.0-20220915042425-85b0183464bc + gitee.com/chentanyang/ogx/extra/ogxdebug v0.0.0-20220915042425-85b0183464bc + gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 +) + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/ogx/example/basic/go.sum b/ogx/example/basic/go.sum new file mode 100644 index 00000000..3a67d2ca --- /dev/null +++ b/ogx/example/basic/go.sum @@ -0,0 +1,125 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 h1:npfLM9/QpkmdK+XY9X2pcC2EX5gosyn/6dRDRd2sEJs= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4/go.mod h1:2UEp+ug6ls6C0pLfZgBn7VBzBntFUzxJuy+6FlQ7qyI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/example/basic/main.go b/ogx/example/basic/main.go new file mode 100644 index 00000000..8e352afc --- /dev/null +++ b/ogx/example/basic/main.go @@ -0,0 +1,148 @@ +package main + +import ( + "context" + "database/sql" + "fmt" + "log" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/dialect/ogdialect" + "gitee.com/chentanyang/ogx/extra/ogxdebug" + + _ "gitee.com/opengauss/openGauss-connector-go-pq" +) + +func main() { + ctx := context.Background() + + connStr := "host=192.168.20.40 port=26000 user=cuih password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + if err != nil { + panic(err) + } + var date string + err = opengaussdb.QueryRow("select current_date").Scan(&date) + if err != nil { + log.Fatal(err) + } + fmt.Println(date) + db := ogx.NewDB(opengaussdb, ogdialect.New()) + db.AddQueryHook(ogxdebug.NewQueryHook( + ogxdebug.WithVerbose(true), + ogxdebug.FromEnv("ogxDEBUG"), + )) + + if err := resetSchema(ctx, db); err != nil { + panic(err) + } + defer func() { + _ = dropSchema(ctx, db, (*User)(nil), (*Story)(nil)) + }() + + // Select all users. + users := make([]User, 0) + if err := db.NewSelect().Model(&users).OrderExpr("id ASC").Scan(ctx); err != nil { + panic(err) + } + fmt.Printf("all users: %v\n\n", users) + + // Select one user by primary key. + user1 := new(User) + if err := db.NewSelect().Model(user1).Where("id = ?", 1).Scan(ctx); err != nil { + panic(err) + } + fmt.Printf("user1: %v\n\n", user1) + + // Select a story and the associated author in a single query. + story := new(Story) + if err := db.NewSelect(). + Model(story). + Relation("Author"). + Limit(1). + Scan(ctx); err != nil { + panic(err) + } + fmt.Printf("story and the author: %v\n\n", story) + + // Select a user into a map. + var m map[string]interface{} + if err := db.NewSelect(). + Model((*User)(nil)). + Limit(1). + Scan(ctx, &m); err != nil { + panic(err) + } + fmt.Printf("user map: %v\n\n", m) + + // Select all users scanning each column into a separate slice. + var ids []int64 + var names []string + if err := db.NewSelect(). + ColumnExpr("id, name"). + Model((*User)(nil)). + OrderExpr("id ASC"). + Scan(ctx, &ids, &names); err != nil { + panic(err) + } + fmt.Printf("users columns: %v %v\n\n", ids, names) + +} + +type User struct { + ID int64 `ogx:",pk,autoincrement"` + Name string + Emails []string +} + +func (u User) String() string { + return fmt.Sprintf("User<%d %s %v>", u.ID, u.Name, u.Emails) +} + +type Story struct { + ID int64 `ogx:",pk,autoincrement"` + Title string + AuthorID int64 + Author *User `ogx:"rel:belongs-to,join:author_id=id"` +} + +func resetSchema(ctx context.Context, db *ogx.DB) error { + if err := db.ResetModel(ctx, (*User)(nil), (*Story)(nil)); err != nil { + return err + } + + users := []User{ + { + Name: "admin", + Emails: []string{"admin1@admin", "admin2@admin"}, + }, + { + Name: "root", + Emails: []string{"root1@root", "root2@root"}, + }, + } + if _, err := db.NewInsert().Model(&users).Exec(ctx); err != nil { + return err + } + + stories := []Story{ + { + Title: "Cool story", + AuthorID: users[0].ID, + }, + } + if _, err := db.NewInsert().Model(&stories).Exec(ctx); err != nil { + return err + } + + return nil +} + +func dropSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewDropTable().Model(model).IfExists().Cascade().Exec(ctx); err != nil { + return err + } + } + return nil +} diff --git a/ogx/example/create-table-index/go.mod b/ogx/example/create-table-index/go.mod new file mode 100644 index 00000000..83eb63a3 --- /dev/null +++ b/ogx/example/create-table-index/go.mod @@ -0,0 +1,32 @@ +module gitee.com/chentanyang/ogx/example/og-listen + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +replace gitee.com/chentanyang/ogx/extra/ogxdebug => ../../extra/ogxdebug + +replace gitee.com/chentanyang/ogx/dialect/ogdialect => ../../dialect/ogdialect + +require ( + gitee.com/chentanyang/ogx v1.1.7 + gitee.com/chentanyang/ogx/dialect/ogdialect v1.1.7 + gitee.com/chentanyang/ogx/extra/ogxdebug v1.1.7 + gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 +) + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/ogx/example/create-table-index/go.sum b/ogx/example/create-table-index/go.sum new file mode 100644 index 00000000..3a67d2ca --- /dev/null +++ b/ogx/example/create-table-index/go.sum @@ -0,0 +1,125 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 h1:npfLM9/QpkmdK+XY9X2pcC2EX5gosyn/6dRDRd2sEJs= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4/go.mod h1:2UEp+ug6ls6C0pLfZgBn7VBzBntFUzxJuy+6FlQ7qyI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/example/create-table-index/main.go b/ogx/example/create-table-index/main.go new file mode 100644 index 00000000..c6fa409b --- /dev/null +++ b/ogx/example/create-table-index/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "context" + "database/sql" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/dialect/ogdialect" + "gitee.com/chentanyang/ogx/extra/ogxdebug" + + _ "gitee.com/opengauss/openGauss-connector-go-pq" +) + +type Book struct { + ID int64 `ogx:",pk,autoincrement"` + Name string + CategoryID int64 +} + +var _ ogx.AfterCreateTableHook = (*Book)(nil) + +func (*Book) AfterCreateTable(ctx context.Context, query *ogx.CreateTableQuery) error { + _, err := query.DB().NewCreateIndex(). + Model((*Book)(nil)). + Index("category_id_idx"). + Column("category_id"). + Exec(ctx) + return err +} + +func main() { + ctx := context.Background() + + connStr := "host=192.168.20.40 port=26000 user=cuih password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + if err != nil { + panic(err) + } + opengaussdb.SetMaxOpenConns(1) + + db := ogx.NewDB(opengaussdb, ogdialect.New()) + db.AddQueryHook(ogxdebug.NewQueryHook(ogxdebug.WithVerbose(true))) + + if _, err := db.NewCreateTable().Model((*Book)(nil)).Exec(ctx); err != nil { + panic(err) + } + defer func() { + _ = dropSchema(ctx, db, (*Book)(nil)) + }() + +} + +func dropSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewDropTable().Model(model).IfExists().Cascade().Exec(ctx); err != nil { + return err + } + } + return nil +} diff --git a/ogx/example/cursor-pagination/go.mod b/ogx/example/cursor-pagination/go.mod new file mode 100644 index 00000000..8c530941 --- /dev/null +++ b/ogx/example/cursor-pagination/go.mod @@ -0,0 +1,34 @@ +module gitee.com/chentanyang/ogx/example/cursor-pagination + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +replace gitee.com/chentanyang/ogx/dbfixture => ../../dbfixture + +replace gitee.com/chentanyang/ogx/extra/ogxdebug => ../../extra/ogxdebug + +replace gitee.com/chentanyang/ogx/dialect/ogdialect => ../../dialect/ogdialect + +require ( + gitee.com/chentanyang/ogx v1.1.7 + gitee.com/chentanyang/ogx/dialect/ogdialect v0.0.0-20220915042425-85b0183464bc + gitee.com/chentanyang/ogx/extra/ogxdebug v0.0.0-20220915042425-85b0183464bc + gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 +) + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/ogx/example/cursor-pagination/go.sum b/ogx/example/cursor-pagination/go.sum new file mode 100644 index 00000000..3a67d2ca --- /dev/null +++ b/ogx/example/cursor-pagination/go.sum @@ -0,0 +1,125 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 h1:npfLM9/QpkmdK+XY9X2pcC2EX5gosyn/6dRDRd2sEJs= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4/go.mod h1:2UEp+ug6ls6C0pLfZgBn7VBzBntFUzxJuy+6FlQ7qyI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/example/cursor-pagination/main.go b/ogx/example/cursor-pagination/main.go new file mode 100644 index 00000000..82962286 --- /dev/null +++ b/ogx/example/cursor-pagination/main.go @@ -0,0 +1,145 @@ +package main + +import ( + "context" + "database/sql" + "fmt" + "sort" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/dialect/ogdialect" + "gitee.com/chentanyang/ogx/extra/ogxdebug" + + _ "gitee.com/opengauss/openGauss-connector-go-pq" +) + +func main() { + ctx := context.Background() + + connStr := "host=192.168.20.40 port=26000 user=cuih password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + if err != nil { + panic(err) + } + opengaussdb.SetMaxOpenConns(1) + + db := ogx.NewDB(opengaussdb, ogdialect.New()) + db.AddQueryHook(ogxdebug.NewQueryHook( + ogxdebug.WithVerbose(true), + ogxdebug.FromEnv("ogxDEBUG"), + )) + + if err := resetDB(ctx, db); err != nil { + panic(err) + } + defer func() { + _ = dropSchema(ctx, db, (*Entry)(nil)) + }() + + page1, cursor, err := selectNextPage(ctx, db, 0) + if err != nil { + panic(err) + } + + page2, cursor, err := selectNextPage(ctx, db, cursor.End) + if err != nil { + panic(err) + } + + page3, cursor, err := selectNextPage(ctx, db, cursor.End) + if err != nil { + panic(err) + } + + prevPage, _, err := selectPrevPage(ctx, db, cursor.Start) + if err != nil { + panic(err) + } + + fmt.Println("page #1", page1) + fmt.Println("page #2", page2) + fmt.Println("page #3", page3) + fmt.Println("prev page", prevPage) +} + +type Entry struct { + ID int64 `ogx:",pk,autoincrement"` + Text string +} + +func (e Entry) String() string { + return fmt.Sprint(e.ID) +} + +// Cursor holds pointers to the first and last items on a page. +// It is used with cursor-based pagination. +type Cursor struct { + Start int64 // pointer to the first item for the previous page + End int64 // pointer to the last item for the next page +} + +func NewCursor(entries []Entry) Cursor { + if len(entries) == 0 { + return Cursor{} + } + return Cursor{ + Start: entries[0].ID, + End: entries[len(entries)-1].ID, + } +} + +func selectNextPage(ctx context.Context, db *ogx.DB, cursor int64) ([]Entry, Cursor, error) { + var entries []Entry + if err := db.NewSelect(). + Model(&entries). + Where("id > ?", cursor). + OrderExpr("id ASC"). + Limit(10). + Scan(ctx); err != nil { + return nil, Cursor{}, err + } + return entries, NewCursor(entries), nil +} + +func selectPrevPage(ctx context.Context, db *ogx.DB, cursor int64) ([]Entry, Cursor, error) { + var entries []Entry + if err := db.NewSelect(). + Model(&entries). + Where("id < ?", cursor). + OrderExpr("id DESC"). + Limit(10). + Scan(ctx); err != nil { + return nil, Cursor{}, err + } + sort.Slice(entries, func(i, j int) bool { + return entries[i].ID < entries[j].ID + }) + return entries, NewCursor(entries), nil +} + +func resetDB(ctx context.Context, db *ogx.DB) error { + if err := db.ResetModel(ctx, (*Entry)(nil)); err != nil { + return err + } + + seed := make([]Entry, 100) + + for i := range seed { + seed[i] = Entry{ID: int64(i + 1)} + } + + if _, err := db.NewInsert().Model(&seed).Exec(ctx); err != nil { + return err + } + + return nil +} + +func dropSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewDropTable().Model(model).IfExists().Cascade().Exec(ctx); err != nil { + return err + } + } + return nil +} diff --git a/ogx/example/custom-type/go.mod b/ogx/example/custom-type/go.mod new file mode 100644 index 00000000..fb46d59e --- /dev/null +++ b/ogx/example/custom-type/go.mod @@ -0,0 +1,34 @@ +module gitee.com/chentanyang/ogx/example/custom-type + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +replace gitee.com/chentanyang/ogx/dbfixture => ../../dbfixture + +replace gitee.com/chentanyang/ogx/extra/ogxdebug => ../../extra/ogxdebug + +replace gitee.com/chentanyang/ogx/dialect/ogdialect => ../../dialect/ogdialect + +require ( + gitee.com/chentanyang/ogx v1.1.7 + gitee.com/chentanyang/ogx/dialect/ogdialect v0.0.0-20220915042425-85b0183464bc + gitee.com/chentanyang/ogx/extra/ogxdebug v0.0.0-20220915042425-85b0183464bc + gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 +) + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/ogx/example/custom-type/go.sum b/ogx/example/custom-type/go.sum new file mode 100644 index 00000000..3a67d2ca --- /dev/null +++ b/ogx/example/custom-type/go.sum @@ -0,0 +1,125 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 h1:npfLM9/QpkmdK+XY9X2pcC2EX5gosyn/6dRDRd2sEJs= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4/go.mod h1:2UEp+ug6ls6C0pLfZgBn7VBzBntFUzxJuy+6FlQ7qyI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/example/custom-type/main.go b/ogx/example/custom-type/main.go new file mode 100644 index 00000000..766be16b --- /dev/null +++ b/ogx/example/custom-type/main.go @@ -0,0 +1,88 @@ +package main + +import ( + "context" + "database/sql" + "database/sql/driver" + "fmt" + "time" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/dialect/ogdialect" + "gitee.com/chentanyang/ogx/extra/ogxdebug" + + _ "gitee.com/opengauss/openGauss-connector-go-pq" +) + +func main() { + ctx := context.Background() + + connStr := "host=192.168.20.40 port=26000 user=cuih password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + + if err != nil { + panic(err) + } + opengaussdb.SetMaxOpenConns(1) + + db := ogx.NewDB(opengaussdb, ogdialect.New()) + db.AddQueryHook(ogxdebug.NewQueryHook( + ogxdebug.WithVerbose(true), + ogxdebug.FromEnv("ogxDEBUG"), + )) + + src := Now() + var dest Time + if err := db.NewSelect().ColumnExpr("?", src).Scan(ctx, &dest); err != nil { + panic(err) + } + + fmt.Println("src", src) + fmt.Println("dest", dest) +} + +const timeFormat = "15:04:05.999999999" + +type Time struct { + time.Time +} + +func NewTime(t time.Time) Time { + t = t.UTC() + return Time{ + Time: time.Date(0, 1, 1, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.UTC), + } +} + +func Now() Time { + return NewTime(time.Now()) +} + +var _ driver.Valuer = (*Time)(nil) + +// Value returns the time as string using timeFormat. +func (tm Time) Value() (driver.Value, error) { + return tm.UTC().Format(timeFormat), nil +} + +var _ sql.Scanner = (*Time)(nil) + +// Scan scans the time parsing it if necessary using timeFormat. +func (tm *Time) Scan(src interface{}) (err error) { + switch src := src.(type) { + case time.Time: + *tm = NewTime(src) + return nil + case string: + tm.Time, err = time.ParseInLocation(timeFormat, src, time.UTC) + return err + case []byte: + tm.Time, err = time.ParseInLocation(timeFormat, string(src), time.UTC) + return err + case nil: + tm.Time = time.Time{} + return nil + default: + return fmt.Errorf("unsupported data type: %T", src) + } +} diff --git a/ogx/example/fixture/fixture.yml b/ogx/example/fixture/fixture.yml new file mode 100644 index 00000000..15a09a64 --- /dev/null +++ b/ogx/example/fixture/fixture.yml @@ -0,0 +1,19 @@ +- model: User + rows: + - _id: smith + name: {John Smith} + email: john@smith.com + attrs: { foo: bar, bar: baz } + created_at: '{{ now }}' + updated_at: '{{ now }}' + - _id: doe + name: {Jonh Doe} + email: john@doe.com + created_at: '{{ now }}' + +- model: Org + rows: + - name: "{{ $.User.smith.Name.String }}'s Org" + owner_id: '{{ $.User.smith.ID }}' + - name: "{{ $.User.doe.Name.String }}'s Org" + owner_id: '{{ $.User.doe.ID }}' diff --git a/ogx/example/fixture/go.mod b/ogx/example/fixture/go.mod new file mode 100644 index 00000000..1da0b310 --- /dev/null +++ b/ogx/example/fixture/go.mod @@ -0,0 +1,35 @@ +module gitee.com/chentanyang/ogx/example/fixture + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +replace gitee.com/chentanyang/ogx/dbfixture => ../../dbfixture + +replace gitee.com/chentanyang/ogx/extra/ogxdebug => ../../extra/ogxdebug + +replace gitee.com/chentanyang/ogx/dialect/ogdialect => ../../dialect/ogdialect + +require ( + gitee.com/chentanyang/ogx v1.1.7 + gitee.com/chentanyang/ogx/dbfixture v0.0.0-20220915042425-85b0183464bc + gitee.com/chentanyang/ogx/dialect/ogdialect v0.0.0-20220915042425-85b0183464bc + gitee.com/chentanyang/ogx/extra/ogxdebug v0.0.0-20220915042425-85b0183464bc + gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 +) + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/ogx/example/fixture/go.sum b/ogx/example/fixture/go.sum new file mode 100644 index 00000000..0e87af25 --- /dev/null +++ b/ogx/example/fixture/go.sum @@ -0,0 +1,128 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 h1:npfLM9/QpkmdK+XY9X2pcC2EX5gosyn/6dRDRd2sEJs= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4/go.mod h1:2UEp+ug6ls6C0pLfZgBn7VBzBntFUzxJuy+6FlQ7qyI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/example/fixture/main.go b/ogx/example/fixture/main.go new file mode 100644 index 00000000..d81808c0 --- /dev/null +++ b/ogx/example/fixture/main.go @@ -0,0 +1,95 @@ +package main + +import ( + "bytes" + "context" + "database/sql" + "fmt" + "os" + "time" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/dbfixture" + "gitee.com/chentanyang/ogx/dialect/ogdialect" + "gitee.com/chentanyang/ogx/extra/ogxdebug" + + _ "gitee.com/opengauss/openGauss-connector-go-pq" +) + +type User struct { + ID int64 `ogx:",pk,autoincrement"` + Name sql.NullString + Email string + Attrs map[string]interface{} `ogx:",nullzero"` + CreatedAt time.Time + UpdatedAt sql.NullTime +} + +type Org struct { + ID int64 `ogx:",pk,autoincrement"` + Name string + OwnerID int64 + Owner *User `ogx:"rel:belongs-to"` +} + +func main() { + ctx := context.Background() + connStr := "host=192.168.20.40 port=26000 user=cuih password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + if err != nil { + panic(err) + } + opengaussdb.SetMaxOpenConns(1) + + db := ogx.NewDB(opengaussdb, ogdialect.New()) + db.AddQueryHook(ogxdebug.NewQueryHook(ogxdebug.WithVerbose(true))) + + // Register models before loading fixtures. + db.RegisterModel((*User)(nil), (*Org)(nil)) + defer func() { + _ = dropSchema(ctx, db, (*User)(nil), (*Org)(nil)) + }() + // Automatically create tables. + fixture := dbfixture.New(db, dbfixture.WithRecreateTables()) + + // Load fixtures. + if err := fixture.Load(ctx, os.DirFS("."), "fixture.yml"); err != nil { + panic(err) + } + + // You can access fixtures by _id and by a primary key. + fmt.Println("Smith", fixture.MustRow("User.smith").(*User)) + fmt.Println("Org with id=1", fixture.MustRow("Org.pk1").(*Org)) + + // Load users and orgs from the database. + var users []User + var orgs []Org + + if err := db.NewSelect().Model(&users).OrderExpr("id").Scan(ctx); err != nil { + panic(err) + } + if err := db.NewSelect().Model(&orgs).OrderExpr("id").Scan(ctx); err != nil { + panic(err) + } + + // And encode the loaded data back as YAML. + + var buf bytes.Buffer + enc := dbfixture.NewEncoder(db, &buf) + + if err := enc.Encode(users, orgs); err != nil { + panic(err) + } + + fmt.Println("") + fmt.Println(buf.String()) +} + +func dropSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewDropTable().Model(model).IfExists().Cascade().Exec(ctx); err != nil { + return err + } + } + return nil +} diff --git a/ogx/example/get-where-fields/go.mod b/ogx/example/get-where-fields/go.mod new file mode 100644 index 00000000..7b9a73fa --- /dev/null +++ b/ogx/example/get-where-fields/go.mod @@ -0,0 +1,30 @@ +module gitee.com/chentanyang/ogx/example/get-where-fields + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +replace gitee.com/chentanyang/ogx/dbfixture => ../../dbfixture + +replace gitee.com/chentanyang/ogx/extra/ogxdebug => ../../extra/ogxdebug + +replace gitee.com/chentanyang/ogx/dialect/ogdialect => ../../dialect/ogdialect + +require ( + gitee.com/chentanyang/ogx v1.1.7 + gitee.com/chentanyang/ogx/dialect/ogdialect v0.0.0-20220915042425-85b0183464bc + gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 +) + +require ( + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/ogx/example/get-where-fields/go.sum b/ogx/example/get-where-fields/go.sum new file mode 100644 index 00000000..1f3024f6 --- /dev/null +++ b/ogx/example/get-where-fields/go.sum @@ -0,0 +1,112 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 h1:npfLM9/QpkmdK+XY9X2pcC2EX5gosyn/6dRDRd2sEJs= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4/go.mod h1:2UEp+ug6ls6C0pLfZgBn7VBzBntFUzxJuy+6FlQ7qyI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/example/get-where-fields/main.go b/ogx/example/get-where-fields/main.go new file mode 100644 index 00000000..a4d96af6 --- /dev/null +++ b/ogx/example/get-where-fields/main.go @@ -0,0 +1,52 @@ +package main + +import ( + "database/sql" + "fmt" + "strings" + + "gitee.com/chentanyang/ogx/dialect/ogdialect" + + "gitee.com/chentanyang/ogx" + + _ "gitee.com/opengauss/openGauss-connector-go-pq" +) + +type Item struct { + ID int64 `ogx:",pk,autoincrement"` +} + +func main() { + connStr := "host=192.168.20.40 port=26000 user=cuih password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + //sqldb, err := sql.Open(sqliteshim.ShimName, "file::memory:?cache=shared") + if err != nil { + panic(err) + } + + db := ogx.NewDB(opengaussdb, ogdialect.New()) + defer db.Close() + + q := db.NewSelect().Model((*Item)(nil)).Where("id > ?", 0).Where("id < ?", 10) + + fmt.Println(GetWhereFields(q.String())) +} + +func GetWhereFields(query string) []string { + q := strings.Split(query, "WHERE ") + if len(q) == 1 { + return nil + } + + whereFields := strings.Split(q[1], " AND ") + + fields := make([]string, len(whereFields)) + + for i := range whereFields { + fields[i] = strings.TrimFunc(whereFields[i], func(r rune) bool { + return r == '(' || r == ')' + }) + } + + return fields +} diff --git a/ogx/example/migrate/README.md b/ogx/example/migrate/README.md new file mode 100644 index 00000000..4b742c8e --- /dev/null +++ b/ogx/example/migrate/README.md @@ -0,0 +1,55 @@ +# Ogx migrations example + +To run migrations: + +```shell +OGXDEBUG=2 go run . db migrate +``` + +To rollback migrations: + +```shell +go run . db rollback +``` + +To view status of migrations: + +```shell +go run . db status +``` + +To create a Go migration: + +```shell +go run . db create_go go_migration_name +``` + +To create a SQL migration: + +```shell +go run . db create_sql sql_migration_name +``` + +To get help: + +```shell +go run . db + +NAME: + ogx db - database commands + +USAGE: + ogx db command [command options] [arguments...] + +COMMANDS: + init create migration tables + migrate migrate database + rollback rollback the last migration group + unlock unlock migrations + create_go create a Go migration + create_sql create a SQL migration + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) +``` \ No newline at end of file diff --git a/ogx/example/migrate/go.mod b/ogx/example/migrate/go.mod new file mode 100644 index 00000000..b92cd183 --- /dev/null +++ b/ogx/example/migrate/go.mod @@ -0,0 +1,37 @@ +module gitee.com/chentanyang/ogx/example/migrate + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +replace gitee.com/chentanyang/ogx/dbfixture => ../../dbfixture + +replace gitee.com/chentanyang/ogx/extra/ogxdebug => ../../extra/ogxdebug + +replace gitee.com/chentanyang/ogx/dialect/ogdialect => ../../dialect/ogdialect + +require ( + gitee.com/chentanyang/ogx v1.1.7 + gitee.com/chentanyang/ogx/dialect/ogdialect v0.0.0-20220915042425-85b0183464bc + gitee.com/chentanyang/ogx/extra/ogxdebug v0.0.0-20220915042425-85b0183464bc + gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 + github.com/urfave/cli/v2 v2.16.3 +) + +require ( + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect +) diff --git a/ogx/example/migrate/go.sum b/ogx/example/migrate/go.sum new file mode 100644 index 00000000..b0848b69 --- /dev/null +++ b/ogx/example/migrate/go.sum @@ -0,0 +1,132 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 h1:npfLM9/QpkmdK+XY9X2pcC2EX5gosyn/6dRDRd2sEJs= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4/go.mod h1:2UEp+ug6ls6C0pLfZgBn7VBzBntFUzxJuy+6FlQ7qyI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/urfave/cli/v2 v2.16.3 h1:gHoFIwpPjoyIMbJp/VFd+/vuD0dAgFK4B6DpEMFJfQk= +github.com/urfave/cli/v2 v2.16.3/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/example/migrate/main.go b/ogx/example/migrate/main.go new file mode 100644 index 00000000..e9c2d41b --- /dev/null +++ b/ogx/example/migrate/main.go @@ -0,0 +1,165 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + "os" + "strings" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/dialect/ogdialect" + "gitee.com/chentanyang/ogx/example/migrate/migrations" + "gitee.com/chentanyang/ogx/extra/ogxdebug" + "gitee.com/chentanyang/ogx/migrate" + "github.com/urfave/cli/v2" + + _ "gitee.com/opengauss/openGauss-connector-go-pq" +) + +func main() { + connStr := "host=192.168.20.40 port=26000 user=cuih password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + if err != nil { + panic(err) + } + + db := ogx.NewDB(opengaussdb, ogdialect.New()) + db.AddQueryHook(ogxdebug.NewQueryHook( + ogxdebug.WithEnabled(false), + ogxdebug.FromEnv(""), + )) + + app := &cli.App{ + Name: "ogx", + + Commands: []*cli.Command{ + newDBCommand(migrate.NewMigrator(db, migrations.Migrations)), + }, + } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} + +func newDBCommand(migrator *migrate.Migrator) *cli.Command { + return &cli.Command{ + Name: "db", + Usage: "database migrations", + Subcommands: []*cli.Command{ + { + Name: "init", + Usage: "create migration tables", + Action: func(c *cli.Context) error { + return migrator.Init(c.Context) + }, + }, + { + Name: "migrate", + Usage: "migrate database", + Action: func(c *cli.Context) error { + group, err := migrator.Migrate(c.Context) + if err != nil { + return err + } + if group.IsZero() { + fmt.Printf("there are no new migrations to run (database is up to date)\n") + return nil + } + fmt.Printf("migrated to %s\n", group) + return nil + }, + }, + { + Name: "rollback", + Usage: "rollback the last migration group", + Action: func(c *cli.Context) error { + group, err := migrator.Rollback(c.Context) + if err != nil { + return err + } + if group.IsZero() { + fmt.Printf("there are no groups to roll back\n") + return nil + } + fmt.Printf("rolled back %s\n", group) + return nil + }, + }, + { + Name: "lock", + Usage: "lock migrations", + Action: func(c *cli.Context) error { + return migrator.Lock(c.Context) + }, + }, + { + Name: "unlock", + Usage: "unlock migrations", + Action: func(c *cli.Context) error { + return migrator.Unlock(c.Context) + }, + }, + { + Name: "create_go", + Usage: "create Go migration", + Action: func(c *cli.Context) error { + name := strings.Join(c.Args().Slice(), "_") + mf, err := migrator.CreateGoMigration(c.Context, name) + if err != nil { + return err + } + fmt.Printf("created migration %s (%s)\n", mf.Name, mf.Path) + return nil + }, + }, + { + Name: "create_sql", + Usage: "create up and down SQL migrations", + Action: func(c *cli.Context) error { + name := strings.Join(c.Args().Slice(), "_") + files, err := migrator.CreateSQLMigrations(c.Context, name) + if err != nil { + return err + } + + for _, mf := range files { + fmt.Printf("created migration %s (%s)\n", mf.Name, mf.Path) + } + + return nil + }, + }, + { + Name: "status", + Usage: "print migrations status", + Action: func(c *cli.Context) error { + ms, err := migrator.MigrationsWithStatus(c.Context) + if err != nil { + return err + } + fmt.Printf("migrations: %s\n", ms) + fmt.Printf("unapplied migrations: %s\n", ms.Unapplied()) + fmt.Printf("last migration group: %s\n", ms.LastGroup()) + return nil + }, + }, + { + Name: "mark_applied", + Usage: "mark migrations as applied without actually running them", + Action: func(c *cli.Context) error { + group, err := migrator.Migrate(c.Context, migrate.WithNopMigration()) + if err != nil { + return err + } + if group.IsZero() { + fmt.Printf("there are no new migrations to mark as applied\n") + return nil + } + fmt.Printf("marked as applied %s\n", group) + return nil + }, + }, + }, + } +} diff --git a/ogx/example/migrate/migrations/20210505110026_foo.go b/ogx/example/migrate/migrations/20210505110026_foo.go new file mode 100644 index 00000000..d872e612 --- /dev/null +++ b/ogx/example/migrate/migrations/20210505110026_foo.go @@ -0,0 +1,18 @@ +package migrations + +import ( + "context" + "fmt" + + "gitee.com/chentanyang/ogx" +) + +func init() { + Migrations.MustRegister(func(ctx context.Context, db *ogx.DB) error { + fmt.Print(" [up migration] ") + return nil + }, func(ctx context.Context, db *ogx.DB) error { + fmt.Print(" [down migration] ") + return nil + }) +} diff --git a/ogx/example/migrate/migrations/20210505110847_bar.down.sql b/ogx/example/migrate/migrations/20210505110847_bar.down.sql new file mode 100644 index 00000000..3477a8cf --- /dev/null +++ b/ogx/example/migrate/migrations/20210505110847_bar.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS test; diff --git a/ogx/example/migrate/migrations/20210505110847_bar.up.sql b/ogx/example/migrate/migrations/20210505110847_bar.up.sql new file mode 100644 index 00000000..8b16c218 --- /dev/null +++ b/ogx/example/migrate/migrations/20210505110847_bar.up.sql @@ -0,0 +1,7 @@ +CREATE TABLE test ( + id bigint PRIMARY KEY +); + +--ogx:split + +ALTER TABLE test ADD COLUMN name varchar(100); diff --git a/ogx/example/migrate/migrations/main.go b/ogx/example/migrate/migrations/main.go new file mode 100644 index 00000000..b5572e8c --- /dev/null +++ b/ogx/example/migrate/migrations/main.go @@ -0,0 +1,11 @@ +package migrations + +import "gitee.com/chentanyang/ogx/migrate" + +var Migrations = migrate.NewMigrations() + +func init() { + if err := Migrations.DiscoverCaller(); err != nil { + panic(err) + } +} diff --git a/ogx/example/model-hooks/go.mod b/ogx/example/model-hooks/go.mod new file mode 100644 index 00000000..91f3079d --- /dev/null +++ b/ogx/example/model-hooks/go.mod @@ -0,0 +1,35 @@ +module gitee.com/chentanyang/ogx/example/model-hooks + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +replace gitee.com/chentanyang/ogx/dbfixture => ../../dbfixture + +replace gitee.com/chentanyang/ogx/extra/ogxdebug => ../../extra/ogxdebug + +replace gitee.com/chentanyang/ogx/dialect/ogdialect => ../../dialect/ogdialect + +require ( + gitee.com/chentanyang/ogx v1.1.7 + gitee.com/chentanyang/ogx/dialect/ogdialect v1.1.7 + gitee.com/chentanyang/ogx/extra/ogxdebug v1.1.7 + gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 + github.com/davecgh/go-spew v1.1.1 +) + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/ogx/example/model-hooks/go.sum b/ogx/example/model-hooks/go.sum new file mode 100644 index 00000000..3a67d2ca --- /dev/null +++ b/ogx/example/model-hooks/go.sum @@ -0,0 +1,125 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 h1:npfLM9/QpkmdK+XY9X2pcC2EX5gosyn/6dRDRd2sEJs= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4/go.mod h1:2UEp+ug6ls6C0pLfZgBn7VBzBntFUzxJuy+6FlQ7qyI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/example/model-hooks/main.go b/ogx/example/model-hooks/main.go new file mode 100644 index 00000000..28eb0efc --- /dev/null +++ b/ogx/example/model-hooks/main.go @@ -0,0 +1,101 @@ +package main + +import ( + "context" + "database/sql" + "time" + + "gitee.com/chentanyang/ogx/dialect/ogdialect" + + "github.com/davecgh/go-spew/spew" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/extra/ogxdebug" + + _ "gitee.com/opengauss/openGauss-connector-go-pq" +) + +func main() { + ctx := context.Background() + + connStr := "host=192.168.20.40 port=26000 user=cuih password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + if err != nil { + panic(err) + } + opengaussdb.SetMaxOpenConns(1) + + db := ogx.NewDB(opengaussdb, ogdialect.New()) + db.AddQueryHook(ogxdebug.NewQueryHook(ogxdebug.WithVerbose(true))) + + if err := db.ResetModel(ctx, (*User)(nil)); err != nil { + panic(err) + } + + defer func() { + _ = dropSchema(ctx, db, (*User)(nil)) + }() + + user := new(User) + + if _, err := db.NewInsert().Model(user).Exec(ctx); err != nil { + panic(err) + } + + if _, err := db.NewUpdate(). + Model(user). + WherePK(). + Exec(ctx); err != nil { + panic(err) + } + + if err := db.NewSelect().Model(user).WherePK().Scan(ctx); err != nil { + panic(err) + } + + spew.Dump(user) +} + +type User struct { + ID int64 `ogx:",pk,autoincrement"` + Password string + CreatedAt time.Time `ogx:",nullzero"` + UpdatedAt time.Time `ogx:",nullzero"` +} + +var _ ogx.BeforeAppendModelHook = (*User)(nil) + +func (u *User) BeforeAppendModel(ctx context.Context, query ogx.Query) error { + switch query.(type) { + case *ogx.InsertQuery: + u.Password = "[encrypted]" + u.CreatedAt = time.Now() + case *ogx.UpdateQuery: + u.UpdatedAt = time.Now() + } + return nil +} + +var _ ogx.BeforeScanRowHook = (*User)(nil) + +func (u *User) BeforeScanRow(ctx context.Context) error { + // Do some initialization. + u.Password = "" + return nil +} + +var _ ogx.AfterScanRowHook = (*User)(nil) + +func (u *User) AfterScanRow(ctx context.Context) error { + u.Password = "[decrypted]" + return nil +} + +func dropSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewDropTable().Model(model).IfExists().Cascade().Exec(ctx); err != nil { + return err + } + } + return nil +} diff --git a/ogx/example/multi-tenant/fixture.yml b/ogx/example/multi-tenant/fixture.yml new file mode 100644 index 00000000..1ab61c60 --- /dev/null +++ b/ogx/example/multi-tenant/fixture.yml @@ -0,0 +1,8 @@ +- model: Story + rows: + - title: Story 1 by author 1 + author_id: 1 + - title: Story 2 by author 1 + author_id: 1 + - title: Story 3 by author 2 + author_id: 2 diff --git a/ogx/example/multi-tenant/go.mod b/ogx/example/multi-tenant/go.mod new file mode 100644 index 00000000..6c07ba78 --- /dev/null +++ b/ogx/example/multi-tenant/go.mod @@ -0,0 +1,36 @@ +module gitee.com/chentanyang/ogx/example/multi-tenant + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +replace gitee.com/chentanyang/ogx/dbfixture => ../../dbfixture + +replace gitee.com/chentanyang/ogx/extra/ogxdebug => ../../extra/ogxdebug + +replace gitee.com/chentanyang/ogx/dialect/ogdialect => ../../dialect/ogdialect + +require ( + gitee.com/chentanyang/ogx v1.1.7 + gitee.com/chentanyang/ogx/dbfixture v1.1.7 + gitee.com/chentanyang/ogx/dialect/ogdialect v1.1.7 + gitee.com/chentanyang/ogx/extra/ogxdebug v1.1.7 + gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 + github.com/davecgh/go-spew v1.1.1 +) + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/ogx/example/multi-tenant/go.sum b/ogx/example/multi-tenant/go.sum new file mode 100644 index 00000000..0e87af25 --- /dev/null +++ b/ogx/example/multi-tenant/go.sum @@ -0,0 +1,128 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 h1:npfLM9/QpkmdK+XY9X2pcC2EX5gosyn/6dRDRd2sEJs= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4/go.mod h1:2UEp+ug6ls6C0pLfZgBn7VBzBntFUzxJuy+6FlQ7qyI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/example/multi-tenant/main.go b/ogx/example/multi-tenant/main.go new file mode 100644 index 00000000..cd46d664 --- /dev/null +++ b/ogx/example/multi-tenant/main.go @@ -0,0 +1,84 @@ +package main + +import ( + "context" + "database/sql" + "os" + + "gitee.com/chentanyang/ogx/dialect/ogdialect" + + "github.com/davecgh/go-spew/spew" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/dbfixture" + "gitee.com/chentanyang/ogx/extra/ogxdebug" + + _ "gitee.com/opengauss/openGauss-connector-go-pq" +) + +func main() { + ctx := context.Background() + + connStr := "host=192.168.20.40 port=26000 user=cuih password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + if err != nil { + panic(err) + } + opengaussdb.SetMaxOpenConns(1) + + db := ogx.NewDB(opengaussdb, ogdialect.New()) + db.AddQueryHook(ogxdebug.NewQueryHook(ogxdebug.WithVerbose(true))) + + // Register models for the fixture. + db.RegisterModel((*Story)(nil)) + defer func() { + _ = dropSchema(ctx, db, (*Story)(nil)) + }() + + // Create tables and load initial data. + fixture := dbfixture.New(db, dbfixture.WithRecreateTables()) + if err := fixture.Load(ctx, os.DirFS("."), "fixture.yml"); err != nil { + panic(err) + } + + { + ctx := context.WithValue(ctx, "tenant_id", 1) + stories, err := selectStories(ctx, db) + if err != nil { + panic(err) + } + spew.Dump(stories) + } +} + +func selectStories(ctx context.Context, db *ogx.DB) ([]*Story, error) { + stories := make([]*Story, 0) + if err := db.NewSelect().Model(&stories).Scan(ctx); err != nil { + return nil, err + } + return stories, nil +} + +type Story struct { + ID int64 `ogx:",pk,autoincrement"` + Title string + AuthorID int64 +} + +var _ ogx.BeforeSelectHook = (*Story)(nil) + +func (s *Story) BeforeSelect(ctx context.Context, query *ogx.SelectQuery) error { + if id := ctx.Value("tenant_id"); id != nil { + query.Where("author_id = ?", id) + } + return nil +} + +func dropSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewDropTable().Model(model).IfExists().Cascade().Exec(ctx); err != nil { + return err + } + } + return nil +} diff --git a/ogx/example/placeholders/go.mod b/ogx/example/placeholders/go.mod new file mode 100644 index 00000000..7fc8b46c --- /dev/null +++ b/ogx/example/placeholders/go.mod @@ -0,0 +1,32 @@ +module gitee.com/chentanyang/ogx/example/placeholders + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +replace gitee.com/chentanyang/ogx/extra/ogxdebug => ../../extra/ogxdebug + +replace gitee.com/chentanyang/ogx/dialect/ogdialect => ../../dialect/ogdialect + +require ( + gitee.com/chentanyang/ogx v1.1.7 + gitee.com/chentanyang/ogx/dialect/ogdialect v0.0.0-20220915042425-85b0183464bc + gitee.com/chentanyang/ogx/extra/ogxdebug v1.1.7 + gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 +) + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/ogx/example/placeholders/go.sum b/ogx/example/placeholders/go.sum new file mode 100644 index 00000000..3a67d2ca --- /dev/null +++ b/ogx/example/placeholders/go.sum @@ -0,0 +1,125 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 h1:npfLM9/QpkmdK+XY9X2pcC2EX5gosyn/6dRDRd2sEJs= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4/go.mod h1:2UEp+ug6ls6C0pLfZgBn7VBzBntFUzxJuy+6FlQ7qyI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/example/placeholders/main.go b/ogx/example/placeholders/main.go new file mode 100644 index 00000000..41faf988 --- /dev/null +++ b/ogx/example/placeholders/main.go @@ -0,0 +1,83 @@ +package main + +import ( + "context" + "database/sql" + "fmt" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/dialect/ogdialect" + "gitee.com/chentanyang/ogx/extra/ogxdebug" + + _ "gitee.com/opengauss/openGauss-connector-go-pq" +) + +type User struct { + ID int64 `ogx:",pk,autoincrement"` + Name string + Emails []string +} + +func main() { + ctx := context.Background() + + connStr := "host=192.168.20.40 port=26000 user=cuih password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + if err != nil { + panic(err) + } + db := ogx.NewDB(opengaussdb, ogdialect.New()) + defer db.Close() + + db.AddQueryHook(ogxdebug.NewQueryHook(ogxdebug.WithVerbose(true))) + + if err := createSchema(ctx, db, (*User)(nil)); err != nil { + panic(err) + } + defer func() { + _, _ = db.NewDropTable().Model((*User)(nil)).IfExists().Cascade().Exec(ctx) + }() + + var tableName, tableAlias, pks, tablePKs, columns, tableColumns string + + if err := db.NewSelect().Model((*User)(nil)). + ColumnExpr("'?TableName'"). + ColumnExpr("'?TableAlias'"). + ColumnExpr("'?PKs'"). + ColumnExpr("'?TablePKs'"). + ColumnExpr("'?Columns'"). + ColumnExpr("'?TableColumns'"). + ModelTableExpr(""). + Scan(ctx, &tableName, &tableAlias, &pks, &tablePKs, &columns, &tableColumns); err != nil { + panic(err) + } + + fmt.Println("tableName", tableName) + fmt.Println("tableAlias", tableAlias) + fmt.Println("pks", pks) + fmt.Println("tablePKs", tablePKs) + fmt.Println("columns", columns) + fmt.Println("tableColumns", tableColumns) +} + +func createSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewCreateTable().Model(model).Exec(ctx); err != nil { + return err + } + } + users := []User{ + { + Name: "admin", + Emails: []string{"admin1@admin", "admin2@admin"}, + }, + { + Name: "root", + Emails: []string{"root1@root", "root2@root"}, + }, + } + if _, err := db.NewInsert().Model(&users).Exec(ctx); err != nil { + return err + } + return nil +} diff --git a/ogx/example/rel-belongs-to/go.mod b/ogx/example/rel-belongs-to/go.mod new file mode 100644 index 00000000..8c7bbbad --- /dev/null +++ b/ogx/example/rel-belongs-to/go.mod @@ -0,0 +1,31 @@ +module gitee.com/chentanyang/ogx/example/rel-belongs-to + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +replace gitee.com/chentanyang/ogx/extra/ogxdebug => ../../extra/ogxdebug + +replace gitee.com/chentanyang/ogx/dialect/ogdialect => ../../dialect/ogdialect + +require ( + gitee.com/chentanyang/ogx v1.1.8 + gitee.com/chentanyang/ogx/dialect/ogdialect v0.0.0-20220915042425-85b0183464bc + gitee.com/chentanyang/ogx/extra/ogxdebug v1.1.7 + gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 +) + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect +) diff --git a/ogx/example/rel-belongs-to/go.sum b/ogx/example/rel-belongs-to/go.sum new file mode 100644 index 00000000..58329b4c --- /dev/null +++ b/ogx/example/rel-belongs-to/go.sum @@ -0,0 +1,124 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 h1:npfLM9/QpkmdK+XY9X2pcC2EX5gosyn/6dRDRd2sEJs= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4/go.mod h1:2UEp+ug6ls6C0pLfZgBn7VBzBntFUzxJuy+6FlQ7qyI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/example/rel-belongs-to/main.go b/ogx/example/rel-belongs-to/main.go new file mode 100644 index 00000000..7590cd0d --- /dev/null +++ b/ogx/example/rel-belongs-to/main.go @@ -0,0 +1,98 @@ +package main + +import ( + "context" + "database/sql" + "fmt" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/dialect/ogdialect" + "gitee.com/chentanyang/ogx/extra/ogxdebug" + + _ "gitee.com/opengauss/openGauss-connector-go-pq" +) + +type Profile struct { + ID int64 `ogx:",pk,autoincrement"` + Lang string +} + +// User has one profile. +type User struct { + ID int64 `ogx:",pk,autoincrement"` + Name string + ProfileID int64 + Profile *Profile `ogx:"rel:belongs-to,join=profile_id=id"` +} + +func main() { + ctx := context.Background() + + connStr := "host=192.168.20.40 port=26000 user=cuih password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + if err != nil { + panic(err) + } + db := ogx.NewDB(opengaussdb, ogdialect.New()) + defer db.Close() + + db.AddQueryHook(ogxdebug.NewQueryHook(ogxdebug.WithVerbose(true))) + + if err := createSchema(ctx, db, (*User)(nil), (*Profile)(nil)); err != nil { + panic(err) + } + defer func() { + _ = dropSchema(ctx, db, (*User)(nil), (*Profile)(nil)) + }() + + var users []User + if err := db.NewSelect(). + Model(&users). + Column("user.*"). + Relation("Profile"). + Scan(ctx); err != nil { + panic(err) + } + + fmt.Println(len(users), "results") + fmt.Println(users[0].ID, users[0].Name, users[0].Profile) + fmt.Println(users[1].ID, users[1].Name, users[1].Profile) + // Output: 2 results + // 1 user 1 &{1 en} + // 2 user 2 &{2 ru} +} + +func createSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewCreateTable().Model(model).Exec(ctx); err != nil { + return err + } + } + + profiles := []*Profile{ + {ID: 1, Lang: "en"}, + {ID: 2, Lang: "ru"}, + } + if _, err := db.NewInsert().Model(&profiles).Exec(ctx); err != nil { + return err + } + + users := []*User{ + {ID: 1, Name: "user 1", ProfileID: 1}, + {ID: 2, Name: "user 2", ProfileID: 2}, + } + if _, err := db.NewInsert().Model(&users).Exec(ctx); err != nil { + return err + } + + return nil +} + +func dropSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewDropTable().Model(model).IfExists().Cascade().Exec(ctx); err != nil { + return err + } + } + return nil +} diff --git a/ogx/example/rel-has-many-polymorphic/fixture.yml b/ogx/example/rel-has-many-polymorphic/fixture.yml new file mode 100644 index 00000000..0c513730 --- /dev/null +++ b/ogx/example/rel-has-many-polymorphic/fixture.yml @@ -0,0 +1,41 @@ +- model: Article + rows: + - _id: a1 + name: article 1 + - _id: a2 + name: article 2 + - _id: a3 + name: article 3 + +- model: Post + rows: + - _id: p1 + name: post 1 + - _id: p2 + name: post 2 + - _id: p3 + name: post 3 + +- model: Comment + rows: + - trackable_id: '{{ $.Article.a1.ID }}' + trackable_type: article + text: Comment 1 for a1 + - trackable_id: '{{ $.Article.a1.ID }}' + trackable_type: article + text: Comment 2 for a1 + - trackable_id: '{{ $.Article.a2.ID }}' + trackable_type: article + text: Comment 1 for a2 + - trackable_id: '{{ $.Article.a2.ID }}' + trackable_type: article + text: Comment 2 for a2 + - trackable_id: '{{ $.Post.p1.ID }}' + trackable_type: post + text: Comment 1 for p1 + - trackable_id: '{{ $.Post.p1.ID }}' + trackable_type: post + text: Comment 2 for p1 + - trackable_id: '{{ $.Post.p1.ID }}' + trackable_type: post + text: Comment 3 for p1 diff --git a/ogx/example/rel-has-many-polymorphic/go.mod b/ogx/example/rel-has-many-polymorphic/go.mod new file mode 100644 index 00000000..2aa44e9e --- /dev/null +++ b/ogx/example/rel-has-many-polymorphic/go.mod @@ -0,0 +1,36 @@ +module gitee.com/chentanyang/ogx/example/rel-has-many-polymorphic + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +replace gitee.com/chentanyang/ogx/dbfixture => ../../dbfixture + +replace gitee.com/chentanyang/ogx/extra/ogxdebug => ../../extra/ogxdebug + +replace gitee.com/chentanyang/ogx/dialect/ogdialect => ../../dialect/ogdialect + +require ( + gitee.com/chentanyang/ogx v1.1.7 + gitee.com/chentanyang/ogx/dbfixture v1.1.7 + gitee.com/chentanyang/ogx/dialect/ogdialect v0.0.0-20220915042425-85b0183464bc + gitee.com/chentanyang/ogx/extra/ogxdebug v1.1.7 + gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 + github.com/davecgh/go-spew v1.1.1 +) + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/ogx/example/rel-has-many-polymorphic/go.sum b/ogx/example/rel-has-many-polymorphic/go.sum new file mode 100644 index 00000000..0e87af25 --- /dev/null +++ b/ogx/example/rel-has-many-polymorphic/go.sum @@ -0,0 +1,128 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 h1:npfLM9/QpkmdK+XY9X2pcC2EX5gosyn/6dRDRd2sEJs= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4/go.mod h1:2UEp+ug6ls6C0pLfZgBn7VBzBntFUzxJuy+6FlQ7qyI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/example/rel-has-many-polymorphic/main.go b/ogx/example/rel-has-many-polymorphic/main.go new file mode 100644 index 00000000..07d07bc8 --- /dev/null +++ b/ogx/example/rel-has-many-polymorphic/main.go @@ -0,0 +1,103 @@ +package main + +import ( + "context" + "database/sql" + "os" + + "github.com/davecgh/go-spew/spew" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/dbfixture" + "gitee.com/chentanyang/ogx/dialect/ogdialect" + "gitee.com/chentanyang/ogx/extra/ogxdebug" + + _ "gitee.com/opengauss/openGauss-connector-go-pq" +) + +type Comment struct { + TrackableID int64 // Article.ID or Post.ID + TrackableType string // "article" or "post" + Text string +} + +type Article struct { + ID int64 `ogx:",pk,autoincrement"` + Name string + + Comments []Comment `ogx:"rel:has-many,join:id=trackable_id,join:type=trackable_type,polymorphic"` +} + +type Post struct { + ID int64 `ogx:",pk,autoincrement"` + Name string + + Comments []Comment `ogx:"rel:has-many,join:id=trackable_id,join:type=trackable_type,polymorphic"` +} + +func main() { + ctx := context.Background() + + connStr := "host=192.168.20.40 port=26000 user=cuih password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + if err != nil { + panic(err) + } + db := ogx.NewDB(opengaussdb, ogdialect.New()) + defer db.Close() + + db.AddQueryHook(ogxdebug.NewQueryHook(ogxdebug.WithVerbose(true))) + + if err := createSchema(ctx, db); err != nil { + panic(err) + } + defer func() { + _ = dropSchema(ctx, db, (*Comment)(nil), (*Article)(nil), (*Post)(nil)) + }() + + { + article := new(Article) + if err := db.NewSelect(). + Model(article). + Relation("Comments"). + Where("id = 1"). + Scan(ctx); err != nil { + panic(err) + } + spew.Dump(article) + } + + { + post := new(Post) + if err := db.NewSelect(). + Model(post). + Relation("Comments"). + Where("id = 1"). + Scan(ctx); err != nil { + panic(err) + } + spew.Dump(post) + } +} + +func createSchema(ctx context.Context, db *ogx.DB) error { + // Register models for the fixture. + db.RegisterModel((*Comment)(nil), (*Article)(nil), (*Post)(nil)) + + // Create tables and load initial data. + fixture := dbfixture.New(db, dbfixture.WithRecreateTables()) + if err := fixture.Load(ctx, os.DirFS("."), "fixture.yml"); err != nil { + return err + } + + return nil +} + +func dropSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewDropTable().Model(model).IfExists().Cascade().Exec(ctx); err != nil { + return err + } + } + return nil +} diff --git a/ogx/example/rel-has-many/go.mod b/ogx/example/rel-has-many/go.mod new file mode 100644 index 00000000..e8e471b5 --- /dev/null +++ b/ogx/example/rel-has-many/go.mod @@ -0,0 +1,31 @@ +module gitee.com/chentanyang/ogx/example/rel-has-many + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +replace gitee.com/chentanyang/ogx/extra/ogxdebug => ../../extra/ogxdebug + +replace gitee.com/chentanyang/ogx/dialect/ogdialect => ../../dialect/ogdialect + +require ( + gitee.com/chentanyang/ogx v1.1.7 + gitee.com/chentanyang/ogx/dialect/ogdialect v0.0.0-20220915042425-85b0183464bc + gitee.com/chentanyang/ogx/extra/ogxdebug v1.1.7 + gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 +) + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect +) diff --git a/ogx/example/rel-has-many/go.sum b/ogx/example/rel-has-many/go.sum new file mode 100644 index 00000000..58329b4c --- /dev/null +++ b/ogx/example/rel-has-many/go.sum @@ -0,0 +1,124 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 h1:npfLM9/QpkmdK+XY9X2pcC2EX5gosyn/6dRDRd2sEJs= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4/go.mod h1:2UEp+ug6ls6C0pLfZgBn7VBzBntFUzxJuy+6FlQ7qyI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/example/rel-has-many/main.go b/ogx/example/rel-has-many/main.go new file mode 100644 index 00000000..30ff808c --- /dev/null +++ b/ogx/example/rel-has-many/main.go @@ -0,0 +1,99 @@ +package main + +import ( + "context" + "database/sql" + "fmt" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/dialect/ogdialect" + "gitee.com/chentanyang/ogx/extra/ogxdebug" + + _ "gitee.com/opengauss/openGauss-connector-go-pq" +) + +type Profile struct { + ID int64 `ogx:",pk,autoincrement"` + Lang string + Active bool + UserID int64 +} + +// User has many profiles. +type User struct { + ID int64 `ogx:",pk,autoincrement"` + Name string + Profiles []*Profile `ogx:"rel:has-many,join:id=user_id"` +} + +func main() { + ctx := context.Background() + + connStr := "host=192.168.20.40 port=26000 user=cuih password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + if err != nil { + panic(err) + } + db := ogx.NewDB(opengaussdb, ogdialect.New()) + defer db.Close() + + db.AddQueryHook(ogxdebug.NewQueryHook(ogxdebug.WithVerbose(true))) + + if err := createSchema(ctx, db, (*User)(nil), (*Profile)(nil)); err != nil { + panic(err) + } + defer func() { + _ = dropSchema(ctx, db, (*User)(nil), (*Profile)(nil)) + }() + + user := new(User) + if err := db.NewSelect(). + Model(user). + Column("user.*"). + Relation("Profiles", func(q *ogx.SelectQuery) *ogx.SelectQuery { + return q.Where("active IS TRUE") + }). + OrderExpr("\"user\".id ASC"). + Limit(1). + Scan(ctx); err != nil { + panic(err) + } + fmt.Println(user.ID, user.Name, user.Profiles[0], user.Profiles[1]) + // Output: 1 user 1 &{1 en true 1} &{2 ru true 1} +} + +func createSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewCreateTable().Model(model).Exec(ctx); err != nil { + return err + } + } + + users := []*User{ + {ID: 1, Name: "user 1"}, + {ID: 2, Name: "user 2"}, + } + if _, err := db.NewInsert().Model(&users).Exec(ctx); err != nil { + return err + } + + profiles := []*Profile{ + {ID: 1, Lang: "en", Active: true, UserID: 1}, + {ID: 2, Lang: "ru", Active: true, UserID: 1}, + {ID: 3, Lang: "md", Active: false, UserID: 1}, + } + if _, err := db.NewInsert().Model(&profiles).Exec(ctx); err != nil { + return err + } + + return nil +} + +func dropSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewDropTable().Model(model).IfExists().Cascade().Exec(ctx); err != nil { + return err + } + } + return nil +} diff --git a/ogx/example/rel-has-one/go.mod b/ogx/example/rel-has-one/go.mod new file mode 100644 index 00000000..dcdd97dc --- /dev/null +++ b/ogx/example/rel-has-one/go.mod @@ -0,0 +1,31 @@ +module gitee.com/chentanyang/ogx/example/rel-has-one + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +replace gitee.com/chentanyang/ogx/extra/ogxdebug => ../../extra/ogxdebug + +replace gitee.com/chentanyang/ogx/dialect/ogdialect => ../../dialect/ogdialect + +require ( + gitee.com/chentanyang/ogx v1.1.7 + gitee.com/chentanyang/ogx/dialect/ogdialect v0.0.0-20220915042425-85b0183464bc + gitee.com/chentanyang/ogx/extra/ogxdebug v1.1.7 + gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 +) + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect +) diff --git a/ogx/example/rel-has-one/go.sum b/ogx/example/rel-has-one/go.sum new file mode 100644 index 00000000..58329b4c --- /dev/null +++ b/ogx/example/rel-has-one/go.sum @@ -0,0 +1,124 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 h1:npfLM9/QpkmdK+XY9X2pcC2EX5gosyn/6dRDRd2sEJs= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4/go.mod h1:2UEp+ug6ls6C0pLfZgBn7VBzBntFUzxJuy+6FlQ7qyI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/example/rel-has-one/main.go b/ogx/example/rel-has-one/main.go new file mode 100644 index 00000000..9d0b0bb1 --- /dev/null +++ b/ogx/example/rel-has-one/main.go @@ -0,0 +1,98 @@ +package main + +import ( + "context" + "database/sql" + "fmt" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/dialect/ogdialect" + "gitee.com/chentanyang/ogx/extra/ogxdebug" + + _ "gitee.com/opengauss/openGauss-connector-go-pq" +) + +// Profile belongs to User. +type Profile struct { + ID int64 `ogx:",pk,autoincrement"` + Lang string + UserID int64 +} + +type User struct { + ID int64 `ogx:",pk,autoincrement"` + Name string + Profile *Profile `ogx:"rel:has-one,join:id=user_id"` +} + +func main() { + ctx := context.Background() + + connStr := "host=192.168.20.40 port=26000 user=cuih password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + if err != nil { + panic(err) + } + db := ogx.NewDB(opengaussdb, ogdialect.New()) + defer db.Close() + + db.AddQueryHook(ogxdebug.NewQueryHook(ogxdebug.WithVerbose(true))) + + if err := createSchema(ctx, db, (*User)(nil), (*Profile)(nil)); err != nil { + panic(err) + } + defer func() { + _ = dropSchema(ctx, db, (*User)(nil), (*Profile)(nil)) + }() + + var users []User + if err := db.NewSelect(). + Model(&users). + Column("user.*"). + Relation("Profile"). + Scan(ctx); err != nil { + panic(err) + } + + fmt.Println(len(users), "results") + fmt.Println(users[0].ID, users[0].Name, users[0].Profile) + fmt.Println(users[1].ID, users[1].Name, users[1].Profile) + // Output: 2 results + // 1 user 1 &{1 en 1} + // 2 user 2 &{2 ru 2} +} + +func createSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewCreateTable().Model(model).Exec(ctx); err != nil { + return err + } + } + + users := []*User{ + {ID: 1, Name: "user 1"}, + {ID: 2, Name: "user 2"}, + } + if _, err := db.NewInsert().Model(&users).Exec(ctx); err != nil { + return err + } + + profiles := []*Profile{ + {ID: 1, Lang: "en", UserID: 1}, + {ID: 2, Lang: "ru", UserID: 2}, + } + if _, err := db.NewInsert().Model(&profiles).Exec(ctx); err != nil { + return err + } + + return nil +} + +func dropSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewDropTable().Model(model).IfExists().Cascade().Exec(ctx); err != nil { + return err + } + } + return nil +} diff --git a/ogx/example/rel-join-condition/go.mod b/ogx/example/rel-join-condition/go.mod new file mode 100644 index 00000000..a3bb12cf --- /dev/null +++ b/ogx/example/rel-join-condition/go.mod @@ -0,0 +1,31 @@ +module gitee.com/chentanyang/ogx/example/rel-join-condition + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +replace gitee.com/chentanyang/ogx/extra/ogxdebug => ../../extra/ogxdebug + +replace gitee.com/chentanyang/ogx/dialect/ogdialect => ../../dialect/ogdialect + +require ( + gitee.com/chentanyang/ogx v1.1.7 + gitee.com/chentanyang/ogx/dialect/ogdialect v0.0.0-20220915042425-85b0183464bc + gitee.com/chentanyang/ogx/extra/ogxdebug v1.1.7 + gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 +) + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect +) diff --git a/ogx/example/rel-join-condition/go.sum b/ogx/example/rel-join-condition/go.sum new file mode 100644 index 00000000..58329b4c --- /dev/null +++ b/ogx/example/rel-join-condition/go.sum @@ -0,0 +1,124 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 h1:npfLM9/QpkmdK+XY9X2pcC2EX5gosyn/6dRDRd2sEJs= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4/go.mod h1:2UEp+ug6ls6C0pLfZgBn7VBzBntFUzxJuy+6FlQ7qyI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/example/rel-join-condition/main.go b/ogx/example/rel-join-condition/main.go new file mode 100644 index 00000000..7236df58 --- /dev/null +++ b/ogx/example/rel-join-condition/main.go @@ -0,0 +1,107 @@ +package main + +import ( + "context" + "database/sql" + "fmt" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/dialect/ogdialect" + "gitee.com/chentanyang/ogx/extra/ogxdebug" + + _ "gitee.com/opengauss/openGauss-connector-go-pq" +) + +type Profile struct { + ID int64 `ogx:",pk,autoincrement"` + Lang string + Active bool + UserID int64 +} + +// User has many profiles. +type User struct { + ID int64 `ogx:",pk,autoincrement"` + Name string + Profiles []*Profile `ogx:"rel:has-many,join:id=user_id,join_on:active IS TRUE"` +} + +func main() { + ctx := context.Background() + + connStr := "host=192.168.20.40 port=26000 user=cuih password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + if err != nil { + panic(err) + } + db := ogx.NewDB(opengaussdb, ogdialect.New()) + defer db.Close() + + db.AddQueryHook(ogxdebug.NewQueryHook(ogxdebug.WithVerbose(true))) + + if err := createSchema(ctx, db, (*User)(nil), (*Profile)(nil)); err != nil { + panic(err) + } + // defer func() { + // _ = dropSchema(ctx, db, (*User)(nil), (*Profile)(nil)) + // }() + + user := new(User) + if err := db.NewSelect(). + Model(user). + Column("user.*"). + Relation("Profiles", func(q *ogx.SelectQuery) *ogx.SelectQuery { + return q.Where("lang = 'ru'") + }). + OrderExpr("\"user\".id ASC"). + Limit(1). + Scan(ctx); err != nil { + panic(err) + } + + fmt.Printf("user.ID: %d, user.Name: %q\n", user.ID, user.Name) + fmt.Printf("user.Profiles: ") + for _, p := range user.Profiles { + fmt.Printf("%v, ", p) + } + fmt.Println() + // Output: user.ID: 1, user.Name: "user 1" + // user.Profiles: &{2 ru true 1}, +} + +func createSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewCreateTable().Model(model).Exec(ctx); err != nil { + return err + } + } + + users := []*User{ + {ID: 1, Name: "user 1"}, + {ID: 2, Name: "user 2"}, + } + if _, err := db.NewInsert().Model(&users).Exec(ctx); err != nil { + return err + } + + profiles := []*Profile{ + {ID: 1, Lang: "en", Active: true, UserID: 1}, + {ID: 2, Lang: "ru", Active: true, UserID: 1}, + {ID: 3, Lang: "ru", Active: false, UserID: 1}, + {ID: 4, Lang: "md", Active: false, UserID: 1}, + } + if _, err := db.NewInsert().Model(&profiles).Exec(ctx); err != nil { + return err + } + + return nil +} + +func dropSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewDropTable().Model(model).IfExists().Cascade().Exec(ctx); err != nil { + return err + } + } + return nil +} diff --git a/ogx/example/rel-many-to-many-self/go.mod b/ogx/example/rel-many-to-many-self/go.mod new file mode 100644 index 00000000..1b9d9206 --- /dev/null +++ b/ogx/example/rel-many-to-many-self/go.mod @@ -0,0 +1,31 @@ +module gitee.com/chentanyang/ogx/example/rel-many-to-many-self + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +replace gitee.com/chentanyang/ogx/extra/ogxdebug => ../../extra/ogxdebug + +replace gitee.com/chentanyang/ogx/dialect/ogdialect => ../../dialect/ogdialect + +require ( + gitee.com/chentanyang/ogx v1.1.7 + gitee.com/chentanyang/ogx/dialect/ogdialect v0.0.0-20220915042425-85b0183464bc + gitee.com/chentanyang/ogx/extra/ogxdebug v1.1.7 + gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 +) + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect +) diff --git a/ogx/example/rel-many-to-many-self/go.sum b/ogx/example/rel-many-to-many-self/go.sum new file mode 100644 index 00000000..58329b4c --- /dev/null +++ b/ogx/example/rel-many-to-many-self/go.sum @@ -0,0 +1,124 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 h1:npfLM9/QpkmdK+XY9X2pcC2EX5gosyn/6dRDRd2sEJs= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4/go.mod h1:2UEp+ug6ls6C0pLfZgBn7VBzBntFUzxJuy+6FlQ7qyI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/example/rel-many-to-many-self/main.go b/ogx/example/rel-many-to-many-self/main.go new file mode 100644 index 00000000..27ccecbc --- /dev/null +++ b/ogx/example/rel-many-to-many-self/main.go @@ -0,0 +1,108 @@ +package main + +import ( + "context" + "database/sql" + "fmt" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/dialect/ogdialect" + "gitee.com/chentanyang/ogx/extra/ogxdebug" + + _ "gitee.com/opengauss/openGauss-connector-go-pq" +) + +type Item struct { + ID int64 `ogx:",pk,autoincrement"` + Children []Item `ogx:"m2m:item_to_items,join:Item=Child"` +} + +type ItemToItem struct { + ItemID int64 `ogx:",pk"` + Item *Item `ogx:"rel:belongs-to,join:item_id=id"` + ChildID int64 `ogx:",pk"` + Child *Item `ogx:"rel:belongs-to,join:child_id=id"` +} + +func main() { + ctx := context.Background() + + connStr := "host=192.168.20.40 port=26000 user=cuih password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + if err != nil { + panic(err) + } + db := ogx.NewDB(opengaussdb, ogdialect.New()) + defer db.Close() + + db.AddQueryHook(ogxdebug.NewQueryHook(ogxdebug.WithVerbose(true))) + + // Register many to many model so ogx can better recognize m2m relation. + // This should be done before you use the model for the first time. + db.RegisterModel((*ItemToItem)(nil)) + + if err := createSchema(ctx, db, (*Item)(nil), (*ItemToItem)(nil)); err != nil { + panic(err) + } + defer func() { + _ = dropSchema(ctx, db, (*Item)(nil), (*ItemToItem)(nil)) + }() + + item1 := new(Item) + if err := db.NewSelect(). + Model(item1). + Relation("Children"). + Order("item.id ASC"). + Where("id = 1"). + Scan(ctx); err != nil { + panic(err) + } + + item2 := new(Item) + if err := db.NewSelect(). + Model(item2). + Relation("Children"). + Order("item.id ASC"). + Where("id = 2"). + Scan(ctx); err != nil { + panic(err) + } + + fmt.Println("item1", item1.ID, "children", item1.Children[0].ID, item1.Children[1].ID) + fmt.Println("item2", item2.ID, "children", item2.Children[0].ID, item2.Children[1].ID) +} + +func createSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if err := db.ResetModel(ctx, model); err != nil { + return err + } + } + + values := []interface{}{ + &Item{ID: 1}, + &Item{ID: 2}, + &Item{ID: 3}, + &Item{ID: 4}, + &ItemToItem{ItemID: 1, ChildID: 2}, + &ItemToItem{ItemID: 1, ChildID: 3}, + &ItemToItem{ItemID: 2, ChildID: 3}, + &ItemToItem{ItemID: 2, ChildID: 4}, + } + for _, value := range values { + if _, err := db.NewInsert().Model(value).Exec(ctx); err != nil { + return err + } + } + + return nil +} + +func dropSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewDropTable().Model(model).IfExists().Cascade().Exec(ctx); err != nil { + return err + } + } + return nil +} diff --git a/ogx/example/rel-many-to-many/go.mod b/ogx/example/rel-many-to-many/go.mod new file mode 100644 index 00000000..617c3f75 --- /dev/null +++ b/ogx/example/rel-many-to-many/go.mod @@ -0,0 +1,31 @@ +module gitee.com/chentanyang/ogx/example/rel-many-to-many + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +replace gitee.com/chentanyang/ogx/extra/ogxdebug => ../../extra/ogxdebug + +replace gitee.com/chentanyang/ogx/dialect/ogdialect => ../../dialect/ogdialect + +require ( + gitee.com/chentanyang/ogx v1.1.7 + gitee.com/chentanyang/ogx/dialect/ogdialect v0.0.0-20220915042425-85b0183464bc + gitee.com/chentanyang/ogx/extra/ogxdebug v1.1.7 + gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 +) + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect +) diff --git a/ogx/example/rel-many-to-many/go.sum b/ogx/example/rel-many-to-many/go.sum new file mode 100644 index 00000000..58329b4c --- /dev/null +++ b/ogx/example/rel-many-to-many/go.sum @@ -0,0 +1,124 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 h1:npfLM9/QpkmdK+XY9X2pcC2EX5gosyn/6dRDRd2sEJs= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4/go.mod h1:2UEp+ug6ls6C0pLfZgBn7VBzBntFUzxJuy+6FlQ7qyI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/example/rel-many-to-many/main.go b/ogx/example/rel-many-to-many/main.go new file mode 100644 index 00000000..381c9b4b --- /dev/null +++ b/ogx/example/rel-many-to-many/main.go @@ -0,0 +1,126 @@ +package main + +import ( + "context" + "database/sql" + "fmt" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/dialect/ogdialect" + "gitee.com/chentanyang/ogx/extra/ogxdebug" + + _ "gitee.com/opengauss/openGauss-connector-go-pq" +) + +type Order struct { + ID int64 `ogx:",pk,autoincrement"` + // Order and Item in join:Order=Item are fields in OrderToItem model + Items []Item `ogx:"m2m:order_to_items,join:Order=Item"` +} + +type Item struct { + ID int64 `ogx:",pk,autoincrement"` + // Order and Item in join:Order=Item are fields in OrderToItem model + Orders []Order `ogx:"m2m:order_to_items,join:Item=Order"` +} + +type OrderToItem struct { + OrderID int64 `ogx:",pk"` + Order *Order `ogx:"rel:belongs-to,join:order_id=id"` + ItemID int64 `ogx:",pk"` + Item *Item `ogx:"rel:belongs-to,join:item_id=id"` +} + +func main() { + ctx := context.Background() + + connStr := "host=192.168.20.40 port=26000 user=cuih password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + if err != nil { + panic(err) + } + db := ogx.NewDB(opengaussdb, ogdialect.New()) + defer db.Close() + + db.AddQueryHook(ogxdebug.NewQueryHook(ogxdebug.WithVerbose(true))) + + // Register many to many model so ogx can better recognize m2m relation. + // This should be done before you use the model for the first time. + db.RegisterModel((*OrderToItem)(nil)) + + if err := createSchema(ctx, db, (*Order)(nil), (*Item)(nil), (*OrderToItem)(nil)); err != nil { + panic(err) + } + defer func() { + _ = dropSchema(ctx, db, (*Item)(nil), (*Order)(nil), (*Item)(nil), (*OrderToItem)(nil)) + }() + + order := new(Order) + if err := db.NewSelect(). + Model(order). + Relation("Items"). + Order("order.id ASC"). + Limit(1). + Scan(ctx); err != nil { + panic(err) + } + fmt.Println("Order", order.ID, "Items", order.Items[0].ID, order.Items[1].ID) + fmt.Println() + + order = new(Order) + if err := db.NewSelect(). + Model(order). + Relation("Items", func(q *ogx.SelectQuery) *ogx.SelectQuery { + q = q.OrderExpr("item.id DESC") + return q + }). + Limit(1). + Scan(ctx); err != nil { + panic(err) + } + fmt.Println("Order", order.ID, "Items", order.Items[0].ID, order.Items[1].ID) + fmt.Println() + + item := new(Item) + if err := db.NewSelect(). + Model(item). + Relation("Orders"). + Order("item.id ASC"). + Limit(1). + Scan(ctx); err != nil { + panic(err) + } + fmt.Println("Item", item.ID, "Order", item.Orders[0].ID) +} + +func createSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewCreateTable().Model(model).Exec(ctx); err != nil { + return err + } + } + + values := []interface{}{ + &Item{ID: 1}, + &Item{ID: 2}, + &Order{ID: 1}, + &OrderToItem{OrderID: 1, ItemID: 1}, + &OrderToItem{OrderID: 1, ItemID: 2}, + } + for _, value := range values { + if _, err := db.NewInsert().Model(value).Exec(ctx); err != nil { + return err + } + } + + return nil +} + +func dropSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewDropTable().Model(model).IfExists().Cascade().Exec(ctx); err != nil { + return err + } + } + return nil +} diff --git a/ogx/example/string-representation/go.mod b/ogx/example/string-representation/go.mod new file mode 100644 index 00000000..7d8faa84 --- /dev/null +++ b/ogx/example/string-representation/go.mod @@ -0,0 +1,27 @@ +module gitee.com/chentanyang/ogx/example/string-representation + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +replace gitee.com/chentanyang/ogx/extra/ogxdebug => ../../extra/ogxdebug + +replace gitee.com/chentanyang/ogx/dialect/ogdialect => ../../dialect/ogdialect + +require ( + gitee.com/chentanyang/ogx v1.1.7 + gitee.com/chentanyang/ogx/dialect/ogdialect v0.0.0-20220915042425-85b0183464bc + gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 +) + +require ( + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect +) diff --git a/ogx/example/string-representation/go.sum b/ogx/example/string-representation/go.sum new file mode 100644 index 00000000..c9537768 --- /dev/null +++ b/ogx/example/string-representation/go.sum @@ -0,0 +1,111 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 h1:npfLM9/QpkmdK+XY9X2pcC2EX5gosyn/6dRDRd2sEJs= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4/go.mod h1:2UEp+ug6ls6C0pLfZgBn7VBzBntFUzxJuy+6FlQ7qyI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/example/string-representation/main.go b/ogx/example/string-representation/main.go new file mode 100644 index 00000000..cfbaac35 --- /dev/null +++ b/ogx/example/string-representation/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "database/sql" + "fmt" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/dialect/ogdialect" + + _ "gitee.com/opengauss/openGauss-connector-go-pq" +) + +type Item struct { + ID int64 `ogx:",pk,autoincrement"` +} + +func main() { + connStr := "host=192.168.20.40 port=26000 user=cuih password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + if err != nil { + panic(err) + } + db := ogx.NewDB(opengaussdb, ogdialect.New()) + defer db.Close() + + q := db.NewSelect().Model((*Item)(nil)).Where("id > ?", 0) + + fmt.Println(q.String()) +} diff --git a/ogx/example/trivial/go.mod b/ogx/example/trivial/go.mod new file mode 100644 index 00000000..c0845daa --- /dev/null +++ b/ogx/example/trivial/go.mod @@ -0,0 +1,32 @@ +module gitee.com/chentanyang/ogx/example/trivial + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +replace gitee.com/chentanyang/ogx/extra/ogxdebug => ../../extra/ogxdebug + +replace gitee.com/chentanyang/ogx/dialect/ogdialect => ../../dialect/ogdialect + +require ( + gitee.com/chentanyang/ogx v1.1.7 + gitee.com/chentanyang/ogx/dialect/ogdialect v1.1.7 + gitee.com/chentanyang/ogx/extra/ogxdebug v1.1.7 + gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 +) + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/ogx/example/trivial/go.sum b/ogx/example/trivial/go.sum new file mode 100644 index 00000000..3a67d2ca --- /dev/null +++ b/ogx/example/trivial/go.sum @@ -0,0 +1,125 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 h1:npfLM9/QpkmdK+XY9X2pcC2EX5gosyn/6dRDRd2sEJs= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4/go.mod h1:2UEp+ug6ls6C0pLfZgBn7VBzBntFUzxJuy+6FlQ7qyI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/example/trivial/main.go b/ogx/example/trivial/main.go new file mode 100644 index 00000000..7989c071 --- /dev/null +++ b/ogx/example/trivial/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "context" + "database/sql" + "fmt" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/dialect/ogdialect" + "gitee.com/chentanyang/ogx/extra/ogxdebug" + + _ "gitee.com/opengauss/openGauss-connector-go-pq" +) + +func main() { + ctx := context.Background() + + connStr := "host=192.168.20.40 port=26000 user=cuih password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + if err != nil { + panic(err) + } + db := ogx.NewDB(opengaussdb, ogdialect.New()) + defer db.Close() + + // Print all queries to stdout. + db.AddQueryHook(ogxdebug.NewQueryHook(ogxdebug.WithVerbose(true))) + + var rnd float64 + + // Select a random number. + if err := db.NewSelect().ColumnExpr("random()").Scan(ctx, &rnd); err != nil { + panic(err) + } + + fmt.Println(rnd) +} diff --git a/ogx/example/tx-composition/go.mod b/ogx/example/tx-composition/go.mod new file mode 100644 index 00000000..cfe1b743 --- /dev/null +++ b/ogx/example/tx-composition/go.mod @@ -0,0 +1,34 @@ +module gitee.com/chentanyang/ogx/example/tx-composition + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +replace gitee.com/chentanyang/ogx/dbfixture => ../../dbfixture + +replace gitee.com/chentanyang/ogx/extra/ogxdebug => ../../extra/ogxdebug + +replace gitee.com/chentanyang/ogx/dialect/ogdialect => ../../dialect/ogdialect + +require ( + gitee.com/chentanyang/ogx v1.1.7 + gitee.com/chentanyang/ogx/dialect/ogdialect v0.0.0-20220915042425-85b0183464bc + gitee.com/chentanyang/ogx/extra/ogxdebug v1.1.7 + gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 +) + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/ogx/example/tx-composition/go.sum b/ogx/example/tx-composition/go.sum new file mode 100644 index 00000000..3a67d2ca --- /dev/null +++ b/ogx/example/tx-composition/go.sum @@ -0,0 +1,125 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4 h1:npfLM9/QpkmdK+XY9X2pcC2EX5gosyn/6dRDRd2sEJs= +gitee.com/opengauss/openGauss-connector-go-pq v1.0.4/go.mod h1:2UEp+ug6ls6C0pLfZgBn7VBzBntFUzxJuy+6FlQ7qyI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/example/tx-composition/main.go b/ogx/example/tx-composition/main.go new file mode 100644 index 00000000..bd306d67 --- /dev/null +++ b/ogx/example/tx-composition/main.go @@ -0,0 +1,92 @@ +package main + +import ( + "context" + "database/sql" + + "gitee.com/chentanyang/ogx" + "gitee.com/chentanyang/ogx/dialect/ogdialect" + "gitee.com/chentanyang/ogx/extra/ogxdebug" + + _ "gitee.com/opengauss/openGauss-connector-go-pq" +) + +func main() { + ctx := context.Background() + + connStr := "host=192.168.20.40 port=26000 user=cuih password=Gauss@123 dbname=test sslmode=disable" + opengaussdb, err := sql.Open("opengauss", connStr) + if err != nil { + panic(err) + } + db := ogx.NewDB(opengaussdb, ogdialect.New()) + defer db.Close() + + db.AddQueryHook(ogxdebug.NewQueryHook(ogxdebug.WithVerbose(true))) + + if err := db.ResetModel(ctx, (*User)(nil), (*Profile)(nil)); err != nil { + panic(err) + } + defer func() { + _ = dropSchema(ctx, db, (*User)(nil), (*Profile)(nil)) + }() + + if err := insertUserAndProfile(ctx, db); err != nil { + panic(err) + } + + if err := db.RunInTx(ctx, nil, func(ctx context.Context, tx ogx.Tx) error { + return insertUserAndProfile(ctx, tx) + }); err != nil { + panic(err) + } +} + +func insertUserAndProfile(ctx context.Context, db ogx.IDB) error { + user := &User{ + Name: "Smith", + } + if err := InsertUser(ctx, db, user); err != nil { + return err + } + + profile := &Profile{ + UserID: user.ID, + Email: "iam@smith.com", + } + if err := InsertProfile(ctx, db, profile); err != nil { + return err + } + + return nil +} + +type User struct { + ID int64 `ogx:",pk,autoincrement"` + Name string +} + +func InsertUser(ctx context.Context, db ogx.IDB, user *User) error { + _, err := db.NewInsert().Model(user).Exec(ctx) + return err +} + +type Profile struct { + ID int64 `ogx:",pk,autoincrement"` + UserID int64 + Email string +} + +func InsertProfile(ctx context.Context, db ogx.IDB, profile *Profile) error { + _, err := db.NewInsert().Model(profile).Exec(ctx) + return err +} + +func dropSchema(ctx context.Context, db *ogx.DB, models ...interface{}) error { + for _, model := range models { + if _, err := db.NewDropTable().Model(model).IfExists().Cascade().Exec(ctx); err != nil { + return err + } + } + return nil +} diff --git a/ogx/extra/ogxbig/README.md b/ogx/extra/ogxbig/README.md new file mode 100644 index 00000000..aaf1f8c4 --- /dev/null +++ b/ogx/extra/ogxbig/README.md @@ -0,0 +1,98 @@ + +**ogxbig** is a wrapper around math/big package to let us use big.int type in ogx + +Disclaimer: math/big does not implement database/sql scan/value methods and it can't be used in ogx. This package uses math/big in its heart and extends its usefulness even into postgres. +## Types + +* Int + - Int wraps around big.Int and supports its base functionalities +* Float + - Float is a ogx counterpart of big.Float + +## Example use + +``` + type TableWithBigint struct { + ID uint64 + Name string + Deposit *ogxbig.Int + Residue *ogxbig.Float + } + +``` + +### Mathematical Operations: + +This package supports basic mathematical operations such as addition, subtraction, division, negation etc. + +Example : + +``` + x := ogxbig.FromInt64(100) + y , err := ogxbig.FromString("9999999999999999999999999999999999999999") + + if err!=nil { + panic(err) + } + + y.Add(x) // 9999999999999999999999999999999999999999 + 100 + y.Sub(x) // 9999999999999999999999999999999999999999 - 100 + y.Neg() // -9999999999999999999999999999999999999999 + // on the fly operation + c:= ogxbig.FromInt64(100).Mul(y) // 100 * 100 = 10000 + c.Div(x) // 10000/100 = 100 + c.Neg().Abs() // |-10000| = 10000 + +``` + +For extracting math/big's Int you can simply do as follows: + +``` + d:= ogxbig.ToMathBig(x) +``` + +Now you can do your calculations and convert it back to ogxbig with: + +``` + x = ogxbig.FromBigint(d) +``` + +### comparisons: + +let we have x , y as two bigint.Bigint numbers in ogxtypes. +``` + x:= ogxbig.FromInt64(100) + y:= ogxbig.FromInt64(90) +``` + +For comparing the above numbers, we can do as follow: + +``` + cmp:=x.Cmp(y) +``` + +**cmp** has the following methods: + +``` + // equal + Eq() bool + // greater than + Gt() bool + // lower than + Lt() bool + // Greater or equal + Geq() bool + // Lower or equal + Leq() bool + +``` + +Thus, we have the following results: + +``` + x.Cmp(y).Eq() // 100 == 90 : false + x.Cmp(y).Geq() // 100 >= 90 : true + x.Cmp(y).Gt() // 100 > 90 : true + x.Cmp(y).Lt() // 100 < 90 : false + x.Cmp(y).Leq() // 100 <= 90 : false +``` \ No newline at end of file diff --git a/ogx/extra/ogxbig/float.go b/ogx/extra/ogxbig/float.go new file mode 100644 index 00000000..809995f8 --- /dev/null +++ b/ogx/extra/ogxbig/float.go @@ -0,0 +1,102 @@ +package ogxbig + +import ( + "database/sql" + "database/sql/driver" + "fmt" + "math/big" +) + +type ( + Float big.Float +) + +func NewFloat() *Float { + return new(Float) +} + +func (f *Float) FromMathFloat(fl *big.Float) *Float { + return (*Float)(fl) +} + +func (f *Float) Value() (driver.Value, error) { + return (*big.Float)(f).String(), nil +} + +func (f *Float) Scan(value interface{}) error { + + var i sql.NullString + + if err := i.Scan(value); err != nil { + return err + } + + if _, ok := (*big.Float)(f).SetString(i.String); ok { + return nil + } + + return fmt.Errorf("Error converting type %T into Float", value) +} + +func (f *Float) toMathFloat() *big.Float { + return (*big.Float)(f) +} + +func (f *Float) Add(target *Float) *Float { + return f.FromMathFloat(new(big.Float).Add(f.toMathFloat(), target.toMathFloat())) +} +func (f *Float) Sub(target *Float) *Float { + return f.FromMathFloat(new(big.Float).Sub(f.toMathFloat(), target.toMathFloat())) +} +func (f *Float) Mul(target *Float) *Float { + return f.FromMathFloat(new(big.Float).Mul(f.toMathFloat(), target.toMathFloat())) +} +func (f *Float) Div(target *Float) *Float { + return f.FromMathFloat(new(big.Float).Quo(f.toMathFloat(), target.toMathFloat())) +} +func (f *Float) Neg() *Float { + return f.FromMathFloat(new(big.Float).Neg(f.toMathFloat())) +} +func (f *Float) Abs() *Float { + return f.FromMathFloat(new(big.Float).Abs(f.toMathFloat())) +} + +func (f *Float) String() string { + return (f.toMathFloat()).String() +} +func (f *Float) ToFloat64() (float64, int8) { + x, a := f.toMathFloat().Float64() + return x, int8(a) +} + +func (f *Float) FromString(inp string) (*Float, error) { + _f, _, err := big.ParseFloat(inp, 10, 10, big.ToZero) + if err != nil { + return nil, err + } + return f.FromMathFloat(_f), nil +} + +func (f *Float) Cmp(target *Float) Cmp { + return &cmpFloat{r: f.toMathFloat().Cmp(target.toMathFloat())} +} + +func (c *cmpFloat) Eq() bool { + return c.r == 0 +} + +func (c *cmpFloat) Lt() bool { + return c.r < 0 +} + +func (c *cmpFloat) Gt() bool { + return c.r > 0 +} + +func (c *cmpFloat) Leq() bool { + return c.r == 0 || c.r < 0 +} + +func (c *cmpFloat) Geq() bool { + return c.r == 0 || c.r > 0 +} diff --git a/ogx/extra/ogxbig/go.mod b/ogx/extra/ogxbig/go.mod new file mode 100644 index 00000000..609058a0 --- /dev/null +++ b/ogx/extra/ogxbig/go.mod @@ -0,0 +1,15 @@ +module gitee.com/chentanyang/ogx/extra/ogxbig + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +require ( + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect +) diff --git a/ogx/extra/ogxbig/go.sum b/ogx/extra/ogxbig/go.sum new file mode 100644 index 00000000..e43413cc --- /dev/null +++ b/ogx/extra/ogxbig/go.sum @@ -0,0 +1,13 @@ +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ogx/extra/ogxbig/int.go b/ogx/extra/ogxbig/int.go new file mode 100644 index 00000000..4d741827 --- /dev/null +++ b/ogx/extra/ogxbig/int.go @@ -0,0 +1,140 @@ +package ogxbig + +import ( + "database/sql" + "database/sql/driver" + "fmt" + "math/big" + + "gopkg.in/yaml.v3" +) + +type Int big.Int + +func NewInt() *Int { + return new(Int) +} +func newBigint(x *big.Int) *Int { + return (*Int)(x) +} + +// same as NewBigint() +func FromMathBig(x *big.Int) *Int { + return (*Int)(x) +} + +func FromInt64(x int64) *Int { + return FromMathBig(big.NewInt(x)) +} + +func (i *Int) FromString(x string) (*Int, error) { + if x == "" { + return FromInt64(0), nil + } + a := big.NewInt(0) + b, ok := a.SetString(x, 10) + + if !ok { + return nil, fmt.Errorf("cannot create Int from string") + } + + return newBigint(b), nil +} + +func (b *Int) Value() (driver.Value, error) { + return (*big.Int)(b).String(), nil +} + +func (b *Int) Scan(value interface{}) error { + + var i sql.NullString + + if err := i.Scan(value); err != nil { + return err + } + + if _, ok := (*big.Int)(b).SetString(i.String, 10); ok { + return nil + } + + return fmt.Errorf("Error converting type %T into Bigint", value) +} + +func (b *Int) ToMathBig() *big.Int { + return (*big.Int)(b) +} + +func (b *Int) Sub(x *Int) *Int { + return (*Int)(big.NewInt(0).Sub(b.ToMathBig(), x.ToMathBig())) +} + +func (b *Int) Add(x *Int) *Int { + return (*Int)(big.NewInt(0).Add(b.ToMathBig(), x.ToMathBig())) +} + +func (b *Int) Mul(x *Int) *Int { + return (*Int)(big.NewInt(0).Mul(b.ToMathBig(), x.ToMathBig())) +} + +func (b *Int) Div(x *Int) *Int { + return (*Int)(big.NewInt(0).Div(b.ToMathBig(), x.ToMathBig())) +} + +func (b *Int) Neg() *Int { + return (*Int)(big.NewInt(0).Neg(b.ToMathBig())) +} + +func (b *Int) ToUInt64() uint64 { + return b.ToMathBig().Uint64() +} + +func (b *Int) ToInt64() int64 { + return b.ToMathBig().Int64() +} + +func (b *Int) String() string { + return b.ToMathBig().String() +} + +func (b *Int) Abs() *Int { + return (*Int)(new(big.Int).Abs(b.ToMathBig())) +} + +var _ yaml.Unmarshaler = (*Int)(nil) + +// @todo , this part needs to be fixed +func (b *Int) UnmarshalYAML(value *yaml.Node) error { + var str string + if err := value.Decode(&str); err != nil { + return err + } + // ineffassign + // ignored to be fixed later + // b, err := NewInt().FromString(str) + + return nil +} + +func (b *Int) Cmp(target *Int) Cmp { + return &cmpInt{r: b.ToMathBig().Cmp(target.ToMathBig())} +} + +func (c *cmpInt) Eq() bool { + return c.r == 0 +} + +func (c *cmpInt) Lt() bool { + return c.r < 0 +} + +func (c *cmpInt) Gt() bool { + return c.r > 0 +} + +func (c *cmpInt) Leq() bool { + return c.r == 0 || c.r < 0 +} + +func (c *cmpInt) Geq() bool { + return c.r == 0 || c.r > 0 +} diff --git a/ogx/extra/ogxbig/int_test.go b/ogx/extra/ogxbig/int_test.go new file mode 100644 index 00000000..d5be87a7 --- /dev/null +++ b/ogx/extra/ogxbig/int_test.go @@ -0,0 +1,233 @@ +package ogxbig_test + +import ( + "fmt" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" + + "gitee.com/chentanyang/ogx/extra/ogxbig" +) + +func TestInt(t *testing.T) { + a := big.NewInt(100) + b := big.NewInt(200) + + t.Run("multiply", func(t *testing.T) { + x := ogxbig.FromMathBig(a) + y := ogxbig.FromMathBig(b) + // 100 * 200 = 20000 + assert.Equal(t, ogxbig.FromMathBig(big.NewInt(20000)), x.Mul(y)) + }) + t.Run("add", func(t *testing.T) { + x := ogxbig.FromMathBig(a) + y := ogxbig.FromMathBig(b) + // 100 + 200 = 300 + assert.Equal(t, ogxbig.FromMathBig(big.NewInt(300)), x.Add(y)) + }) + + t.Run("sub", func(t *testing.T) { + x := ogxbig.FromMathBig(a) + y := ogxbig.FromMathBig(b) + // 100 -200 = -100 + assert.Equal(t, ogxbig.FromMathBig(big.NewInt(-100)), x.Sub(y)) + }) + + t.Run("div", func(t *testing.T) { + x := ogxbig.FromMathBig(a) + y := ogxbig.FromMathBig(b) + // 200 / 100 = 2 + assert.Equal(t, ogxbig.FromMathBig(big.NewInt(2)), y.Div(x)) + }) + + t.Run("negation", func(t *testing.T) { + x := ogxbig.FromMathBig(a) + assert.Equal(t, ogxbig.FromMathBig(big.NewInt(-100)), x.Neg()) + }) + + t.Run("int64", func(t *testing.T) { + x := ogxbig.FromMathBig(a) + assert.Equal(t, int64(-100), x.Neg().ToInt64()) + }) + + t.Run("uint64", func(t *testing.T) { + x := ogxbig.FromMathBig(a) + assert.Equal(t, uint64(100), x.ToUInt64()) + }) + t.Run("toString", func(t *testing.T) { + x := ogxbig.FromMathBig(a) + assert.Equal(t, "100", x.String()) + }) + t.Run("fromString", func(t *testing.T) { + x, err := ogxbig.NewInt().FromString("100") + assert.Nil(t, err) + assert.Equal(t, "100", x.String()) + }) + t.Run("fromInt64", func(t *testing.T) { + x := ogxbig.FromInt64(100000000) + assert.Equal(t, int64(100000000), x.ToInt64()) + }) + + t.Run("Abs", func(t *testing.T) { + x := ogxbig.FromMathBig(a) + + assert.Equal(t, x.Neg().Abs(), x) + }) + t.Run("compare: ", func(t *testing.T) { + x := ogxbig.FromMathBig(a) // 100 + y := ogxbig.FromMathBig(b) // 200 + + cmp := x.Cmp(y) + + t.Run("eq ?", func(t *testing.T) { + assert.Equal(t, cmp.Eq(), false) + }) + t.Run("lt ?", func(t *testing.T) { + assert.Equal(t, cmp.Lt(), true) + }) + t.Run("gt ?", func(t *testing.T) { + assert.Equal(t, cmp.Gt(), false) + }) + t.Run("leq ?", func(t *testing.T) { + assert.Equal(t, cmp.Leq(), true) + }) + t.Run("geq ?", func(t *testing.T) { + assert.Equal(t, cmp.Geq(), false) + }) + }) + + t.Run("empty string ", func(t *testing.T) { + + x, err := ogxbig.NewInt().FromString("") + assert.Nil(t, err) + assert.Equal(t, x.ToInt64(), int64(0)) + }) + +} + +func TestFloat(t *testing.T) { + + cases := []struct { + f1 float64 + f2 float64 + diff float64 + mul float64 + sum float64 + div float64 + eq bool + geq bool + leq bool + lt bool + gt bool + }{ + { + f1: 1.01, + f2: 1.02, + diff: 0.01, + mul: 1.0302, + sum: 2.03, + div: 1, + eq: false, + geq: true, + lt: false, + gt: true, + leq: false, + }, + { + f1: 10.001, + f2: 10.01, + diff: 0.009, + sum: 20.011, + mul: 100.11001, + div: 1, + eq: false, + geq: true, + lt: false, + gt: true, + leq: false, + }, + { + f1: 1, + f2: 1, + diff: 0, + sum: 2, + mul: 1, + div: 1, + eq: true, + geq: true, + leq: true, + lt: false, + gt: false, + }, + } + + for _, c := range cases { + t.Run(fmt.Sprintf("%f , %f ", c.f1, c.f2), func(t *testing.T) { + f1 := big.NewFloat(c.f1) + f2 := big.NewFloat(c.f2) + diff := big.NewFloat(c.diff) + mul := big.NewFloat(c.mul) + sum := big.NewFloat(c.sum) + div := big.NewFloat(c.div) + + ogxF1 := ogxbig.NewFloat().FromMathFloat(f1) + ogxF2 := ogxbig.NewFloat().FromMathFloat(f2) + ogxDiff := ogxbig.NewFloat().FromMathFloat(diff) + ogxMul := ogxbig.NewFloat().FromMathFloat(mul) + ogxSum := ogxbig.NewFloat().FromMathFloat(sum) + ogxDiv := ogxbig.NewFloat().FromMathFloat(div) + + cmp := ogxF2.Cmp(ogxF1) + + assert.Equal(t, ogxDiff.String(), ogxF2.Sub(ogxF1).String()) + assert.Equal(t, ogxMul.String(), ogxF2.Mul(ogxF1).String()) + assert.Equal(t, ogxSum.String(), ogxF2.Add(ogxF1).String()) + assert.Equal(t, ogxDiv.String(), ogxF2.Div(ogxF2).String()) + + assert.Equal(t, cmp.Eq(), c.eq) + assert.Equal(t, cmp.Geq(), c.geq) + assert.Equal(t, cmp.Gt(), c.gt) + assert.Equal(t, cmp.Leq(), c.leq) + assert.Equal(t, cmp.Lt(), c.lt) + }) + } + + f, err := ogxbig.NewFloat().FromString("-100") + assert.NoError(t, err) + + assert.Equal(t, f.Abs().String(), "100") + + f2 := ogxbig.NewFloat().FromMathFloat(big.NewFloat(100)) + + assert.Equal(t, f.String(), f2.Neg().String()) +} + +func TestFixture(t *testing.T) { + data := ` +- id: 1 + name: ethereum + base: wei + equals: 1000000000000000000 +- id: 2 + name: bitcoin + base: satoshi + equals: 1000000000 +` + type CryptoNetwork struct { + ID int + Name string + Base string + Equals *ogxbig.Int + } + + cryptoNet := []CryptoNetwork{} + + err := yaml.Unmarshal([]byte(data), &cryptoNet) + + assert.NoError(t, err) + + // @Todo + // we expect that the decoded values become convertible to ogxbig.Int +} diff --git a/ogx/extra/ogxbig/ogxbig.go b/ogx/extra/ogxbig/ogxbig.go new file mode 100644 index 00000000..2e50a30a --- /dev/null +++ b/ogx/extra/ogxbig/ogxbig.go @@ -0,0 +1,22 @@ +package ogxbig + +type Cmp interface { + // equal + Eq() bool + // greater than + Gt() bool + // lower than + Lt() bool + // Greater or equal + Geq() bool + // Lower or equal + Leq() bool +} +type ( + cmpInt struct { + r int + } + cmpFloat struct { + r int + } +) diff --git a/ogx/extra/ogxdebug/debug.go b/ogx/extra/ogxdebug/debug.go new file mode 100644 index 00000000..05cce86a --- /dev/null +++ b/ogx/extra/ogxdebug/debug.go @@ -0,0 +1,136 @@ +package ogxdebug + +import ( + "context" + "database/sql" + "fmt" + "io" + "os" + "reflect" + "time" + + "github.com/fatih/color" + + "gitee.com/chentanyang/ogx" +) + +type Option func(*QueryHook) + +// WithEnabled enables/disables the hook. +func WithEnabled(on bool) Option { + return func(h *QueryHook) { + h.enabled = on + } +} + +// WithVerbose configures the hook to log all queries +// (by default, only failed queries are logged). +func WithVerbose(on bool) Option { + return func(h *QueryHook) { + h.verbose = on + } +} + +// WithWriter sets the log output to an io.Writer +// the default is os.Stderr +func WithWriter(w io.Writer) Option { + return func(h *QueryHook) { + h.writer = w + } +} + +// FromEnv configures the hook using the environment variable value. +// For example, WithEnv("OGXDEBUG"): +// - OGXDEBUG=0 - disables the hook. +// - OGXDEBUG=1 - enables the hook. +// - OGXDEBUG=2 - enables the hook and verbose mode. +func FromEnv(key string) Option { + if key == "" { + key = "OGXDEBUG" + } + return func(h *QueryHook) { + if env, ok := os.LookupEnv(key); ok { + h.enabled = (env != "" && env != "0") + h.verbose = (env == "2") + } + } +} + +type QueryHook struct { + enabled bool + verbose bool + writer io.Writer +} + +var _ ogx.QueryHook = (*QueryHook)(nil) + +func NewQueryHook(opts ...Option) *QueryHook { + h := &QueryHook{ + enabled: true, + writer: os.Stderr, + } + for _, opt := range opts { + opt(h) + } + return h +} + +func (h *QueryHook) BeforeQuery( + ctx context.Context, event *ogx.QueryEvent, +) context.Context { + return ctx +} + +func (h *QueryHook) AfterQuery(ctx context.Context, event *ogx.QueryEvent) { + if !h.enabled { + return + } + + if !h.verbose { + switch event.Err { + case nil, sql.ErrNoRows, sql.ErrTxDone: + return + } + } + + now := time.Now() + dur := now.Sub(event.StartTime) + + args := []interface{}{ + "[ogx]", + now.Format(" 15:04:05.000 "), + formatOperation(event), + fmt.Sprintf(" %10s ", dur.Round(time.Microsecond)), + event.Query, + } + + if event.Err != nil { + typ := reflect.TypeOf(event.Err).String() + args = append(args, + "\t", + color.New(color.BgRed).Sprintf(" %s ", typ+": "+event.Err.Error()), + ) + } + + fmt.Fprintln(h.writer, args...) +} + +func formatOperation(event *ogx.QueryEvent) string { + operation := event.Operation() + return operationColor(operation).Sprintf(" %-16s ", operation) +} + +func operationColor(operation string) *color.Color { + switch operation { + case "SELECT": + return color.New(color.BgGreen, color.FgHiWhite) + case "INSERT": + return color.New(color.BgBlue, color.FgHiWhite) + case "UPDATE": + return color.New(color.BgYellow, color.FgHiBlack) + case "DELETE": + return color.New(color.BgMagenta, color.FgHiWhite) + default: + return color.New(color.BgWhite, color.FgHiBlack) + } +} diff --git a/ogx/extra/ogxdebug/go.mod b/ogx/extra/ogxdebug/go.mod new file mode 100644 index 00000000..2d4658f5 --- /dev/null +++ b/ogx/extra/ogxdebug/go.mod @@ -0,0 +1,20 @@ +module gitee.com/chentanyang/ogx/extra/ogxdebug + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +require ( + gitee.com/chentanyang/ogx v1.1.7 + github.com/fatih/color v1.13.0 +) + +require ( + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect +) diff --git a/ogx/extra/ogxdebug/go.sum b/ogx/extra/ogxdebug/go.sum new file mode 100644 index 00000000..16375532 --- /dev/null +++ b/ogx/extra/ogxdebug/go.sum @@ -0,0 +1,33 @@ +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/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ogx/extra/ogxjson/json.go b/ogx/extra/ogxjson/json.go new file mode 100644 index 00000000..ddcdd0e6 --- /dev/null +++ b/ogx/extra/ogxjson/json.go @@ -0,0 +1,26 @@ +package ogxjson + +import ( + "encoding/json" + "io" +) + +var _ Provider = (*StdProvider)(nil) + +type StdProvider struct{} + +func (StdProvider) Marshal(v interface{}) ([]byte, error) { + return json.Marshal(v) +} + +func (StdProvider) Unmarshal(data []byte, v interface{}) error { + return json.Unmarshal(data, v) +} + +func (StdProvider) NewEncoder(w io.Writer) Encoder { + return json.NewEncoder(w) +} + +func (StdProvider) NewDecoder(r io.Reader) Decoder { + return json.NewDecoder(r) +} diff --git a/ogx/extra/ogxjson/provider.go b/ogx/extra/ogxjson/provider.go new file mode 100644 index 00000000..c989fd33 --- /dev/null +++ b/ogx/extra/ogxjson/provider.go @@ -0,0 +1,43 @@ +package ogxjson + +import ( + "io" +) + +var provider Provider = StdProvider{} + +func SetProvider(p Provider) { + provider = p +} + +type Provider interface { + Marshal(v interface{}) ([]byte, error) + Unmarshal(data []byte, v interface{}) error + NewEncoder(w io.Writer) Encoder + NewDecoder(r io.Reader) Decoder +} + +type Decoder interface { + Decode(v interface{}) error + UseNumber() +} + +type Encoder interface { + Encode(v interface{}) error +} + +func Marshal(v interface{}) ([]byte, error) { + return provider.Marshal(v) +} + +func Unmarshal(data []byte, v interface{}) error { + return provider.Unmarshal(data, v) +} + +func NewEncoder(w io.Writer) Encoder { + return provider.NewEncoder(w) +} + +func NewDecoder(r io.Reader) Decoder { + return provider.NewDecoder(r) +} diff --git a/ogx/extra/ogxrelic/go.mod b/ogx/extra/ogxrelic/go.mod new file mode 100644 index 00000000..aecb9d41 --- /dev/null +++ b/ogx/extra/ogxrelic/go.mod @@ -0,0 +1,24 @@ +module gitee.com/chentanyang/ogx/extra/ogxrelic + +go 1.18 + +replace gitee.com/chentanyang/ogx => ../.. + +require ( + gitee.com/chentanyang/ogx v1.1.7 + github.com/newrelic/go-agent/v3 v3.18.2 +) + +require ( + github.com/golang/protobuf v1.5.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + golang.org/x/text v0.3.7 // indirect + google.golang.org/genproto v0.0.0-20220914210030-581e60b4ef85 // indirect + google.golang.org/grpc v1.49.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect +) diff --git a/ogx/extra/ogxrelic/go.sum b/ogx/extra/ogxrelic/go.sum new file mode 100644 index 00000000..b403a486 --- /dev/null +++ b/ogx/extra/ogxrelic/go.sum @@ -0,0 +1,136 @@ +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= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/newrelic/go-agent/v3 v3.18.2 h1:28Nr54mkzKeoRF6lUPs4V1TRxLoPOgbnX9RWP5iDDCs= +github.com/newrelic/go-agent/v3 v3.18.2/go.mod h1:BFJOlbZWRlPTXKYIC1TTTtQKTnYntEJaU0VU507hDc0= +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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/net v0.0.0-20190108225652-1e06a53dbb7e/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/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-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-20190423024810-112230192c58/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-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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +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= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20220914210030-581e60b4ef85 h1:lkYqfLZL9+9C+SltHOTeOHL6uueWYYkGp5NoeOZQsis= +google.golang.org/genproto v0.0.0-20220914210030-581e60b4ef85/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ogx/extra/ogxrelic/option.go b/ogx/extra/ogxrelic/option.go new file mode 100644 index 00000000..8219fb0a --- /dev/null +++ b/ogx/extra/ogxrelic/option.go @@ -0,0 +1,33 @@ +package ogxrelic + +import newrelic "github.com/newrelic/go-agent/v3/newrelic" + +type Option func(h *QueryHook) + +// WithDBName sets the database name to report in recorded segments +func WithDBName(name string) Option { + return func(h *QueryHook) { + h.baseSegment.DatabaseName = name + } +} + +// WithProduct sets the product to report in recorded segments +func WithProduct(product newrelic.DatastoreProduct) Option { + return func(h *QueryHook) { + h.baseSegment.Product = product + } +} + +// WithHost sets the host to report in recorded segments +func WithHost(host string) Option { + return func(h *QueryHook) { + h.baseSegment.Host = host + } +} + +// WithPortPathOrId sets the Port/Path/ID to report in recorded segments +func WithPortPathOrId(portPathOrId string) Option { + return func(h *QueryHook) { + h.baseSegment.PortPathOrID = portPathOrId + } +} diff --git a/ogx/extra/ogxrelic/relic.go b/ogx/extra/ogxrelic/relic.go new file mode 100644 index 00000000..18b09e19 --- /dev/null +++ b/ogx/extra/ogxrelic/relic.go @@ -0,0 +1,52 @@ +package ogxrelic + +import ( + "context" + + "github.com/newrelic/go-agent/v3/newrelic" + "github.com/newrelic/go-agent/v3/newrelic/sqlparse" + + "gitee.com/chentanyang/ogx" +) + +type QueryHook struct { + baseSegment newrelic.DatastoreSegment +} + +var _ ogx.QueryHook = (*QueryHook)(nil) + +type nrOgxCtxKey string + +const nrOgxSegmentKey nrOgxCtxKey = "nrogxsegment" + +// NewQueryHook creates a new ogx.QueryHook which reports database usage +// information to new relic. +func NewQueryHook(options ...Option) *QueryHook { + h := &QueryHook{} + for _, o := range options { + o(h) + } + return h +} + +func (q *QueryHook) BeforeQuery(ctx context.Context, qe *ogx.QueryEvent) context.Context { + segment := q.baseSegment + + if qe.Model != nil { + if t, ok := qe.Model.(ogx.TableModel); ok { + segment.Operation = qe.Operation() + segment.Collection = t.Table().Name + } else { + sqlparse.ParseQuery(&segment, qe.Query) + } + } else { + sqlparse.ParseQuery(&segment, qe.Query) + } + segment.StartTime = newrelic.FromContext(ctx).StartSegmentNow() + return context.WithValue(ctx, nrOgxSegmentKey, &segment) + +} +func (q *QueryHook) AfterQuery(ctx context.Context, qe *ogx.QueryEvent) { + segment := ctx.Value(nrOgxSegmentKey).(*newrelic.DatastoreSegment) + segment.End() +} diff --git a/ogx/go.mod b/ogx/go.mod new file mode 100644 index 00000000..3fa3c56e --- /dev/null +++ b/ogx/go.mod @@ -0,0 +1,20 @@ +module gitee.com/chentanyang/ogx + +go 1.18 + +require ( + github.com/jinzhu/inflection v1.0.0 + github.com/stretchr/testify v1.7.0 + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc + github.com/vmihailenco/msgpack/v5 v5.3.5 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/pretty v0.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/sys v0.0.0-20220913175220-63ea55921009 // indirect + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect +) diff --git a/ogx/go.sum b/ogx/go.sum new file mode 100644 index 00000000..6bff153e --- /dev/null +++ b/ogx/go.sum @@ -0,0 +1,29 @@ +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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw= +golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ogx/hook.go b/ogx/hook.go new file mode 100644 index 00000000..bee0d6f1 --- /dev/null +++ b/ogx/hook.go @@ -0,0 +1,109 @@ +package ogx + +import ( + "context" + "database/sql" + "strings" + "sync/atomic" + "time" +) + +type QueryEvent struct { + DB *DB + + IQuery Query + Query string + QueryTemplate string + QueryArgs []interface{} + Model Model + + StartTime time.Time + Result sql.Result + Err error + + Stash map[interface{}]interface{} +} + +func (e *QueryEvent) Operation() string { + if e.IQuery != nil { + return e.IQuery.Operation() + } + return queryOperation(e.Query) +} + +func queryOperation(query string) string { + if idx := strings.IndexByte(query, ' '); idx > 0 { + query = query[:idx] + } + if len(query) > 16 { + query = query[:16] + } + return query +} + +type QueryHook interface { + BeforeQuery(context.Context, *QueryEvent) context.Context + AfterQuery(context.Context, *QueryEvent) +} + +func (db *DB) beforeQuery( + ctx context.Context, + iquery Query, + queryTemplate string, + queryArgs []interface{}, + query string, + model Model, +) (context.Context, *QueryEvent) { + atomic.AddUint32(&db.stats.Queries, 1) + + if len(db.queryHooks) == 0 { + return ctx, nil + } + + event := &QueryEvent{ + DB: db, + + Model: model, + IQuery: iquery, + Query: query, + QueryTemplate: queryTemplate, + QueryArgs: queryArgs, + + StartTime: time.Now(), + } + + for _, hook := range db.queryHooks { + ctx = hook.BeforeQuery(ctx, event) + } + + return ctx, event +} + +func (db *DB) afterQuery( + ctx context.Context, + event *QueryEvent, + res sql.Result, + err error, +) { + switch err { + case nil, sql.ErrNoRows: + // nothing + default: + atomic.AddUint32(&db.stats.Errors, 1) + } + + if event == nil { + return + } + + event.Result = res + event.Err = err + + db.afterQueryFromIndex(ctx, event, len(db.queryHooks)-1) +} + +func (db *DB) afterQueryFromIndex(ctx context.Context, event *QueryEvent, hookIndex int) { + for ; hookIndex >= 0; hookIndex-- { + db.queryHooks[hookIndex].AfterQuery(ctx, event) + } +} diff --git a/ogx/internal/flag.go b/ogx/internal/flag.go new file mode 100644 index 00000000..22d2db29 --- /dev/null +++ b/ogx/internal/flag.go @@ -0,0 +1,16 @@ +package internal + +type Flag uint64 + +func (flag Flag) Has(other Flag) bool { + return flag&other != 0 +} + +func (flag Flag) Set(other Flag) Flag { + return flag | other +} + +func (flag Flag) Remove(other Flag) Flag { + flag &= ^other + return flag +} diff --git a/ogx/internal/hex.go b/ogx/internal/hex.go new file mode 100644 index 00000000..6fae2bb7 --- /dev/null +++ b/ogx/internal/hex.go @@ -0,0 +1,43 @@ +package internal + +import ( + fasthex "github.com/tmthrgd/go-hex" +) + +type HexEncoder struct { + b []byte + written bool +} + +func NewHexEncoder(b []byte) *HexEncoder { + return &HexEncoder{ + b: b, + } +} + +func (enc *HexEncoder) Bytes() []byte { + return enc.b +} + +func (enc *HexEncoder) Write(b []byte) (int, error) { + if !enc.written { + enc.b = append(enc.b, '\'') + enc.b = append(enc.b, `\x`...) + enc.written = true + } + + i := len(enc.b) + enc.b = append(enc.b, make([]byte, fasthex.EncodedLen(len(b)))...) + fasthex.Encode(enc.b[i:], b) + + return len(b), nil +} + +func (enc *HexEncoder) Close() error { + if enc.written { + enc.b = append(enc.b, '\'') + } else { + enc.b = append(enc.b, "NULL"...) + } + return nil +} diff --git a/ogx/internal/logger.go b/ogx/internal/logger.go new file mode 100644 index 00000000..ee91709b --- /dev/null +++ b/ogx/internal/logger.go @@ -0,0 +1,27 @@ +package internal + +import ( + "fmt" + "log" + "os" +) + +var Warn = log.New(os.Stderr, "WARN: ogx: ", log.LstdFlags) + +var Deprecated = log.New(os.Stderr, "DEPRECATED: ogx: ", log.LstdFlags) + +type Logging interface { + Printf(format string, v ...interface{}) +} + +type logger struct { + log *log.Logger +} + +func (l *logger) Printf(format string, v ...interface{}) { + _ = l.log.Output(2, fmt.Sprintf(format, v...)) +} + +var Logger Logging = &logger{ + log: log.New(os.Stderr, "ogx: ", log.LstdFlags|log.Lshortfile), +} diff --git a/ogx/internal/map_key.go b/ogx/internal/map_key.go new file mode 100644 index 00000000..bb5fcca8 --- /dev/null +++ b/ogx/internal/map_key.go @@ -0,0 +1,67 @@ +package internal + +import "reflect" + +var ifaceType = reflect.TypeOf((*interface{})(nil)).Elem() + +type MapKey struct { + iface interface{} +} + +func NewMapKey(is []interface{}) MapKey { + return MapKey{ + iface: newMapKey(is), + } +} + +func newMapKey(is []interface{}) interface{} { + switch len(is) { + case 1: + ptr := new([1]interface{}) + copy((*ptr)[:], is) + return *ptr + case 2: + ptr := new([2]interface{}) + copy((*ptr)[:], is) + return *ptr + case 3: + ptr := new([3]interface{}) + copy((*ptr)[:], is) + return *ptr + case 4: + ptr := new([4]interface{}) + copy((*ptr)[:], is) + return *ptr + case 5: + ptr := new([5]interface{}) + copy((*ptr)[:], is) + return *ptr + case 6: + ptr := new([6]interface{}) + copy((*ptr)[:], is) + return *ptr + case 7: + ptr := new([7]interface{}) + copy((*ptr)[:], is) + return *ptr + case 8: + ptr := new([8]interface{}) + copy((*ptr)[:], is) + return *ptr + case 9: + ptr := new([9]interface{}) + copy((*ptr)[:], is) + return *ptr + case 10: + ptr := new([10]interface{}) + copy((*ptr)[:], is) + return *ptr + default: + } + + at := reflect.New(reflect.ArrayOf(len(is), ifaceType)).Elem() + for i, v := range is { + *(at.Index(i).Addr().Interface().(*interface{})) = v + } + return at.Interface() +} diff --git a/ogx/internal/parser/parser.go b/ogx/internal/parser/parser.go new file mode 100644 index 00000000..0dbcaf7f --- /dev/null +++ b/ogx/internal/parser/parser.go @@ -0,0 +1,141 @@ +package parser + +import ( + "bytes" + "strconv" + + "gitee.com/chentanyang/ogx/internal" +) + +type Parser struct { + b []byte + i int +} + +func New(b []byte) *Parser { + return &Parser{ + b: b, + } +} + +func NewString(s string) *Parser { + return New(internal.Bytes(s)) +} + +func (p *Parser) Valid() bool { + return p.i < len(p.b) +} + +func (p *Parser) Bytes() []byte { + return p.b[p.i:] +} + +func (p *Parser) Read() byte { + if p.Valid() { + c := p.b[p.i] + p.Advance() + return c + } + return 0 +} + +func (p *Parser) Peek() byte { + if p.Valid() { + return p.b[p.i] + } + return 0 +} + +func (p *Parser) Advance() { + p.i++ +} + +func (p *Parser) Skip(skip byte) bool { + if p.Peek() == skip { + p.Advance() + return true + } + return false +} + +func (p *Parser) SkipBytes(skip []byte) bool { + if len(skip) > len(p.b[p.i:]) { + return false + } + if !bytes.Equal(p.b[p.i:p.i+len(skip)], skip) { + return false + } + p.i += len(skip) + return true +} + +func (p *Parser) ReadSep(sep byte) ([]byte, bool) { + ind := bytes.IndexByte(p.b[p.i:], sep) + if ind == -1 { + b := p.b[p.i:] + p.i = len(p.b) + return b, false + } + + b := p.b[p.i : p.i+ind] + p.i += ind + 1 + return b, true +} + +func (p *Parser) ReadIdentifier() (string, bool) { + if p.i < len(p.b) && p.b[p.i] == '(' { + s := p.i + 1 + if ind := bytes.IndexByte(p.b[s:], ')'); ind != -1 { + b := p.b[s : s+ind] + p.i = s + ind + 1 + return internal.String(b), false + } + } + + ind := len(p.b) - p.i + var alpha bool + for i, c := range p.b[p.i:] { + if isNum(c) { + continue + } + if isAlpha(c) || (i > 0 && alpha && c == '_') { + alpha = true + continue + } + ind = i + break + } + if ind == 0 { + return "", false + } + b := p.b[p.i : p.i+ind] + p.i += ind + return internal.String(b), !alpha +} + +func (p *Parser) ReadNumber() int { + ind := len(p.b) - p.i + for i, c := range p.b[p.i:] { + if !isNum(c) { + ind = i + break + } + } + if ind == 0 { + return 0 + } + n, err := strconv.Atoi(string(p.b[p.i : p.i+ind])) + if err != nil { + panic(err) + } + p.i += ind + return n +} + +func isNum(c byte) bool { + return c >= '0' && c <= '9' +} + +func isAlpha(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') +} diff --git a/ogx/internal/safe.go b/ogx/internal/safe.go new file mode 100644 index 00000000..fd2f4340 --- /dev/null +++ b/ogx/internal/safe.go @@ -0,0 +1,12 @@ +//go:build appengine +// +build appengine + +package internal + +func String(b []byte) string { + return string(b) +} + +func Bytes(s string) []byte { + return []byte(s) +} diff --git a/ogx/internal/tagparser/parser.go b/ogx/internal/tagparser/parser.go new file mode 100644 index 00000000..a3905853 --- /dev/null +++ b/ogx/internal/tagparser/parser.go @@ -0,0 +1,184 @@ +package tagparser + +import ( + "strings" +) + +type Tag struct { + Name string + Options map[string][]string +} + +func (t Tag) IsZero() bool { + return t.Name == "" && t.Options == nil +} + +func (t Tag) HasOption(name string) bool { + _, ok := t.Options[name] + return ok +} + +func (t Tag) Option(name string) (string, bool) { + if vs, ok := t.Options[name]; ok { + return vs[len(vs)-1], true + } + return "", false +} + +func Parse(s string) Tag { + if s == "" { + return Tag{} + } + p := parser{ + s: s, + } + p.parse() + return p.tag +} + +type parser struct { + s string + i int + + tag Tag + seenName bool // for empty names +} + +func (p *parser) setName(name string) { + if p.seenName { + p.addOption(name, "") + } else { + p.seenName = true + p.tag.Name = name + } +} + +func (p *parser) addOption(key, value string) { + p.seenName = true + if key == "" { + return + } + if p.tag.Options == nil { + p.tag.Options = make(map[string][]string) + } + if vs, ok := p.tag.Options[key]; ok { + p.tag.Options[key] = append(vs, value) + } else { + p.tag.Options[key] = []string{value} + } +} + +func (p *parser) parse() { + for p.valid() { + p.parseKeyValue() + if p.peek() == ',' { + p.i++ + } + } +} + +func (p *parser) parseKeyValue() { + start := p.i + + for p.valid() { + switch c := p.read(); c { + case ',': + key := p.s[start : p.i-1] + p.setName(key) + return + case ':': + key := p.s[start : p.i-1] + value := p.parseValue() + p.addOption(key, value) + return + case '"': + key := p.parseQuotedValue() + p.setName(key) + return + } + } + + key := p.s[start:p.i] + p.setName(key) +} + +func (p *parser) parseValue() string { + start := p.i + + for p.valid() { + switch c := p.read(); c { + case '"': + return p.parseQuotedValue() + case ',': + return p.s[start : p.i-1] + case '(': + p.skipPairs('(', ')') + } + } + + if p.i == start { + return "" + } + return p.s[start:p.i] +} + +func (p *parser) parseQuotedValue() string { + if i := strings.IndexByte(p.s[p.i:], '"'); i >= 0 && p.s[p.i+i-1] != '\\' { + s := p.s[p.i : p.i+i] + p.i += i + 1 + return s + } + + b := make([]byte, 0, 16) + + for p.valid() { + switch c := p.read(); c { + case '\\': + b = append(b, p.read()) + case '"': + return string(b) + default: + b = append(b, c) + } + } + + return "" +} + +func (p *parser) skipPairs(start, end byte) { + var lvl int + for p.valid() { + switch c := p.read(); c { + case '"': + _ = p.parseQuotedValue() + case start: + lvl++ + case end: + if lvl == 0 { + return + } + lvl-- + } + } +} + +func (p *parser) valid() bool { + return p.i < len(p.s) +} + +func (p *parser) read() byte { + if !p.valid() { + return 0 + } + c := p.s[p.i] + p.i++ + return c +} + +func (p *parser) peek() byte { + if !p.valid() { + return 0 + } + c := p.s[p.i] + return c +} diff --git a/ogx/internal/tagparser/parser_test.go b/ogx/internal/tagparser/parser_test.go new file mode 100644 index 00000000..05173c9e --- /dev/null +++ b/ogx/internal/tagparser/parser_test.go @@ -0,0 +1,45 @@ +package tagparser_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "gitee.com/chentanyang/ogx/internal/tagparser" +) + +var tagTests = []struct { + tag string + name string + options map[string][]string +}{ + {"", "", nil}, + {"hello", "hello", nil}, + {"hello,world", "hello", map[string][]string{"world": {""}}}, + {`"hello,world'`, "", nil}, + {`"hello:world"`, `hello:world`, nil}, + {",hello", "", map[string][]string{"hello": {""}}}, + {",hello,world", "", map[string][]string{"hello": {""}, "world": {""}}}, + {"hello:", "", map[string][]string{"hello": {""}}}, + {"hello:world", "", map[string][]string{"hello": {"world"}}}, + {"hello:world,foo", "", map[string][]string{"hello": {"world"}, "foo": {""}}}, + {"hello:world,foo:bar", "", map[string][]string{"hello": {"world"}, "foo": {"bar"}}}, + {"hello:\"world1,world2\"", "", map[string][]string{"hello": {"world1,world2"}}}, + {`hello:"world1,world2",world3`, "", map[string][]string{"hello": {"world1,world2"}, "world3": {""}}}, + {`hello:"world1:world2",world3`, "", map[string][]string{"hello": {"world1:world2"}, "world3": {""}}}, + {`hello:"D'Angelo, esquire",foo:bar`, "", map[string][]string{"hello": {"D'Angelo, esquire"}, "foo": {"bar"}}}, + {`hello:"world('foo', 'bar')"`, "", map[string][]string{"hello": {"world('foo', 'bar')"}}}, + {" hello,foo: bar ", " hello", map[string][]string{"foo": {" bar "}}}, + {"foo:bar(hello, world)", "", map[string][]string{"foo": {"bar(hello, world)"}}}, + {"foo:bar(hello(), world)", "", map[string][]string{"foo": {"bar(hello(), world)"}}}, + {"type:geometry(POINT, 4326)", "", map[string][]string{"type": {"geometry(POINT, 4326)"}}}, + {"foo:bar,foo:baz", "", map[string][]string{"foo": {"bar", "baz"}}}, +} + +func TestTagParser(t *testing.T) { + for i, test := range tagTests { + tag := tagparser.Parse(test.tag) + require.Equal(t, test.name, tag.Name, "#%d", i) + require.Equal(t, test.options, tag.Options, "#%d", i) + } +} diff --git a/ogx/internal/time.go b/ogx/internal/time.go new file mode 100644 index 00000000..0474d780 --- /dev/null +++ b/ogx/internal/time.go @@ -0,0 +1,61 @@ +package internal + +import ( + "fmt" + "time" +) + +const ( + dateFormat = "2006-01-02" + timeFormat = "15:04:05.999999999" + timetzFormat1 = "15:04:05.999999999-07:00:00" + timetzFormat2 = "15:04:05.999999999-07:00" + timetzFormat3 = "15:04:05.999999999-07" + timestampFormat = "2006-01-02 15:04:05.999999999" + timestamptzFormat1 = "2006-01-02 15:04:05.999999999-07:00:00" + timestamptzFormat2 = "2006-01-02 15:04:05.999999999-07:00" + timestamptzFormat3 = "2006-01-02 15:04:05.999999999-07" +) + +func ParseTime(s string) (time.Time, error) { + l := len(s) + + if l >= len("2006-01-02 15:04:05") { + switch s[10] { + case ' ': + if c := s[l-6]; c == '+' || c == '-' { + return time.Parse(timestamptzFormat2, s) + } + if c := s[l-3]; c == '+' || c == '-' { + return time.Parse(timestamptzFormat3, s) + } + if c := s[l-9]; c == '+' || c == '-' { + return time.Parse(timestamptzFormat1, s) + } + return time.ParseInLocation(timestampFormat, s, time.UTC) + case 'T': + return time.Parse(time.RFC3339Nano, s) + } + } + + if l >= len("15:04:05-07") { + if c := s[l-6]; c == '+' || c == '-' { + return time.Parse(timetzFormat2, s) + } + if c := s[l-3]; c == '+' || c == '-' { + return time.Parse(timetzFormat3, s) + } + if c := s[l-9]; c == '+' || c == '-' { + return time.Parse(timetzFormat1, s) + } + } + + if l < len("15:04:05") { + return time.Time{}, fmt.Errorf("ogx: can't parse time=%q", s) + } + + if s[2] == ':' { + return time.ParseInLocation(timeFormat, s, time.UTC) + } + return time.ParseInLocation(dateFormat, s, time.UTC) +} diff --git a/ogx/internal/underscore.go b/ogx/internal/underscore.go new file mode 100644 index 00000000..9de52fb7 --- /dev/null +++ b/ogx/internal/underscore.go @@ -0,0 +1,67 @@ +package internal + +func IsUpper(c byte) bool { + return c >= 'A' && c <= 'Z' +} + +func IsLower(c byte) bool { + return c >= 'a' && c <= 'z' +} + +func ToUpper(c byte) byte { + return c - 32 +} + +func ToLower(c byte) byte { + return c + 32 +} + +// Underscore converts "CamelCasedString" to "camel_cased_string". +func Underscore(s string) string { + r := make([]byte, 0, len(s)+5) + for i := 0; i < len(s); i++ { + c := s[i] + if IsUpper(c) { + if i > 0 && i+1 < len(s) && (IsLower(s[i-1]) || IsLower(s[i+1])) { + r = append(r, '_', ToLower(c)) + } else { + r = append(r, ToLower(c)) + } + } else { + r = append(r, c) + } + } + return string(r) +} + +func CamelCased(s string) string { + r := make([]byte, 0, len(s)) + upperNext := true + for i := 0; i < len(s); i++ { + c := s[i] + if c == '_' { + upperNext = true + continue + } + if upperNext { + if IsLower(c) { + c = ToUpper(c) + } + upperNext = false + } + r = append(r, c) + } + return string(r) +} + +func ToExported(s string) string { + if len(s) == 0 { + return s + } + if c := s[0]; IsLower(c) { + b := []byte(s) + b[0] = ToUpper(c) + return string(b) + } + return s +} diff --git a/ogx/internal/unsafe.go b/ogx/internal/unsafe.go new file mode 100644 index 00000000..9f2e418f --- /dev/null +++ b/ogx/internal/unsafe.go @@ -0,0 +1,21 @@ +//go:build !appengine +// +build !appengine + +package internal + +import "unsafe" + +// String converts byte slice to string. +func String(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + +// Bytes converts string to byte slice. +func Bytes(s string) []byte { + return *(*[]byte)(unsafe.Pointer( + &struct { + string + Cap int + }{s, len(s)}, + )) +} diff --git a/ogx/internal/util.go b/ogx/internal/util.go new file mode 100644 index 00000000..c831dc65 --- /dev/null +++ b/ogx/internal/util.go @@ -0,0 +1,57 @@ +package internal + +import ( + "reflect" +) + +func MakeSliceNextElemFunc(v reflect.Value) func() reflect.Value { + if v.Kind() == reflect.Array { + var pos int + return func() reflect.Value { + v := v.Index(pos) + pos++ + return v + } + } + + elemType := v.Type().Elem() + + if elemType.Kind() == reflect.Ptr { + elemType = elemType.Elem() + return func() reflect.Value { + if v.Len() < v.Cap() { + v.Set(v.Slice(0, v.Len()+1)) + elem := v.Index(v.Len() - 1) + if elem.IsNil() { + elem.Set(reflect.New(elemType)) + } + return elem.Elem() + } + + elem := reflect.New(elemType) + v.Set(reflect.Append(v, elem)) + return elem.Elem() + } + } + + zero := reflect.Zero(elemType) + return func() reflect.Value { + if v.Len() < v.Cap() { + v.Set(v.Slice(0, v.Len()+1)) + return v.Index(v.Len() - 1) + } + + v.Set(reflect.Append(v, zero)) + return v.Index(v.Len() - 1) + } +} + +func Unwrap(err error) error { + u, ok := err.(interface { + Unwrap() error + }) + if !ok { + return nil + } + return u.Unwrap() +} diff --git a/ogx/migrate/migration.go b/ogx/migrate/migration.go new file mode 100644 index 00000000..fe4f3991 --- /dev/null +++ b/ogx/migrate/migration.go @@ -0,0 +1,286 @@ +package migrate + +import ( + "bufio" + "bytes" + "context" + "fmt" + "io/fs" + "sort" + "strings" + "time" + + "gitee.com/chentanyang/ogx" +) + +type Migration struct { + ogx.BaseModel + + ID int64 `ogx:",pk,autoincrement"` + Name string + GroupID int64 + MigratedAt time.Time `ogx:",notnull,nullzero,default:current_timestamp"` + + Up MigrationFunc `ogx:"-"` + Down MigrationFunc `ogx:"-"` +} + +func (m Migration) String() string { + return m.Name +} + +func (m Migration) IsApplied() bool { + return m.ID > 0 +} + +type MigrationFunc func(ctx context.Context, db *ogx.DB) error + +func NewSQLMigrationFunc(fsys fs.FS, name string) MigrationFunc { + return func(ctx context.Context, db *ogx.DB) error { + isTx := strings.HasSuffix(name, ".tx.up.sql") || strings.HasSuffix(name, ".tx.down.sql") + + f, err := fsys.Open(name) + if err != nil { + return err + } + + scanner := bufio.NewScanner(f) + var queries []string + + var query []byte + for scanner.Scan() { + b := scanner.Bytes() + + const prefix = "--ogx:" + if bytes.HasPrefix(b, []byte(prefix)) { + b = b[len(prefix):] + if bytes.Equal(b, []byte("split")) { + queries = append(queries, string(query)) + query = query[:0] + continue + } + return fmt.Errorf("ogx: unknown directive: %q", b) + } + + query = append(query, b...) + query = append(query, '\n') + } + + if len(query) > 0 { + queries = append(queries, string(query)) + } + if err := scanner.Err(); err != nil { + return err + } + + var idb ogx.IConn + + if isTx { + tx, err := db.BeginTx(ctx, nil) + if err != nil { + return err + } + idb = tx + } else { + conn, err := db.Conn(ctx) + if err != nil { + return err + } + idb = conn + } + + var retErr error + + defer func() { + if tx, ok := idb.(ogx.Tx); ok { + retErr = tx.Commit() + return + } + + if conn, ok := idb.(ogx.Conn); ok { + retErr = conn.Close() + return + } + + panic("not reached") + }() + + for _, q := range queries { + _, err = idb.ExecContext(ctx, q) + if err != nil { + return err + } + } + + return retErr + } +} + +const goTemplate = `package %s + +import ( + "context" + "fmt" + + "gitee.com/chentanyang/ogx" +) + +func init() { + Migrations.MustRegister(func(ctx context.Context, db *ogx.DB) error { + fmt.Print(" [up migration] ") + return nil + }, func(ctx context.Context, db *ogx.DB) error { + fmt.Print(" [down migration] ") + return nil + }) +} +` + +const sqlTemplate = `SET statement_timeout = 0; + +--ogx:split + +SELECT 1 + +--ogx:split + +SELECT 2 +` + +//------------------------------------------------------------------------------ + +type MigrationSlice []Migration + +func (ms MigrationSlice) String() string { + if len(ms) == 0 { + return "empty" + } + + if len(ms) > 5 { + return fmt.Sprintf("%d migrations (%s ... %s)", len(ms), ms[0].Name, ms[len(ms)-1].Name) + } + + var sb strings.Builder + + for i := range ms { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString(ms[i].Name) + } + + return sb.String() +} + +// Applied returns applied migrations in descending order +// (the order is important and is used in Rollback). +func (ms MigrationSlice) Applied() MigrationSlice { + var applied MigrationSlice + for i := range ms { + if ms[i].IsApplied() { + applied = append(applied, ms[i]) + } + } + sortDesc(applied) + return applied +} + +// Unapplied returns unapplied migrations in ascending order +// (the order is important and is used in Migrate). +func (ms MigrationSlice) Unapplied() MigrationSlice { + var unapplied MigrationSlice + for i := range ms { + if !ms[i].IsApplied() { + unapplied = append(unapplied, ms[i]) + } + } + sortAsc(unapplied) + return unapplied +} + +// LastGroupID returns the last applied migration group id. +// The id is 0 when there are no migration groups. +func (ms MigrationSlice) LastGroupID() int64 { + var lastGroupID int64 + for i := range ms { + groupID := ms[i].GroupID + if groupID > lastGroupID { + lastGroupID = groupID + } + } + return lastGroupID +} + +// LastGroup returns the last applied migration group. +func (ms MigrationSlice) LastGroup() *MigrationGroup { + group := &MigrationGroup{ + ID: ms.LastGroupID(), + } + if group.ID == 0 { + return group + } + for i := range ms { + if ms[i].GroupID == group.ID { + group.Migrations = append(group.Migrations, ms[i]) + } + } + return group +} + +type MigrationGroup struct { + ID int64 + Migrations MigrationSlice +} + +func (g MigrationGroup) IsZero() bool { + return g.ID == 0 && len(g.Migrations) == 0 +} + +func (g MigrationGroup) String() string { + if g.IsZero() { + return "nil" + } + return fmt.Sprintf("group #%d (%s)", g.ID, g.Migrations) +} + +type MigrationFile struct { + Name string + Path string + Content string +} + +//------------------------------------------------------------------------------ + +type migrationConfig struct { + nop bool +} + +func newMigrationConfig(opts []MigrationOption) *migrationConfig { + cfg := new(migrationConfig) + for _, opt := range opts { + opt(cfg) + } + return cfg +} + +type MigrationOption func(cfg *migrationConfig) + +func WithNopMigration() MigrationOption { + return func(cfg *migrationConfig) { + cfg.nop = true + } +} + +//------------------------------------------------------------------------------ + +func sortAsc(ms MigrationSlice) { + sort.Slice(ms, func(i, j int) bool { + return ms[i].Name < ms[j].Name + }) +} + +func sortDesc(ms MigrationSlice) { + sort.Slice(ms, func(i, j int) bool { + return ms[i].Name > ms[j].Name + }) +} diff --git a/ogx/migrate/migrations.go b/ogx/migrate/migrations.go new file mode 100644 index 00000000..7518e0d8 --- /dev/null +++ b/ogx/migrate/migrations.go @@ -0,0 +1,168 @@ +package migrate + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "regexp" + "runtime" + "strings" +) + +type MigrationsOption func(m *Migrations) + +func WithMigrationsDirectory(directory string) MigrationsOption { + return func(m *Migrations) { + m.explicitDirectory = directory + } +} + +type Migrations struct { + ms MigrationSlice + + explicitDirectory string + implicitDirectory string +} + +func NewMigrations(opts ...MigrationsOption) *Migrations { + m := new(Migrations) + for _, opt := range opts { + opt(m) + } + m.implicitDirectory = filepath.Dir(migrationFile()) + return m +} + +func (m *Migrations) Sorted() MigrationSlice { + migrations := make(MigrationSlice, len(m.ms)) + copy(migrations, m.ms) + sortAsc(migrations) + return migrations +} + +func (m *Migrations) MustRegister(up, down MigrationFunc) { + if err := m.Register(up, down); err != nil { + panic(err) + } +} + +func (m *Migrations) Register(up, down MigrationFunc) error { + fpath := migrationFile() + name, err := extractMigrationName(fpath) + if err != nil { + return err + } + + m.Add(Migration{ + Name: name, + Up: up, + Down: down, + }) + + return nil +} + +func (m *Migrations) Add(migration Migration) { + if migration.Name == "" { + panic("migration name is required") + } + m.ms = append(m.ms, migration) +} + +func (m *Migrations) DiscoverCaller() error { + dir := filepath.Dir(migrationFile()) + return m.Discover(os.DirFS(dir)) +} + +func (m *Migrations) Discover(fsys fs.FS) error { + return fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + + if !strings.HasSuffix(path, ".up.sql") && !strings.HasSuffix(path, ".down.sql") { + return nil + } + + name, err := extractMigrationName(path) + if err != nil { + return err + } + + migration := m.getOrCreateMigration(name) + if err != nil { + return err + } + migrationFunc := NewSQLMigrationFunc(fsys, path) + + if strings.HasSuffix(path, ".up.sql") { + migration.Up = migrationFunc + return nil + } + if strings.HasSuffix(path, ".down.sql") { + migration.Down = migrationFunc + return nil + } + + return errors.New("migrate: not reached") + }) +} + +func (m *Migrations) getOrCreateMigration(name string) *Migration { + for i := range m.ms { + m := &m.ms[i] + if m.Name == name { + return m + } + } + + m.ms = append(m.ms, Migration{Name: name}) + return &m.ms[len(m.ms)-1] +} + +func (m *Migrations) getDirectory() string { + if m.explicitDirectory != "" { + return m.explicitDirectory + } + if m.implicitDirectory != "" { + return m.implicitDirectory + } + return filepath.Dir(migrationFile()) +} + +func migrationFile() string { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(1, pcs[:]) + frames := runtime.CallersFrames(pcs[:n]) + + for { + f, ok := frames.Next() + if !ok { + break + } + if !strings.Contains(f.Function, "/ogx/migrate.") { + return f.File + } + } + + return "" +} + +var fnameRE = regexp.MustCompile(`^(\d{14})_[0-9a-z_\-]+\.`) + +func extractMigrationName(fpath string) (string, error) { + fname := filepath.Base(fpath) + + matches := fnameRE.FindStringSubmatch(fname) + if matches == nil { + return "", fmt.Errorf("migrate: unsupported migration name format: %q", fname) + } + + return matches[1], nil +} diff --git a/ogx/migrate/migrator.go b/ogx/migrate/migrator.go new file mode 100644 index 00000000..832c4abf --- /dev/null +++ b/ogx/migrate/migrator.go @@ -0,0 +1,402 @@ +package migrate + +import ( + "context" + "errors" + "fmt" + "io/ioutil" + "path/filepath" + "regexp" + "time" + + "gitee.com/chentanyang/ogx" +) + +type MigratorOption func(m *Migrator) + +func WithTableName(table string) MigratorOption { + return func(m *Migrator) { + m.table = table + } +} + +func WithLocksTableName(table string) MigratorOption { + return func(m *Migrator) { + m.locksTable = table + } +} + +// WithMarkAppliedOnSuccess sets the migrator to only mark migrations as applied/unapplied +// when their up/down is successful +func WithMarkAppliedOnSuccess(enabled bool) MigratorOption { + return func(m *Migrator) { + m.markAppliedOnSuccess = enabled + } +} + +type Migrator struct { + db *ogx.DB + migrations *Migrations + + ms MigrationSlice + + table string + locksTable string + markAppliedOnSuccess bool +} + +func NewMigrator(db *ogx.DB, migrations *Migrations, opts ...MigratorOption) *Migrator { + m := &Migrator{ + db: db, + migrations: migrations, + + ms: migrations.ms, + + table: "ogx_migrations", + locksTable: "ogx_migration_locks", + } + for _, opt := range opts { + opt(m) + } + return m +} + +func (m *Migrator) DB() *ogx.DB { + return m.db +} + +// MigrationsWithStatus returns migrations with status in ascending order. +func (m *Migrator) MigrationsWithStatus(ctx context.Context) (MigrationSlice, error) { + sorted, _, err := m.migrationsWithStatus(ctx) + return sorted, err +} + +func (m *Migrator) migrationsWithStatus(ctx context.Context) (MigrationSlice, int64, error) { + sorted := m.migrations.Sorted() + + applied, err := m.selectAppliedMigrations(ctx) + if err != nil { + return nil, 0, err + } + + appliedMap := migrationMap(applied) + for i := range sorted { + m1 := &sorted[i] + if m2, ok := appliedMap[m1.Name]; ok { + m1.ID = m2.ID + m1.GroupID = m2.GroupID + m1.MigratedAt = m2.MigratedAt + } + } + + return sorted, applied.LastGroupID(), nil +} + +func (m *Migrator) Init(ctx context.Context) error { + if _, err := m.db.NewCreateTable(). + Model((*Migration)(nil)). + ModelTableExpr(m.table). + IfNotExists(). + Exec(ctx); err != nil { + return err + } + if _, err := m.db.NewCreateTable(). + Model((*migrationLock)(nil)). + ModelTableExpr(m.locksTable). + IfNotExists(). + Exec(ctx); err != nil { + return err + } + return nil +} + +func (m *Migrator) Reset(ctx context.Context) error { + if _, err := m.db.NewDropTable(). + Model((*Migration)(nil)). + ModelTableExpr(m.table). + IfExists(). + Exec(ctx); err != nil { + return err + } + if _, err := m.db.NewDropTable(). + Model((*migrationLock)(nil)). + ModelTableExpr(m.locksTable). + IfExists(). + Exec(ctx); err != nil { + return err + } + return m.Init(ctx) +} + +// Migrate runs unapplied migrations. If a migration fails, migrate immediately exits. +func (m *Migrator) Migrate(ctx context.Context, opts ...MigrationOption) (*MigrationGroup, error) { + cfg := newMigrationConfig(opts) + + if err := m.validate(); err != nil { + return nil, err + } + + if err := m.Lock(ctx); err != nil { + return nil, err + } + defer m.Unlock(ctx) //nolint:errcheck + + migrations, lastGroupID, err := m.migrationsWithStatus(ctx) + if err != nil { + return nil, err + } + migrations = migrations.Unapplied() + + group := new(MigrationGroup) + if len(migrations) == 0 { + return group, nil + } + group.ID = lastGroupID + 1 + + for i := range migrations { + migration := &migrations[i] + migration.GroupID = group.ID + + if !m.markAppliedOnSuccess { + if err := m.MarkApplied(ctx, migration); err != nil { + return group, err + } + } + + group.Migrations = migrations[:i+1] + + if !cfg.nop && migration.Up != nil { + if err := migration.Up(ctx, m.db); err != nil { + return group, err + } + } + + if m.markAppliedOnSuccess { + if err := m.MarkApplied(ctx, migration); err != nil { + return group, err + } + } + } + + return group, nil +} + +func (m *Migrator) Rollback(ctx context.Context, opts ...MigrationOption) (*MigrationGroup, error) { + cfg := newMigrationConfig(opts) + + if err := m.validate(); err != nil { + return nil, err + } + + if err := m.Lock(ctx); err != nil { + return nil, err + } + defer m.Unlock(ctx) //nolint:errcheck + + migrations, err := m.MigrationsWithStatus(ctx) + if err != nil { + return nil, err + } + + lastGroup := migrations.LastGroup() + + for i := len(lastGroup.Migrations) - 1; i >= 0; i-- { + migration := &lastGroup.Migrations[i] + + if !m.markAppliedOnSuccess { + if err := m.MarkUnapplied(ctx, migration); err != nil { + return lastGroup, err + } + } + + if !cfg.nop && migration.Down != nil { + if err := migration.Down(ctx, m.db); err != nil { + return lastGroup, err + } + } + + if m.markAppliedOnSuccess { + if err := m.MarkUnapplied(ctx, migration); err != nil { + return lastGroup, err + } + } + } + + return lastGroup, nil +} + +type goMigrationConfig struct { + packageName string +} + +type GoMigrationOption func(cfg *goMigrationConfig) + +func WithPackageName(name string) GoMigrationOption { + return func(cfg *goMigrationConfig) { + cfg.packageName = name + } +} + +// CreateGoMigration creates a Go migration file. +func (m *Migrator) CreateGoMigration( + ctx context.Context, name string, opts ...GoMigrationOption, +) (*MigrationFile, error) { + cfg := &goMigrationConfig{ + packageName: "migrations", + } + for _, opt := range opts { + opt(cfg) + } + + name, err := m.genMigrationName(name) + if err != nil { + return nil, err + } + + fname := name + ".go" + fpath := filepath.Join(m.migrations.getDirectory(), fname) + content := fmt.Sprintf(goTemplate, cfg.packageName) + + if err := ioutil.WriteFile(fpath, []byte(content), 0o644); err != nil { + return nil, err + } + + mf := &MigrationFile{ + Name: fname, + Path: fpath, + Content: content, + } + return mf, nil +} + +// CreateSQLMigrations creates an up and down SQL migration files. +func (m *Migrator) CreateSQLMigrations(ctx context.Context, name string) ([]*MigrationFile, error) { + name, err := m.genMigrationName(name) + if err != nil { + return nil, err + } + + up, err := m.createSQL(ctx, name+".up.sql") + if err != nil { + return nil, err + } + + down, err := m.createSQL(ctx, name+".down.sql") + if err != nil { + return nil, err + } + + return []*MigrationFile{up, down}, nil +} + +func (m *Migrator) createSQL(ctx context.Context, fname string) (*MigrationFile, error) { + fpath := filepath.Join(m.migrations.getDirectory(), fname) + + if err := ioutil.WriteFile(fpath, []byte(sqlTemplate), 0o644); err != nil { + return nil, err + } + + mf := &MigrationFile{ + Name: fname, + Path: fpath, + Content: goTemplate, + } + return mf, nil +} + +var nameRE = regexp.MustCompile(`^[0-9a-z_\-]+$`) + +func (m *Migrator) genMigrationName(name string) (string, error) { + const timeFormat = "20060102150405" + + if name == "" { + return "", errors.New("migrate: migration name can't be empty") + } + if !nameRE.MatchString(name) { + return "", fmt.Errorf("migrate: invalid migration name: %q", name) + } + + version := time.Now().UTC().Format(timeFormat) + return fmt.Sprintf("%s_%s", version, name), nil +} + +// MarkApplied marks the migration as applied (completed). +func (m *Migrator) MarkApplied(ctx context.Context, migration *Migration) error { + _, err := m.db.NewInsert().Model(migration). + ModelTableExpr(m.table). + Exec(ctx) + return err +} + +// MarkUnapplied marks the migration as unapplied (new). +func (m *Migrator) MarkUnapplied(ctx context.Context, migration *Migration) error { + _, err := m.db.NewDelete(). + Model(migration). + ModelTableExpr(m.table). + Where("id = ?", migration.ID). + Exec(ctx) + return err +} + +// selectAppliedMigrations selects applied (applied) migrations in descending order. +func (m *Migrator) selectAppliedMigrations(ctx context.Context) (MigrationSlice, error) { + var ms MigrationSlice + if err := m.db.NewSelect(). + ColumnExpr("*"). + Model(&ms). + ModelTableExpr(m.table). + Scan(ctx); err != nil { + return nil, err + } + return ms, nil +} + +func (m *Migrator) formattedTableName(db *ogx.DB) string { + return db.Formatter().FormatQuery(m.table) +} + +func (m *Migrator) validate() error { + if len(m.ms) == 0 { + return errors.New("migrate: there are no any migrations") + } + return nil +} + +//------------------------------------------------------------------------------ + +type migrationLock struct { + ID int64 `ogx:",pk,autoincrement"` + TableName string `ogx:",unique"` +} + +func (m *Migrator) Lock(ctx context.Context) error { + lock := &migrationLock{ + TableName: m.formattedTableName(m.db), + } + if _, err := m.db.NewInsert(). + Model(lock). + ModelTableExpr(m.locksTable). + Exec(ctx); err != nil { + return fmt.Errorf("migrate: migrations table is already locked (%w)", err) + } + return nil +} + +func (m *Migrator) Unlock(ctx context.Context) error { + tableName := m.formattedTableName(m.db) + _, err := m.db.NewDelete(). + Model((*migrationLock)(nil)). + ModelTableExpr(m.locksTable). + Where("? = ?", ogx.Ident("table_name"), tableName). + Exec(ctx) + return err +} + +func migrationMap(ms MigrationSlice) map[string]*Migration { + mp := make(map[string]*Migration) + for i := range ms { + m := &ms[i] + mp[m.Name] = m + } + return mp +} diff --git a/ogx/model.go b/ogx/model.go new file mode 100644 index 00000000..b64f849a --- /dev/null +++ b/ogx/model.go @@ -0,0 +1,201 @@ +package ogx + +import ( + "context" + "database/sql" + "errors" + "fmt" + "reflect" + "time" + + "gitee.com/chentanyang/ogx/schema" +) + +var errNilModel = errors.New("ogx: Model(nil)") + +var timeType = reflect.TypeOf((*time.Time)(nil)).Elem() + +type Model = schema.Model + +type rowScanner interface { + ScanRow(ctx context.Context, rows *sql.Rows) error +} + +type TableModel interface { + Model + + schema.BeforeAppendModelHook + schema.BeforeScanRowHook + schema.AfterScanRowHook + ScanColumn(column string, src interface{}) error + + Table() *schema.Table + Relation() *schema.Relation + + join(string) *relationJoin + getJoin(string) *relationJoin + getJoins() []relationJoin + addJoin(relationJoin) *relationJoin + + rootValue() reflect.Value + parentIndex() []int + mount(reflect.Value) + + updateSoftDeleteField(time.Time) error +} + +func newModel(db *DB, dest []interface{}) (Model, error) { + if len(dest) == 1 { + return _newModel(db, dest[0], true) + } + + values := make([]reflect.Value, len(dest)) + + for i, el := range dest { + v := reflect.ValueOf(el) + if v.Kind() != reflect.Ptr { + return nil, fmt.Errorf("ogx: Scan(non-pointer %T)", dest) + } + + v = v.Elem() + if v.Kind() != reflect.Slice { + return newScanModel(db, dest), nil + } + + values[i] = v + } + + return newSliceModel(db, dest, values), nil +} + +func newSingleModel(db *DB, dest interface{}) (Model, error) { + return _newModel(db, dest, false) +} + +func _newModel(db *DB, dest interface{}, scan bool) (Model, error) { + switch dest := dest.(type) { + case nil: + return nil, errNilModel + case Model: + return dest, nil + case sql.Scanner: + if !scan { + return nil, fmt.Errorf("ogx: Model(unsupported %T)", dest) + } + return newScanModel(db, []interface{}{dest}), nil + } + + v := reflect.ValueOf(dest) + if !v.IsValid() { + return nil, errNilModel + } + if v.Kind() != reflect.Ptr { + return nil, fmt.Errorf("ogx: Model(non-pointer %T)", dest) + } + + if v.IsNil() { + typ := v.Type().Elem() + if typ.Kind() == reflect.Struct { + return newStructTableModel(db, dest, db.Table(typ)), nil + } + return nil, fmt.Errorf("ogx: Model(nil %T)", dest) + } + + v = v.Elem() + + switch v.Kind() { + case reflect.Map: + typ := v.Type() + if err := validMap(typ); err != nil { + return nil, err + } + mapPtr := v.Addr().Interface().(*map[string]interface{}) + return newMapModel(db, mapPtr), nil + case reflect.Struct: + if v.Type() != timeType { + return newStructTableModelValue(db, dest, v), nil + } + case reflect.Slice: + switch elemType := sliceElemType(v); elemType.Kind() { + case reflect.Struct: + if elemType != timeType { + return newSliceTableModel(db, dest, v, elemType), nil + } + case reflect.Map: + if err := validMap(elemType); err != nil { + return nil, err + } + slicePtr := v.Addr().Interface().(*[]map[string]interface{}) + return newMapSliceModel(db, slicePtr), nil + } + return newSliceModel(db, []interface{}{dest}, []reflect.Value{v}), nil + } + + if scan { + return newScanModel(db, []interface{}{dest}), nil + } + + return nil, fmt.Errorf("ogx: Model(unsupported %T)", dest) +} + +func newTableModelIndex( + db *DB, + table *schema.Table, + root reflect.Value, + index []int, + rel *schema.Relation, +) (TableModel, error) { + typ := typeByIndex(table.Type, index) + + if typ.Kind() == reflect.Struct { + return &structTableModel{ + db: db, + table: table.Dialect().Tables().Get(typ), + rel: rel, + + root: root, + index: index, + }, nil + } + + if typ.Kind() == reflect.Slice { + structType := indirectType(typ.Elem()) + if structType.Kind() == reflect.Struct { + m := sliceTableModel{ + structTableModel: structTableModel{ + db: db, + table: table.Dialect().Tables().Get(structType), + rel: rel, + + root: root, + index: index, + }, + } + m.init(typ) + return &m, nil + } + } + + return nil, fmt.Errorf("ogx: NewModel(%s)", typ) +} + +func validMap(typ reflect.Type) error { + if typ.Key().Kind() != reflect.String || typ.Elem().Kind() != reflect.Interface { + return fmt.Errorf("ogx: Model(unsupported %s) (expected *map[string]interface{})", + typ) + } + return nil +} + +//------------------------------------------------------------------------------ + +func isSingleRowModel(m Model) bool { + switch m.(type) { + case *mapModel, + *structTableModel, + *scanModel: + return true + default: + return false + } +} diff --git a/ogx/model_map.go b/ogx/model_map.go new file mode 100644 index 00000000..67427c19 --- /dev/null +++ b/ogx/model_map.go @@ -0,0 +1,183 @@ +package ogx + +import ( + "context" + "database/sql" + "reflect" + "sort" + + "gitee.com/chentanyang/ogx/schema" +) + +type mapModel struct { + db *DB + + dest *map[string]interface{} + m map[string]interface{} + + rows *sql.Rows + columns []string + _columnTypes []*sql.ColumnType + scanIndex int +} + +var _ Model = (*mapModel)(nil) + +func newMapModel(db *DB, dest *map[string]interface{}) *mapModel { + m := &mapModel{ + db: db, + dest: dest, + } + if dest != nil { + m.m = *dest + } + return m +} + +func (m *mapModel) Value() interface{} { + return m.dest +} + +func (m *mapModel) ScanRows(ctx context.Context, rows *sql.Rows) (int, error) { + if !rows.Next() { + return 0, rows.Err() + } + + columns, err := rows.Columns() + if err != nil { + return 0, err + } + + m.rows = rows + m.columns = columns + dest := makeDest(m, len(columns)) + + if m.m == nil { + m.m = make(map[string]interface{}, len(m.columns)) + } + + m.scanIndex = 0 + if err := rows.Scan(dest...); err != nil { + return 0, err + } + + *m.dest = m.m + + return 1, nil +} + +func (m *mapModel) Scan(src interface{}) error { + if _, ok := src.([]byte); !ok { + return m.scanRaw(src) + } + + columnTypes, err := m.columnTypes() + if err != nil { + return err + } + + scanType := columnTypes[m.scanIndex].ScanType() + switch scanType.Kind() { + case reflect.Interface: + return m.scanRaw(src) + case reflect.Slice: + if scanType.Elem().Kind() == reflect.Uint8 { + return m.scanRaw(src) + } + } + + dest := reflect.New(scanType).Elem() + if err := schema.Scanner(scanType)(dest, src); err != nil { + return err + } + + return m.scanRaw(dest.Interface()) +} + +func (m *mapModel) columnTypes() ([]*sql.ColumnType, error) { + if m._columnTypes == nil { + columnTypes, err := m.rows.ColumnTypes() + if err != nil { + return nil, err + } + m._columnTypes = columnTypes + } + return m._columnTypes, nil +} + +func (m *mapModel) scanRaw(src interface{}) error { + columnName := m.columns[m.scanIndex] + m.scanIndex++ + m.m[columnName] = src + return nil +} + +func (m *mapModel) appendColumnsValues(fmter schema.Formatter, b []byte) []byte { + keys := make([]string, 0, len(m.m)) + + for k := range m.m { + keys = append(keys, k) + } + sort.Strings(keys) + + b = append(b, " ("...) + + for i, k := range keys { + if i > 0 { + b = append(b, ", "...) + } + b = fmter.AppendIdent(b, k) + } + + b = append(b, ") VALUES ("...) + + isTemplate := fmter.IsNop() + for i, k := range keys { + if i > 0 { + b = append(b, ", "...) + } + if isTemplate { + b = append(b, '?') + } else { + b = schema.Append(fmter, b, m.m[k]) + } + } + + b = append(b, ")"...) + + return b +} + +func (m *mapModel) appendSet(fmter schema.Formatter, b []byte) []byte { + keys := make([]string, 0, len(m.m)) + + for k := range m.m { + keys = append(keys, k) + } + sort.Strings(keys) + + isTemplate := fmter.IsNop() + for i, k := range keys { + if i > 0 { + b = append(b, ", "...) + } + + b = fmter.AppendIdent(b, k) + b = append(b, " = "...) + if isTemplate { + b = append(b, '?') + } else { + b = schema.Append(fmter, b, m.m[k]) + } + } + + return b +} + +func makeDest(v interface{}, n int) []interface{} { + dest := make([]interface{}, n) + for i := range dest { + dest[i] = v + } + return dest +} diff --git a/ogx/model_map_slice.go b/ogx/model_map_slice.go new file mode 100644 index 00000000..eb4cc65d --- /dev/null +++ b/ogx/model_map_slice.go @@ -0,0 +1,162 @@ +package ogx + +import ( + "context" + "database/sql" + "errors" + "sort" + + "gitee.com/chentanyang/ogx/dialect/feature" + "gitee.com/chentanyang/ogx/schema" +) + +type mapSliceModel struct { + mapModel + dest *[]map[string]interface{} + + keys []string +} + +var _ Model = (*mapSliceModel)(nil) + +func newMapSliceModel(db *DB, dest *[]map[string]interface{}) *mapSliceModel { + return &mapSliceModel{ + mapModel: mapModel{ + db: db, + }, + dest: dest, + } +} + +func (m *mapSliceModel) Value() interface{} { + return m.dest +} + +func (m *mapSliceModel) SetCap(cap int) { + if cap > 100 { + cap = 100 + } + if slice := *m.dest; len(slice) < cap { + *m.dest = make([]map[string]interface{}, 0, cap) + } +} + +func (m *mapSliceModel) ScanRows(ctx context.Context, rows *sql.Rows) (int, error) { + columns, err := rows.Columns() + if err != nil { + return 0, err + } + + m.rows = rows + m.columns = columns + dest := makeDest(m, len(columns)) + + slice := *m.dest + if len(slice) > 0 { + slice = slice[:0] + } + + var n int + + for rows.Next() { + m.m = make(map[string]interface{}, len(m.columns)) + + m.scanIndex = 0 + if err := rows.Scan(dest...); err != nil { + return 0, err + } + + slice = append(slice, m.m) + n++ + } + if err := rows.Err(); err != nil { + return 0, err + } + + *m.dest = slice + return n, nil +} + +func (m *mapSliceModel) appendColumns(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if err := m.initKeys(); err != nil { + return nil, err + } + + for i, k := range m.keys { + if i > 0 { + b = append(b, ", "...) + } + b = fmter.AppendIdent(b, k) + } + + return b, nil +} + +func (m *mapSliceModel) appendValues(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if err := m.initKeys(); err != nil { + return nil, err + } + slice := *m.dest + + b = append(b, "VALUES "...) + if m.db.features.Has(feature.ValuesRow) { + b = append(b, "ROW("...) + } else { + b = append(b, '(') + } + + if fmter.IsNop() { + for i := range m.keys { + if i > 0 { + b = append(b, ", "...) + } + b = append(b, '?') + } + return b, nil + } + + for i, el := range slice { + if i > 0 { + b = append(b, "), "...) + if m.db.features.Has(feature.ValuesRow) { + b = append(b, "ROW("...) + } else { + b = append(b, '(') + } + } + + for j, key := range m.keys { + if j > 0 { + b = append(b, ", "...) + } + b = schema.Append(fmter, b, el[key]) + } + } + + b = append(b, ')') + + return b, nil +} + +func (m *mapSliceModel) initKeys() error { + if m.keys != nil { + return nil + } + + slice := *m.dest + if len(slice) == 0 { + return errors.New("ogx: map slice is empty") + } + + first := slice[0] + keys := make([]string, 0, len(first)) + + for k := range first { + keys = append(keys, k) + } + + sort.Strings(keys) + m.keys = keys + + return nil +} diff --git a/ogx/model_scan.go b/ogx/model_scan.go new file mode 100644 index 00000000..066b5629 --- /dev/null +++ b/ogx/model_scan.go @@ -0,0 +1,56 @@ +package ogx + +import ( + "context" + "database/sql" + "reflect" + + "gitee.com/chentanyang/ogx/schema" +) + +type scanModel struct { + db *DB + + dest []interface{} + scanIndex int +} + +var _ Model = (*scanModel)(nil) + +func newScanModel(db *DB, dest []interface{}) *scanModel { + return &scanModel{ + db: db, + dest: dest, + } +} + +func (m *scanModel) Value() interface{} { + return m.dest +} + +func (m *scanModel) ScanRows(ctx context.Context, rows *sql.Rows) (int, error) { + if !rows.Next() { + return 0, rows.Err() + } + + dest := makeDest(m, len(m.dest)) + + m.scanIndex = 0 + if err := rows.Scan(dest...); err != nil { + return 0, err + } + + return 1, nil +} + +func (m *scanModel) ScanRow(ctx context.Context, rows *sql.Rows) error { + return rows.Scan(m.dest...) +} + +func (m *scanModel) Scan(src interface{}) error { + dest := reflect.ValueOf(m.dest[m.scanIndex]) + m.scanIndex++ + + scanner := schema.Scanner(dest.Type()) + return scanner(dest, src) +} diff --git a/ogx/model_slice.go b/ogx/model_slice.go new file mode 100644 index 00000000..12df360c --- /dev/null +++ b/ogx/model_slice.go @@ -0,0 +1,82 @@ +package ogx + +import ( + "context" + "database/sql" + "reflect" + + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/schema" +) + +type sliceInfo struct { + nextElem func() reflect.Value + scan schema.ScannerFunc +} + +type sliceModel struct { + dest []interface{} + values []reflect.Value + scanIndex int + info []sliceInfo +} + +var _ Model = (*sliceModel)(nil) + +func newSliceModel(db *DB, dest []interface{}, values []reflect.Value) *sliceModel { + return &sliceModel{ + dest: dest, + values: values, + } +} + +func (m *sliceModel) Value() interface{} { + return m.dest +} + +func (m *sliceModel) ScanRows(ctx context.Context, rows *sql.Rows) (int, error) { + columns, err := rows.Columns() + if err != nil { + return 0, err + } + + m.info = make([]sliceInfo, len(m.values)) + for i, v := range m.values { + if v.IsValid() && v.Len() > 0 { + v.Set(v.Slice(0, 0)) + } + + m.info[i] = sliceInfo{ + nextElem: internal.MakeSliceNextElemFunc(v), + scan: schema.Scanner(v.Type().Elem()), + } + } + + if len(columns) == 0 { + return 0, nil + } + dest := makeDest(m, len(columns)) + + var n int + + for rows.Next() { + m.scanIndex = 0 + if err := rows.Scan(dest...); err != nil { + return 0, err + } + n++ + } + if err := rows.Err(); err != nil { + return 0, err + } + + return n, nil +} + +func (m *sliceModel) Scan(src interface{}) error { + info := m.info[m.scanIndex] + m.scanIndex++ + + dest := info.nextElem() + return info.scan(dest, src) +} diff --git a/ogx/model_table_has_many.go b/ogx/model_table_has_many.go new file mode 100644 index 00000000..0db7af2e --- /dev/null +++ b/ogx/model_table_has_many.go @@ -0,0 +1,149 @@ +package ogx + +import ( + "context" + "database/sql" + "fmt" + "reflect" + + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/schema" +) + +type hasManyModel struct { + *sliceTableModel + baseTable *schema.Table + rel *schema.Relation + + baseValues map[internal.MapKey][]reflect.Value + structKey []interface{} +} + +var _ TableModel = (*hasManyModel)(nil) + +func newHasManyModel(j *relationJoin) *hasManyModel { + baseTable := j.BaseModel.Table() + joinModel := j.JoinModel.(*sliceTableModel) + baseValues := baseValues(joinModel, j.Relation.BaseFields) + if len(baseValues) == 0 { + return nil + } + m := hasManyModel{ + sliceTableModel: joinModel, + baseTable: baseTable, + rel: j.Relation, + + baseValues: baseValues, + } + if !m.sliceOfPtr { + m.strct = reflect.New(m.table.Type).Elem() + } + return &m +} + +func (m *hasManyModel) ScanRows(ctx context.Context, rows *sql.Rows) (int, error) { + columns, err := rows.Columns() + if err != nil { + return 0, err + } + + m.columns = columns + dest := makeDest(m, len(columns)) + + var n int + + for rows.Next() { + if m.sliceOfPtr { + m.strct = reflect.New(m.table.Type).Elem() + } else { + m.strct.Set(m.table.ZeroValue) + } + m.structInited = false + + m.scanIndex = 0 + m.structKey = m.structKey[:0] + if err := rows.Scan(dest...); err != nil { + return 0, err + } + + if err := m.parkStruct(); err != nil { + return 0, err + } + + n++ + } + if err := rows.Err(); err != nil { + return 0, err + } + + return n, nil +} + +func (m *hasManyModel) Scan(src interface{}) error { + column := m.columns[m.scanIndex] + m.scanIndex++ + + field, err := m.table.Field(column) + if err != nil { + return err + } + + if err := field.ScanValue(m.strct, src); err != nil { + return err + } + + for _, f := range m.rel.JoinFields { + if f.Name == field.Name { + m.structKey = append(m.structKey, field.Value(m.strct).Interface()) + break + } + } + + return nil +} + +func (m *hasManyModel) parkStruct() error { + baseValues, ok := m.baseValues[internal.NewMapKey(m.structKey)] + if !ok { + return fmt.Errorf( + "ogx: has-many relation=%s does not have base %s with id=%q (check join conditions)", + m.rel.Field.GoName, m.baseTable, m.structKey) + } + + for i, v := range baseValues { + if !m.sliceOfPtr { + v.Set(reflect.Append(v, m.strct)) + continue + } + + if i == 0 { + v.Set(reflect.Append(v, m.strct.Addr())) + continue + } + + clone := reflect.New(m.strct.Type()).Elem() + clone.Set(m.strct) + v.Set(reflect.Append(v, clone.Addr())) + } + + return nil +} + +func baseValues(model TableModel, fields []*schema.Field) map[internal.MapKey][]reflect.Value { + fieldIndex := model.Relation().Field.Index + m := make(map[internal.MapKey][]reflect.Value) + key := make([]interface{}, 0, len(fields)) + walk(model.rootValue(), model.parentIndex(), func(v reflect.Value) { + key = modelKey(key[:0], v, fields) + mapKey := internal.NewMapKey(key) + m[mapKey] = append(m[mapKey], v.FieldByIndex(fieldIndex)) + }) + return m +} + +func modelKey(key []interface{}, strct reflect.Value, fields []*schema.Field) []interface{} { + for _, f := range fields { + key = append(key, f.Value(strct).Interface()) + } + return key +} diff --git a/ogx/model_table_m2m.go b/ogx/model_table_m2m.go new file mode 100644 index 00000000..f20d9694 --- /dev/null +++ b/ogx/model_table_m2m.go @@ -0,0 +1,138 @@ +package ogx + +import ( + "context" + "database/sql" + "fmt" + "reflect" + + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/schema" +) + +type m2mModel struct { + *sliceTableModel + baseTable *schema.Table + rel *schema.Relation + + baseValues map[internal.MapKey][]reflect.Value + structKey []interface{} +} + +var _ TableModel = (*m2mModel)(nil) + +func newM2MModel(j *relationJoin) *m2mModel { + baseTable := j.BaseModel.Table() + joinModel := j.JoinModel.(*sliceTableModel) + baseValues := baseValues(joinModel, baseTable.PKs) + if len(baseValues) == 0 { + return nil + } + m := &m2mModel{ + sliceTableModel: joinModel, + baseTable: baseTable, + rel: j.Relation, + + baseValues: baseValues, + } + if !m.sliceOfPtr { + m.strct = reflect.New(m.table.Type).Elem() + } + return m +} + +func (m *m2mModel) ScanRows(ctx context.Context, rows *sql.Rows) (int, error) { + columns, err := rows.Columns() + if err != nil { + return 0, err + } + + m.columns = columns + dest := makeDest(m, len(columns)) + + var n int + + for rows.Next() { + if m.sliceOfPtr { + m.strct = reflect.New(m.table.Type).Elem() + } else { + m.strct.Set(m.table.ZeroValue) + } + m.structInited = false + + m.scanIndex = 0 + m.structKey = m.structKey[:0] + if err := rows.Scan(dest...); err != nil { + return 0, err + } + + if err := m.parkStruct(); err != nil { + return 0, err + } + + n++ + } + if err := rows.Err(); err != nil { + return 0, err + } + + return n, nil +} + +func (m *m2mModel) Scan(src interface{}) error { + column := m.columns[m.scanIndex] + m.scanIndex++ + + field, ok := m.table.FieldMap[column] + if !ok { + return m.scanM2MColumn(column, src) + } + + if err := field.ScanValue(m.strct, src); err != nil { + return err + } + + for _, fk := range m.rel.M2MBaseFields { + if fk.Name == field.Name { + m.structKey = append(m.structKey, field.Value(m.strct).Interface()) + break + } + } + + return nil +} + +func (m *m2mModel) scanM2MColumn(column string, src interface{}) error { + for _, field := range m.rel.M2MBaseFields { + if field.Name == column { + dest := reflect.New(field.IndirectType).Elem() + if err := field.Scan(dest, src); err != nil { + return err + } + m.structKey = append(m.structKey, dest.Interface()) + break + } + } + + _, err := m.scanColumn(column, src) + return err +} + +func (m *m2mModel) parkStruct() error { + baseValues, ok := m.baseValues[internal.NewMapKey(m.structKey)] + if !ok { + return fmt.Errorf( + "ogx: m2m relation=%s does not have base %s with key=%q (check join conditions)", + m.rel.Field.GoName, m.baseTable, m.structKey) + } + + for _, v := range baseValues { + if m.sliceOfPtr { + v.Set(reflect.Append(v, m.strct.Addr())) + } else { + v.Set(reflect.Append(v, m.strct)) + } + } + + return nil +} diff --git a/ogx/model_table_slice.go b/ogx/model_table_slice.go new file mode 100644 index 00000000..eca4cc59 --- /dev/null +++ b/ogx/model_table_slice.go @@ -0,0 +1,122 @@ +package ogx + +import ( + "context" + "database/sql" + "reflect" + "time" + + "gitee.com/chentanyang/ogx/schema" +) + +type sliceTableModel struct { + structTableModel + + slice reflect.Value + sliceLen int + sliceOfPtr bool + nextElem func() reflect.Value +} + +var _ TableModel = (*sliceTableModel)(nil) + +func newSliceTableModel( + db *DB, dest interface{}, slice reflect.Value, elemType reflect.Type, +) *sliceTableModel { + m := &sliceTableModel{ + structTableModel: structTableModel{ + db: db, + table: db.Table(elemType), + dest: dest, + root: slice, + }, + + slice: slice, + sliceLen: slice.Len(), + nextElem: makeSliceNextElemFunc(slice), + } + m.init(slice.Type()) + return m +} + +func (m *sliceTableModel) init(sliceType reflect.Type) { + switch sliceType.Elem().Kind() { + case reflect.Ptr, reflect.Interface: + m.sliceOfPtr = true + } +} + +func (m *sliceTableModel) join(name string) *relationJoin { + return m._join(m.slice, name) +} + +func (m *sliceTableModel) ScanRows(ctx context.Context, rows *sql.Rows) (int, error) { + columns, err := rows.Columns() + if err != nil { + return 0, err + } + + m.columns = columns + dest := makeDest(m, len(columns)) + + if m.slice.IsValid() && m.slice.Len() > 0 { + m.slice.Set(m.slice.Slice(0, 0)) + } + + var n int + + for rows.Next() { + m.strct = m.nextElem() + m.structInited = false + + if err := m.scanRow(ctx, rows, dest); err != nil { + return 0, err + } + + n++ + } + if err := rows.Err(); err != nil { + return 0, err + } + + return n, nil +} + +var _ schema.BeforeAppendModelHook = (*sliceTableModel)(nil) + +func (m *sliceTableModel) BeforeAppendModel(ctx context.Context, query Query) error { + if !m.table.HasBeforeAppendModelHook() || !m.slice.IsValid() { + return nil + } + + sliceLen := m.slice.Len() + for i := 0; i < sliceLen; i++ { + strct := m.slice.Index(i) + if !m.sliceOfPtr { + strct = strct.Addr() + } + err := strct.Interface().(schema.BeforeAppendModelHook).BeforeAppendModel(ctx, query) + if err != nil { + return err + } + } + return nil +} + +// Inherit these hooks from structTableModel. +var ( + _ schema.BeforeScanRowHook = (*sliceTableModel)(nil) + _ schema.AfterScanRowHook = (*sliceTableModel)(nil) +) + +func (m *sliceTableModel) updateSoftDeleteField(tm time.Time) error { + sliceLen := m.slice.Len() + for i := 0; i < sliceLen; i++ { + strct := indirect(m.slice.Index(i)) + fv := m.table.SoftDeleteField.Value(strct) + if err := m.table.UpdateSoftDeleteField(fv, tm); err != nil { + return err + } + } + return nil +} diff --git a/ogx/model_table_struct.go b/ogx/model_table_struct.go new file mode 100644 index 00000000..f924cc18 --- /dev/null +++ b/ogx/model_table_struct.go @@ -0,0 +1,355 @@ +package ogx + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "strings" + "time" + + "gitee.com/chentanyang/ogx/schema" +) + +type structTableModel struct { + db *DB + table *schema.Table + + rel *schema.Relation + joins []relationJoin + + dest interface{} + root reflect.Value + index []int + + strct reflect.Value + structInited bool + structInitErr error + + columns []string + scanIndex int +} + +var _ TableModel = (*structTableModel)(nil) + +func newStructTableModel(db *DB, dest interface{}, table *schema.Table) *structTableModel { + return &structTableModel{ + db: db, + table: table, + dest: dest, + } +} + +func newStructTableModelValue(db *DB, dest interface{}, v reflect.Value) *structTableModel { + return &structTableModel{ + db: db, + table: db.Table(v.Type()), + dest: dest, + root: v, + strct: v, + } +} + +func (m *structTableModel) Value() interface{} { + return m.dest +} + +func (m *structTableModel) Table() *schema.Table { + return m.table +} + +func (m *structTableModel) Relation() *schema.Relation { + return m.rel +} + +func (m *structTableModel) initStruct() error { + if m.structInited { + return m.structInitErr + } + m.structInited = true + + switch m.strct.Kind() { + case reflect.Invalid: + m.structInitErr = errNilModel + return m.structInitErr + case reflect.Interface: + m.strct = m.strct.Elem() + } + + if m.strct.Kind() == reflect.Ptr { + if m.strct.IsNil() { + m.strct.Set(reflect.New(m.strct.Type().Elem())) + m.strct = m.strct.Elem() + } else { + m.strct = m.strct.Elem() + } + } + + m.mountJoins() + + return nil +} + +func (m *structTableModel) mountJoins() { + for i := range m.joins { + j := &m.joins[i] + switch j.Relation.Type { + case schema.HasOneRelation, schema.BelongsToRelation: + j.JoinModel.mount(m.strct) + } + } +} + +var _ schema.BeforeAppendModelHook = (*structTableModel)(nil) + +func (m *structTableModel) BeforeAppendModel(ctx context.Context, query Query) error { + if !m.table.HasBeforeAppendModelHook() || !m.strct.IsValid() { + return nil + } + return m.strct.Addr().Interface().(schema.BeforeAppendModelHook).BeforeAppendModel(ctx, query) +} + +var _ schema.BeforeScanRowHook = (*structTableModel)(nil) + +func (m *structTableModel) BeforeScanRow(ctx context.Context) error { + if m.table.HasBeforeScanRowHook() { + return m.strct.Addr().Interface().(schema.BeforeScanRowHook).BeforeScanRow(ctx) + } + return nil +} + +var _ schema.AfterScanRowHook = (*structTableModel)(nil) + +func (m *structTableModel) AfterScanRow(ctx context.Context) error { + if !m.structInited { + return nil + } + + if m.table.HasAfterScanRowHook() { + firstErr := m.strct.Addr().Interface().(schema.AfterScanRowHook).AfterScanRow(ctx) + + for _, j := range m.joins { + switch j.Relation.Type { + case schema.HasOneRelation, schema.BelongsToRelation: + if err := j.JoinModel.AfterScanRow(ctx); err != nil && firstErr == nil { + firstErr = err + } + } + } + + return firstErr + } + + return nil +} + +func (m *structTableModel) getJoin(name string) *relationJoin { + for i := range m.joins { + j := &m.joins[i] + if j.Relation.Field.Name == name || j.Relation.Field.GoName == name { + return j + } + } + return nil +} + +func (m *structTableModel) getJoins() []relationJoin { + return m.joins +} + +func (m *structTableModel) addJoin(j relationJoin) *relationJoin { + m.joins = append(m.joins, j) + return &m.joins[len(m.joins)-1] +} + +func (m *structTableModel) join(name string) *relationJoin { + return m._join(m.strct, name) +} + +func (m *structTableModel) _join(bind reflect.Value, name string) *relationJoin { + path := strings.Split(name, ".") + index := make([]int, 0, len(path)) + + currJoin := relationJoin{ + BaseModel: m, + JoinModel: m, + } + var lastJoin *relationJoin + + for _, name := range path { + relation, ok := currJoin.JoinModel.Table().Relations[name] + if !ok { + return nil + } + + currJoin.Relation = relation + index = append(index, relation.Field.Index...) + + if j := currJoin.JoinModel.getJoin(name); j != nil { + currJoin.BaseModel = j.BaseModel + currJoin.JoinModel = j.JoinModel + + lastJoin = j + } else { + model, err := newTableModelIndex(m.db, m.table, bind, index, relation) + if err != nil { + return nil + } + + currJoin.Parent = lastJoin + currJoin.BaseModel = currJoin.JoinModel + currJoin.JoinModel = model + + lastJoin = currJoin.BaseModel.addJoin(currJoin) + } + } + + return lastJoin +} + +func (m *structTableModel) rootValue() reflect.Value { + return m.root +} + +func (m *structTableModel) parentIndex() []int { + return m.index[:len(m.index)-len(m.rel.Field.Index)] +} + +func (m *structTableModel) mount(host reflect.Value) { + m.strct = host.FieldByIndex(m.rel.Field.Index) + m.structInited = false +} + +func (m *structTableModel) updateSoftDeleteField(tm time.Time) error { + if !m.strct.IsValid() { + return nil + } + fv := m.table.SoftDeleteField.Value(m.strct) + return m.table.UpdateSoftDeleteField(fv, tm) +} + +func (m *structTableModel) ScanRows(ctx context.Context, rows *sql.Rows) (int, error) { + if !rows.Next() { + return 0, rows.Err() + } + + var n int + + if err := m.ScanRow(ctx, rows); err != nil { + return 0, err + } + n++ + + // And discard the rest. This is especially important for SQLite3, which can return + // a row like it was inserted sucessfully and then return an actual error for the next row. + // See issues/100. + for rows.Next() { + n++ + } + if err := rows.Err(); err != nil { + return 0, err + } + + return n, nil +} + +func (m *structTableModel) ScanRow(ctx context.Context, rows *sql.Rows) error { + columns, err := rows.Columns() + if err != nil { + return err + } + + m.columns = columns + dest := makeDest(m, len(columns)) + + return m.scanRow(ctx, rows, dest) +} + +func (m *structTableModel) scanRow(ctx context.Context, rows *sql.Rows, dest []interface{}) error { + if err := m.BeforeScanRow(ctx); err != nil { + return err + } + + m.scanIndex = 0 + if err := rows.Scan(dest...); err != nil { + return err + } + + if err := m.AfterScanRow(ctx); err != nil { + return err + } + + return nil +} + +func (m *structTableModel) Scan(src interface{}) error { + column := m.columns[m.scanIndex] + m.scanIndex++ + + return m.ScanColumn(unquote(column), src) +} + +func (m *structTableModel) ScanColumn(column string, src interface{}) error { + if ok, err := m.scanColumn(column, src); ok { + return err + } + if column == "" || column[0] == '_' || m.db.flags.Has(discardUnknownColumns) { + return nil + } + return fmt.Errorf("ogx: %s does not have column %q", m.table.TypeName, column) +} + +func (m *structTableModel) scanColumn(column string, src interface{}) (bool, error) { + if src != nil { + if err := m.initStruct(); err != nil { + return true, err + } + } + + if field, ok := m.table.FieldMap[column]; ok { + if src == nil && m.isNil() { + return true, nil + } + return true, field.ScanValue(m.strct, src) + } + + if joinName, column := splitColumn(column); joinName != "" { + if join := m.getJoin(joinName); join != nil { + return true, join.JoinModel.ScanColumn(column, src) + } + + if m.table.ModelName == joinName { + return true, m.ScanColumn(column, src) + } + } + + return false, nil +} + +func (m *structTableModel) isNil() bool { + return m.strct.Kind() == reflect.Ptr && m.strct.IsNil() +} + +func (m *structTableModel) AppendNamedArg( + fmter schema.Formatter, b []byte, name string, +) ([]byte, bool) { + return m.table.AppendNamedArg(fmter, b, name, m.strct) +} + +// sqlite3 sometimes does not unquote columns. +func unquote(s string) string { + if s == "" { + return s + } + if s[0] == '"' && s[len(s)-1] == '"' { + return s[1 : len(s)-1] + } + return s +} + +func splitColumn(s string) (string, string) { + if i := strings.Index(s, "__"); i >= 0 { + return s[:i], s[i+2:] + } + return "", s +} diff --git a/ogx/ogx.go b/ogx/ogx.go new file mode 100644 index 00000000..55f9d2e6 --- /dev/null +++ b/ogx/ogx.go @@ -0,0 +1,79 @@ +package ogx + +import ( + "context" + + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/schema" +) + +type ( + Safe = schema.Safe + Ident = schema.Ident + + NullTime = schema.NullTime + BaseModel = schema.BaseModel + Query = schema.Query + + BeforeAppendModelHook = schema.BeforeAppendModelHook + + BeforeScanRowHook = schema.BeforeScanRowHook + AfterScanRowHook = schema.AfterScanRowHook +) + +type BeforeSelectHook interface { + BeforeSelect(ctx context.Context, query *SelectQuery) error +} + +type AfterSelectHook interface { + AfterSelect(ctx context.Context, query *SelectQuery) error +} + +type BeforeInsertHook interface { + BeforeInsert(ctx context.Context, query *InsertQuery) error +} + +type AfterInsertHook interface { + AfterInsert(ctx context.Context, query *InsertQuery) error +} + +type BeforeUpdateHook interface { + BeforeUpdate(ctx context.Context, query *UpdateQuery) error +} + +type AfterUpdateHook interface { + AfterUpdate(ctx context.Context, query *UpdateQuery) error +} + +type BeforeDeleteHook interface { + BeforeDelete(ctx context.Context, query *DeleteQuery) error +} + +type AfterDeleteHook interface { + AfterDelete(ctx context.Context, query *DeleteQuery) error +} + +type BeforeCreateTableHook interface { + BeforeCreateTable(ctx context.Context, query *CreateTableQuery) error +} + +type AfterCreateTableHook interface { + AfterCreateTable(ctx context.Context, query *CreateTableQuery) error +} + +type BeforeDropTableHook interface { + BeforeDropTable(ctx context.Context, query *DropTableQuery) error +} + +type AfterDropTableHook interface { + AfterDropTable(ctx context.Context, query *DropTableQuery) error +} + +// SetLogger overwriters default Ogx logger. +func SetLogger(logger internal.Logging) { + internal.Logger = logger +} + +func In(slice interface{}) schema.QueryAppender { + return schema.In(slice) +} diff --git a/ogx/query_base.go b/ogx/query_base.go new file mode 100644 index 00000000..45a0c1fe --- /dev/null +++ b/ogx/query_base.go @@ -0,0 +1,1337 @@ +package ogx + +import ( + "context" + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "time" + + "gitee.com/chentanyang/ogx/dialect/feature" + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/schema" +) + +const ( + forceDeleteFlag internal.Flag = 1 << iota + deletedFlag + allWithDeletedFlag +) + +type withQuery struct { + name string + query schema.QueryAppender +} + +// IConn is a common interface for *sql.DB, *sql.Conn, and *sql.Tx. +type IConn interface { + QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) + ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) + QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row +} + +var ( + _ IConn = (*sql.DB)(nil) + _ IConn = (*sql.Conn)(nil) + _ IConn = (*sql.Tx)(nil) + _ IConn = (*DB)(nil) + _ IConn = (*Conn)(nil) + _ IConn = (*Tx)(nil) +) + +// IDB is a common interface for *ogx.DB, ogx.Conn, and ogx.Tx. +type IDB interface { + IConn + Dialect() schema.Dialect + + NewValues(model interface{}) *ValuesQuery + NewSelect() *SelectQuery + NewInsert() *InsertQuery + NewUpdate() *UpdateQuery + NewDelete() *DeleteQuery + NewRaw(query string, args ...interface{}) *RawQuery + NewCreateTable() *CreateTableQuery + NewDropTable() *DropTableQuery + NewCreateIndex() *CreateIndexQuery + NewDropIndex() *DropIndexQuery + NewTruncateTable() *TruncateTableQuery + NewAddColumn() *AddColumnQuery + NewDropColumn() *DropColumnQuery + + BeginTx(ctx context.Context, opts *sql.TxOptions) (Tx, error) + RunInTx(ctx context.Context, opts *sql.TxOptions, f func(ctx context.Context, tx Tx) error) error +} + +var ( + _ IDB = (*DB)(nil) + _ IDB = (*Conn)(nil) + _ IDB = (*Tx)(nil) +) + +// QueryBuilder is used for common query methods +type QueryBuilder interface { + Query + Where(query string, args ...interface{}) QueryBuilder + WhereGroup(sep string, fn func(QueryBuilder) QueryBuilder) QueryBuilder + WhereOr(query string, args ...interface{}) QueryBuilder + WhereDeleted() QueryBuilder + WhereAllWithDeleted() QueryBuilder + WherePK(cols ...string) QueryBuilder + Unwrap() interface{} +} + +var ( + _ QueryBuilder = (*selectQueryBuilder)(nil) + _ QueryBuilder = (*updateQueryBuilder)(nil) + _ QueryBuilder = (*deleteQueryBuilder)(nil) +) + +type baseQuery struct { + db *DB + conn IConn + + model Model + err error + + tableModel TableModel + table *schema.Table + + with []withQuery + modelTableName schema.QueryWithArgs + tables []schema.QueryWithArgs + columns []schema.QueryWithArgs + + flags internal.Flag +} + +func (q *baseQuery) DB() *DB { + return q.db +} + +func (q *baseQuery) GetConn() IConn { + return q.conn +} + +func (q *baseQuery) GetModel() Model { + return q.model +} + +func (q *baseQuery) GetTableName() string { + if q.table != nil { + return q.table.Name + } + + for _, wq := range q.with { + if v, ok := wq.query.(Query); ok { + if model := v.GetModel(); model != nil { + return v.GetTableName() + } + } + } + + if q.modelTableName.Query != "" { + return q.modelTableName.Query + } + + if len(q.tables) > 0 { + b, _ := q.tables[0].AppendQuery(q.db.fmter, nil) + if len(b) < 64 { + return string(b) + } + } + + return "" +} + +func (q *baseQuery) setConn(db IConn) { + // Unwrap Ogx wrappers to not call query hooks twice. + switch db := db.(type) { + case *DB: + q.conn = db.DB + case Conn: + q.conn = db.Conn + case Tx: + q.conn = db.Tx + default: + q.conn = db + } +} + +func (q *baseQuery) setModel(modeli interface{}) { + model, err := newSingleModel(q.db, modeli) + if err != nil { + q.setErr(err) + return + } + + q.model = model + if tm, ok := model.(TableModel); ok { + q.tableModel = tm + q.table = tm.Table() + } +} + +func (q *baseQuery) setErr(err error) { + if q.err == nil { + q.err = err + } +} + +func (q *baseQuery) getModel(dest []interface{}) (Model, error) { + if len(dest) == 0 { + if q.model != nil { + return q.model, nil + } + return nil, errNilModel + } + return newModel(q.db, dest) +} + +func (q *baseQuery) beforeAppendModel(ctx context.Context, query Query) error { + if q.tableModel != nil { + return q.tableModel.BeforeAppendModel(ctx, query) + } + return nil +} + +func (q *baseQuery) hasFeature(feature feature.Feature) bool { + return q.db.features.Has(feature) +} + +//------------------------------------------------------------------------------ + +func (q *baseQuery) checkSoftDelete() error { + if q.table == nil { + return errors.New("ogx: can't use soft deletes without a table") + } + if q.table.SoftDeleteField == nil { + return fmt.Errorf("%s does not have a soft delete field", q.table) + } + if q.tableModel == nil { + return errors.New("ogx: can't use soft deletes without a table model") + } + return nil +} + +// Deleted adds `WHERE deleted_at IS NOT NULL` clause for soft deleted models. +func (q *baseQuery) whereDeleted() { + if err := q.checkSoftDelete(); err != nil { + q.setErr(err) + return + } + q.flags = q.flags.Set(deletedFlag) + q.flags = q.flags.Remove(allWithDeletedFlag) +} + +// AllWithDeleted changes query to return all rows including soft deleted ones. +func (q *baseQuery) whereAllWithDeleted() { + if err := q.checkSoftDelete(); err != nil { + q.setErr(err) + return + } + q.flags = q.flags.Set(allWithDeletedFlag).Remove(deletedFlag) +} + +func (q *baseQuery) isSoftDelete() bool { + if q.table != nil { + return q.table.SoftDeleteField != nil && + !q.flags.Has(allWithDeletedFlag) && + !q.flags.Has(forceDeleteFlag) + } + return false +} + +//------------------------------------------------------------------------------ + +func (q *baseQuery) addWith(name string, query schema.QueryAppender) { + q.with = append(q.with, withQuery{ + name: name, + query: query, + }) +} + +func (q *baseQuery) appendWith(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if len(q.with) == 0 { + return b, nil + } + + b = append(b, "WITH "...) + for i, with := range q.with { + if i > 0 { + b = append(b, ", "...) + } + + b, err = q.appendCTE(fmter, b, with) + if err != nil { + return nil, err + } + } + b = append(b, ' ') + return b, nil +} + +func (q *baseQuery) appendCTE( + fmter schema.Formatter, b []byte, cte withQuery, +) (_ []byte, err error) { + if !fmter.Dialect().Features().Has(feature.WithValues) { + if values, ok := cte.query.(*ValuesQuery); ok { + return q.appendSelectFromValues(fmter, b, cte, values) + } + } + + b = fmter.AppendIdent(b, cte.name) + + if q, ok := cte.query.(schema.ColumnsAppender); ok { + b = append(b, " ("...) + b, err = q.AppendColumns(fmter, b) + if err != nil { + return nil, err + } + b = append(b, ")"...) + } + + b = append(b, " AS ("...) + + b, err = cte.query.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + + b = append(b, ")"...) + return b, nil +} + +func (q *baseQuery) appendSelectFromValues( + fmter schema.Formatter, b []byte, cte withQuery, values *ValuesQuery, +) (_ []byte, err error) { + b = fmter.AppendIdent(b, cte.name) + b = append(b, " AS (SELECT * FROM ("...) + + b, err = cte.query.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + + b = append(b, ") AS t"...) + if q, ok := cte.query.(schema.ColumnsAppender); ok { + b = append(b, " ("...) + b, err = q.AppendColumns(fmter, b) + if err != nil { + return nil, err + } + b = append(b, ")"...) + } + b = append(b, ")"...) + + return b, nil +} + +//------------------------------------------------------------------------------ + +func (q *baseQuery) addTable(table schema.QueryWithArgs) { + q.tables = append(q.tables, table) +} + +func (q *baseQuery) addColumn(column schema.QueryWithArgs) { + q.columns = append(q.columns, column) +} + +func (q *baseQuery) excludeColumn(columns []string) { + if q.table == nil { + q.setErr(errNilModel) + return + } + + if q.columns == nil { + for _, f := range q.table.Fields { + q.columns = append(q.columns, schema.UnsafeIdent(f.Name)) + } + } + + if len(columns) == 1 && columns[0] == "*" { + q.columns = make([]schema.QueryWithArgs, 0) + return + } + + for _, column := range columns { + if !q._excludeColumn(column) { + q.setErr(fmt.Errorf("ogx: can't find column=%q", column)) + return + } + } +} + +func (q *baseQuery) _excludeColumn(column string) bool { + for i, col := range q.columns { + if col.Args == nil && col.Query == column { + q.columns = append(q.columns[:i], q.columns[i+1:]...) + return true + } + } + return false +} + +//------------------------------------------------------------------------------ + +func (q *baseQuery) modelHasTableName() bool { + if !q.modelTableName.IsZero() { + return q.modelTableName.Query != "" + } + return q.table != nil +} + +func (q *baseQuery) hasTables() bool { + return q.modelHasTableName() || len(q.tables) > 0 +} + +func (q *baseQuery) appendTables( + fmter schema.Formatter, b []byte, +) (_ []byte, err error) { + return q._appendTables(fmter, b, false) +} + +func (q *baseQuery) appendTablesWithAlias( + fmter schema.Formatter, b []byte, +) (_ []byte, err error) { + return q._appendTables(fmter, b, true) +} + +func (q *baseQuery) _appendTables( + fmter schema.Formatter, b []byte, withAlias bool, +) (_ []byte, err error) { + startLen := len(b) + + if q.modelHasTableName() { + if !q.modelTableName.IsZero() { + b, err = q.modelTableName.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } else { + b = fmter.AppendQuery(b, string(q.table.SQLNameForSelects)) + if withAlias && q.table.SQLAlias != q.table.SQLNameForSelects { + b = append(b, " AS "...) + b = append(b, q.table.SQLAlias...) + } + } + } + + for _, table := range q.tables { + if len(b) > startLen { + b = append(b, ", "...) + } + b, err = table.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + + return b, nil +} + +func (q *baseQuery) appendFirstTable(fmter schema.Formatter, b []byte) ([]byte, error) { + return q._appendFirstTable(fmter, b, false) +} + +func (q *baseQuery) appendFirstTableWithAlias( + fmter schema.Formatter, b []byte, +) ([]byte, error) { + return q._appendFirstTable(fmter, b, true) +} + +func (q *baseQuery) _appendFirstTable( + fmter schema.Formatter, b []byte, withAlias bool, +) ([]byte, error) { + if !q.modelTableName.IsZero() { + return q.modelTableName.AppendQuery(fmter, b) + } + + if q.table != nil { + b = fmter.AppendQuery(b, string(q.table.SQLName)) + if withAlias { + b = append(b, " AS "...) + b = append(b, q.table.SQLAlias...) + } + return b, nil + } + + if len(q.tables) > 0 { + return q.tables[0].AppendQuery(fmter, b) + } + + return nil, errors.New("ogx: query does not have a table") +} + +func (q *baseQuery) hasMultiTables() bool { + if q.modelHasTableName() { + return len(q.tables) >= 1 + } + return len(q.tables) >= 2 +} + +func (q *baseQuery) appendOtherTables(fmter schema.Formatter, b []byte) (_ []byte, err error) { + tables := q.tables + if !q.modelHasTableName() { + tables = tables[1:] + } + for i, table := range tables { + if i > 0 { + b = append(b, ", "...) + } + b, err = table.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + return b, nil +} + +//------------------------------------------------------------------------------ + +func (q *baseQuery) appendColumns(fmter schema.Formatter, b []byte) (_ []byte, err error) { + for i, f := range q.columns { + if i > 0 { + b = append(b, ", "...) + } + b, err = f.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + return b, nil +} + +func (q *baseQuery) getFields() ([]*schema.Field, error) { + if len(q.columns) == 0 { + if q.table == nil { + return nil, errNilModel + } + return q.table.Fields, nil + } + return q._getFields(false) +} + +func (q *baseQuery) getDataFields() ([]*schema.Field, error) { + if len(q.columns) == 0 { + if q.table == nil { + return nil, errNilModel + } + return q.table.DataFields, nil + } + return q._getFields(true) +} + +func (q *baseQuery) _getFields(omitPK bool) ([]*schema.Field, error) { + fields := make([]*schema.Field, 0, len(q.columns)) + for _, col := range q.columns { + if col.Args != nil { + continue + } + + field, err := q.table.Field(col.Query) + if err != nil { + return nil, err + } + + if omitPK && field.IsPK { + continue + } + + fields = append(fields, field) + } + return fields, nil +} + +func (q *baseQuery) scan( + ctx context.Context, + iquery Query, + query string, + model Model, + hasDest bool, +) (sql.Result, error) { + ctx, event := q.db.beforeQuery(ctx, iquery, query, nil, query, q.model) + + rows, err := q.conn.QueryContext(ctx, query) + if err != nil { + q.db.afterQuery(ctx, event, nil, err) + return nil, err + } + defer rows.Close() + + numRow, err := model.ScanRows(ctx, rows) + if err != nil { + q.db.afterQuery(ctx, event, nil, err) + return nil, err + } + + if numRow == 0 && hasDest && isSingleRowModel(model) { + err = sql.ErrNoRows + } + + res := driver.RowsAffected(numRow) + q.db.afterQuery(ctx, event, res, err) + + return res, err +} + +func (q *baseQuery) exec( + ctx context.Context, + iquery Query, + query string, +) (sql.Result, error) { + ctx, event := q.db.beforeQuery(ctx, iquery, query, nil, query, q.model) + res, err := q.conn.ExecContext(ctx, query) + q.db.afterQuery(ctx, event, nil, err) + return res, err +} + +//------------------------------------------------------------------------------ + +func (q *baseQuery) AppendNamedArg(fmter schema.Formatter, b []byte, name string) ([]byte, bool) { + if q.table == nil { + return b, false + } + + if m, ok := q.tableModel.(*structTableModel); ok { + if b, ok := m.AppendNamedArg(fmter, b, name); ok { + return b, ok + } + } + + switch name { + case "TableName": + b = fmter.AppendQuery(b, string(q.table.SQLName)) + return b, true + case "TableAlias": + b = fmter.AppendQuery(b, string(q.table.SQLAlias)) + return b, true + case "PKs": + b = appendColumns(b, "", q.table.PKs) + return b, true + case "TablePKs": + b = appendColumns(b, q.table.SQLAlias, q.table.PKs) + return b, true + case "Columns": + b = appendColumns(b, "", q.table.Fields) + return b, true + case "TableColumns": + b = appendColumns(b, q.table.SQLAlias, q.table.Fields) + return b, true + } + + return b, false +} + +//------------------------------------------------------------------------------ + +func (q *baseQuery) Dialect() schema.Dialect { + return q.db.Dialect() +} + +func (q *baseQuery) NewValues(model interface{}) *ValuesQuery { + return NewValuesQuery(q.db, model).Conn(q.conn) +} + +func (q *baseQuery) NewSelect() *SelectQuery { + return NewSelectQuery(q.db).Conn(q.conn) +} + +func (q *baseQuery) NewInsert() *InsertQuery { + return NewInsertQuery(q.db).Conn(q.conn) +} + +func (q *baseQuery) NewUpdate() *UpdateQuery { + return NewUpdateQuery(q.db).Conn(q.conn) +} + +func (q *baseQuery) NewDelete() *DeleteQuery { + return NewDeleteQuery(q.db).Conn(q.conn) +} + +func (q *baseQuery) NewRaw(query string, args ...interface{}) *RawQuery { + return NewRawQuery(q.db, query, args...).Conn(q.conn) +} + +func (q *baseQuery) NewCreateTable() *CreateTableQuery { + return NewCreateTableQuery(q.db).Conn(q.conn) +} + +func (q *baseQuery) NewDropTable() *DropTableQuery { + return NewDropTableQuery(q.db).Conn(q.conn) +} + +func (q *baseQuery) NewCreateIndex() *CreateIndexQuery { + return NewCreateIndexQuery(q.db).Conn(q.conn) +} + +func (q *baseQuery) NewDropIndex() *DropIndexQuery { + return NewDropIndexQuery(q.db).Conn(q.conn) +} + +func (q *baseQuery) NewTruncateTable() *TruncateTableQuery { + return NewTruncateTableQuery(q.db).Conn(q.conn) +} + +func (q *baseQuery) NewAddColumn() *AddColumnQuery { + return NewAddColumnQuery(q.db).Conn(q.conn) +} + +func (q *baseQuery) NewDropColumn() *DropColumnQuery { + return NewDropColumnQuery(q.db).Conn(q.conn) +} + +//------------------------------------------------------------------------------ + +func appendColumns(b []byte, table schema.Safe, fields []*schema.Field) []byte { + for i, f := range fields { + if i > 0 { + b = append(b, ", "...) + } + + if len(table) > 0 { + b = append(b, table...) + b = append(b, '.') + } + b = append(b, f.SQLName...) + } + return b +} + +func formatterWithModel(fmter schema.Formatter, model schema.NamedArgAppender) schema.Formatter { + if fmter.IsNop() { + return fmter + } + return fmter.WithArg(model) +} + +//------------------------------------------------------------------------------ + +type whereBaseQuery struct { + baseQuery + + where []schema.QueryWithSep + whereFields []*schema.Field +} + +func (q *whereBaseQuery) addWhere(where schema.QueryWithSep) { + q.where = append(q.where, where) +} + +func (q *whereBaseQuery) addWhereGroup(sep string, where []schema.QueryWithSep) { + if len(where) == 0 { + return + } + + q.addWhere(schema.SafeQueryWithSep("", nil, sep)) + q.addWhere(schema.SafeQueryWithSep("", nil, "(")) + + where[0].Sep = "" + q.where = append(q.where, where...) + + q.addWhere(schema.SafeQueryWithSep("", nil, ")")) +} + +func (q *whereBaseQuery) addWhereCols(cols []string) { + if q.table == nil { + err := fmt.Errorf("ogx: got %T, but WherePK requires a struct or slice-based model", q.model) + q.setErr(err) + return + } + if q.whereFields != nil { + err := errors.New("ogx: WherePK can only be called once") + q.setErr(err) + return + } + + if cols == nil { + if err := q.table.CheckPKs(); err != nil { + q.setErr(err) + return + } + q.whereFields = q.table.PKs + return + } + + q.whereFields = make([]*schema.Field, len(cols)) + for i, col := range cols { + field, err := q.table.Field(col) + if err != nil { + q.setErr(err) + return + } + q.whereFields[i] = field + } +} + +func (q *whereBaseQuery) mustAppendWhere( + fmter schema.Formatter, b []byte, withAlias bool, +) ([]byte, error) { + if len(q.where) == 0 && q.whereFields == nil { + err := errors.New("ogx: Update and Delete queries require at least one Where") + return nil, err + } + return q.appendWhere(fmter, b, withAlias) +} + +func (q *whereBaseQuery) appendWhere( + fmter schema.Formatter, b []byte, withAlias bool, +) (_ []byte, err error) { + if len(q.where) == 0 && q.whereFields == nil && !q.isSoftDelete() { + return b, nil + } + + b = append(b, " WHERE "...) + startLen := len(b) + + if len(q.where) > 0 { + b, err = appendWhere(fmter, b, q.where) + if err != nil { + return nil, err + } + } + + if q.isSoftDelete() { + if len(b) > startLen { + b = append(b, " AND "...) + } + + if withAlias { + b = append(b, q.tableModel.Table().SQLAlias...) + } else { + b = append(b, q.tableModel.Table().SQLName...) + } + b = append(b, '.') + + field := q.tableModel.Table().SoftDeleteField + b = append(b, field.SQLName...) + + if field.IsPtr || field.NullZero { + if q.flags.Has(deletedFlag) { + b = append(b, " IS NOT NULL"...) + } else { + b = append(b, " IS NULL"...) + } + } else { + if q.flags.Has(deletedFlag) { + b = append(b, " != "...) + } else { + b = append(b, " = "...) + } + b = fmter.Dialect().AppendTime(b, time.Time{}) + } + } + + if q.whereFields != nil { + if len(b) > startLen { + b = append(b, " AND "...) + } + b, err = q.appendWhereFields(fmter, b, q.whereFields, withAlias) + if err != nil { + return nil, err + } + } + + return b, nil +} + +func appendWhere( + fmter schema.Formatter, b []byte, where []schema.QueryWithSep, +) (_ []byte, err error) { + for i, where := range where { + if i > 0 { + b = append(b, where.Sep...) + } + + if where.Query == "" { + continue + } + + b = append(b, '(') + b, err = where.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + b = append(b, ')') + } + return b, nil +} + +func (q *whereBaseQuery) appendWhereFields( + fmter schema.Formatter, b []byte, fields []*schema.Field, withAlias bool, +) (_ []byte, err error) { + if q.table == nil { + err := fmt.Errorf("ogx: got %T, but WherePK requires struct or slice-based model", q.model) + return nil, err + } + + switch model := q.tableModel.(type) { + case *structTableModel: + return q.appendWhereStructFields(fmter, b, model, fields, withAlias) + case *sliceTableModel: + return q.appendWhereSliceFields(fmter, b, model, fields, withAlias) + default: + return nil, fmt.Errorf("ogx: WhereColumn does not support %T", q.tableModel) + } +} + +func (q *whereBaseQuery) appendWhereStructFields( + fmter schema.Formatter, + b []byte, + model *structTableModel, + fields []*schema.Field, + withAlias bool, +) (_ []byte, err error) { + if !model.strct.IsValid() { + return nil, errNilModel + } + + isTemplate := fmter.IsNop() + b = append(b, '(') + for i, f := range fields { + if i > 0 { + b = append(b, " AND "...) + } + if withAlias { + b = append(b, q.table.SQLAlias...) + b = append(b, '.') + } + b = append(b, f.SQLName...) + b = append(b, " = "...) + if isTemplate { + b = append(b, '?') + } else { + b = f.AppendValue(fmter, b, model.strct) + } + } + b = append(b, ')') + return b, nil +} + +func (q *whereBaseQuery) appendWhereSliceFields( + fmter schema.Formatter, + b []byte, + model *sliceTableModel, + fields []*schema.Field, + withAlias bool, +) (_ []byte, err error) { + if len(fields) > 1 { + b = append(b, '(') + } + if withAlias { + b = appendColumns(b, q.table.SQLAlias, fields) + } else { + b = appendColumns(b, "", fields) + } + if len(fields) > 1 { + b = append(b, ')') + } + + b = append(b, " IN ("...) + + isTemplate := fmter.IsNop() + slice := model.slice + sliceLen := slice.Len() + for i := 0; i < sliceLen; i++ { + if i > 0 { + if isTemplate { + break + } + b = append(b, ", "...) + } + + el := indirect(slice.Index(i)) + + if len(fields) > 1 { + b = append(b, '(') + } + for i, f := range fields { + if i > 0 { + b = append(b, ", "...) + } + if isTemplate { + b = append(b, '?') + } else { + b = f.AppendValue(fmter, b, el) + } + } + if len(fields) > 1 { + b = append(b, ')') + } + } + + b = append(b, ')') + + return b, nil +} + +//------------------------------------------------------------------------------ + +type returningQuery struct { + returning []schema.QueryWithArgs + returningFields []*schema.Field +} + +func (q *returningQuery) addReturning(ret schema.QueryWithArgs) { + q.returning = append(q.returning, ret) +} + +func (q *returningQuery) addReturningField(field *schema.Field) { + if len(q.returning) > 0 { + return + } + for _, f := range q.returningFields { + if f == field { + return + } + } + q.returningFields = append(q.returningFields, field) +} + +func (q *returningQuery) appendReturning( + fmter schema.Formatter, b []byte, +) (_ []byte, err error) { + return q._appendReturning(fmter, b, "") +} + +func (q *returningQuery) appendOutput( + fmter schema.Formatter, b []byte, +) (_ []byte, err error) { + return q._appendReturning(fmter, b, "INSERTED") +} + +func (q *returningQuery) _appendReturning( + fmter schema.Formatter, b []byte, table string, +) (_ []byte, err error) { + for i, f := range q.returning { + if i > 0 { + b = append(b, ", "...) + } + b, err = f.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + + if len(q.returning) > 0 { + return b, nil + } + + b = appendColumns(b, schema.Safe(table), q.returningFields) + return b, nil +} + +func (q *returningQuery) hasReturning() bool { + if len(q.returning) == 1 { + if ret := q.returning[0]; len(ret.Args) == 0 { + switch ret.Query { + case "", "null", "NULL": + return false + } + } + } + return len(q.returning) > 0 || len(q.returningFields) > 0 +} + +//------------------------------------------------------------------------------ + +type columnValue struct { + column string + value schema.QueryWithArgs +} + +type customValueQuery struct { + modelValues map[string]schema.QueryWithArgs + extraValues []columnValue +} + +func (q *customValueQuery) addValue( + table *schema.Table, column string, value string, args []interface{}, +) { + if _, ok := table.FieldMap[column]; ok { + if q.modelValues == nil { + q.modelValues = make(map[string]schema.QueryWithArgs) + } + q.modelValues[column] = schema.SafeQuery(value, args) + } else { + q.extraValues = append(q.extraValues, columnValue{ + column: column, + value: schema.SafeQuery(value, args), + }) + } +} + +//------------------------------------------------------------------------------ + +type setQuery struct { + set []schema.QueryWithArgs +} + +func (q *setQuery) addSet(set schema.QueryWithArgs) { + q.set = append(q.set, set) +} + +func (q setQuery) appendSet(fmter schema.Formatter, b []byte) (_ []byte, err error) { + for i, f := range q.set { + if i > 0 { + b = append(b, ", "...) + } + b, err = f.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + return b, nil +} + +//------------------------------------------------------------------------------ + +type cascadeQuery struct { + cascade bool + restrict bool +} + +func (q cascadeQuery) appendCascade(fmter schema.Formatter, b []byte) []byte { + if !fmter.HasFeature(feature.TableCascade) { + return b + } + if q.cascade { + b = append(b, " CASCADE"...) + } + if q.restrict { + b = append(b, " RESTRICT"...) + } + return b +} + +//------------------------------------------------------------------------------ + +type idxHintsQuery struct { + use *indexHints + ignore *indexHints + force *indexHints +} + +type indexHints struct { + names []schema.QueryWithArgs + forJoin []schema.QueryWithArgs + forOrderBy []schema.QueryWithArgs + forGroupBy []schema.QueryWithArgs +} + +func (ih *idxHintsQuery) lazyUse() *indexHints { + if ih.use == nil { + ih.use = new(indexHints) + } + return ih.use +} + +func (ih *idxHintsQuery) lazyIgnore() *indexHints { + if ih.ignore == nil { + ih.ignore = new(indexHints) + } + return ih.ignore +} + +func (ih *idxHintsQuery) lazyForce() *indexHints { + if ih.force == nil { + ih.force = new(indexHints) + } + return ih.force +} + +func (ih *idxHintsQuery) appendIndexes(hints []schema.QueryWithArgs, indexes ...string) []schema.QueryWithArgs { + for _, idx := range indexes { + hints = append(hints, schema.UnsafeIdent(idx)) + } + return hints +} + +func (ih *idxHintsQuery) addUseIndex(indexes ...string) { + if len(indexes) == 0 { + return + } + ih.lazyUse().names = ih.appendIndexes(ih.use.names, indexes...) +} + +func (ih *idxHintsQuery) addUseIndexForJoin(indexes ...string) { + if len(indexes) == 0 { + return + } + ih.lazyUse().forJoin = ih.appendIndexes(ih.use.forJoin, indexes...) +} + +func (ih *idxHintsQuery) addUseIndexForOrderBy(indexes ...string) { + if len(indexes) == 0 { + return + } + ih.lazyUse().forOrderBy = ih.appendIndexes(ih.use.forOrderBy, indexes...) +} + +func (ih *idxHintsQuery) addUseIndexForGroupBy(indexes ...string) { + if len(indexes) == 0 { + return + } + ih.lazyUse().forGroupBy = ih.appendIndexes(ih.use.forGroupBy, indexes...) +} + +func (ih *idxHintsQuery) addIgnoreIndex(indexes ...string) { + if len(indexes) == 0 { + return + } + ih.lazyIgnore().names = ih.appendIndexes(ih.ignore.names, indexes...) +} + +func (ih *idxHintsQuery) addIgnoreIndexForJoin(indexes ...string) { + if len(indexes) == 0 { + return + } + ih.lazyIgnore().forJoin = ih.appendIndexes(ih.ignore.forJoin, indexes...) +} + +func (ih *idxHintsQuery) addIgnoreIndexForOrderBy(indexes ...string) { + if len(indexes) == 0 { + return + } + ih.lazyIgnore().forOrderBy = ih.appendIndexes(ih.ignore.forOrderBy, indexes...) +} + +func (ih *idxHintsQuery) addIgnoreIndexForGroupBy(indexes ...string) { + if len(indexes) == 0 { + return + } + ih.lazyIgnore().forGroupBy = ih.appendIndexes(ih.ignore.forGroupBy, indexes...) +} + +func (ih *idxHintsQuery) addForceIndex(indexes ...string) { + if len(indexes) == 0 { + return + } + ih.lazyForce().names = ih.appendIndexes(ih.force.names, indexes...) +} + +func (ih *idxHintsQuery) addForceIndexForJoin(indexes ...string) { + if len(indexes) == 0 { + return + } + ih.lazyForce().forJoin = ih.appendIndexes(ih.force.forJoin, indexes...) +} + +func (ih *idxHintsQuery) addForceIndexForOrderBy(indexes ...string) { + if len(indexes) == 0 { + return + } + ih.lazyForce().forOrderBy = ih.appendIndexes(ih.force.forOrderBy, indexes...) +} + +func (ih *idxHintsQuery) addForceIndexForGroupBy(indexes ...string) { + if len(indexes) == 0 { + return + } + ih.lazyForce().forGroupBy = ih.appendIndexes(ih.force.forGroupBy, indexes...) +} + +func (ih *idxHintsQuery) appendIndexHints( + fmter schema.Formatter, b []byte, +) ([]byte, error) { + type IdxHint struct { + Name string + Values []schema.QueryWithArgs + } + + var hints []IdxHint + if ih.use != nil { + hints = append(hints, []IdxHint{ + { + Name: "USE INDEX", + Values: ih.use.names, + }, + { + Name: "USE INDEX FOR JOIN", + Values: ih.use.forJoin, + }, + { + Name: "USE INDEX FOR ORDER BY", + Values: ih.use.forOrderBy, + }, + { + Name: "USE INDEX FOR GROUP BY", + Values: ih.use.forGroupBy, + }, + }...) + } + + if ih.ignore != nil { + hints = append(hints, []IdxHint{ + { + Name: "IGNORE INDEX", + Values: ih.ignore.names, + }, + { + Name: "IGNORE INDEX FOR JOIN", + Values: ih.ignore.forJoin, + }, + { + Name: "IGNORE INDEX FOR ORDER BY", + Values: ih.ignore.forOrderBy, + }, + { + Name: "IGNORE INDEX FOR GROUP BY", + Values: ih.ignore.forGroupBy, + }, + }...) + } + + if ih.force != nil { + hints = append(hints, []IdxHint{ + { + Name: "FORCE INDEX", + Values: ih.force.names, + }, + { + Name: "FORCE INDEX FOR JOIN", + Values: ih.force.forJoin, + }, + { + Name: "FORCE INDEX FOR ORDER BY", + Values: ih.force.forOrderBy, + }, + { + Name: "FORCE INDEX FOR GROUP BY", + Values: ih.force.forGroupBy, + }, + }...) + } + + var err error + for _, h := range hints { + b, err = ih.bufIndexHint(h.Name, h.Values, fmter, b) + if err != nil { + return nil, err + } + } + return b, nil +} + +func (ih *idxHintsQuery) bufIndexHint( + name string, + hints []schema.QueryWithArgs, + fmter schema.Formatter, b []byte, +) ([]byte, error) { + var err error + if len(hints) == 0 { + return b, nil + } + b = append(b, fmt.Sprintf(" %s (", name)...) + for i, f := range hints { + if i > 0 { + b = append(b, ", "...) + } + b, err = f.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + b = append(b, ")"...) + return b, nil +} diff --git a/ogx/query_column_add.go b/ogx/query_column_add.go new file mode 100644 index 00000000..4206ecbe --- /dev/null +++ b/ogx/query_column_add.go @@ -0,0 +1,116 @@ +package ogx + +import ( + "context" + "database/sql" + "fmt" + + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/schema" +) + +type AddColumnQuery struct { + baseQuery + + ifNotExists bool +} + +var _ Query = (*AddColumnQuery)(nil) + +func NewAddColumnQuery(db *DB) *AddColumnQuery { + q := &AddColumnQuery{ + baseQuery: baseQuery{ + db: db, + conn: db.DB, + }, + } + return q +} + +func (q *AddColumnQuery) Conn(db IConn) *AddColumnQuery { + q.setConn(db) + return q +} + +func (q *AddColumnQuery) Model(model interface{}) *AddColumnQuery { + q.setModel(model) + return q +} + +//------------------------------------------------------------------------------ + +func (q *AddColumnQuery) Table(tables ...string) *AddColumnQuery { + for _, table := range tables { + q.addTable(schema.UnsafeIdent(table)) + } + return q +} + +func (q *AddColumnQuery) TableExpr(query string, args ...interface{}) *AddColumnQuery { + q.addTable(schema.SafeQuery(query, args)) + return q +} + +func (q *AddColumnQuery) ModelTableExpr(query string, args ...interface{}) *AddColumnQuery { + q.modelTableName = schema.SafeQuery(query, args) + return q +} + +//------------------------------------------------------------------------------ + +func (q *AddColumnQuery) ColumnExpr(query string, args ...interface{}) *AddColumnQuery { + q.addColumn(schema.SafeQuery(query, args)) + return q +} + +func (q *AddColumnQuery) IfNotExists() *AddColumnQuery { + q.ifNotExists = true + return q +} + +//------------------------------------------------------------------------------ + +func (q *AddColumnQuery) Operation() string { + return "ADD COLUMN" +} + +func (q *AddColumnQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if q.err != nil { + return nil, q.err + } + if len(q.columns) != 1 { + return nil, fmt.Errorf("ogx: AddColumnQuery requires exactly one column") + } + + b = append(b, "ALTER TABLE "...) + + b, err = q.appendFirstTable(fmter, b) + if err != nil { + return nil, err + } + + b = append(b, " ADD "...) + + if q.ifNotExists { + b = append(b, "IF NOT EXISTS "...) + } + + b, err = q.columns[0].AppendQuery(fmter, b) + if err != nil { + return nil, err + } + + return b, nil +} + +//------------------------------------------------------------------------------ + +func (q *AddColumnQuery) Exec(ctx context.Context, dest ...interface{}) (sql.Result, error) { + queryBytes, err := q.AppendQuery(q.db.fmter, q.db.makeQueryBytes()) + if err != nil { + return nil, err + } + + query := internal.String(queryBytes) + return q.exec(ctx, q, query) +} diff --git a/ogx/query_column_drop.go b/ogx/query_column_drop.go new file mode 100644 index 00000000..fc1d29aa --- /dev/null +++ b/ogx/query_column_drop.go @@ -0,0 +1,118 @@ +package ogx + +import ( + "context" + "database/sql" + "fmt" + + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/schema" +) + +type DropColumnQuery struct { + baseQuery +} + +var _ Query = (*DropColumnQuery)(nil) + +func NewDropColumnQuery(db *DB) *DropColumnQuery { + q := &DropColumnQuery{ + baseQuery: baseQuery{ + db: db, + conn: db.DB, + }, + } + return q +} + +func (q *DropColumnQuery) Conn(db IConn) *DropColumnQuery { + q.setConn(db) + return q +} + +func (q *DropColumnQuery) Model(model interface{}) *DropColumnQuery { + q.setModel(model) + return q +} + +//------------------------------------------------------------------------------ + +func (q *DropColumnQuery) Table(tables ...string) *DropColumnQuery { + for _, table := range tables { + q.addTable(schema.UnsafeIdent(table)) + } + return q +} + +func (q *DropColumnQuery) TableExpr(query string, args ...interface{}) *DropColumnQuery { + q.addTable(schema.SafeQuery(query, args)) + return q +} + +func (q *DropColumnQuery) ModelTableExpr(query string, args ...interface{}) *DropColumnQuery { + q.modelTableName = schema.SafeQuery(query, args) + return q +} + +//------------------------------------------------------------------------------ + +func (q *DropColumnQuery) Column(columns ...string) *DropColumnQuery { + for _, column := range columns { + q.addColumn(schema.UnsafeIdent(column)) + } + return q +} + +func (q *DropColumnQuery) ColumnExpr(query string, args ...interface{}) *DropColumnQuery { + q.addColumn(schema.SafeQuery(query, args)) + return q +} + +//------------------------------------------------------------------------------ + +func (q *DropColumnQuery) Operation() string { + return "DROP COLUMN" +} + +func (q *DropColumnQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if q.err != nil { + return nil, q.err + } + if len(q.columns) != 1 { + return nil, fmt.Errorf("ogx: DropColumnQuery requires exactly one column") + } + + b = append(b, "ALTER TABLE "...) + + b, err = q.appendFirstTable(fmter, b) + if err != nil { + return nil, err + } + + b = append(b, " DROP COLUMN "...) + + b, err = q.columns[0].AppendQuery(fmter, b) + if err != nil { + return nil, err + } + + return b, nil +} + +//------------------------------------------------------------------------------ + +func (q *DropColumnQuery) Exec(ctx context.Context, dest ...interface{}) (sql.Result, error) { + queryBytes, err := q.AppendQuery(q.db.fmter, q.db.makeQueryBytes()) + if err != nil { + return nil, err + } + + query := internal.String(queryBytes) + + res, err := q.exec(ctx, q, query) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/ogx/query_delete.go b/ogx/query_delete.go new file mode 100644 index 00000000..2af67f75 --- /dev/null +++ b/ogx/query_delete.go @@ -0,0 +1,349 @@ +package ogx + +import ( + "context" + "database/sql" + "time" + + "gitee.com/chentanyang/ogx/dialect/feature" + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/schema" +) + +type DeleteQuery struct { + whereBaseQuery + returningQuery +} + +var _ Query = (*DeleteQuery)(nil) + +func NewDeleteQuery(db *DB) *DeleteQuery { + q := &DeleteQuery{ + whereBaseQuery: whereBaseQuery{ + baseQuery: baseQuery{ + db: db, + conn: db.DB, + }, + }, + } + return q +} + +func (q *DeleteQuery) Conn(db IConn) *DeleteQuery { + q.setConn(db) + return q +} + +func (q *DeleteQuery) Model(model interface{}) *DeleteQuery { + q.setModel(model) + return q +} + +// Apply calls the fn passing the DeleteQuery as an argument. +func (q *DeleteQuery) Apply(fn func(*DeleteQuery) *DeleteQuery) *DeleteQuery { + return fn(q) +} + +func (q *DeleteQuery) With(name string, query schema.QueryAppender) *DeleteQuery { + q.addWith(name, query) + return q +} + +func (q *DeleteQuery) Table(tables ...string) *DeleteQuery { + for _, table := range tables { + q.addTable(schema.UnsafeIdent(table)) + } + return q +} + +func (q *DeleteQuery) TableExpr(query string, args ...interface{}) *DeleteQuery { + q.addTable(schema.SafeQuery(query, args)) + return q +} + +func (q *DeleteQuery) ModelTableExpr(query string, args ...interface{}) *DeleteQuery { + q.modelTableName = schema.SafeQuery(query, args) + return q +} + +//------------------------------------------------------------------------------ + +func (q *DeleteQuery) WherePK(cols ...string) *DeleteQuery { + q.addWhereCols(cols) + return q +} + +func (q *DeleteQuery) Where(query string, args ...interface{}) *DeleteQuery { + q.addWhere(schema.SafeQueryWithSep(query, args, " AND ")) + return q +} + +func (q *DeleteQuery) WhereOr(query string, args ...interface{}) *DeleteQuery { + q.addWhere(schema.SafeQueryWithSep(query, args, " OR ")) + return q +} + +func (q *DeleteQuery) WhereGroup(sep string, fn func(*DeleteQuery) *DeleteQuery) *DeleteQuery { + saved := q.where + q.where = nil + + q = fn(q) + + where := q.where + q.where = saved + + q.addWhereGroup(sep, where) + + return q +} + +func (q *DeleteQuery) WhereDeleted() *DeleteQuery { + q.whereDeleted() + return q +} + +func (q *DeleteQuery) WhereAllWithDeleted() *DeleteQuery { + q.whereAllWithDeleted() + return q +} + +func (q *DeleteQuery) ForceDelete() *DeleteQuery { + q.flags = q.flags.Set(forceDeleteFlag) + return q +} + +//------------------------------------------------------------------------------ + +// Returning adds a RETURNING clause to the query. +// +// To suppress the auto-generated RETURNING clause, use `Returning("NULL")`. +func (q *DeleteQuery) Returning(query string, args ...interface{}) *DeleteQuery { + q.addReturning(schema.SafeQuery(query, args)) + return q +} + +func (q *DeleteQuery) hasReturning() bool { + if !q.db.features.Has(feature.Returning) { + return false + } + return q.returningQuery.hasReturning() +} + +//------------------------------------------------------------------------------ + +func (q *DeleteQuery) Operation() string { + return "DELETE" +} + +func (q *DeleteQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if q.err != nil { + return nil, q.err + } + + fmter = formatterWithModel(fmter, q) + + if q.isSoftDelete() { + now := time.Now() + + if err := q.tableModel.updateSoftDeleteField(now); err != nil { + return nil, err + } + + upd := &UpdateQuery{ + whereBaseQuery: q.whereBaseQuery, + returningQuery: q.returningQuery, + } + upd.Set(q.softDeleteSet(fmter, now)) + + return upd.AppendQuery(fmter, b) + } + + q = q.WhereDeleted() + withAlias := q.db.features.Has(feature.DeleteTableAlias) + + b, err = q.appendWith(fmter, b) + if err != nil { + return nil, err + } + + b = append(b, "DELETE FROM "...) + + if withAlias { + b, err = q.appendFirstTableWithAlias(fmter, b) + } else { + b, err = q.appendFirstTable(fmter, b) + } + if err != nil { + return nil, err + } + + if q.hasMultiTables() { + b = append(b, " USING "...) + b, err = q.appendOtherTables(fmter, b) + if err != nil { + return nil, err + } + } + + b, err = q.mustAppendWhere(fmter, b, withAlias) + if err != nil { + return nil, err + } + + if q.hasFeature(feature.Returning) && q.hasReturning() { + b = append(b, " RETURNING "...) + b, err = q.appendReturning(fmter, b) + if err != nil { + return nil, err + } + } + + return b, nil +} + +func (q *DeleteQuery) isSoftDelete() bool { + return q.tableModel != nil && q.table.SoftDeleteField != nil && !q.flags.Has(forceDeleteFlag) +} + +func (q *DeleteQuery) softDeleteSet(fmter schema.Formatter, tm time.Time) string { + b := make([]byte, 0, 32) + if fmter.HasFeature(feature.UpdateMultiTable) { + b = append(b, q.table.SQLAlias...) + b = append(b, '.') + } + b = append(b, q.table.SoftDeleteField.SQLName...) + b = append(b, " = "...) + b = schema.Append(fmter, b, tm) + return internal.String(b) +} + +//------------------------------------------------------------------------------ + +func (q *DeleteQuery) Exec(ctx context.Context, dest ...interface{}) (sql.Result, error) { + if q.err != nil { + return nil, q.err + } + + if q.table != nil { + if err := q.beforeDeleteHook(ctx); err != nil { + return nil, err + } + } + + if err := q.beforeAppendModel(ctx, q); err != nil { + return nil, err + } + + queryBytes, err := q.AppendQuery(q.db.fmter, q.db.makeQueryBytes()) + if err != nil { + return nil, err + } + + query := internal.String(queryBytes) + + var res sql.Result + + if hasDest := len(dest) > 0; hasDest || q.hasReturning() { + model, err := q.getModel(dest) + if err != nil { + return nil, err + } + + res, err = q.scan(ctx, q, query, model, hasDest) + if err != nil { + return nil, err + } + } else { + res, err = q.exec(ctx, q, query) + if err != nil { + return nil, err + } + } + + if q.table != nil { + if err := q.afterDeleteHook(ctx); err != nil { + return nil, err + } + } + + return res, nil +} + +func (q *DeleteQuery) beforeDeleteHook(ctx context.Context) error { + if hook, ok := q.table.ZeroIface.(BeforeDeleteHook); ok { + if err := hook.BeforeDelete(ctx, q); err != nil { + return err + } + } + return nil +} + +func (q *DeleteQuery) afterDeleteHook(ctx context.Context) error { + if hook, ok := q.table.ZeroIface.(AfterDeleteHook); ok { + if err := hook.AfterDelete(ctx, q); err != nil { + return err + } + } + return nil +} + +func (q *DeleteQuery) String() string { + buf, err := q.AppendQuery(q.db.Formatter(), nil) + if err != nil { + panic(err) + } + + return string(buf) +} + +//------------------------------------------------------------------------------ + +func (q *DeleteQuery) QueryBuilder() QueryBuilder { + return &deleteQueryBuilder{q} +} + +func (q *DeleteQuery) ApplyQueryBuilder(fn func(QueryBuilder) QueryBuilder) *DeleteQuery { + return fn(q.QueryBuilder()).Unwrap().(*DeleteQuery) +} + +type deleteQueryBuilder struct { + *DeleteQuery +} + +func (q *deleteQueryBuilder) WhereGroup( + sep string, fn func(QueryBuilder) QueryBuilder, +) QueryBuilder { + q.DeleteQuery = q.DeleteQuery.WhereGroup(sep, func(qs *DeleteQuery) *DeleteQuery { + return fn(q).(*deleteQueryBuilder).DeleteQuery + }) + return q +} + +func (q *deleteQueryBuilder) Where(query string, args ...interface{}) QueryBuilder { + q.DeleteQuery.Where(query, args...) + return q +} + +func (q *deleteQueryBuilder) WhereOr(query string, args ...interface{}) QueryBuilder { + q.DeleteQuery.WhereOr(query, args...) + return q +} + +func (q *deleteQueryBuilder) WhereDeleted() QueryBuilder { + q.DeleteQuery.WhereDeleted() + return q +} + +func (q *deleteQueryBuilder) WhereAllWithDeleted() QueryBuilder { + q.DeleteQuery.WhereAllWithDeleted() + return q +} + +func (q *deleteQueryBuilder) WherePK(cols ...string) QueryBuilder { + q.DeleteQuery.WherePK(cols...) + return q +} + +func (q *deleteQueryBuilder) Unwrap() interface{} { + return q.DeleteQuery +} diff --git a/ogx/query_index_create.go b/ogx/query_index_create.go new file mode 100644 index 00000000..baa2569a --- /dev/null +++ b/ogx/query_index_create.go @@ -0,0 +1,249 @@ +package ogx + +import ( + "context" + "database/sql" + + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/schema" +) + +type CreateIndexQuery struct { + whereBaseQuery + + unique bool + fulltext bool + spatial bool + concurrently bool + ifNotExists bool + + index schema.QueryWithArgs + using schema.QueryWithArgs + include []schema.QueryWithArgs +} + +var _ Query = (*CreateIndexQuery)(nil) + +func NewCreateIndexQuery(db *DB) *CreateIndexQuery { + q := &CreateIndexQuery{ + whereBaseQuery: whereBaseQuery{ + baseQuery: baseQuery{ + db: db, + conn: db.DB, + }, + }, + } + return q +} + +func (q *CreateIndexQuery) Conn(db IConn) *CreateIndexQuery { + q.setConn(db) + return q +} + +func (q *CreateIndexQuery) Model(model interface{}) *CreateIndexQuery { + q.setModel(model) + return q +} + +func (q *CreateIndexQuery) Unique() *CreateIndexQuery { + q.unique = true + return q +} + +func (q *CreateIndexQuery) Concurrently() *CreateIndexQuery { + q.concurrently = true + return q +} + +func (q *CreateIndexQuery) IfNotExists() *CreateIndexQuery { + q.ifNotExists = true + return q +} + +//------------------------------------------------------------------------------ + +func (q *CreateIndexQuery) Index(query string) *CreateIndexQuery { + q.index = schema.UnsafeIdent(query) + return q +} + +func (q *CreateIndexQuery) IndexExpr(query string, args ...interface{}) *CreateIndexQuery { + q.index = schema.SafeQuery(query, args) + return q +} + +//------------------------------------------------------------------------------ + +func (q *CreateIndexQuery) Table(tables ...string) *CreateIndexQuery { + for _, table := range tables { + q.addTable(schema.UnsafeIdent(table)) + } + return q +} + +func (q *CreateIndexQuery) TableExpr(query string, args ...interface{}) *CreateIndexQuery { + q.addTable(schema.SafeQuery(query, args)) + return q +} + +func (q *CreateIndexQuery) ModelTableExpr(query string, args ...interface{}) *CreateIndexQuery { + q.modelTableName = schema.SafeQuery(query, args) + return q +} + +func (q *CreateIndexQuery) Using(query string, args ...interface{}) *CreateIndexQuery { + q.using = schema.SafeQuery(query, args) + return q +} + +//------------------------------------------------------------------------------ + +func (q *CreateIndexQuery) Column(columns ...string) *CreateIndexQuery { + for _, column := range columns { + q.addColumn(schema.UnsafeIdent(column)) + } + return q +} + +func (q *CreateIndexQuery) ColumnExpr(query string, args ...interface{}) *CreateIndexQuery { + q.addColumn(schema.SafeQuery(query, args)) + return q +} + +func (q *CreateIndexQuery) ExcludeColumn(columns ...string) *CreateIndexQuery { + q.excludeColumn(columns) + return q +} + +//------------------------------------------------------------------------------ + +func (q *CreateIndexQuery) Include(columns ...string) *CreateIndexQuery { + for _, column := range columns { + q.include = append(q.include, schema.UnsafeIdent(column)) + } + return q +} + +func (q *CreateIndexQuery) IncludeExpr(query string, args ...interface{}) *CreateIndexQuery { + q.include = append(q.include, schema.SafeQuery(query, args)) + return q +} + +//------------------------------------------------------------------------------ + +func (q *CreateIndexQuery) Where(query string, args ...interface{}) *CreateIndexQuery { + q.addWhere(schema.SafeQueryWithSep(query, args, " AND ")) + return q +} + +func (q *CreateIndexQuery) WhereOr(query string, args ...interface{}) *CreateIndexQuery { + q.addWhere(schema.SafeQueryWithSep(query, args, " OR ")) + return q +} + +//------------------------------------------------------------------------------ + +func (q *CreateIndexQuery) Operation() string { + return "CREATE INDEX" +} + +func (q *CreateIndexQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if q.err != nil { + return nil, q.err + } + + b = append(b, "CREATE "...) + + if q.unique { + b = append(b, "UNIQUE "...) + } + if q.fulltext { + b = append(b, "FULLTEXT "...) + } + if q.spatial { + b = append(b, "SPATIAL "...) + } + + b = append(b, "INDEX "...) + + if q.concurrently { + b = append(b, "CONCURRENTLY "...) + } + if q.ifNotExists { + b = append(b, "IF NOT EXISTS "...) + } + + b, err = q.index.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + + b = append(b, " ON "...) + b, err = q.appendFirstTable(fmter, b) + if err != nil { + return nil, err + } + + if !q.using.IsZero() { + b = append(b, " USING "...) + b, err = q.using.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + + b = append(b, " ("...) + for i, col := range q.columns { + if i > 0 { + b = append(b, ", "...) + } + b, err = col.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + b = append(b, ')') + + if len(q.include) > 0 { + b = append(b, " INCLUDE ("...) + for i, col := range q.include { + if i > 0 { + b = append(b, ", "...) + } + b, err = col.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + b = append(b, ')') + } + + if len(q.where) > 0 { + b = append(b, " WHERE "...) + b, err = appendWhere(fmter, b, q.where) + if err != nil { + return nil, err + } + } + + return b, nil +} + +//------------------------------------------------------------------------------ + +func (q *CreateIndexQuery) Exec(ctx context.Context, dest ...interface{}) (sql.Result, error) { + queryBytes, err := q.AppendQuery(q.db.fmter, q.db.makeQueryBytes()) + if err != nil { + return nil, err + } + + query := internal.String(queryBytes) + + res, err := q.exec(ctx, q, query) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/ogx/query_index_drop.go b/ogx/query_index_drop.go new file mode 100644 index 00000000..4cf214a6 --- /dev/null +++ b/ogx/query_index_drop.go @@ -0,0 +1,116 @@ +package ogx + +import ( + "context" + "database/sql" + + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/schema" +) + +type DropIndexQuery struct { + baseQuery + cascadeQuery + + concurrently bool + ifExists bool + + index schema.QueryWithArgs +} + +var _ Query = (*DropIndexQuery)(nil) + +func NewDropIndexQuery(db *DB) *DropIndexQuery { + q := &DropIndexQuery{ + baseQuery: baseQuery{ + db: db, + conn: db.DB, + }, + } + return q +} + +func (q *DropIndexQuery) Conn(db IConn) *DropIndexQuery { + q.setConn(db) + return q +} + +func (q *DropIndexQuery) Model(model interface{}) *DropIndexQuery { + q.setModel(model) + return q +} + +//------------------------------------------------------------------------------ + +func (q *DropIndexQuery) Concurrently() *DropIndexQuery { + q.concurrently = true + return q +} + +func (q *DropIndexQuery) IfExists() *DropIndexQuery { + q.ifExists = true + return q +} + +func (q *DropIndexQuery) Cascade() *DropIndexQuery { + q.cascade = true + return q +} + +func (q *DropIndexQuery) Restrict() *DropIndexQuery { + q.restrict = true + return q +} + +func (q *DropIndexQuery) Index(query string, args ...interface{}) *DropIndexQuery { + q.index = schema.SafeQuery(query, args) + return q +} + +//------------------------------------------------------------------------------ + +func (q *DropIndexQuery) Operation() string { + return "DROP INDEX" +} + +func (q *DropIndexQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if q.err != nil { + return nil, q.err + } + + b = append(b, "DROP INDEX "...) + + if q.concurrently { + b = append(b, "CONCURRENTLY "...) + } + if q.ifExists { + b = append(b, "IF EXISTS "...) + } + + b, err = q.index.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + + b = q.appendCascade(fmter, b) + + return b, nil +} + +//------------------------------------------------------------------------------ + +func (q *DropIndexQuery) Exec(ctx context.Context, dest ...interface{}) (sql.Result, error) { + queryBytes, err := q.AppendQuery(q.db.fmter, q.db.makeQueryBytes()) + if err != nil { + return nil, err + } + + query := internal.String(queryBytes) + + res, err := q.exec(ctx, q, query) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/ogx/query_insert.go b/ogx/query_insert.go new file mode 100644 index 00000000..9e94ad09 --- /dev/null +++ b/ogx/query_insert.go @@ -0,0 +1,652 @@ +package ogx + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "strings" + + "gitee.com/chentanyang/ogx/dialect/feature" + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/schema" +) + +type InsertQuery struct { + whereBaseQuery + returningQuery + customValueQuery + + on schema.QueryWithArgs + setQuery + + ignore bool + replace bool +} + +var _ Query = (*InsertQuery)(nil) + +func NewInsertQuery(db *DB) *InsertQuery { + q := &InsertQuery{ + whereBaseQuery: whereBaseQuery{ + baseQuery: baseQuery{ + db: db, + conn: db.DB, + }, + }, + } + return q +} + +func (q *InsertQuery) Conn(db IConn) *InsertQuery { + q.setConn(db) + return q +} + +func (q *InsertQuery) Model(model interface{}) *InsertQuery { + q.setModel(model) + return q +} + +// Apply calls the fn passing the SelectQuery as an argument. +func (q *InsertQuery) Apply(fn func(*InsertQuery) *InsertQuery) *InsertQuery { + return fn(q) +} + +func (q *InsertQuery) With(name string, query schema.QueryAppender) *InsertQuery { + q.addWith(name, query) + return q +} + +//------------------------------------------------------------------------------ + +func (q *InsertQuery) Table(tables ...string) *InsertQuery { + for _, table := range tables { + q.addTable(schema.UnsafeIdent(table)) + } + return q +} + +func (q *InsertQuery) TableExpr(query string, args ...interface{}) *InsertQuery { + q.addTable(schema.SafeQuery(query, args)) + return q +} + +func (q *InsertQuery) ModelTableExpr(query string, args ...interface{}) *InsertQuery { + q.modelTableName = schema.SafeQuery(query, args) + return q +} + +//------------------------------------------------------------------------------ + +func (q *InsertQuery) Column(columns ...string) *InsertQuery { + for _, column := range columns { + q.addColumn(schema.UnsafeIdent(column)) + } + return q +} + +func (q *InsertQuery) ColumnExpr(query string, args ...interface{}) *InsertQuery { + q.addColumn(schema.SafeQuery(query, args)) + return q +} + +func (q *InsertQuery) ExcludeColumn(columns ...string) *InsertQuery { + q.excludeColumn(columns) + return q +} + +// Value overwrites model value for the column. +func (q *InsertQuery) Value(column string, expr string, args ...interface{}) *InsertQuery { + if q.table == nil { + q.err = errNilModel + return q + } + q.addValue(q.table, column, expr, args) + return q +} + +func (q *InsertQuery) Where(query string, args ...interface{}) *InsertQuery { + q.addWhere(schema.SafeQueryWithSep(query, args, " AND ")) + return q +} + +func (q *InsertQuery) WhereOr(query string, args ...interface{}) *InsertQuery { + q.addWhere(schema.SafeQueryWithSep(query, args, " OR ")) + return q +} + +//------------------------------------------------------------------------------ + +// Returning adds a RETURNING clause to the query. +// +// To suppress the auto-generated RETURNING clause, use `Returning("")`. +func (q *InsertQuery) Returning(query string, args ...interface{}) *InsertQuery { + q.addReturning(schema.SafeQuery(query, args)) + return q +} + +//------------------------------------------------------------------------------ + +// Ignore generates different queries depending on the DBMS: +// - On MySQL, it generates `INSERT IGNORE INTO`. +// - On PostgreSQL, it generates `ON CONFLICT DO NOTHING`. +func (q *InsertQuery) Ignore() *InsertQuery { + if q.db.fmter.HasFeature(feature.InsertOnConflict) { + return q.On("CONFLICT DO NOTHING") + } + if q.db.fmter.HasFeature(feature.InsertIgnore) { + q.ignore = true + } + return q +} + +// Replaces generates a `REPLACE INTO` query (MySQL and MariaDB). +func (q *InsertQuery) Replace() *InsertQuery { + q.replace = true + return q +} + +//------------------------------------------------------------------------------ + +func (q *InsertQuery) Operation() string { + return "INSERT" +} + +func (q *InsertQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if q.err != nil { + return nil, q.err + } + + fmter = formatterWithModel(fmter, q) + + b, err = q.appendWith(fmter, b) + if err != nil { + return nil, err + } + + if q.replace { + b = append(b, "REPLACE "...) + } else { + b = append(b, "INSERT "...) + if q.ignore { + b = append(b, "IGNORE "...) + } + } + b = append(b, "INTO "...) + + if q.db.features.Has(feature.InsertTableAlias) && !q.on.IsZero() { + b, err = q.appendFirstTableWithAlias(fmter, b) + } else { + b, err = q.appendFirstTable(fmter, b) + } + if err != nil { + return nil, err + } + + b, err = q.appendColumnsValues(fmter, b) + if err != nil { + return nil, err + } + + b, err = q.appendOn(fmter, b) + if err != nil { + return nil, err + } + + if q.hasFeature(feature.InsertReturning) && q.hasReturning() { + b = append(b, " RETURNING "...) + b, err = q.appendReturning(fmter, b) + if err != nil { + return nil, err + } + } + + return b, nil +} + +func (q *InsertQuery) appendColumnsValues( + fmter schema.Formatter, b []byte, +) (_ []byte, err error) { + if q.hasMultiTables() { + if q.columns != nil { + b = append(b, " ("...) + b, err = q.appendColumns(fmter, b) + if err != nil { + return nil, err + } + b = append(b, ")"...) + } + + if q.hasFeature(feature.Output) && q.hasReturning() { + b = append(b, " OUTPUT "...) + b, err = q.appendOutput(fmter, b) + if err != nil { + return nil, err + } + } + + b = append(b, " SELECT "...) + + if q.columns != nil { + b, err = q.appendColumns(fmter, b) + if err != nil { + return nil, err + } + } else { + b = append(b, "*"...) + } + + b = append(b, " FROM "...) + b, err = q.appendOtherTables(fmter, b) + if err != nil { + return nil, err + } + + return b, nil + } + + if m, ok := q.model.(*mapModel); ok { + return m.appendColumnsValues(fmter, b), nil + } + if _, ok := q.model.(*mapSliceModel); ok { + return nil, fmt.Errorf("Insert(*[]map[string]interface{}) is not supported") + } + + if q.model == nil { + return nil, errNilModel + } + + // Build fields to populate RETURNING clause. + fields, err := q.getFields() + if err != nil { + return nil, err + } + + b = append(b, " ("...) + b = q.appendFields(fmter, b, fields) + b = append(b, ")"...) + + if q.hasFeature(feature.Output) && q.hasReturning() { + b = append(b, " OUTPUT "...) + b, err = q.appendOutput(fmter, b) + if err != nil { + return nil, err + } + } + + b = append(b, " VALUES ("...) + + switch model := q.tableModel.(type) { + case *structTableModel: + b, err = q.appendStructValues(fmter, b, fields, model.strct) + if err != nil { + return nil, err + } + case *sliceTableModel: + b, err = q.appendSliceValues(fmter, b, fields, model.slice) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("ogx: Insert does not support %T", q.tableModel) + } + + b = append(b, ')') + + return b, nil +} + +func (q *InsertQuery) appendStructValues( + fmter schema.Formatter, b []byte, fields []*schema.Field, strct reflect.Value, +) (_ []byte, err error) { + isTemplate := fmter.IsNop() + for i, f := range fields { + if i > 0 { + b = append(b, ", "...) + } + + app, ok := q.modelValues[f.Name] + if ok { + b, err = app.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + q.addReturningField(f) + continue + } + + switch { + case isTemplate: + b = append(b, '?') + case (f.IsPtr && f.HasNilValue(strct)) || (f.NullZero && f.HasZeroValue(strct)): + if q.db.features.Has(feature.DefaultPlaceholder) { + b = append(b, "DEFAULT"...) + } else if f.SQLDefault != "" { + b = append(b, f.SQLDefault...) + } else { + b = append(b, "NULL"...) + } + q.addReturningField(f) + default: + b = f.AppendValue(fmter, b, strct) + } + } + + for i, v := range q.extraValues { + if i > 0 || len(fields) > 0 { + b = append(b, ", "...) + } + + b, err = v.value.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + + return b, nil +} + +func (q *InsertQuery) appendSliceValues( + fmter schema.Formatter, b []byte, fields []*schema.Field, slice reflect.Value, +) (_ []byte, err error) { + if fmter.IsNop() { + return q.appendStructValues(fmter, b, fields, reflect.Value{}) + } + + sliceLen := slice.Len() + for i := 0; i < sliceLen; i++ { + if i > 0 { + b = append(b, "), ("...) + } + el := indirect(slice.Index(i)) + b, err = q.appendStructValues(fmter, b, fields, el) + if err != nil { + return nil, err + } + } + + return b, nil +} + +func (q *InsertQuery) getFields() ([]*schema.Field, error) { + hasIdentity := q.db.features.Has(feature.Identity) + + if len(q.columns) > 0 || q.db.features.Has(feature.DefaultPlaceholder) && !hasIdentity { + return q.baseQuery.getFields() + } + + var strct reflect.Value + + switch model := q.tableModel.(type) { + case *structTableModel: + strct = model.strct + case *sliceTableModel: + if model.sliceLen == 0 { + return nil, fmt.Errorf("ogx: Insert(empty %T)", model.slice.Type()) + } + strct = indirect(model.slice.Index(0)) + default: + return nil, errNilModel + } + + fields := make([]*schema.Field, 0, len(q.table.Fields)) + + for _, f := range q.table.Fields { + if hasIdentity && f.AutoIncrement { + q.addReturningField(f) + continue + } + if f.NotNull && f.SQLDefault == "" { + if (f.IsPtr && f.HasNilValue(strct)) || (f.NullZero && f.HasZeroValue(strct)) { + q.addReturningField(f) + continue + } + } + fields = append(fields, f) + } + + return fields, nil +} + +func (q *InsertQuery) appendFields( + fmter schema.Formatter, b []byte, fields []*schema.Field, +) []byte { + b = appendColumns(b, "", fields) + for i, v := range q.extraValues { + if i > 0 || len(fields) > 0 { + b = append(b, ", "...) + } + b = fmter.AppendIdent(b, v.column) + } + return b +} + +//------------------------------------------------------------------------------ + +func (q *InsertQuery) On(s string, args ...interface{}) *InsertQuery { + q.on = schema.SafeQuery(s, args) + return q +} + +func (q *InsertQuery) Set(query string, args ...interface{}) *InsertQuery { + q.addSet(schema.SafeQuery(query, args)) + return q +} + +func (q *InsertQuery) appendOn(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if q.on.IsZero() { + return b, nil + } + + b = append(b, " ON "...) + b, err = q.on.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + + if len(q.set) > 0 { + if fmter.HasFeature(feature.InsertOnDuplicateKey) { + b = append(b, ' ') + } else { + b = append(b, " SET "...) + } + + b, err = q.appendSet(fmter, b) + if err != nil { + return nil, err + } + } else if q.onConflictDoUpdate() { + fields, err := q.getDataFields() + if err != nil { + return nil, err + } + + if len(fields) == 0 { + fields = q.tableModel.Table().DataFields + } + + b = q.appendSetExcluded(b, fields) + } else if q.onDuplicateKeyUpdate() { + fields, err := q.getDataFields() + if err != nil { + return nil, err + } + + if len(fields) == 0 { + fields = q.tableModel.Table().DataFields + } + + b = q.appendSetValues(b, fields) + } + + if len(q.where) > 0 { + b = append(b, " WHERE "...) + + b, err = appendWhere(fmter, b, q.where) + if err != nil { + return nil, err + } + } + + return b, nil +} + +func (q *InsertQuery) onConflictDoUpdate() bool { + return strings.HasSuffix(strings.ToUpper(q.on.Query), " DO UPDATE") +} + +func (q *InsertQuery) onDuplicateKeyUpdate() bool { + return strings.ToUpper(q.on.Query) == "DUPLICATE KEY UPDATE" +} + +func (q *InsertQuery) appendSetExcluded(b []byte, fields []*schema.Field) []byte { + b = append(b, " SET "...) + for i, f := range fields { + if i > 0 { + b = append(b, ", "...) + } + b = append(b, f.SQLName...) + b = append(b, " = EXCLUDED."...) + b = append(b, f.SQLName...) + } + return b +} + +func (q *InsertQuery) appendSetValues(b []byte, fields []*schema.Field) []byte { + b = append(b, " "...) + for i, f := range fields { + if i > 0 { + b = append(b, ", "...) + } + b = append(b, f.SQLName...) + b = append(b, " = VALUES("...) + b = append(b, f.SQLName...) + b = append(b, ")"...) + } + return b +} + +//------------------------------------------------------------------------------ + +func (q *InsertQuery) Exec(ctx context.Context, dest ...interface{}) (sql.Result, error) { + if q.table != nil { + if err := q.beforeInsertHook(ctx); err != nil { + return nil, err + } + } + + if q.err != nil { + return nil, q.err + } + if err := q.beforeAppendModel(ctx, q); err != nil { + return nil, err + } + + queryBytes, err := q.AppendQuery(q.db.fmter, q.db.makeQueryBytes()) + if err != nil { + return nil, err + } + + query := internal.String(queryBytes) + var res sql.Result + + if hasDest := len(dest) > 0; hasDest || + (q.hasReturning() && q.hasFeature(feature.InsertReturning|feature.Output)) { + model, err := q.getModel(dest) + if err != nil { + return nil, err + } + + res, err = q.scan(ctx, q, query, model, hasDest) + if err != nil { + return nil, err + } + } else { + res, err = q.exec(ctx, q, query) + if err != nil { + return nil, err + } + + if err := q.tryLastInsertID(res, dest); err != nil { + return nil, err + } + } + + if q.table != nil { + if err := q.afterInsertHook(ctx); err != nil { + return nil, err + } + } + + return res, nil +} + +func (q *InsertQuery) beforeInsertHook(ctx context.Context) error { + if hook, ok := q.table.ZeroIface.(BeforeInsertHook); ok { + if err := hook.BeforeInsert(ctx, q); err != nil { + return err + } + } + return nil +} + +func (q *InsertQuery) afterInsertHook(ctx context.Context) error { + if hook, ok := q.table.ZeroIface.(AfterInsertHook); ok { + if err := hook.AfterInsert(ctx, q); err != nil { + return err + } + } + return nil +} + +func (q *InsertQuery) tryLastInsertID(res sql.Result, dest []interface{}) error { + if q.db.features.Has(feature.Returning) || + q.db.features.Has(feature.Output) || + q.table == nil || + len(q.table.PKs) != 1 || + !q.table.PKs[0].AutoIncrement { + return nil + } + + id, err := res.LastInsertId() + if err != nil { + return err + } + if id == 0 { + return nil + } + + model, err := q.getModel(dest) + if err != nil { + return err + } + + pk := q.table.PKs[0] + switch model := model.(type) { + case *structTableModel: + if err := pk.ScanValue(model.strct, id); err != nil { + return err + } + case *sliceTableModel: + sliceLen := model.slice.Len() + for i := 0; i < sliceLen; i++ { + strct := indirect(model.slice.Index(i)) + if err := pk.ScanValue(strct, id); err != nil { + return err + } + id++ + } + } + + return nil +} + +func (q *InsertQuery) String() string { + buf, err := q.AppendQuery(q.db.Formatter(), nil) + if err != nil { + panic(err) + } + + return string(buf) +} diff --git a/ogx/query_raw.go b/ogx/query_raw.go new file mode 100644 index 00000000..76169c98 --- /dev/null +++ b/ogx/query_raw.go @@ -0,0 +1,65 @@ +package ogx + +import ( + "context" + + "gitee.com/chentanyang/ogx/schema" +) + +type RawQuery struct { + baseQuery + + query string + args []interface{} +} + +// Deprecated: Use NewRaw instead. When add it to IDB, it conflicts with the sql.Conn#Raw +func (db *DB) Raw(query string, args ...interface{}) *RawQuery { + return &RawQuery{ + baseQuery: baseQuery{ + db: db, + conn: db.DB, + }, + query: query, + args: args, + } +} + +func NewRawQuery(db *DB, query string, args ...interface{}) *RawQuery { + return &RawQuery{ + baseQuery: baseQuery{ + db: db, + conn: db.DB, + }, + query: query, + args: args, + } +} + +func (q *RawQuery) Conn(db IConn) *RawQuery { + q.setConn(db) + return q +} + +func (q *RawQuery) Scan(ctx context.Context, dest ...interface{}) error { + if q.err != nil { + return q.err + } + + model, err := q.getModel(dest) + if err != nil { + return err + } + + query := q.db.format(q.query, q.args) + _, err = q.scan(ctx, q, query, model, true) + return err +} + +func (q *RawQuery) AppendQuery(fmter schema.Formatter, b []byte) ([]byte, error) { + return fmter.AppendQuery(b, q.query, q.args...), nil +} + +func (q *RawQuery) Operation() string { + return "SELECT" +} diff --git a/ogx/query_select.go b/ogx/query_select.go new file mode 100644 index 00000000..60d8a0c7 --- /dev/null +++ b/ogx/query_select.go @@ -0,0 +1,1202 @@ +package ogx + +import ( + "bytes" + "context" + "database/sql" + "errors" + "fmt" + "strconv" + "strings" + "sync" + + "gitee.com/chentanyang/ogx/dialect" + + "gitee.com/chentanyang/ogx/dialect/feature" + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/schema" +) + +type union struct { + expr string + query *SelectQuery +} + +type SelectQuery struct { + whereBaseQuery + idxHintsQuery + + distinctOn []schema.QueryWithArgs + joins []joinQuery + group []schema.QueryWithArgs + having []schema.QueryWithArgs + order []schema.QueryWithArgs + limit int32 + offset int32 + selFor schema.QueryWithArgs + + union []union +} + +var _ Query = (*SelectQuery)(nil) + +func NewSelectQuery(db *DB) *SelectQuery { + return &SelectQuery{ + whereBaseQuery: whereBaseQuery{ + baseQuery: baseQuery{ + db: db, + conn: db.DB, + }, + }, + } +} + +func (q *SelectQuery) Conn(db IConn) *SelectQuery { + q.setConn(db) + return q +} + +func (q *SelectQuery) Model(model interface{}) *SelectQuery { + q.setModel(model) + return q +} + +// Apply calls the fn passing the SelectQuery as an argument. +func (q *SelectQuery) Apply(fn func(*SelectQuery) *SelectQuery) *SelectQuery { + return fn(q) +} + +func (q *SelectQuery) With(name string, query schema.QueryAppender) *SelectQuery { + q.addWith(name, query) + return q +} + +func (q *SelectQuery) Distinct() *SelectQuery { + q.distinctOn = make([]schema.QueryWithArgs, 0) + return q +} + +func (q *SelectQuery) DistinctOn(query string, args ...interface{}) *SelectQuery { + q.distinctOn = append(q.distinctOn, schema.SafeQuery(query, args)) + return q +} + +//------------------------------------------------------------------------------ + +func (q *SelectQuery) Table(tables ...string) *SelectQuery { + for _, table := range tables { + q.addTable(schema.UnsafeIdent(table)) + } + return q +} + +func (q *SelectQuery) TableExpr(query string, args ...interface{}) *SelectQuery { + q.addTable(schema.SafeQuery(query, args)) + return q +} + +func (q *SelectQuery) ModelTableExpr(query string, args ...interface{}) *SelectQuery { + q.modelTableName = schema.SafeQuery(query, args) + return q +} + +//------------------------------------------------------------------------------ + +func (q *SelectQuery) Column(columns ...string) *SelectQuery { + for _, column := range columns { + q.addColumn(schema.UnsafeIdent(column)) + } + return q +} + +func (q *SelectQuery) ColumnExpr(query string, args ...interface{}) *SelectQuery { + q.addColumn(schema.SafeQuery(query, args)) + return q +} + +func (q *SelectQuery) ExcludeColumn(columns ...string) *SelectQuery { + q.excludeColumn(columns) + return q +} + +//------------------------------------------------------------------------------ + +func (q *SelectQuery) WherePK(cols ...string) *SelectQuery { + q.addWhereCols(cols) + return q +} + +func (q *SelectQuery) Where(query string, args ...interface{}) *SelectQuery { + q.addWhere(schema.SafeQueryWithSep(query, args, " AND ")) + return q +} + +func (q *SelectQuery) WhereOr(query string, args ...interface{}) *SelectQuery { + q.addWhere(schema.SafeQueryWithSep(query, args, " OR ")) + return q +} + +func (q *SelectQuery) WhereGroup(sep string, fn func(*SelectQuery) *SelectQuery) *SelectQuery { + saved := q.where + q.where = nil + + q = fn(q) + + where := q.where + q.where = saved + + q.addWhereGroup(sep, where) + + return q +} + +func (q *SelectQuery) WhereDeleted() *SelectQuery { + q.whereDeleted() + return q +} + +func (q *SelectQuery) WhereAllWithDeleted() *SelectQuery { + q.whereAllWithDeleted() + return q +} + +//------------------------------------------------------------------------------ + +func (q *SelectQuery) UseIndex(indexes ...string) *SelectQuery { + if q.db.dialect.Name() == dialect.MySQL { + q.addUseIndex(indexes...) + } + return q +} + +func (q *SelectQuery) UseIndexForJoin(indexes ...string) *SelectQuery { + if q.db.dialect.Name() == dialect.MySQL { + q.addUseIndexForJoin(indexes...) + } + return q +} + +func (q *SelectQuery) UseIndexForOrderBy(indexes ...string) *SelectQuery { + if q.db.dialect.Name() == dialect.MySQL { + q.addUseIndexForOrderBy(indexes...) + } + return q +} + +func (q *SelectQuery) UseIndexForGroupBy(indexes ...string) *SelectQuery { + if q.db.dialect.Name() == dialect.MySQL { + q.addUseIndexForGroupBy(indexes...) + } + return q +} + +func (q *SelectQuery) IgnoreIndex(indexes ...string) *SelectQuery { + if q.db.dialect.Name() == dialect.MySQL { + q.addIgnoreIndex(indexes...) + } + return q +} + +func (q *SelectQuery) IgnoreIndexForJoin(indexes ...string) *SelectQuery { + if q.db.dialect.Name() == dialect.MySQL { + q.addIgnoreIndexForJoin(indexes...) + } + return q +} + +func (q *SelectQuery) IgnoreIndexForOrderBy(indexes ...string) *SelectQuery { + if q.db.dialect.Name() == dialect.MySQL { + q.addIgnoreIndexForOrderBy(indexes...) + } + return q +} + +func (q *SelectQuery) IgnoreIndexForGroupBy(indexes ...string) *SelectQuery { + if q.db.dialect.Name() == dialect.MySQL { + q.addIgnoreIndexForGroupBy(indexes...) + } + return q +} + +func (q *SelectQuery) ForceIndex(indexes ...string) *SelectQuery { + if q.db.dialect.Name() == dialect.MySQL { + q.addForceIndex(indexes...) + } + return q +} + +func (q *SelectQuery) ForceIndexForJoin(indexes ...string) *SelectQuery { + if q.db.dialect.Name() == dialect.MySQL { + q.addForceIndexForJoin(indexes...) + } + return q +} + +func (q *SelectQuery) ForceIndexForOrderBy(indexes ...string) *SelectQuery { + if q.db.dialect.Name() == dialect.MySQL { + q.addForceIndexForOrderBy(indexes...) + } + return q +} + +func (q *SelectQuery) ForceIndexForGroupBy(indexes ...string) *SelectQuery { + if q.db.dialect.Name() == dialect.MySQL { + q.addForceIndexForGroupBy(indexes...) + } + return q +} + +//------------------------------------------------------------------------------ + +func (q *SelectQuery) Group(columns ...string) *SelectQuery { + for _, column := range columns { + q.group = append(q.group, schema.UnsafeIdent(column)) + } + return q +} + +func (q *SelectQuery) GroupExpr(group string, args ...interface{}) *SelectQuery { + q.group = append(q.group, schema.SafeQuery(group, args)) + return q +} + +func (q *SelectQuery) Having(having string, args ...interface{}) *SelectQuery { + q.having = append(q.having, schema.SafeQuery(having, args)) + return q +} + +func (q *SelectQuery) Order(orders ...string) *SelectQuery { + for _, order := range orders { + if order == "" { + continue + } + + index := strings.IndexByte(order, ' ') + if index == -1 { + q.order = append(q.order, schema.UnsafeIdent(order)) + continue + } + + field := order[:index] + sort := order[index+1:] + + switch strings.ToUpper(sort) { + case "ASC", "DESC", "ASC NULLS FIRST", "DESC NULLS FIRST", + "ASC NULLS LAST", "DESC NULLS LAST": + q.order = append(q.order, schema.SafeQuery("? ?", []interface{}{ + Ident(field), + Safe(sort), + })) + default: + q.order = append(q.order, schema.UnsafeIdent(order)) + } + } + return q +} + +func (q *SelectQuery) OrderExpr(query string, args ...interface{}) *SelectQuery { + q.order = append(q.order, schema.SafeQuery(query, args)) + return q +} + +func (q *SelectQuery) Limit(n int) *SelectQuery { + q.limit = int32(n) + return q +} + +func (q *SelectQuery) Offset(n int) *SelectQuery { + q.offset = int32(n) + return q +} + +func (q *SelectQuery) For(s string, args ...interface{}) *SelectQuery { + q.selFor = schema.SafeQuery(s, args) + return q +} + +//------------------------------------------------------------------------------ + +func (q *SelectQuery) Union(other *SelectQuery) *SelectQuery { + return q.addUnion(" UNION ", other) +} + +func (q *SelectQuery) UnionAll(other *SelectQuery) *SelectQuery { + return q.addUnion(" UNION ALL ", other) +} + +func (q *SelectQuery) Intersect(other *SelectQuery) *SelectQuery { + return q.addUnion(" INTERSECT ", other) +} + +func (q *SelectQuery) IntersectAll(other *SelectQuery) *SelectQuery { + return q.addUnion(" INTERSECT ALL ", other) +} + +func (q *SelectQuery) Except(other *SelectQuery) *SelectQuery { + return q.addUnion(" EXCEPT ", other) +} + +func (q *SelectQuery) ExceptAll(other *SelectQuery) *SelectQuery { + return q.addUnion(" EXCEPT ALL ", other) +} + +func (q *SelectQuery) addUnion(expr string, other *SelectQuery) *SelectQuery { + q.union = append(q.union, union{ + expr: expr, + query: other, + }) + return q +} + +//------------------------------------------------------------------------------ + +func (q *SelectQuery) Join(join string, args ...interface{}) *SelectQuery { + q.joins = append(q.joins, joinQuery{ + join: schema.SafeQuery(join, args), + }) + return q +} + +func (q *SelectQuery) JoinOn(cond string, args ...interface{}) *SelectQuery { + return q.joinOn(cond, args, " AND ") +} + +func (q *SelectQuery) JoinOnOr(cond string, args ...interface{}) *SelectQuery { + return q.joinOn(cond, args, " OR ") +} + +func (q *SelectQuery) joinOn(cond string, args []interface{}, sep string) *SelectQuery { + if len(q.joins) == 0 { + q.err = errors.New("ogx: query has no joins") + return q + } + j := &q.joins[len(q.joins)-1] + j.on = append(j.on, schema.SafeQueryWithSep(cond, args, sep)) + return q +} + +//------------------------------------------------------------------------------ + +// Relation adds a relation to the query. +func (q *SelectQuery) Relation(name string, apply ...func(*SelectQuery) *SelectQuery) *SelectQuery { + if len(apply) > 1 { + panic("only one apply function is supported") + } + + if q.tableModel == nil { + q.setErr(errNilModel) + return q + } + + join := q.tableModel.join(name) + if join == nil { + q.setErr(fmt.Errorf("%s does not have relation=%q", q.table, name)) + return q + } + + var apply1, apply2 func(*SelectQuery) *SelectQuery + + if len(join.Relation.Condition) > 0 { + apply1 = func(q *SelectQuery) *SelectQuery { + for _, opt := range join.Relation.Condition { + q.addWhere(schema.SafeQueryWithSep(opt, nil, " AND ")) + } + + return q + } + } + + if len(apply) == 1 { + apply2 = apply[0] + } + + join.apply = func(q *SelectQuery) *SelectQuery { + if apply1 != nil { + q = apply1(q) + } + if apply2 != nil { + q = apply2(q) + } + + return q + } + + return q +} + +func (q *SelectQuery) forEachInlineRelJoin(fn func(*relationJoin) error) error { + if q.tableModel == nil { + return nil + } + return q._forEachInlineRelJoin(fn, q.tableModel.getJoins()) +} + +func (q *SelectQuery) _forEachInlineRelJoin(fn func(*relationJoin) error, joins []relationJoin) error { + for i := range joins { + j := &joins[i] + switch j.Relation.Type { + case schema.HasOneRelation, schema.BelongsToRelation: + if err := fn(j); err != nil { + return err + } + if err := q._forEachInlineRelJoin(fn, j.JoinModel.getJoins()); err != nil { + return err + } + } + } + return nil +} + +func (q *SelectQuery) selectJoins(ctx context.Context, joins []relationJoin) error { + for i := range joins { + j := &joins[i] + + var err error + + switch j.Relation.Type { + case schema.HasOneRelation, schema.BelongsToRelation: + err = q.selectJoins(ctx, j.JoinModel.getJoins()) + case schema.HasManyRelation: + err = j.selectMany(ctx, q.db.NewSelect().Conn(q.conn)) + case schema.ManyToManyRelation: + err = j.selectM2M(ctx, q.db.NewSelect().Conn(q.conn)) + default: + panic("not reached") + } + + if err != nil { + return err + } + } + return nil +} + +//------------------------------------------------------------------------------ + +func (q *SelectQuery) Operation() string { + return "SELECT" +} + +func (q *SelectQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, err error) { + return q.appendQuery(fmter, b, false) +} + +func (q *SelectQuery) appendQuery( + fmter schema.Formatter, b []byte, count bool, +) (_ []byte, err error) { + if q.err != nil { + return nil, q.err + } + + fmter = formatterWithModel(fmter, q) + + cteCount := count && (len(q.group) > 0 || q.distinctOn != nil) + if cteCount { + b = append(b, "WITH _count_wrapper AS ("...) + } + + if len(q.union) > 0 { + b = append(b, '(') + } + + b, err = q.appendWith(fmter, b) + if err != nil { + return nil, err + } + + b = append(b, "SELECT "...) + + if len(q.distinctOn) > 0 { + b = append(b, "DISTINCT ON ("...) + for i, app := range q.distinctOn { + if i > 0 { + b = append(b, ", "...) + } + b, err = app.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + b = append(b, ") "...) + } else if q.distinctOn != nil { + b = append(b, "DISTINCT "...) + } + + if count && !cteCount { + b = append(b, "count(*)"...) + } else { + b, err = q.appendColumns(fmter, b) + if err != nil { + return nil, err + } + } + + if q.hasTables() { + b, err = q.appendTables(fmter, b) + if err != nil { + return nil, err + } + } + + if err := q.forEachInlineRelJoin(func(j *relationJoin) error { + b = append(b, ' ') + b, err = j.appendHasOneJoin(fmter, b, q) + return err + }); err != nil { + return nil, err + } + + for _, j := range q.joins { + b, err = j.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + + b, err = q.appendIndexHints(fmter, b) + if err != nil { + return nil, err + } + + b, err = q.appendWhere(fmter, b, true) + if err != nil { + return nil, err + } + + if len(q.group) > 0 { + b = append(b, " GROUP BY "...) + for i, f := range q.group { + if i > 0 { + b = append(b, ", "...) + } + b, err = f.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + } + + if len(q.having) > 0 { + b = append(b, " HAVING "...) + for i, f := range q.having { + if i > 0 { + b = append(b, " AND "...) + } + b = append(b, '(') + b, err = f.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + b = append(b, ')') + } + } + + if !count { + b, err = q.appendOrder(fmter, b) + if err != nil { + return nil, err + } + + if fmter.Dialect().Features().Has(feature.OffsetFetch) { + if q.limit > 0 && q.offset > 0 { + b = append(b, " OFFSET "...) + b = strconv.AppendInt(b, int64(q.offset), 10) + b = append(b, " ROWS"...) + + b = append(b, " FETCH NEXT "...) + b = strconv.AppendInt(b, int64(q.limit), 10) + b = append(b, " ROWS ONLY"...) + } else if q.limit > 0 { + b = append(b, " OFFSET 0 ROWS"...) + + b = append(b, " FETCH NEXT "...) + b = strconv.AppendInt(b, int64(q.limit), 10) + b = append(b, " ROWS ONLY"...) + } else if q.offset > 0 { + b = append(b, " OFFSET "...) + b = strconv.AppendInt(b, int64(q.offset), 10) + b = append(b, " ROWS"...) + } + } else { + if q.limit > 0 { + b = append(b, " LIMIT "...) + b = strconv.AppendInt(b, int64(q.limit), 10) + } + if q.offset > 0 { + b = append(b, " OFFSET "...) + b = strconv.AppendInt(b, int64(q.offset), 10) + } + } + + if !q.selFor.IsZero() { + b = append(b, " FOR "...) + b, err = q.selFor.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + } + + if len(q.union) > 0 { + b = append(b, ')') + + for _, u := range q.union { + b = append(b, u.expr...) + b = append(b, '(') + b, err = u.query.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + b = append(b, ')') + } + } + + if cteCount { + b = append(b, ") SELECT count(*) FROM _count_wrapper"...) + } + + return b, nil +} + +func (q *SelectQuery) appendColumns(fmter schema.Formatter, b []byte) (_ []byte, err error) { + start := len(b) + + switch { + case q.columns != nil: + for i, col := range q.columns { + if i > 0 { + b = append(b, ", "...) + } + + if col.Args == nil && q.table != nil { + if field, ok := q.table.FieldMap[col.Query]; ok { + b = append(b, q.table.SQLAlias...) + b = append(b, '.') + b = append(b, field.SQLName...) + continue + } + } + + b, err = col.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + case q.table != nil: + if len(q.table.Fields) > 10 && fmter.IsNop() { + b = append(b, q.table.SQLAlias...) + b = append(b, '.') + b = fmter.Dialect().AppendString(b, fmt.Sprintf("%d columns", len(q.table.Fields))) + } else { + b = appendColumns(b, q.table.SQLAlias, q.table.Fields) + } + default: + b = append(b, '*') + } + + if err := q.forEachInlineRelJoin(func(join *relationJoin) error { + if len(b) != start { + b = append(b, ", "...) + start = len(b) + } + + b, err = q.appendInlineRelColumns(fmter, b, join) + if err != nil { + return err + } + + return nil + }); err != nil { + return nil, err + } + + b = bytes.TrimSuffix(b, []byte(", ")) + + return b, nil +} + +func (q *SelectQuery) appendInlineRelColumns( + fmter schema.Formatter, b []byte, join *relationJoin, +) (_ []byte, err error) { + join.applyTo(q) + + if join.columns != nil { + table := join.JoinModel.Table() + for i, col := range join.columns { + if i > 0 { + b = append(b, ", "...) + } + + if col.Args == nil { + if field, ok := table.FieldMap[col.Query]; ok { + b = join.appendAlias(fmter, b) + b = append(b, '.') + b = append(b, field.SQLName...) + b = append(b, " AS "...) + b = join.appendAliasColumn(fmter, b, field.Name) + continue + } + } + + b, err = col.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + return b, nil + } + + for i, field := range join.JoinModel.Table().Fields { + if i > 0 { + b = append(b, ", "...) + } + b = join.appendAlias(fmter, b) + b = append(b, '.') + b = append(b, field.SQLName...) + b = append(b, " AS "...) + b = join.appendAliasColumn(fmter, b, field.Name) + } + return b, nil +} + +func (q *SelectQuery) appendTables(fmter schema.Formatter, b []byte) (_ []byte, err error) { + b = append(b, " FROM "...) + return q.appendTablesWithAlias(fmter, b) +} + +func (q *SelectQuery) appendOrder(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if len(q.order) > 0 { + b = append(b, " ORDER BY "...) + + for i, f := range q.order { + if i > 0 { + b = append(b, ", "...) + } + b, err = f.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + + return b, nil + } + return b, nil +} + +//------------------------------------------------------------------------------ + +func (q *SelectQuery) Rows(ctx context.Context) (*sql.Rows, error) { + if q.err != nil { + return nil, q.err + } + + if err := q.beforeAppendModel(ctx, q); err != nil { + return nil, err + } + + queryBytes, err := q.AppendQuery(q.db.fmter, q.db.makeQueryBytes()) + if err != nil { + return nil, err + } + + query := internal.String(queryBytes) + return q.conn.QueryContext(ctx, query) +} + +func (q *SelectQuery) Exec(ctx context.Context, dest ...interface{}) (res sql.Result, err error) { + if q.err != nil { + return nil, q.err + } + if err := q.beforeAppendModel(ctx, q); err != nil { + return nil, err + } + + queryBytes, err := q.AppendQuery(q.db.fmter, q.db.makeQueryBytes()) + if err != nil { + return nil, err + } + + query := internal.String(queryBytes) + + if len(dest) > 0 { + model, err := q.getModel(dest) + if err != nil { + return nil, err + } + + res, err = q.scan(ctx, q, query, model, true) + if err != nil { + return nil, err + } + } else { + res, err = q.exec(ctx, q, query) + if err != nil { + return nil, err + } + } + + return res, nil +} + +func (q *SelectQuery) Scan(ctx context.Context, dest ...interface{}) error { + if q.err != nil { + return q.err + } + + model, err := q.getModel(dest) + if err != nil { + return err + } + + if q.table != nil { + if err := q.beforeSelectHook(ctx); err != nil { + return err + } + } + + if err := q.beforeAppendModel(ctx, q); err != nil { + return err + } + + queryBytes, err := q.AppendQuery(q.db.fmter, q.db.makeQueryBytes()) + if err != nil { + return err + } + + query := internal.String(queryBytes) + + res, err := q.scan(ctx, q, query, model, true) + if err != nil { + return err + } + + if n, _ := res.RowsAffected(); n > 0 { + if tableModel, ok := model.(TableModel); ok { + if err := q.selectJoins(ctx, tableModel.getJoins()); err != nil { + return err + } + } + } + + if q.table != nil { + if err := q.afterSelectHook(ctx); err != nil { + return err + } + } + + return nil +} + +func (q *SelectQuery) beforeSelectHook(ctx context.Context) error { + if hook, ok := q.table.ZeroIface.(BeforeSelectHook); ok { + if err := hook.BeforeSelect(ctx, q); err != nil { + return err + } + } + return nil +} + +func (q *SelectQuery) afterSelectHook(ctx context.Context) error { + if hook, ok := q.table.ZeroIface.(AfterSelectHook); ok { + if err := hook.AfterSelect(ctx, q); err != nil { + return err + } + } + return nil +} + +func (q *SelectQuery) Count(ctx context.Context) (int, error) { + if q.err != nil { + return 0, q.err + } + + qq := countQuery{q} + + queryBytes, err := qq.AppendQuery(q.db.fmter, nil) + if err != nil { + return 0, err + } + + query := internal.String(queryBytes) + ctx, event := q.db.beforeQuery(ctx, qq, query, nil, query, q.model) + + var num int + err = q.conn.QueryRowContext(ctx, query).Scan(&num) + + q.db.afterQuery(ctx, event, nil, err) + + return num, err +} + +func (q *SelectQuery) ScanAndCount(ctx context.Context, dest ...interface{}) (int, error) { + if _, ok := q.conn.(*DB); ok { + return q.scanAndCountConc(ctx, dest...) + } + return q.scanAndCountSeq(ctx, dest...) +} + +func (q *SelectQuery) scanAndCountConc(ctx context.Context, dest ...interface{}) (int, error) { + var count int + var wg sync.WaitGroup + var mu sync.Mutex + var firstErr error + + if q.limit >= 0 { + wg.Add(1) + go func() { + defer wg.Done() + + if err := q.Scan(ctx, dest...); err != nil { + mu.Lock() + if firstErr == nil { + firstErr = err + } + mu.Unlock() + } + }() + } + + wg.Add(1) + go func() { + defer wg.Done() + + var err error + count, err = q.Count(ctx) + if err != nil { + mu.Lock() + if firstErr == nil { + firstErr = err + } + mu.Unlock() + } + }() + + wg.Wait() + return count, firstErr +} + +func (q *SelectQuery) scanAndCountSeq(ctx context.Context, dest ...interface{}) (int, error) { + var firstErr error + + if q.limit >= 0 { + firstErr = q.Scan(ctx, dest...) + } + + count, err := q.Count(ctx) + if err != nil && firstErr == nil { + firstErr = err + } + + return count, firstErr +} + +func (q *SelectQuery) Exists(ctx context.Context) (bool, error) { + if q.err != nil { + return false, q.err + } + + if q.hasFeature(feature.SelectExists) { + return q.selectExists(ctx) + } + return q.whereExists(ctx) +} + +func (q *SelectQuery) selectExists(ctx context.Context) (bool, error) { + qq := selectExistsQuery{q} + + queryBytes, err := qq.AppendQuery(q.db.fmter, nil) + if err != nil { + return false, err + } + + query := internal.String(queryBytes) + ctx, event := q.db.beforeQuery(ctx, qq, query, nil, query, q.model) + + var exists bool + err = q.conn.QueryRowContext(ctx, query).Scan(&exists) + + q.db.afterQuery(ctx, event, nil, err) + + return exists, err +} + +func (q *SelectQuery) whereExists(ctx context.Context) (bool, error) { + qq := whereExistsQuery{q} + + queryBytes, err := qq.AppendQuery(q.db.fmter, nil) + if err != nil { + return false, err + } + + query := internal.String(queryBytes) + ctx, event := q.db.beforeQuery(ctx, qq, query, nil, query, q.model) + + res, err := q.exec(ctx, q, query) + + q.db.afterQuery(ctx, event, nil, err) + + if err != nil { + return false, err + } + + n, err := res.RowsAffected() + if err != nil { + return false, err + } + + return n == 1, nil +} + +func (q *SelectQuery) String() string { + buf, err := q.AppendQuery(q.db.Formatter(), nil) + if err != nil { + panic(err) + } + + return string(buf) +} + +//------------------------------------------------------------------------------ + +func (q *SelectQuery) QueryBuilder() QueryBuilder { + return &selectQueryBuilder{q} +} + +func (q *SelectQuery) ApplyQueryBuilder(fn func(QueryBuilder) QueryBuilder) *SelectQuery { + return fn(q.QueryBuilder()).Unwrap().(*SelectQuery) +} + +type selectQueryBuilder struct { + *SelectQuery +} + +func (q *selectQueryBuilder) WhereGroup( + sep string, fn func(QueryBuilder) QueryBuilder, +) QueryBuilder { + q.SelectQuery = q.SelectQuery.WhereGroup(sep, func(qs *SelectQuery) *SelectQuery { + return fn(q).(*selectQueryBuilder).SelectQuery + }) + return q +} + +func (q *selectQueryBuilder) Where(query string, args ...interface{}) QueryBuilder { + q.SelectQuery.Where(query, args...) + return q +} + +func (q *selectQueryBuilder) WhereOr(query string, args ...interface{}) QueryBuilder { + q.SelectQuery.WhereOr(query, args...) + return q +} + +func (q *selectQueryBuilder) WhereDeleted() QueryBuilder { + q.SelectQuery.WhereDeleted() + return q +} + +func (q *selectQueryBuilder) WhereAllWithDeleted() QueryBuilder { + q.SelectQuery.WhereAllWithDeleted() + return q +} + +func (q *selectQueryBuilder) WherePK(cols ...string) QueryBuilder { + q.SelectQuery.WherePK(cols...) + return q +} + +func (q *selectQueryBuilder) Unwrap() interface{} { + return q.SelectQuery +} + +//------------------------------------------------------------------------------ + +type joinQuery struct { + join schema.QueryWithArgs + on []schema.QueryWithSep +} + +func (j *joinQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, err error) { + b = append(b, ' ') + + b, err = j.join.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + + if len(j.on) > 0 { + b = append(b, " ON "...) + for i, on := range j.on { + if i > 0 { + b = append(b, on.Sep...) + } + + b = append(b, '(') + b, err = on.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + b = append(b, ')') + } + } + + return b, nil +} + +//------------------------------------------------------------------------------ + +type countQuery struct { + *SelectQuery +} + +func (q countQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if q.err != nil { + return nil, q.err + } + return q.appendQuery(fmter, b, true) +} + +//------------------------------------------------------------------------------ + +type selectExistsQuery struct { + *SelectQuery +} + +func (q selectExistsQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if q.err != nil { + return nil, q.err + } + + b = append(b, "SELECT EXISTS ("...) + + b, err = q.appendQuery(fmter, b, false) + if err != nil { + return nil, err + } + + b = append(b, ")"...) + + return b, nil +} + +//------------------------------------------------------------------------------ + +type whereExistsQuery struct { + *SelectQuery +} + +func (q whereExistsQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if q.err != nil { + return nil, q.err + } + + b = append(b, "SELECT 1 WHERE EXISTS ("...) + + b, err = q.appendQuery(fmter, b, false) + if err != nil { + return nil, err + } + + b = append(b, ")"...) + + return b, nil +} diff --git a/ogx/query_table_create.go b/ogx/query_table_create.go new file mode 100644 index 00000000..740c0226 --- /dev/null +++ b/ogx/query_table_create.go @@ -0,0 +1,344 @@ +package ogx + +import ( + "context" + "database/sql" + "sort" + "strconv" + + "gitee.com/chentanyang/ogx/dialect/feature" + "gitee.com/chentanyang/ogx/dialect/sqltype" + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/schema" +) + +type CreateTableQuery struct { + baseQuery + + temp bool + ifNotExists bool + varchar int + + fks []schema.QueryWithArgs + partitionBy schema.QueryWithArgs + tablespace schema.QueryWithArgs +} + +var _ Query = (*CreateTableQuery)(nil) + +func NewCreateTableQuery(db *DB) *CreateTableQuery { + q := &CreateTableQuery{ + baseQuery: baseQuery{ + db: db, + conn: db.DB, + }, + } + return q +} + +func (q *CreateTableQuery) Conn(db IConn) *CreateTableQuery { + q.setConn(db) + return q +} + +func (q *CreateTableQuery) Model(model interface{}) *CreateTableQuery { + q.setModel(model) + return q +} + +// ------------------------------------------------------------------------------ + +func (q *CreateTableQuery) Table(tables ...string) *CreateTableQuery { + for _, table := range tables { + q.addTable(schema.UnsafeIdent(table)) + } + return q +} + +func (q *CreateTableQuery) TableExpr(query string, args ...interface{}) *CreateTableQuery { + q.addTable(schema.SafeQuery(query, args)) + return q +} + +func (q *CreateTableQuery) ModelTableExpr(query string, args ...interface{}) *CreateTableQuery { + q.modelTableName = schema.SafeQuery(query, args) + return q +} + +func (q *CreateTableQuery) ColumnExpr(query string, args ...interface{}) *CreateTableQuery { + q.addColumn(schema.SafeQuery(query, args)) + return q +} + +// ------------------------------------------------------------------------------ + +func (q *CreateTableQuery) Temp() *CreateTableQuery { + q.temp = true + return q +} + +func (q *CreateTableQuery) IfNotExists() *CreateTableQuery { + q.ifNotExists = true + return q +} + +func (q *CreateTableQuery) Varchar(n int) *CreateTableQuery { + q.varchar = n + return q +} + +func (q *CreateTableQuery) ForeignKey(query string, args ...interface{}) *CreateTableQuery { + q.fks = append(q.fks, schema.SafeQuery(query, args)) + return q +} + +func (q *CreateTableQuery) PartitionBy(query string, args ...interface{}) *CreateTableQuery { + q.partitionBy = schema.SafeQuery(query, args) + return q +} + +func (q *CreateTableQuery) TableSpace(tablespace string) *CreateTableQuery { + q.tablespace = schema.UnsafeIdent(tablespace) + return q +} + +func (q *CreateTableQuery) WithForeignKeys() *CreateTableQuery { + for _, relation := range q.tableModel.Table().Relations { + if relation.Type == schema.ManyToManyRelation || + relation.Type == schema.HasManyRelation { + continue + } + + q = q.ForeignKey("(?) REFERENCES ? (?) ? ?", + Safe(appendColumns(nil, "", relation.BaseFields)), + relation.JoinTable.SQLName, + Safe(appendColumns(nil, "", relation.JoinFields)), + Safe(relation.OnUpdate), + Safe(relation.OnDelete), + ) + } + return q +} + +//------------------------------------------------------------------------------ + +func (q *CreateTableQuery) Operation() string { + return "CREATE TABLE" +} + +func (q *CreateTableQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if q.err != nil { + return nil, q.err + } + if q.table == nil { + return nil, errNilModel + } + + b = append(b, "CREATE "...) + if q.temp { + b = append(b, "TEMP "...) + } + b = append(b, "TABLE "...) + if q.ifNotExists && fmter.Dialect().Features().Has(feature.TableNotExists) { + b = append(b, "IF NOT EXISTS "...) + } + b, err = q.appendFirstTable(fmter, b) + if err != nil { + return nil, err + } + + b = append(b, " ("...) + + for i, field := range q.table.Fields { + if i > 0 { + b = append(b, ", "...) + } + + b = append(b, field.SQLName...) + b = append(b, " "...) + b = q.appendSQLType(b, field) + if field.NotNull { + b = append(b, " NOT NULL"...) + } + if field.AutoIncrement { + switch { + case fmter.Dialect().Features().Has(feature.AutoIncrement): + b = append(b, " AUTO_INCREMENT"...) + case fmter.Dialect().Features().Has(feature.Identity): + b = append(b, " IDENTITY"...) + } + } + if field.Identity { + if fmter.Dialect().Features().Has(feature.GeneratedIdentity) { + b = append(b, " GENERATED BY DEFAULT AS IDENTITY"...) + } + } + if field.SQLDefault != "" { + b = append(b, " DEFAULT "...) + b = append(b, field.SQLDefault...) + } + } + + for i, col := range q.columns { + // Only pre-pend the comma if we are on subsequent iterations, or if there were fields/columns appended before + // this. This way if we are only appending custom column expressions we will not produce a syntax error with a + // leading comma. + if i > 0 || len(q.table.Fields) > 0 { + b = append(b, ", "...) + } + b, err = col.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + + b = q.appendPKConstraint(b, q.table.PKs) + b = q.appendUniqueConstraints(fmter, b) + b, err = q.appendFKConstraints(fmter, b) + if err != nil { + return nil, err + } + + b = append(b, ")"...) + + if !q.partitionBy.IsZero() { + b = append(b, " PARTITION BY "...) + b, err = q.partitionBy.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + + if !q.tablespace.IsZero() { + b = append(b, " TABLESPACE "...) + b, err = q.tablespace.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + + return b, nil +} + +func (q *CreateTableQuery) appendSQLType(b []byte, field *schema.Field) []byte { + if field.CreateTableSQLType != field.DiscoveredSQLType { + return append(b, field.CreateTableSQLType...) + } + + if q.varchar > 0 && + field.CreateTableSQLType == sqltype.VarChar { + b = append(b, "varchar("...) + b = strconv.AppendInt(b, int64(q.varchar), 10) + b = append(b, ")"...) + return b + } + + return append(b, field.CreateTableSQLType...) +} + +func (q *CreateTableQuery) appendUniqueConstraints(fmter schema.Formatter, b []byte) []byte { + unique := q.table.Unique + + keys := make([]string, 0, len(unique)) + for key := range unique { + keys = append(keys, key) + } + sort.Strings(keys) + + for _, key := range keys { + if key == "" { + for _, field := range unique[key] { + b = q.appendUniqueConstraint(fmter, b, key, field) + } + continue + } + b = q.appendUniqueConstraint(fmter, b, key, unique[key]...) + } + + return b +} + +func (q *CreateTableQuery) appendUniqueConstraint( + fmter schema.Formatter, b []byte, name string, fields ...*schema.Field, +) []byte { + if name != "" { + b = append(b, ", CONSTRAINT "...) + b = fmter.AppendIdent(b, name) + } else { + b = append(b, ","...) + } + b = append(b, " UNIQUE ("...) + b = appendColumns(b, "", fields) + b = append(b, ")"...) + return b +} + +func (q *CreateTableQuery) appendFKConstraints( + fmter schema.Formatter, b []byte, +) (_ []byte, err error) { + for _, fk := range q.fks { + b = append(b, ", FOREIGN KEY "...) + b, err = fk.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + return b, nil +} + +func (q *CreateTableQuery) appendPKConstraint(b []byte, pks []*schema.Field) []byte { + if len(pks) == 0 { + return b + } + + b = append(b, ", PRIMARY KEY ("...) + b = appendColumns(b, "", pks) + b = append(b, ")"...) + return b +} + +// ------------------------------------------------------------------------------ + +func (q *CreateTableQuery) Exec(ctx context.Context, dest ...interface{}) (sql.Result, error) { + if err := q.beforeCreateTableHook(ctx); err != nil { + return nil, err + } + + queryBytes, err := q.AppendQuery(q.db.fmter, q.db.makeQueryBytes()) + if err != nil { + return nil, err + } + + query := internal.String(queryBytes) + + res, err := q.exec(ctx, q, query) + if err != nil { + return nil, err + } + + if q.table != nil { + if err := q.afterCreateTableHook(ctx); err != nil { + return nil, err + } + } + + return res, nil +} + +func (q *CreateTableQuery) beforeCreateTableHook(ctx context.Context) error { + if hook, ok := q.table.ZeroIface.(BeforeCreateTableHook); ok { + if err := hook.BeforeCreateTable(ctx, q); err != nil { + return err + } + } + return nil +} + +func (q *CreateTableQuery) afterCreateTableHook(ctx context.Context) error { + if hook, ok := q.table.ZeroIface.(AfterCreateTableHook); ok { + if err := hook.AfterCreateTable(ctx, q); err != nil { + return err + } + } + return nil +} diff --git a/ogx/query_table_drop.go b/ogx/query_table_drop.go new file mode 100644 index 00000000..23b335cd --- /dev/null +++ b/ogx/query_table_drop.go @@ -0,0 +1,148 @@ +package ogx + +import ( + "context" + "database/sql" + + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/schema" +) + +type DropTableQuery struct { + baseQuery + cascadeQuery + + ifExists bool +} + +var _ Query = (*DropTableQuery)(nil) + +func NewDropTableQuery(db *DB) *DropTableQuery { + q := &DropTableQuery{ + baseQuery: baseQuery{ + db: db, + conn: db.DB, + }, + } + return q +} + +func (q *DropTableQuery) Conn(db IConn) *DropTableQuery { + q.setConn(db) + return q +} + +func (q *DropTableQuery) Model(model interface{}) *DropTableQuery { + q.setModel(model) + return q +} + +//------------------------------------------------------------------------------ + +func (q *DropTableQuery) Table(tables ...string) *DropTableQuery { + for _, table := range tables { + q.addTable(schema.UnsafeIdent(table)) + } + return q +} + +func (q *DropTableQuery) TableExpr(query string, args ...interface{}) *DropTableQuery { + q.addTable(schema.SafeQuery(query, args)) + return q +} + +func (q *DropTableQuery) ModelTableExpr(query string, args ...interface{}) *DropTableQuery { + q.modelTableName = schema.SafeQuery(query, args) + return q +} + +//------------------------------------------------------------------------------ + +func (q *DropTableQuery) IfExists() *DropTableQuery { + q.ifExists = true + return q +} + +func (q *DropTableQuery) Cascade() *DropTableQuery { + q.cascade = true + return q +} + +func (q *DropTableQuery) Restrict() *DropTableQuery { + q.restrict = true + return q +} + +//------------------------------------------------------------------------------ + +func (q *DropTableQuery) Operation() string { + return "DROP TABLE" +} + +func (q *DropTableQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if q.err != nil { + return nil, q.err + } + + b = append(b, "DROP TABLE "...) + if q.ifExists { + b = append(b, "IF EXISTS "...) + } + + b, err = q.appendTables(fmter, b) + if err != nil { + return nil, err + } + + b = q.appendCascade(fmter, b) + + return b, nil +} + +//------------------------------------------------------------------------------ + +func (q *DropTableQuery) Exec(ctx context.Context, dest ...interface{}) (sql.Result, error) { + if q.table != nil { + if err := q.beforeDropTableHook(ctx); err != nil { + return nil, err + } + } + + queryBytes, err := q.AppendQuery(q.db.fmter, q.db.makeQueryBytes()) + if err != nil { + return nil, err + } + + query := internal.String(queryBytes) + + res, err := q.exec(ctx, q, query) + if err != nil { + return nil, err + } + + if q.table != nil { + if err := q.afterDropTableHook(ctx); err != nil { + return nil, err + } + } + + return res, nil +} + +func (q *DropTableQuery) beforeDropTableHook(ctx context.Context) error { + if hook, ok := q.table.ZeroIface.(BeforeDropTableHook); ok { + if err := hook.BeforeDropTable(ctx, q); err != nil { + return err + } + } + return nil +} + +func (q *DropTableQuery) afterDropTableHook(ctx context.Context) error { + if hook, ok := q.table.ZeroIface.(AfterDropTableHook); ok { + if err := hook.AfterDropTable(ctx, q); err != nil { + return err + } + } + return nil +} diff --git a/ogx/query_table_truncate.go b/ogx/query_table_truncate.go new file mode 100644 index 00000000..e3ed7f4a --- /dev/null +++ b/ogx/query_table_truncate.go @@ -0,0 +1,132 @@ +package ogx + +import ( + "context" + "database/sql" + + "gitee.com/chentanyang/ogx/dialect/feature" + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/schema" +) + +type TruncateTableQuery struct { + baseQuery + cascadeQuery + + continueIdentity bool +} + +var _ Query = (*TruncateTableQuery)(nil) + +func NewTruncateTableQuery(db *DB) *TruncateTableQuery { + q := &TruncateTableQuery{ + baseQuery: baseQuery{ + db: db, + conn: db.DB, + }, + } + return q +} + +func (q *TruncateTableQuery) Conn(db IConn) *TruncateTableQuery { + q.setConn(db) + return q +} + +func (q *TruncateTableQuery) Model(model interface{}) *TruncateTableQuery { + q.setModel(model) + return q +} + +//------------------------------------------------------------------------------ + +func (q *TruncateTableQuery) Table(tables ...string) *TruncateTableQuery { + for _, table := range tables { + q.addTable(schema.UnsafeIdent(table)) + } + return q +} + +func (q *TruncateTableQuery) TableExpr(query string, args ...interface{}) *TruncateTableQuery { + q.addTable(schema.SafeQuery(query, args)) + return q +} + +//------------------------------------------------------------------------------ + +func (q *TruncateTableQuery) ContinueIdentity() *TruncateTableQuery { + q.continueIdentity = true + return q +} + +func (q *TruncateTableQuery) Cascade() *TruncateTableQuery { + q.cascade = true + return q +} + +func (q *TruncateTableQuery) Restrict() *TruncateTableQuery { + q.restrict = true + return q +} + +//------------------------------------------------------------------------------ + +func (q *TruncateTableQuery) Operation() string { + return "TRUNCATE TABLE" +} + +func (q *TruncateTableQuery) AppendQuery( + fmter schema.Formatter, b []byte, +) (_ []byte, err error) { + if q.err != nil { + return nil, q.err + } + + if !fmter.HasFeature(feature.TableTruncate) { + b = append(b, "DELETE FROM "...) + + b, err = q.appendTables(fmter, b) + if err != nil { + return nil, err + } + + return b, nil + } + + b = append(b, "TRUNCATE TABLE "...) + + b, err = q.appendTables(fmter, b) + if err != nil { + return nil, err + } + + if q.db.features.Has(feature.TableIdentity) { + if q.continueIdentity { + b = append(b, " CONTINUE IDENTITY"...) + } else { + b = append(b, " RESTART IDENTITY"...) + } + } + + b = q.appendCascade(fmter, b) + + return b, nil +} + +//------------------------------------------------------------------------------ + +func (q *TruncateTableQuery) Exec(ctx context.Context, dest ...interface{}) (sql.Result, error) { + queryBytes, err := q.AppendQuery(q.db.fmter, q.db.makeQueryBytes()) + if err != nil { + return nil, err + } + + query := internal.String(queryBytes) + + res, err := q.exec(ctx, q, query) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/ogx/query_update.go b/ogx/query_update.go new file mode 100644 index 00000000..cb4a49ee --- /dev/null +++ b/ogx/query_update.go @@ -0,0 +1,585 @@ +package ogx + +import ( + "context" + "database/sql" + "errors" + "fmt" + + "gitee.com/chentanyang/ogx/dialect" + + "gitee.com/chentanyang/ogx/dialect/feature" + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/schema" +) + +type UpdateQuery struct { + whereBaseQuery + returningQuery + customValueQuery + setQuery + idxHintsQuery + + omitZero bool +} + +var _ Query = (*UpdateQuery)(nil) + +func NewUpdateQuery(db *DB) *UpdateQuery { + q := &UpdateQuery{ + whereBaseQuery: whereBaseQuery{ + baseQuery: baseQuery{ + db: db, + conn: db.DB, + }, + }, + } + return q +} + +func (q *UpdateQuery) Conn(db IConn) *UpdateQuery { + q.setConn(db) + return q +} + +func (q *UpdateQuery) Model(model interface{}) *UpdateQuery { + q.setModel(model) + return q +} + +// Apply calls the fn passing the SelectQuery as an argument. +func (q *UpdateQuery) Apply(fn func(*UpdateQuery) *UpdateQuery) *UpdateQuery { + return fn(q) +} + +func (q *UpdateQuery) With(name string, query schema.QueryAppender) *UpdateQuery { + q.addWith(name, query) + return q +} + +//------------------------------------------------------------------------------ + +func (q *UpdateQuery) Table(tables ...string) *UpdateQuery { + for _, table := range tables { + q.addTable(schema.UnsafeIdent(table)) + } + return q +} + +func (q *UpdateQuery) TableExpr(query string, args ...interface{}) *UpdateQuery { + q.addTable(schema.SafeQuery(query, args)) + return q +} + +func (q *UpdateQuery) ModelTableExpr(query string, args ...interface{}) *UpdateQuery { + q.modelTableName = schema.SafeQuery(query, args) + return q +} + +//------------------------------------------------------------------------------ + +func (q *UpdateQuery) Column(columns ...string) *UpdateQuery { + for _, column := range columns { + q.addColumn(schema.UnsafeIdent(column)) + } + return q +} + +func (q *UpdateQuery) ExcludeColumn(columns ...string) *UpdateQuery { + q.excludeColumn(columns) + return q +} + +func (q *UpdateQuery) Set(query string, args ...interface{}) *UpdateQuery { + q.addSet(schema.SafeQuery(query, args)) + return q +} + +func (q *UpdateQuery) SetColumn(column string, query string, args ...interface{}) *UpdateQuery { + if q.db.HasFeature(feature.UpdateMultiTable) { + column = q.table.Alias + "." + column + } + q.addSet(schema.SafeQuery(column+" = "+query, args)) + return q +} + +// Value overwrites model value for the column. +func (q *UpdateQuery) Value(column string, query string, args ...interface{}) *UpdateQuery { + if q.table == nil { + q.err = errNilModel + return q + } + q.addValue(q.table, column, query, args) + return q +} + +func (q *UpdateQuery) OmitZero() *UpdateQuery { + q.omitZero = true + return q +} + +//------------------------------------------------------------------------------ + +func (q *UpdateQuery) WherePK(cols ...string) *UpdateQuery { + q.addWhereCols(cols) + return q +} + +func (q *UpdateQuery) Where(query string, args ...interface{}) *UpdateQuery { + q.addWhere(schema.SafeQueryWithSep(query, args, " AND ")) + return q +} + +func (q *UpdateQuery) WhereOr(query string, args ...interface{}) *UpdateQuery { + q.addWhere(schema.SafeQueryWithSep(query, args, " OR ")) + return q +} + +func (q *UpdateQuery) WhereGroup(sep string, fn func(*UpdateQuery) *UpdateQuery) *UpdateQuery { + saved := q.where + q.where = nil + + q = fn(q) + + where := q.where + q.where = saved + + q.addWhereGroup(sep, where) + + return q +} + +func (q *UpdateQuery) WhereDeleted() *UpdateQuery { + q.whereDeleted() + return q +} + +func (q *UpdateQuery) WhereAllWithDeleted() *UpdateQuery { + q.whereAllWithDeleted() + return q +} + +//------------------------------------------------------------------------------ + +// Returning adds a RETURNING clause to the query. +// +// To suppress the auto-generated RETURNING clause, use `Returning("NULL")`. +func (q *UpdateQuery) Returning(query string, args ...interface{}) *UpdateQuery { + q.addReturning(schema.SafeQuery(query, args)) + return q +} + +func (q *UpdateQuery) hasReturning() bool { + if !q.db.features.Has(feature.Returning) { + return false + } + return q.returningQuery.hasReturning() +} + +//------------------------------------------------------------------------------ + +func (q *UpdateQuery) Operation() string { + return "UPDATE" +} + +func (q *UpdateQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if q.err != nil { + return nil, q.err + } + + fmter = formatterWithModel(fmter, q) + + b, err = q.appendWith(fmter, b) + if err != nil { + return nil, err + } + + b = append(b, "UPDATE "...) + + if fmter.HasFeature(feature.UpdateMultiTable) { + b, err = q.appendTablesWithAlias(fmter, b) + } else if fmter.HasFeature(feature.UpdateTableAlias) { + b, err = q.appendFirstTableWithAlias(fmter, b) + } else { + b, err = q.appendFirstTable(fmter, b) + } + if err != nil { + return nil, err + } + + b, err = q.appendIndexHints(fmter, b) + if err != nil { + return nil, err + } + + b, err = q.mustAppendSet(fmter, b) + if err != nil { + return nil, err + } + + if !fmter.HasFeature(feature.UpdateMultiTable) { + b, err = q.appendOtherTables(fmter, b) + if err != nil { + return nil, err + } + } + + b, err = q.mustAppendWhere(fmter, b, q.hasTableAlias(fmter)) + if err != nil { + return nil, err + } + + if q.hasFeature(feature.Returning) && q.hasReturning() { + b = append(b, " RETURNING "...) + b, err = q.appendReturning(fmter, b) + if err != nil { + return nil, err + } + } + + return b, nil +} + +func (q *UpdateQuery) mustAppendSet(fmter schema.Formatter, b []byte) (_ []byte, err error) { + b = append(b, " SET "...) + + if len(q.set) > 0 { + return q.appendSet(fmter, b) + } + + if m, ok := q.model.(*mapModel); ok { + return m.appendSet(fmter, b), nil + } + + if q.tableModel == nil { + return nil, errNilModel + } + + switch model := q.tableModel.(type) { + case *structTableModel: + b, err = q.appendSetStruct(fmter, b, model) + if err != nil { + return nil, err + } + case *sliceTableModel: + return nil, errors.New("ogx: to bulk Update, use CTE and VALUES") + default: + return nil, fmt.Errorf("ogx: Update does not support %T", q.tableModel) + } + + return b, nil +} + +func (q *UpdateQuery) appendSetStruct( + fmter schema.Formatter, b []byte, model *structTableModel, +) ([]byte, error) { + fields, err := q.getDataFields() + if err != nil { + return nil, err + } + + isTemplate := fmter.IsNop() + pos := len(b) + for _, f := range fields { + if f.SkipUpdate() { + continue + } + + app, hasValue := q.modelValues[f.Name] + + if !hasValue && q.omitZero && f.HasZeroValue(model.strct) { + continue + } + + if len(b) != pos { + b = append(b, ", "...) + pos = len(b) + } + + b = append(b, f.SQLName...) + b = append(b, " = "...) + + if isTemplate { + b = append(b, '?') + continue + } + + if hasValue { + b, err = app.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } else { + b = f.AppendValue(fmter, b, model.strct) + } + } + + for i, v := range q.extraValues { + if i > 0 || len(fields) > 0 { + b = append(b, ", "...) + } + + b = append(b, v.column...) + b = append(b, " = "...) + + b, err = v.value.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + + return b, nil +} + +func (q *UpdateQuery) appendOtherTables(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if !q.hasMultiTables() { + return b, nil + } + + b = append(b, " FROM "...) + + b, err = q.whereBaseQuery.appendOtherTables(fmter, b) + if err != nil { + return nil, err + } + + return b, nil +} + +//------------------------------------------------------------------------------ + +func (q *UpdateQuery) Bulk() *UpdateQuery { + model, ok := q.model.(*sliceTableModel) + if !ok { + q.setErr(fmt.Errorf("ogx: Bulk requires a slice, got %T", q.model)) + return q + } + + set, err := q.updateSliceSet(q.db.fmter, model) + if err != nil { + q.setErr(err) + return q + } + + values := q.db.NewValues(model) + values.customValueQuery = q.customValueQuery + + return q.With("_data", values). + Model(model). + TableExpr("_data"). + Set(set). + Where(q.updateSliceWhere(q.db.fmter, model)) +} + +func (q *UpdateQuery) updateSliceSet( + fmter schema.Formatter, model *sliceTableModel, +) (string, error) { + fields, err := q.getDataFields() + if err != nil { + return "", err + } + + var b []byte + for i, field := range fields { + if i > 0 { + b = append(b, ", "...) + } + if fmter.HasFeature(feature.UpdateMultiTable) { + b = append(b, model.table.SQLAlias...) + b = append(b, '.') + } + b = append(b, field.SQLName...) + b = append(b, " = _data."...) + b = append(b, field.SQLName...) + } + return internal.String(b), nil +} + +func (q *UpdateQuery) updateSliceWhere(fmter schema.Formatter, model *sliceTableModel) string { + var b []byte + for i, pk := range model.table.PKs { + if i > 0 { + b = append(b, " AND "...) + } + if q.hasTableAlias(fmter) { + b = append(b, model.table.SQLAlias...) + } else { + b = append(b, model.table.SQLName...) + } + b = append(b, '.') + b = append(b, pk.SQLName...) + b = append(b, " = _data."...) + b = append(b, pk.SQLName...) + } + return internal.String(b) +} + +//------------------------------------------------------------------------------ + +func (q *UpdateQuery) Exec(ctx context.Context, dest ...interface{}) (sql.Result, error) { + if q.err != nil { + return nil, q.err + } + + if q.table != nil { + if err := q.beforeUpdateHook(ctx); err != nil { + return nil, err + } + } + + if err := q.beforeAppendModel(ctx, q); err != nil { + return nil, err + } + + queryBytes, err := q.AppendQuery(q.db.fmter, q.db.makeQueryBytes()) + if err != nil { + return nil, err + } + + query := internal.String(queryBytes) + + var res sql.Result + + if hasDest := len(dest) > 0; hasDest || q.hasReturning() { + model, err := q.getModel(dest) + if err != nil { + return nil, err + } + + res, err = q.scan(ctx, q, query, model, hasDest) + if err != nil { + return nil, err + } + } else { + res, err = q.exec(ctx, q, query) + if err != nil { + return nil, err + } + } + + if q.table != nil { + if err := q.afterUpdateHook(ctx); err != nil { + return nil, err + } + } + + return res, nil +} + +func (q *UpdateQuery) beforeUpdateHook(ctx context.Context) error { + if hook, ok := q.table.ZeroIface.(BeforeUpdateHook); ok { + if err := hook.BeforeUpdate(ctx, q); err != nil { + return err + } + } + return nil +} + +func (q *UpdateQuery) afterUpdateHook(ctx context.Context) error { + if hook, ok := q.table.ZeroIface.(AfterUpdateHook); ok { + if err := hook.AfterUpdate(ctx, q); err != nil { + return err + } + } + return nil +} + +// FQN returns a fully qualified column name, for example, table_name.column_name or +// table_alias.column_alias. +func (q *UpdateQuery) FQN(column string) Ident { + if q.table == nil { + panic("UpdateQuery.SetName requires a model") + } + if q.hasTableAlias(q.db.fmter) { + return Ident(q.table.Alias + "." + column) + } + return Ident(q.table.Name + "." + column) +} + +func (q *UpdateQuery) hasTableAlias(fmter schema.Formatter) bool { + return fmter.HasFeature(feature.UpdateMultiTable | feature.UpdateTableAlias) +} + +func (q *UpdateQuery) String() string { + buf, err := q.AppendQuery(q.db.Formatter(), nil) + if err != nil { + panic(err) + } + + return string(buf) +} + +//------------------------------------------------------------------------------ + +func (q *UpdateQuery) QueryBuilder() QueryBuilder { + return &updateQueryBuilder{q} +} + +func (q *UpdateQuery) ApplyQueryBuilder(fn func(QueryBuilder) QueryBuilder) *UpdateQuery { + return fn(q.QueryBuilder()).Unwrap().(*UpdateQuery) +} + +type updateQueryBuilder struct { + *UpdateQuery +} + +func (q *updateQueryBuilder) WhereGroup( + sep string, fn func(QueryBuilder) QueryBuilder, +) QueryBuilder { + q.UpdateQuery = q.UpdateQuery.WhereGroup(sep, func(qs *UpdateQuery) *UpdateQuery { + return fn(q).(*updateQueryBuilder).UpdateQuery + }) + return q +} + +func (q *updateQueryBuilder) Where(query string, args ...interface{}) QueryBuilder { + q.UpdateQuery.Where(query, args...) + return q +} + +func (q *updateQueryBuilder) WhereOr(query string, args ...interface{}) QueryBuilder { + q.UpdateQuery.WhereOr(query, args...) + return q +} + +func (q *updateQueryBuilder) WhereDeleted() QueryBuilder { + q.UpdateQuery.WhereDeleted() + return q +} + +func (q *updateQueryBuilder) WhereAllWithDeleted() QueryBuilder { + q.UpdateQuery.WhereAllWithDeleted() + return q +} + +func (q *updateQueryBuilder) WherePK(cols ...string) QueryBuilder { + q.UpdateQuery.WherePK(cols...) + return q +} + +func (q *updateQueryBuilder) Unwrap() interface{} { + return q.UpdateQuery +} + +//------------------------------------------------------------------------------ + +func (q *UpdateQuery) UseIndex(indexes ...string) *UpdateQuery { + if q.db.dialect.Name() == dialect.MySQL { + q.addUseIndex(indexes...) + } + return q +} + +func (q *UpdateQuery) IgnoreIndex(indexes ...string) *UpdateQuery { + if q.db.dialect.Name() == dialect.MySQL { + q.addIgnoreIndex(indexes...) + } + return q +} + +func (q *UpdateQuery) ForceIndex(indexes ...string) *UpdateQuery { + if q.db.dialect.Name() == dialect.MySQL { + q.addForceIndex(indexes...) + } + return q +} diff --git a/ogx/query_values.go b/ogx/query_values.go new file mode 100644 index 00000000..a255c8a2 --- /dev/null +++ b/ogx/query_values.go @@ -0,0 +1,222 @@ +package ogx + +import ( + "fmt" + "reflect" + "strconv" + + "gitee.com/chentanyang/ogx/dialect/feature" + "gitee.com/chentanyang/ogx/schema" +) + +type ValuesQuery struct { + baseQuery + customValueQuery + + withOrder bool +} + +var ( + _ Query = (*ValuesQuery)(nil) + _ schema.NamedArgAppender = (*ValuesQuery)(nil) +) + +func NewValuesQuery(db *DB, model interface{}) *ValuesQuery { + q := &ValuesQuery{ + baseQuery: baseQuery{ + db: db, + conn: db.DB, + }, + } + q.setModel(model) + return q +} + +func (q *ValuesQuery) Conn(db IConn) *ValuesQuery { + q.setConn(db) + return q +} + +func (q *ValuesQuery) Column(columns ...string) *ValuesQuery { + for _, column := range columns { + q.addColumn(schema.UnsafeIdent(column)) + } + return q +} + +// Value overwrites model value for the column. +func (q *ValuesQuery) Value(column string, expr string, args ...interface{}) *ValuesQuery { + if q.table == nil { + q.err = errNilModel + return q + } + q.addValue(q.table, column, expr, args) + return q +} + +func (q *ValuesQuery) WithOrder() *ValuesQuery { + q.withOrder = true + return q +} + +func (q *ValuesQuery) AppendNamedArg(fmter schema.Formatter, b []byte, name string) ([]byte, bool) { + switch name { + case "Columns": + bb, err := q.AppendColumns(fmter, b) + if err != nil { + q.setErr(err) + return b, true + } + return bb, true + } + return b, false +} + +// AppendColumns appends the table columns. It is used by CTE. +func (q *ValuesQuery) AppendColumns(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if q.err != nil { + return nil, q.err + } + if q.model == nil { + return nil, errNilModel + } + + if q.tableModel != nil { + fields, err := q.getFields() + if err != nil { + return nil, err + } + + b = appendColumns(b, "", fields) + + if q.withOrder { + b = append(b, ", _order"...) + } + + return b, nil + } + + switch model := q.model.(type) { + case *mapSliceModel: + return model.appendColumns(fmter, b) + } + + return nil, fmt.Errorf("ogx: Values does not support %T", q.model) +} + +func (q *ValuesQuery) Operation() string { + return "VALUES" +} + +func (q *ValuesQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if q.err != nil { + return nil, q.err + } + if q.model == nil { + return nil, errNilModel + } + + fmter = formatterWithModel(fmter, q) + + if q.tableModel != nil { + fields, err := q.getFields() + if err != nil { + return nil, err + } + return q.appendQuery(fmter, b, fields) + } + + switch model := q.model.(type) { + case *mapSliceModel: + return model.appendValues(fmter, b) + } + + return nil, fmt.Errorf("ogx: Values does not support %T", q.model) +} + +func (q *ValuesQuery) appendQuery( + fmter schema.Formatter, + b []byte, + fields []*schema.Field, +) (_ []byte, err error) { + b = append(b, "VALUES "...) + if q.db.features.Has(feature.ValuesRow) { + b = append(b, "ROW("...) + } else { + b = append(b, '(') + } + + switch model := q.tableModel.(type) { + case *structTableModel: + b, err = q.appendValues(fmter, b, fields, model.strct) + if err != nil { + return nil, err + } + + if q.withOrder { + b = append(b, ", "...) + b = strconv.AppendInt(b, 0, 10) + } + case *sliceTableModel: + slice := model.slice + sliceLen := slice.Len() + for i := 0; i < sliceLen; i++ { + if i > 0 { + b = append(b, "), "...) + if q.db.features.Has(feature.ValuesRow) { + b = append(b, "ROW("...) + } else { + b = append(b, '(') + } + } + + b, err = q.appendValues(fmter, b, fields, slice.Index(i)) + if err != nil { + return nil, err + } + + if q.withOrder { + b = append(b, ", "...) + b = strconv.AppendInt(b, int64(i), 10) + } + } + default: + return nil, fmt.Errorf("ogx: Values does not support %T", q.model) + } + + b = append(b, ')') + + return b, nil +} + +func (q *ValuesQuery) appendValues( + fmter schema.Formatter, b []byte, fields []*schema.Field, strct reflect.Value, +) (_ []byte, err error) { + isTemplate := fmter.IsNop() + for i, f := range fields { + if i > 0 { + b = append(b, ", "...) + } + + app, ok := q.modelValues[f.Name] + if ok { + b, err = app.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + continue + } + + if isTemplate { + b = append(b, '?') + } else { + b = f.AppendValue(fmter, b, indirect(strct)) + } + + if fmter.HasFeature(feature.DoubleColonCast) { + b = append(b, "::"...) + b = append(b, f.UserSQLType...) + } + } + return b, nil +} diff --git a/ogx/relation_join.go b/ogx/relation_join.go new file mode 100644 index 00000000..59103123 --- /dev/null +++ b/ogx/relation_join.go @@ -0,0 +1,398 @@ +package ogx + +import ( + "context" + "reflect" + + "gitee.com/chentanyang/ogx/dialect/feature" + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/schema" +) + +type relationJoin struct { + Parent *relationJoin + BaseModel TableModel + JoinModel TableModel + Relation *schema.Relation + + apply func(*SelectQuery) *SelectQuery + columns []schema.QueryWithArgs +} + +func (j *relationJoin) applyTo(q *SelectQuery) { + if j.apply == nil { + return + } + + var table *schema.Table + var columns []schema.QueryWithArgs + + // Save state. + table, q.table = q.table, j.JoinModel.Table() + columns, q.columns = q.columns, nil + + q = j.apply(q) + + // Restore state. + q.table = table + j.columns, q.columns = q.columns, columns +} + +func (j *relationJoin) Select(ctx context.Context, q *SelectQuery) error { + switch j.Relation.Type { + } + panic("not reached") +} + +func (j *relationJoin) selectMany(ctx context.Context, q *SelectQuery) error { + q = j.manyQuery(q) + if q == nil { + return nil + } + return q.Scan(ctx) +} + +func (j *relationJoin) manyQuery(q *SelectQuery) *SelectQuery { + hasManyModel := newHasManyModel(j) + if hasManyModel == nil { + return nil + } + + q = q.Model(hasManyModel) + + var where []byte + + if q.db.dialect.Features().Has(feature.CompositeIn) { + return j.manyQueryCompositeIn(where, q) + } + return j.manyQueryMulti(where, q) +} + +func (j *relationJoin) manyQueryCompositeIn(where []byte, q *SelectQuery) *SelectQuery { + if len(j.Relation.JoinFields) > 1 { + where = append(where, '(') + } + where = appendColumns(where, j.JoinModel.Table().SQLAlias, j.Relation.JoinFields) + if len(j.Relation.JoinFields) > 1 { + where = append(where, ')') + } + where = append(where, " IN ("...) + where = appendChildValues( + q.db.Formatter(), + where, + j.JoinModel.rootValue(), + j.JoinModel.parentIndex(), + j.Relation.BaseFields, + ) + where = append(where, ")"...) + q = q.Where(internal.String(where)) + + if j.Relation.PolymorphicField != nil { + q = q.Where("? = ?", j.Relation.PolymorphicField.SQLName, j.Relation.PolymorphicValue) + } + + j.applyTo(q) + q = q.Apply(j.hasManyColumns) + + return q +} + +func (j *relationJoin) manyQueryMulti(where []byte, q *SelectQuery) *SelectQuery { + where = appendMultiValues( + q.db.Formatter(), + where, + j.JoinModel.rootValue(), + j.JoinModel.parentIndex(), + j.Relation.BaseFields, + j.Relation.JoinFields, + j.JoinModel.Table().SQLAlias, + ) + + q = q.Where(internal.String(where)) + + if j.Relation.PolymorphicField != nil { + q = q.Where("? = ?", j.Relation.PolymorphicField.SQLName, j.Relation.PolymorphicValue) + } + + j.applyTo(q) + q = q.Apply(j.hasManyColumns) + + return q +} + +func (j *relationJoin) hasManyColumns(q *SelectQuery) *SelectQuery { + b := make([]byte, 0, 32) + + joinTable := j.JoinModel.Table() + if len(j.columns) > 0 { + for i, col := range j.columns { + if i > 0 { + b = append(b, ", "...) + } + + if col.Args == nil { + if field, ok := joinTable.FieldMap[col.Query]; ok { + b = append(b, joinTable.SQLAlias...) + b = append(b, '.') + b = append(b, field.SQLName...) + continue + } + } + + var err error + b, err = col.AppendQuery(q.db.fmter, b) + if err != nil { + q.setErr(err) + return q + } + + } + } else { + b = appendColumns(b, joinTable.SQLAlias, joinTable.Fields) + } + + q = q.ColumnExpr(internal.String(b)) + + return q +} + +func (j *relationJoin) selectM2M(ctx context.Context, q *SelectQuery) error { + q = j.m2mQuery(q) + if q == nil { + return nil + } + return q.Scan(ctx) +} + +func (j *relationJoin) m2mQuery(q *SelectQuery) *SelectQuery { + fmter := q.db.fmter + + m2mModel := newM2MModel(j) + if m2mModel == nil { + return nil + } + q = q.Model(m2mModel) + + index := j.JoinModel.parentIndex() + baseTable := j.BaseModel.Table() + + if j.Relation.M2MTable != nil { + q = q.ColumnExpr(string(j.Relation.M2MTable.SQLAlias) + ".*") + } + + //nolint + var join []byte + join = append(join, "JOIN "...) + join = fmter.AppendQuery(join, string(j.Relation.M2MTable.SQLName)) + join = append(join, " AS "...) + join = append(join, j.Relation.M2MTable.SQLAlias...) + join = append(join, " ON ("...) + for i, col := range j.Relation.M2MBaseFields { + if i > 0 { + join = append(join, ", "...) + } + join = append(join, j.Relation.M2MTable.SQLAlias...) + join = append(join, '.') + join = append(join, col.SQLName...) + } + join = append(join, ") IN ("...) + join = appendChildValues(fmter, join, j.BaseModel.rootValue(), index, baseTable.PKs) + join = append(join, ")"...) + q = q.Join(internal.String(join)) + + joinTable := j.JoinModel.Table() + for i, m2mJoinField := range j.Relation.M2MJoinFields { + joinField := j.Relation.JoinFields[i] + q = q.Where("?.? = ?.?", + joinTable.SQLAlias, joinField.SQLName, + j.Relation.M2MTable.SQLAlias, m2mJoinField.SQLName) + } + + j.applyTo(q) + q = q.Apply(j.hasManyColumns) + + return q +} + +func (j *relationJoin) hasParent() bool { + if j.Parent != nil { + switch j.Parent.Relation.Type { + case schema.HasOneRelation, schema.BelongsToRelation: + return true + } + } + return false +} + +func (j *relationJoin) appendAlias(fmter schema.Formatter, b []byte) []byte { + quote := fmter.IdentQuote() + + b = append(b, quote) + b = appendAlias(b, j) + b = append(b, quote) + return b +} + +func (j *relationJoin) appendAliasColumn(fmter schema.Formatter, b []byte, column string) []byte { + quote := fmter.IdentQuote() + + b = append(b, quote) + b = appendAlias(b, j) + b = append(b, "__"...) + b = append(b, column...) + b = append(b, quote) + return b +} + +func (j *relationJoin) appendBaseAlias(fmter schema.Formatter, b []byte) []byte { + quote := fmter.IdentQuote() + + if j.hasParent() { + b = append(b, quote) + b = appendAlias(b, j.Parent) + b = append(b, quote) + return b + } + return append(b, j.BaseModel.Table().SQLAlias...) +} + +func (j *relationJoin) appendSoftDelete(b []byte, flags internal.Flag) []byte { + b = append(b, '.') + b = append(b, j.JoinModel.Table().SoftDeleteField.SQLName...) + if flags.Has(deletedFlag) { + b = append(b, " IS NOT NULL"...) + } else { + b = append(b, " IS NULL"...) + } + return b +} + +func appendAlias(b []byte, j *relationJoin) []byte { + if j.hasParent() { + b = appendAlias(b, j.Parent) + b = append(b, "__"...) + } + b = append(b, j.Relation.Field.Name...) + return b +} + +func (j *relationJoin) appendHasOneJoin( + fmter schema.Formatter, b []byte, q *SelectQuery, +) (_ []byte, err error) { + isSoftDelete := j.JoinModel.Table().SoftDeleteField != nil && !q.flags.Has(allWithDeletedFlag) + + b = append(b, "LEFT JOIN "...) + b = fmter.AppendQuery(b, string(j.JoinModel.Table().SQLNameForSelects)) + b = append(b, " AS "...) + b = j.appendAlias(fmter, b) + + b = append(b, " ON "...) + + b = append(b, '(') + for i, baseField := range j.Relation.BaseFields { + if i > 0 { + b = append(b, " AND "...) + } + b = j.appendAlias(fmter, b) + b = append(b, '.') + b = append(b, j.Relation.JoinFields[i].SQLName...) + b = append(b, " = "...) + b = j.appendBaseAlias(fmter, b) + b = append(b, '.') + b = append(b, baseField.SQLName...) + } + b = append(b, ')') + + if isSoftDelete { + b = append(b, " AND "...) + b = j.appendAlias(fmter, b) + b = j.appendSoftDelete(b, q.flags) + } + + return b, nil +} + +func appendChildValues( + fmter schema.Formatter, b []byte, v reflect.Value, index []int, fields []*schema.Field, +) []byte { + seen := make(map[string]struct{}) + walk(v, index, func(v reflect.Value) { + start := len(b) + + if len(fields) > 1 { + b = append(b, '(') + } + for i, f := range fields { + if i > 0 { + b = append(b, ", "...) + } + b = f.AppendValue(fmter, b, v) + } + if len(fields) > 1 { + b = append(b, ')') + } + b = append(b, ", "...) + + if _, ok := seen[string(b[start:])]; ok { + b = b[:start] + } else { + seen[string(b[start:])] = struct{}{} + } + }) + if len(seen) > 0 { + b = b[:len(b)-2] // trim ", " + } + return b +} + +// appendMultiValues is an alternative to appendChildValues that doesn't use the sql keyword ID +// but instead use a old style ((k1=v1) AND (k2=v2)) OR (...) of conditions. +func appendMultiValues( + fmter schema.Formatter, b []byte, v reflect.Value, index []int, baseFields, joinFields []*schema.Field, joinTable schema.Safe, +) []byte { + // This is based on a mix of appendChildValues and query_base.appendColumns + + // These should never missmatch in length but nice to know if it does + if len(joinFields) != len(baseFields) { + panic("not reached") + } + + // walk the relations + b = append(b, '(') + seen := make(map[string]struct{}) + walk(v, index, func(v reflect.Value) { + start := len(b) + for i, f := range baseFields { + if i > 0 { + b = append(b, " AND "...) + } + if len(baseFields) > 1 { + b = append(b, '(') + } + // Field name + b = append(b, joinTable...) + b = append(b, '.') + b = append(b, []byte(joinFields[i].SQLName)...) + + // Equals value + b = append(b, '=') + b = f.AppendValue(fmter, b, v) + if len(baseFields) > 1 { + b = append(b, ')') + } + } + + b = append(b, ") OR ("...) + + if _, ok := seen[string(b[start:])]; ok { + b = b[:start] + } else { + seen[string(b[start:])] = struct{}{} + } + }) + if len(seen) > 0 { + b = b[:len(b)-6] // trim ") OR (" + } + b = append(b, ')') + return b +} diff --git a/ogx/schema/append.go b/ogx/schema/append.go new file mode 100644 index 00000000..381e9822 --- /dev/null +++ b/ogx/schema/append.go @@ -0,0 +1,101 @@ +package schema + +import ( + "fmt" + "reflect" + "strconv" + "time" + + "gitee.com/chentanyang/ogx/dialect" +) + +func Append(fmter Formatter, b []byte, v interface{}) []byte { + switch v := v.(type) { + case nil: + return dialect.AppendNull(b) + case bool: + return dialect.AppendBool(b, v) + case int: + return strconv.AppendInt(b, int64(v), 10) + case int32: + return strconv.AppendInt(b, int64(v), 10) + case int64: + return strconv.AppendInt(b, v, 10) + case uint: + return strconv.AppendInt(b, int64(v), 10) + case uint32: + return fmter.Dialect().AppendUint32(b, v) + case uint64: + return fmter.Dialect().AppendUint64(b, v) + case float32: + return dialect.AppendFloat32(b, v) + case float64: + return dialect.AppendFloat64(b, v) + case string: + return fmter.Dialect().AppendString(b, v) + case time.Time: + return fmter.Dialect().AppendTime(b, v) + case []byte: + return fmter.Dialect().AppendBytes(b, v) + case QueryAppender: + return AppendQueryAppender(fmter, b, v) + default: + vv := reflect.ValueOf(v) + if vv.Kind() == reflect.Ptr && vv.IsNil() { + return dialect.AppendNull(b) + } + appender := Appender(fmter.Dialect(), vv.Type()) + return appender(fmter, b, vv) + } +} + +//------------------------------------------------------------------------------ + +func In(slice interface{}) QueryAppender { + v := reflect.ValueOf(slice) + if v.Kind() != reflect.Slice { + return &inValues{ + err: fmt.Errorf("ogx: In(non-slice %T)", slice), + } + } + return &inValues{ + slice: v, + } +} + +type inValues struct { + slice reflect.Value + err error +} + +var _ QueryAppender = (*inValues)(nil) + +func (in *inValues) AppendQuery(fmter Formatter, b []byte) (_ []byte, err error) { + if in.err != nil { + return nil, in.err + } + return appendIn(fmter, b, in.slice), nil +} + +func appendIn(fmter Formatter, b []byte, slice reflect.Value) []byte { + sliceLen := slice.Len() + for i := 0; i < sliceLen; i++ { + if i > 0 { + b = append(b, ", "...) + } + + elem := slice.Index(i) + if elem.Kind() == reflect.Interface { + elem = elem.Elem() + } + + if elem.Kind() == reflect.Slice && elem.Type() != bytesType { + b = append(b, '(') + b = appendIn(fmter, b, elem) + b = append(b, ')') + } else { + b = fmter.AppendValue(b, elem) + } + } + return b +} diff --git a/ogx/schema/append_value.go b/ogx/schema/append_value.go new file mode 100644 index 00000000..9ca58fe6 --- /dev/null +++ b/ogx/schema/append_value.go @@ -0,0 +1,318 @@ +package schema + +import ( + "database/sql/driver" + "fmt" + "net" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/vmihailenco/msgpack/v5" + + "gitee.com/chentanyang/ogx/dialect" + "gitee.com/chentanyang/ogx/dialect/sqltype" + "gitee.com/chentanyang/ogx/extra/ogxjson" + "gitee.com/chentanyang/ogx/internal" +) + +type ( + AppenderFunc func(fmter Formatter, b []byte, v reflect.Value) []byte + CustomAppender func(typ reflect.Type) AppenderFunc +) + +var appenders = []AppenderFunc{ + reflect.Bool: AppendBoolValue, + reflect.Int: AppendIntValue, + reflect.Int8: AppendIntValue, + reflect.Int16: AppendIntValue, + reflect.Int32: AppendIntValue, + reflect.Int64: AppendIntValue, + reflect.Uint: AppendUintValue, + reflect.Uint8: AppendUintValue, + reflect.Uint16: AppendUintValue, + reflect.Uint32: appendUint32Value, + reflect.Uint64: appendUint64Value, + reflect.Uintptr: nil, + reflect.Float32: AppendFloat32Value, + reflect.Float64: AppendFloat64Value, + reflect.Complex64: nil, + reflect.Complex128: nil, + reflect.Array: AppendJSONValue, + reflect.Chan: nil, + reflect.Func: nil, + reflect.Interface: nil, + reflect.Map: AppendJSONValue, + reflect.Ptr: nil, + reflect.Slice: AppendJSONValue, + reflect.String: AppendStringValue, + reflect.Struct: AppendJSONValue, + reflect.UnsafePointer: nil, +} + +var appenderMap sync.Map + +func FieldAppender(dialect Dialect, field *Field) AppenderFunc { + if field.Tag.HasOption("msgpack") { + return appendMsgpack + } + + fieldType := field.StructField.Type + + switch strings.ToUpper(field.UserSQLType) { + case sqltype.JSON, sqltype.JSONB: + if fieldType.Implements(driverValuerType) { + return appendDriverValue + } + + if fieldType.Kind() != reflect.Ptr { + if reflect.PtrTo(fieldType).Implements(driverValuerType) { + return addrAppender(appendDriverValue) + } + } + + return AppendJSONValue + } + + return Appender(dialect, fieldType) +} + +func Appender(dialect Dialect, typ reflect.Type) AppenderFunc { + if v, ok := appenderMap.Load(typ); ok { + return v.(AppenderFunc) + } + + fn := appender(dialect, typ) + + if v, ok := appenderMap.LoadOrStore(typ, fn); ok { + return v.(AppenderFunc) + } + return fn +} + +func appender(dialect Dialect, typ reflect.Type) AppenderFunc { + switch typ { + case bytesType: + return appendBytesValue + case timeType: + return appendTimeValue + case timePtrType: + return PtrAppender(appendTimeValue) + case ipType: + return appendIPValue + case ipNetType: + return appendIPNetValue + case jsonRawMessageType: + return appendJSONRawMessageValue + } + + kind := typ.Kind() + + if typ.Implements(queryAppenderType) { + if kind == reflect.Ptr { + return nilAwareAppender(appendQueryAppenderValue) + } + return appendQueryAppenderValue + } + if typ.Implements(driverValuerType) { + if kind == reflect.Ptr { + return nilAwareAppender(appendDriverValue) + } + return appendDriverValue + } + + if kind != reflect.Ptr { + ptr := reflect.PtrTo(typ) + if ptr.Implements(queryAppenderType) { + return addrAppender(appendQueryAppenderValue) + } + if ptr.Implements(driverValuerType) { + return addrAppender(appendDriverValue) + } + } + + switch kind { + case reflect.Interface: + return ifaceAppenderFunc + case reflect.Ptr: + if typ.Implements(jsonMarshalerType) { + return nilAwareAppender(AppendJSONValue) + } + if fn := Appender(dialect, typ.Elem()); fn != nil { + return PtrAppender(fn) + } + case reflect.Slice: + if typ.Elem().Kind() == reflect.Uint8 { + return appendBytesValue + } + case reflect.Array: + if typ.Elem().Kind() == reflect.Uint8 { + return appendArrayBytesValue + } + } + + return appenders[typ.Kind()] +} + +func ifaceAppenderFunc(fmter Formatter, b []byte, v reflect.Value) []byte { + if v.IsNil() { + return dialect.AppendNull(b) + } + elem := v.Elem() + appender := Appender(fmter.Dialect(), elem.Type()) + return appender(fmter, b, elem) +} + +func nilAwareAppender(fn AppenderFunc) AppenderFunc { + return func(fmter Formatter, b []byte, v reflect.Value) []byte { + if v.IsNil() { + return dialect.AppendNull(b) + } + return fn(fmter, b, v) + } +} + +func PtrAppender(fn AppenderFunc) AppenderFunc { + return func(fmter Formatter, b []byte, v reflect.Value) []byte { + if v.IsNil() { + return dialect.AppendNull(b) + } + return fn(fmter, b, v.Elem()) + } +} + +func AppendBoolValue(fmter Formatter, b []byte, v reflect.Value) []byte { + return dialect.AppendBool(b, v.Bool()) +} + +func AppendIntValue(fmter Formatter, b []byte, v reflect.Value) []byte { + return strconv.AppendInt(b, v.Int(), 10) +} + +func AppendUintValue(fmter Formatter, b []byte, v reflect.Value) []byte { + return strconv.AppendUint(b, v.Uint(), 10) +} + +func appendUint32Value(fmter Formatter, b []byte, v reflect.Value) []byte { + return fmter.Dialect().AppendUint32(b, uint32(v.Uint())) +} + +func appendUint64Value(fmter Formatter, b []byte, v reflect.Value) []byte { + return fmter.Dialect().AppendUint64(b, v.Uint()) +} + +func AppendFloat32Value(fmter Formatter, b []byte, v reflect.Value) []byte { + return dialect.AppendFloat32(b, float32(v.Float())) +} + +func AppendFloat64Value(fmter Formatter, b []byte, v reflect.Value) []byte { + return dialect.AppendFloat64(b, float64(v.Float())) +} + +func appendBytesValue(fmter Formatter, b []byte, v reflect.Value) []byte { + return fmter.Dialect().AppendBytes(b, v.Bytes()) +} + +func appendArrayBytesValue(fmter Formatter, b []byte, v reflect.Value) []byte { + if v.CanAddr() { + return fmter.Dialect().AppendBytes(b, v.Slice(0, v.Len()).Bytes()) + } + + tmp := make([]byte, v.Len()) + reflect.Copy(reflect.ValueOf(tmp), v) + b = fmter.Dialect().AppendBytes(b, tmp) + return b +} + +func AppendStringValue(fmter Formatter, b []byte, v reflect.Value) []byte { + return fmter.Dialect().AppendString(b, v.String()) +} + +func AppendJSONValue(fmter Formatter, b []byte, v reflect.Value) []byte { + bb, err := ogxjson.Marshal(v.Interface()) + if err != nil { + return dialect.AppendError(b, err) + } + + if len(bb) > 0 && bb[len(bb)-1] == '\n' { + bb = bb[:len(bb)-1] + } + + return fmter.Dialect().AppendJSON(b, bb) +} + +func appendTimeValue(fmter Formatter, b []byte, v reflect.Value) []byte { + tm := v.Interface().(time.Time) + return fmter.Dialect().AppendTime(b, tm) +} + +func appendIPValue(fmter Formatter, b []byte, v reflect.Value) []byte { + ip := v.Interface().(net.IP) + return fmter.Dialect().AppendString(b, ip.String()) +} + +func appendIPNetValue(fmter Formatter, b []byte, v reflect.Value) []byte { + ipnet := v.Interface().(net.IPNet) + return fmter.Dialect().AppendString(b, ipnet.String()) +} + +func appendJSONRawMessageValue(fmter Formatter, b []byte, v reflect.Value) []byte { + bytes := v.Bytes() + if bytes == nil { + return dialect.AppendNull(b) + } + return fmter.Dialect().AppendString(b, internal.String(bytes)) +} + +func appendQueryAppenderValue(fmter Formatter, b []byte, v reflect.Value) []byte { + return AppendQueryAppender(fmter, b, v.Interface().(QueryAppender)) +} + +func appendDriverValue(fmter Formatter, b []byte, v reflect.Value) []byte { + value, err := v.Interface().(driver.Valuer).Value() + if err != nil { + return dialect.AppendError(b, err) + } + if _, ok := value.(driver.Valuer); ok { + return dialect.AppendError(b, fmt.Errorf("driver.Valuer returns unsupported type %T", value)) + } + return Append(fmter, b, value) +} + +func addrAppender(fn AppenderFunc) AppenderFunc { + return func(fmter Formatter, b []byte, v reflect.Value) []byte { + if !v.CanAddr() { + err := fmt.Errorf("ogx: Append(nonaddressable %T)", v.Interface()) + return dialect.AppendError(b, err) + } + return fn(fmter, b, v.Addr()) + } +} + +func appendMsgpack(fmter Formatter, b []byte, v reflect.Value) []byte { + hexEnc := internal.NewHexEncoder(b) + + enc := msgpack.GetEncoder() + defer msgpack.PutEncoder(enc) + + enc.Reset(hexEnc) + if err := enc.EncodeValue(v); err != nil { + return dialect.AppendError(b, err) + } + + if err := hexEnc.Close(); err != nil { + return dialect.AppendError(b, err) + } + + return hexEnc.Bytes() +} + +func AppendQueryAppender(fmter Formatter, b []byte, app QueryAppender) []byte { + bb, err := app.AppendQuery(fmter, b) + if err != nil { + return dialect.AppendError(b, err) + } + return bb +} diff --git a/ogx/schema/dialect.go b/ogx/schema/dialect.go new file mode 100644 index 00000000..0df93330 --- /dev/null +++ b/ogx/schema/dialect.go @@ -0,0 +1,165 @@ +package schema + +import ( + "database/sql" + "encoding/hex" + "strconv" + "time" + "unicode/utf8" + + "gitee.com/chentanyang/ogx/dialect" + "gitee.com/chentanyang/ogx/dialect/feature" + "gitee.com/chentanyang/ogx/internal/parser" +) + +type Dialect interface { + Init(db *sql.DB) + + Name() dialect.Name + Features() feature.Feature + + Tables() *Tables + OnTable(table *Table) + + IdentQuote() byte + + AppendUint32(b []byte, n uint32) []byte + AppendUint64(b []byte, n uint64) []byte + AppendTime(b []byte, tm time.Time) []byte + AppendString(b []byte, s string) []byte + AppendBytes(b []byte, bs []byte) []byte + AppendJSON(b, jsonb []byte) []byte +} + +//------------------------------------------------------------------------------ + +type BaseDialect struct{} + +func (BaseDialect) AppendUint32(b []byte, n uint32) []byte { + return strconv.AppendUint(b, uint64(n), 10) +} + +func (BaseDialect) AppendUint64(b []byte, n uint64) []byte { + return strconv.AppendUint(b, n, 10) +} + +func (BaseDialect) AppendTime(b []byte, tm time.Time) []byte { + b = append(b, '\'') + b = tm.UTC().AppendFormat(b, "2006-01-02 15:04:05.999999-07:00") + b = append(b, '\'') + return b +} + +func (BaseDialect) AppendString(b []byte, s string) []byte { + b = append(b, '\'') + for _, r := range s { + if r == '\000' { + continue + } + + if r == '\'' { + b = append(b, '\'', '\'') + continue + } + + if r < utf8.RuneSelf { + b = append(b, byte(r)) + continue + } + + l := len(b) + if cap(b)-l < utf8.UTFMax { + b = append(b, make([]byte, utf8.UTFMax)...) + } + n := utf8.EncodeRune(b[l:l+utf8.UTFMax], r) + b = b[:l+n] + } + b = append(b, '\'') + return b +} + +func (BaseDialect) AppendBytes(b, bs []byte) []byte { + if bs == nil { + return dialect.AppendNull(b) + } + + b = append(b, `'\x`...) + + s := len(b) + b = append(b, make([]byte, hex.EncodedLen(len(bs)))...) + hex.Encode(b[s:], bs) + + b = append(b, '\'') + + return b +} + +func (BaseDialect) AppendJSON(b, jsonb []byte) []byte { + b = append(b, '\'') + + p := parser.New(jsonb) + for p.Valid() { + c := p.Read() + switch c { + case '"': + b = append(b, '"') + case '\'': + b = append(b, "''"...) + case '\000': + continue + case '\\': + if p.SkipBytes([]byte("u0000")) { + b = append(b, `\\u0000`...) + } else { + b = append(b, '\\') + if p.Valid() { + b = append(b, p.Read()) + } + } + default: + b = append(b, c) + } + } + + b = append(b, '\'') + + return b +} + +//------------------------------------------------------------------------------ + +type nopDialect struct { + BaseDialect + + tables *Tables + features feature.Feature +} + +func newNopDialect() *nopDialect { + d := new(nopDialect) + d.tables = NewTables(d) + d.features = feature.Returning + return d +} + +func (d *nopDialect) Init(*sql.DB) {} + +func (d *nopDialect) Name() dialect.Name { + return dialect.Invalid +} + +func (d *nopDialect) Features() feature.Feature { + return d.features +} + +func (d *nopDialect) Tables() *Tables { + return d.tables +} + +func (d *nopDialect) OnField(field *Field) {} + +func (d *nopDialect) OnTable(table *Table) {} + +func (d *nopDialect) IdentQuote() byte { + return '"' +} diff --git a/ogx/schema/field.go b/ogx/schema/field.go new file mode 100644 index 00000000..c5f2aecf --- /dev/null +++ b/ogx/schema/field.go @@ -0,0 +1,138 @@ +package schema + +import ( + "fmt" + "reflect" + + "gitee.com/chentanyang/ogx/dialect" + "gitee.com/chentanyang/ogx/internal/tagparser" +) + +type Field struct { + StructField reflect.StructField + IsPtr bool + + Tag tagparser.Tag + IndirectType reflect.Type + Index []int + + Name string // SQL name, .e.g. id + SQLName Safe // escaped SQL name, e.g. "id" + GoName string // struct field name, e.g. Id + + DiscoveredSQLType string + UserSQLType string + CreateTableSQLType string + SQLDefault string + + OnDelete string + OnUpdate string + + IsPK bool + NotNull bool + NullZero bool + AutoIncrement bool + Identity bool + + Append AppenderFunc + Scan ScannerFunc + IsZero IsZeroerFunc +} + +func (f *Field) String() string { + return f.Name +} + +func (f *Field) Clone() *Field { + cp := *f + cp.Index = cp.Index[:len(f.Index):len(f.Index)] + return &cp +} + +func (f *Field) Value(strct reflect.Value) reflect.Value { + return fieldByIndexAlloc(strct, f.Index) +} + +func (f *Field) HasNilValue(v reflect.Value) bool { + if len(f.Index) == 1 { + return v.Field(f.Index[0]).IsNil() + } + + for _, index := range f.Index { + if v.Kind() == reflect.Ptr { + if v.IsNil() { + return true + } + v = v.Elem() + } + v = v.Field(index) + } + return v.IsNil() +} + +func (f *Field) HasZeroValue(v reflect.Value) bool { + if len(f.Index) == 1 { + return f.IsZero(v.Field(f.Index[0])) + } + + for _, index := range f.Index { + if v.Kind() == reflect.Ptr { + if v.IsNil() { + return true + } + v = v.Elem() + } + v = v.Field(index) + } + return f.IsZero(v) +} + +func (f *Field) AppendValue(fmter Formatter, b []byte, strct reflect.Value) []byte { + fv, ok := fieldByIndex(strct, f.Index) + if !ok { + return dialect.AppendNull(b) + } + + if (f.IsPtr && fv.IsNil()) || (f.NullZero && f.IsZero(fv)) { + return dialect.AppendNull(b) + } + if f.Append == nil { + panic(fmt.Errorf("ogx: AppendValue(unsupported %s)", fv.Type())) + } + return f.Append(fmter, b, fv) +} + +func (f *Field) ScanWithCheck(fv reflect.Value, src interface{}) error { + if f.Scan == nil { + return fmt.Errorf("ogx: Scan(unsupported %s)", f.IndirectType) + } + return f.Scan(fv, src) +} + +func (f *Field) ScanValue(strct reflect.Value, src interface{}) error { + if src == nil { + if fv, ok := fieldByIndex(strct, f.Index); ok { + return f.ScanWithCheck(fv, src) + } + return nil + } + + fv := fieldByIndexAlloc(strct, f.Index) + return f.ScanWithCheck(fv, src) +} + +func (f *Field) SkipUpdate() bool { + return f.Tag.HasOption("skipupdate") +} + +func indexEqual(ind1, ind2 []int) bool { + if len(ind1) != len(ind2) { + return false + } + for i, ind := range ind1 { + if ind != ind2[i] { + return false + } + } + return true +} diff --git a/ogx/schema/formatter.go b/ogx/schema/formatter.go new file mode 100644 index 00000000..fa796600 --- /dev/null +++ b/ogx/schema/formatter.go @@ -0,0 +1,246 @@ +package schema + +import ( + "reflect" + "strconv" + "strings" + + "gitee.com/chentanyang/ogx/dialect" + "gitee.com/chentanyang/ogx/dialect/feature" + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/internal/parser" +) + +var nopFormatter = Formatter{ + dialect: newNopDialect(), +} + +type Formatter struct { + dialect Dialect + args *namedArgList +} + +func NewFormatter(dialect Dialect) Formatter { + return Formatter{ + dialect: dialect, + } +} + +func NewNopFormatter() Formatter { + return nopFormatter +} + +func (f Formatter) IsNop() bool { + return f.dialect.Name() == dialect.Invalid +} + +func (f Formatter) Dialect() Dialect { + return f.dialect +} + +func (f Formatter) IdentQuote() byte { + return f.dialect.IdentQuote() +} + +func (f Formatter) AppendIdent(b []byte, ident string) []byte { + return dialect.AppendIdent(b, ident, f.IdentQuote()) +} + +func (f Formatter) AppendValue(b []byte, v reflect.Value) []byte { + if v.Kind() == reflect.Ptr && v.IsNil() { + return dialect.AppendNull(b) + } + appender := Appender(f.dialect, v.Type()) + return appender(f, b, v) +} + +func (f Formatter) HasFeature(feature feature.Feature) bool { + return f.dialect.Features().Has(feature) +} + +func (f Formatter) WithArg(arg NamedArgAppender) Formatter { + return Formatter{ + dialect: f.dialect, + args: f.args.WithArg(arg), + } +} + +func (f Formatter) WithNamedArg(name string, value interface{}) Formatter { + return Formatter{ + dialect: f.dialect, + args: f.args.WithArg(&namedArg{name: name, value: value}), + } +} + +func (f Formatter) FormatQuery(query string, args ...interface{}) string { + if f.IsNop() || (args == nil && f.args == nil) || strings.IndexByte(query, '?') == -1 { + return query + } + return internal.String(f.AppendQuery(nil, query, args...)) +} + +func (f Formatter) AppendQuery(dst []byte, query string, args ...interface{}) []byte { + if f.IsNop() || (args == nil && f.args == nil) || strings.IndexByte(query, '?') == -1 { + return append(dst, query...) + } + return f.append(dst, parser.NewString(query), args) +} + +func (f Formatter) append(dst []byte, p *parser.Parser, args []interface{}) []byte { + var namedArgs NamedArgAppender + if len(args) == 1 { + if v, ok := args[0].(NamedArgAppender); ok { + namedArgs = v + } else if v, ok := newStructArgs(f, args[0]); ok { + namedArgs = v + } + } + + var argIndex int + for p.Valid() { + b, ok := p.ReadSep('?') + if !ok { + dst = append(dst, b...) + continue + } + if len(b) > 0 && b[len(b)-1] == '\\' { + dst = append(dst, b[:len(b)-1]...) + dst = append(dst, '?') + continue + } + dst = append(dst, b...) + + name, numeric := p.ReadIdentifier() + if name != "" { + if numeric { + idx, err := strconv.Atoi(name) + if err != nil { + goto restore_arg + } + + if idx >= len(args) { + goto restore_arg + } + + dst = f.appendArg(dst, args[idx]) + continue + } + + if namedArgs != nil { + dst, ok = namedArgs.AppendNamedArg(f, dst, name) + if ok { + continue + } + } + + dst, ok = f.args.AppendNamedArg(f, dst, name) + if ok { + continue + } + + restore_arg: + dst = append(dst, '?') + dst = append(dst, name...) + continue + } + + if argIndex >= len(args) { + dst = append(dst, '?') + continue + } + + arg := args[argIndex] + argIndex++ + + dst = f.appendArg(dst, arg) + } + + return dst +} + +func (f Formatter) appendArg(b []byte, arg interface{}) []byte { + switch arg := arg.(type) { + case QueryAppender: + bb, err := arg.AppendQuery(f, b) + if err != nil { + return dialect.AppendError(b, err) + } + return bb + default: + return Append(f, b, arg) + } +} + +//------------------------------------------------------------------------------ + +type NamedArgAppender interface { + AppendNamedArg(fmter Formatter, b []byte, name string) ([]byte, bool) +} + +type namedArgList struct { + arg NamedArgAppender + next *namedArgList +} + +func (l *namedArgList) WithArg(arg NamedArgAppender) *namedArgList { + return &namedArgList{ + arg: arg, + next: l, + } +} + +func (l *namedArgList) AppendNamedArg(fmter Formatter, b []byte, name string) ([]byte, bool) { + for l != nil && l.arg != nil { + if b, ok := l.arg.AppendNamedArg(fmter, b, name); ok { + return b, true + } + l = l.next + } + return b, false +} + +//------------------------------------------------------------------------------ + +type namedArg struct { + name string + value interface{} +} + +var _ NamedArgAppender = (*namedArg)(nil) + +func (a *namedArg) AppendNamedArg(fmter Formatter, b []byte, name string) ([]byte, bool) { + if a.name == name { + return fmter.appendArg(b, a.value), true + } + return b, false +} + +//------------------------------------------------------------------------------ + +type structArgs struct { + table *Table + strct reflect.Value +} + +var _ NamedArgAppender = (*structArgs)(nil) + +func newStructArgs(fmter Formatter, strct interface{}) (*structArgs, bool) { + v := reflect.ValueOf(strct) + if !v.IsValid() { + return nil, false + } + + v = reflect.Indirect(v) + if v.Kind() != reflect.Struct { + return nil, false + } + + return &structArgs{ + table: fmter.Dialect().Tables().Get(v.Type()), + strct: v, + }, true +} + +func (m *structArgs) AppendNamedArg(fmter Formatter, b []byte, name string) ([]byte, bool) { + return m.table.AppendNamedArg(fmter, b, name, m.strct) +} diff --git a/ogx/schema/hook.go b/ogx/schema/hook.go new file mode 100644 index 00000000..624601c9 --- /dev/null +++ b/ogx/schema/hook.go @@ -0,0 +1,59 @@ +package schema + +import ( + "context" + "database/sql" + "reflect" +) + +type Model interface { + ScanRows(ctx context.Context, rows *sql.Rows) (int, error) + Value() interface{} +} + +type Query interface { + QueryAppender + Operation() string + GetModel() Model + GetTableName() string +} + +//------------------------------------------------------------------------------ + +type BeforeAppendModelHook interface { + BeforeAppendModel(ctx context.Context, query Query) error +} + +var beforeAppendModelHookType = reflect.TypeOf((*BeforeAppendModelHook)(nil)).Elem() + +//------------------------------------------------------------------------------ + +type BeforeScanHook interface { + BeforeScan(context.Context) error +} + +var beforeScanHookType = reflect.TypeOf((*BeforeScanHook)(nil)).Elem() + +//------------------------------------------------------------------------------ + +type AfterScanHook interface { + AfterScan(context.Context) error +} + +var afterScanHookType = reflect.TypeOf((*AfterScanHook)(nil)).Elem() + +//------------------------------------------------------------------------------ + +type BeforeScanRowHook interface { + BeforeScanRow(context.Context) error +} + +var beforeScanRowHookType = reflect.TypeOf((*BeforeScanRowHook)(nil)).Elem() + +//------------------------------------------------------------------------------ + +type AfterScanRowHook interface { + AfterScanRow(context.Context) error +} + +var afterScanRowHookType = reflect.TypeOf((*AfterScanRowHook)(nil)).Elem() diff --git a/ogx/schema/reflect.go b/ogx/schema/reflect.go new file mode 100644 index 00000000..f13826a6 --- /dev/null +++ b/ogx/schema/reflect.go @@ -0,0 +1,72 @@ +package schema + +import ( + "database/sql/driver" + "encoding/json" + "net" + "reflect" + "time" +) + +var ( + bytesType = reflect.TypeOf((*[]byte)(nil)).Elem() + timePtrType = reflect.TypeOf((*time.Time)(nil)) + timeType = timePtrType.Elem() + ipType = reflect.TypeOf((*net.IP)(nil)).Elem() + ipNetType = reflect.TypeOf((*net.IPNet)(nil)).Elem() + jsonRawMessageType = reflect.TypeOf((*json.RawMessage)(nil)).Elem() + + driverValuerType = reflect.TypeOf((*driver.Valuer)(nil)).Elem() + queryAppenderType = reflect.TypeOf((*QueryAppender)(nil)).Elem() + jsonMarshalerType = reflect.TypeOf((*json.Marshaler)(nil)).Elem() +) + +func indirectType(t reflect.Type) reflect.Type { + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t +} + +func fieldByIndex(v reflect.Value, index []int) (_ reflect.Value, ok bool) { + if len(index) == 1 { + return v.Field(index[0]), true + } + + for i, idx := range index { + if i > 0 { + if v.Kind() == reflect.Ptr { + if v.IsNil() { + return v, false + } + v = v.Elem() + } + } + v = v.Field(idx) + } + return v, true +} + +func fieldByIndexAlloc(v reflect.Value, index []int) reflect.Value { + if len(index) == 1 { + return v.Field(index[0]) + } + + for i, idx := range index { + if i > 0 { + v = indirectNil(v) + } + v = v.Field(idx) + } + return v +} + +func indirectNil(v reflect.Value) reflect.Value { + if v.Kind() == reflect.Ptr { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + } + return v +} diff --git a/ogx/schema/relation.go b/ogx/schema/relation.go new file mode 100644 index 00000000..6636e26a --- /dev/null +++ b/ogx/schema/relation.go @@ -0,0 +1,35 @@ +package schema + +import ( + "fmt" +) + +const ( + InvalidRelation = iota + HasOneRelation + BelongsToRelation + HasManyRelation + ManyToManyRelation +) + +type Relation struct { + Type int + Field *Field + JoinTable *Table + BaseFields []*Field + JoinFields []*Field + OnUpdate string + OnDelete string + Condition []string + + PolymorphicField *Field + PolymorphicValue string + + M2MTable *Table + M2MBaseFields []*Field + M2MJoinFields []*Field +} + +func (r *Relation) String() string { + return fmt.Sprintf("relation=%s", r.Field.GoName) +} diff --git a/ogx/schema/scan.go b/ogx/schema/scan.go new file mode 100644 index 00000000..d438b851 --- /dev/null +++ b/ogx/schema/scan.go @@ -0,0 +1,516 @@ +package schema + +import ( + "bytes" + "database/sql" + "fmt" + "net" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/vmihailenco/msgpack/v5" + + "gitee.com/chentanyang/ogx/dialect/sqltype" + "gitee.com/chentanyang/ogx/extra/ogxjson" + "gitee.com/chentanyang/ogx/internal" +) + +var scannerType = reflect.TypeOf((*sql.Scanner)(nil)).Elem() + +type ScannerFunc func(dest reflect.Value, src interface{}) error + +var scanners []ScannerFunc + +func init() { + scanners = []ScannerFunc{ + reflect.Bool: scanBool, + reflect.Int: scanInt64, + reflect.Int8: scanInt64, + reflect.Int16: scanInt64, + reflect.Int32: scanInt64, + reflect.Int64: scanInt64, + reflect.Uint: scanUint64, + reflect.Uint8: scanUint64, + reflect.Uint16: scanUint64, + reflect.Uint32: scanUint64, + reflect.Uint64: scanUint64, + reflect.Uintptr: scanUint64, + reflect.Float32: scanFloat64, + reflect.Float64: scanFloat64, + reflect.Complex64: nil, + reflect.Complex128: nil, + reflect.Array: nil, + reflect.Interface: scanInterface, + reflect.Map: scanJSON, + reflect.Ptr: nil, + reflect.Slice: scanJSON, + reflect.String: scanString, + reflect.Struct: scanJSON, + reflect.UnsafePointer: nil, + } +} + +var scannerMap sync.Map + +func FieldScanner(dialect Dialect, field *Field) ScannerFunc { + if field.Tag.HasOption("msgpack") { + return scanMsgpack + } + if field.Tag.HasOption("json_use_number") { + return scanJSONUseNumber + } + if field.StructField.Type.Kind() == reflect.Interface { + switch strings.ToUpper(field.UserSQLType) { + case sqltype.JSON, sqltype.JSONB: + return scanJSONIntoInterface + } + } + return Scanner(field.StructField.Type) +} + +func Scanner(typ reflect.Type) ScannerFunc { + if v, ok := scannerMap.Load(typ); ok { + return v.(ScannerFunc) + } + + fn := scanner(typ) + + if v, ok := scannerMap.LoadOrStore(typ, fn); ok { + return v.(ScannerFunc) + } + return fn +} + +func scanner(typ reflect.Type) ScannerFunc { + kind := typ.Kind() + + if kind == reflect.Ptr { + if fn := Scanner(typ.Elem()); fn != nil { + return PtrScanner(fn) + } + } + + switch typ { + case bytesType: + return scanBytes + case timeType: + return scanTime + case ipType: + return scanIP + case ipNetType: + return scanIPNet + case jsonRawMessageType: + return scanBytes + } + + if typ.Implements(scannerType) { + return scanScanner + } + + if kind != reflect.Ptr { + ptr := reflect.PtrTo(typ) + if ptr.Implements(scannerType) { + return addrScanner(scanScanner) + } + } + + if typ.Kind() == reflect.Slice && typ.Elem().Kind() == reflect.Uint8 { + return scanBytes + } + + return scanners[kind] +} + +func scanBool(dest reflect.Value, src interface{}) error { + switch src := src.(type) { + case nil: + dest.SetBool(false) + return nil + case bool: + dest.SetBool(src) + return nil + case int64: + dest.SetBool(src != 0) + return nil + case []byte: + f, err := strconv.ParseBool(internal.String(src)) + if err != nil { + return err + } + dest.SetBool(f) + return nil + case string: + f, err := strconv.ParseBool(src) + if err != nil { + return err + } + dest.SetBool(f) + return nil + default: + return scanError(dest.Type(), src) + } +} + +func scanInt64(dest reflect.Value, src interface{}) error { + switch src := src.(type) { + case nil: + dest.SetInt(0) + return nil + case int64: + dest.SetInt(src) + return nil + case uint64: + dest.SetInt(int64(src)) + return nil + case []byte: + n, err := strconv.ParseInt(internal.String(src), 10, 64) + if err != nil { + return err + } + dest.SetInt(n) + return nil + case string: + n, err := strconv.ParseInt(src, 10, 64) + if err != nil { + return err + } + dest.SetInt(n) + return nil + default: + return scanError(dest.Type(), src) + } +} + +func scanUint64(dest reflect.Value, src interface{}) error { + switch src := src.(type) { + case nil: + dest.SetUint(0) + return nil + case uint64: + dest.SetUint(src) + return nil + case int64: + dest.SetUint(uint64(src)) + return nil + case []byte: + n, err := strconv.ParseUint(internal.String(src), 10, 64) + if err != nil { + return err + } + dest.SetUint(n) + return nil + case string: + n, err := strconv.ParseUint(src, 10, 64) + if err != nil { + return err + } + dest.SetUint(n) + return nil + default: + return scanError(dest.Type(), src) + } +} + +func scanFloat64(dest reflect.Value, src interface{}) error { + switch src := src.(type) { + case nil: + dest.SetFloat(0) + return nil + case float64: + dest.SetFloat(src) + return nil + case []byte: + f, err := strconv.ParseFloat(internal.String(src), 64) + if err != nil { + return err + } + dest.SetFloat(f) + return nil + case string: + f, err := strconv.ParseFloat(src, 64) + if err != nil { + return err + } + dest.SetFloat(f) + return nil + default: + return scanError(dest.Type(), src) + } +} + +func scanString(dest reflect.Value, src interface{}) error { + switch src := src.(type) { + case nil: + dest.SetString("") + return nil + case string: + dest.SetString(src) + return nil + case []byte: + dest.SetString(string(src)) + return nil + case time.Time: + dest.SetString(src.Format(time.RFC3339Nano)) + return nil + case int64: + dest.SetString(strconv.FormatInt(src, 10)) + return nil + case uint64: + dest.SetString(strconv.FormatUint(src, 10)) + return nil + case float64: + dest.SetString(strconv.FormatFloat(src, 'G', -1, 64)) + return nil + default: + return scanError(dest.Type(), src) + } +} + +func scanBytes(dest reflect.Value, src interface{}) error { + switch src := src.(type) { + case nil: + dest.SetBytes(nil) + return nil + case string: + dest.SetBytes([]byte(src)) + return nil + case []byte: + clone := make([]byte, len(src)) + copy(clone, src) + + dest.SetBytes(clone) + return nil + default: + return scanError(dest.Type(), src) + } +} + +func scanTime(dest reflect.Value, src interface{}) error { + switch src := src.(type) { + case nil: + destTime := dest.Addr().Interface().(*time.Time) + *destTime = time.Time{} + return nil + case time.Time: + destTime := dest.Addr().Interface().(*time.Time) + *destTime = src + return nil + case string: + srcTime, err := internal.ParseTime(src) + if err != nil { + return err + } + destTime := dest.Addr().Interface().(*time.Time) + *destTime = srcTime + return nil + case []byte: + srcTime, err := internal.ParseTime(internal.String(src)) + if err != nil { + return err + } + destTime := dest.Addr().Interface().(*time.Time) + *destTime = srcTime + return nil + default: + return scanError(dest.Type(), src) + } +} + +func scanScanner(dest reflect.Value, src interface{}) error { + return dest.Interface().(sql.Scanner).Scan(src) +} + +func scanMsgpack(dest reflect.Value, src interface{}) error { + if src == nil { + return scanNull(dest) + } + + b, err := toBytes(src) + if err != nil { + return err + } + + dec := msgpack.GetDecoder() + defer msgpack.PutDecoder(dec) + + dec.Reset(bytes.NewReader(b)) + return dec.DecodeValue(dest) +} + +func scanJSON(dest reflect.Value, src interface{}) error { + if src == nil { + return scanNull(dest) + } + + b, err := toBytes(src) + if err != nil { + return err + } + + return ogxjson.Unmarshal(b, dest.Addr().Interface()) +} + +func scanJSONUseNumber(dest reflect.Value, src interface{}) error { + if src == nil { + return scanNull(dest) + } + + b, err := toBytes(src) + if err != nil { + return err + } + + dec := ogxjson.NewDecoder(bytes.NewReader(b)) + dec.UseNumber() + return dec.Decode(dest.Addr().Interface()) +} + +func scanIP(dest reflect.Value, src interface{}) error { + if src == nil { + return scanNull(dest) + } + + b, err := toBytes(src) + if err != nil { + return err + } + + ip := net.ParseIP(internal.String(b)) + if ip == nil { + return fmt.Errorf("ogx: invalid ip: %q", b) + } + + ptr := dest.Addr().Interface().(*net.IP) + *ptr = ip + + return nil +} + +func scanIPNet(dest reflect.Value, src interface{}) error { + if src == nil { + return scanNull(dest) + } + + b, err := toBytes(src) + if err != nil { + return err + } + + _, ipnet, err := net.ParseCIDR(internal.String(b)) + if err != nil { + return err + } + + ptr := dest.Addr().Interface().(*net.IPNet) + *ptr = *ipnet + + return nil +} + +func addrScanner(fn ScannerFunc) ScannerFunc { + return func(dest reflect.Value, src interface{}) error { + if !dest.CanAddr() { + return fmt.Errorf("ogx: Scan(nonaddressable %T)", dest.Interface()) + } + return fn(dest.Addr(), src) + } +} + +func toBytes(src interface{}) ([]byte, error) { + switch src := src.(type) { + case string: + return internal.Bytes(src), nil + case []byte: + return src, nil + default: + return nil, fmt.Errorf("ogx: got %T, wanted []byte or string", src) + } +} + +func PtrScanner(fn ScannerFunc) ScannerFunc { + return func(dest reflect.Value, src interface{}) error { + if src == nil { + if !dest.CanAddr() { + if dest.IsNil() { + return nil + } + return fn(dest.Elem(), src) + } + + if !dest.IsNil() { + dest.Set(reflect.New(dest.Type().Elem())) + } + return nil + } + + if dest.IsNil() { + dest.Set(reflect.New(dest.Type().Elem())) + } + + if dest.Kind() == reflect.Map { + return fn(dest, src) + } + + return fn(dest.Elem(), src) + } +} + +func scanNull(dest reflect.Value) error { + if nilable(dest.Kind()) && dest.IsNil() { + return nil + } + dest.Set(reflect.New(dest.Type()).Elem()) + return nil +} + +func scanJSONIntoInterface(dest reflect.Value, src interface{}) error { + if dest.IsNil() { + if src == nil { + return nil + } + + b, err := toBytes(src) + if err != nil { + return err + } + + return ogxjson.Unmarshal(b, dest.Addr().Interface()) + } + + dest = dest.Elem() + if fn := Scanner(dest.Type()); fn != nil { + return fn(dest, src) + } + return scanError(dest.Type(), src) +} + +func scanInterface(dest reflect.Value, src interface{}) error { + if dest.IsNil() { + if src == nil { + return nil + } + dest.Set(reflect.ValueOf(src)) + return nil + } + + dest = dest.Elem() + if fn := Scanner(dest.Type()); fn != nil { + return fn(dest, src) + } + return scanError(dest.Type(), src) +} + +func nilable(kind reflect.Kind) bool { + switch kind { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + return true + } + return false +} + +func scanError(dest reflect.Type, src interface{}) error { + return fmt.Errorf("ogx: can't scan %#v (%T) into %s", src, src, dest.String()) +} diff --git a/ogx/schema/sqlfmt.go b/ogx/schema/sqlfmt.go new file mode 100644 index 00000000..2431dd0a --- /dev/null +++ b/ogx/schema/sqlfmt.go @@ -0,0 +1,87 @@ +package schema + +import ( + "strings" + + "gitee.com/chentanyang/ogx/internal" +) + +type QueryAppender interface { + AppendQuery(fmter Formatter, b []byte) ([]byte, error) +} + +type ColumnsAppender interface { + AppendColumns(fmter Formatter, b []byte) ([]byte, error) +} + +//------------------------------------------------------------------------------ + +// Safe represents a safe SQL query. +type Safe string + +var _ QueryAppender = (*Safe)(nil) + +func (s Safe) AppendQuery(fmter Formatter, b []byte) ([]byte, error) { + return append(b, s...), nil +} + +//------------------------------------------------------------------------------ + +// Ident represents a SQL identifier, for example, table or column name. +type Ident string + +var _ QueryAppender = (*Ident)(nil) + +func (s Ident) AppendQuery(fmter Formatter, b []byte) ([]byte, error) { + return fmter.AppendIdent(b, string(s)), nil +} + +//------------------------------------------------------------------------------ + +type QueryWithArgs struct { + Query string + Args []interface{} +} + +var _ QueryAppender = QueryWithArgs{} + +func SafeQuery(query string, args []interface{}) QueryWithArgs { + if args == nil { + args = make([]interface{}, 0) + } else if len(query) > 0 && strings.IndexByte(query, '?') == -1 { + internal.Warn.Printf("query %q has %v args, but no placeholders", query, args) + } + return QueryWithArgs{ + Query: query, + Args: args, + } +} + +func UnsafeIdent(ident string) QueryWithArgs { + return QueryWithArgs{Query: ident} +} + +func (q QueryWithArgs) IsZero() bool { + return q.Query == "" && q.Args == nil +} + +func (q QueryWithArgs) AppendQuery(fmter Formatter, b []byte) ([]byte, error) { + if q.Args == nil { + return fmter.AppendIdent(b, q.Query), nil + } + return fmter.AppendQuery(b, q.Query, q.Args...), nil +} + +//------------------------------------------------------------------------------ + +type QueryWithSep struct { + QueryWithArgs + Sep string +} + +func SafeQueryWithSep(query string, args []interface{}, sep string) QueryWithSep { + return QueryWithSep{ + QueryWithArgs: SafeQuery(query, args), + Sep: sep, + } +} diff --git a/ogx/schema/sqltype.go b/ogx/schema/sqltype.go new file mode 100644 index 00000000..4664e1dd --- /dev/null +++ b/ogx/schema/sqltype.go @@ -0,0 +1,141 @@ +package schema + +import ( + "bytes" + "database/sql" + "encoding/json" + "reflect" + "time" + + "gitee.com/chentanyang/ogx/dialect" + "gitee.com/chentanyang/ogx/dialect/sqltype" + "gitee.com/chentanyang/ogx/internal" +) + +var ( + ogxNullTimeType = reflect.TypeOf((*NullTime)(nil)).Elem() + nullTimeType = reflect.TypeOf((*sql.NullTime)(nil)).Elem() + nullBoolType = reflect.TypeOf((*sql.NullBool)(nil)).Elem() + nullFloatType = reflect.TypeOf((*sql.NullFloat64)(nil)).Elem() + nullIntType = reflect.TypeOf((*sql.NullInt64)(nil)).Elem() + nullStringType = reflect.TypeOf((*sql.NullString)(nil)).Elem() +) + +var sqlTypes = []string{ + reflect.Bool: sqltype.Boolean, + reflect.Int: sqltype.BigInt, + reflect.Int8: sqltype.SmallInt, + reflect.Int16: sqltype.SmallInt, + reflect.Int32: sqltype.Integer, + reflect.Int64: sqltype.BigInt, + reflect.Uint: sqltype.BigInt, + reflect.Uint8: sqltype.SmallInt, + reflect.Uint16: sqltype.SmallInt, + reflect.Uint32: sqltype.Integer, + reflect.Uint64: sqltype.BigInt, + reflect.Uintptr: sqltype.BigInt, + reflect.Float32: sqltype.Real, + reflect.Float64: sqltype.DoublePrecision, + reflect.Complex64: "", + reflect.Complex128: "", + reflect.Array: "", + reflect.Interface: "", + reflect.Map: sqltype.VarChar, + reflect.Ptr: "", + reflect.Slice: sqltype.VarChar, + reflect.String: sqltype.VarChar, + reflect.Struct: sqltype.VarChar, +} + +func DiscoverSQLType(typ reflect.Type) string { + switch typ { + case timeType, nullTimeType, ogxNullTimeType: + return sqltype.Timestamp + case nullBoolType: + return sqltype.Boolean + case nullFloatType: + return sqltype.DoublePrecision + case nullIntType: + return sqltype.BigInt + case nullStringType: + return sqltype.VarChar + case jsonRawMessageType: + return sqltype.JSON + } + + switch typ.Kind() { + case reflect.Slice: + if typ.Elem().Kind() == reflect.Uint8 { + return sqltype.Blob + } + } + + return sqlTypes[typ.Kind()] +} + +//------------------------------------------------------------------------------ + +var jsonNull = []byte("null") + +// NullTime is a time.Time wrapper that marshals zero time as JSON null and SQL NULL. +type NullTime struct { + time.Time +} + +var ( + _ json.Marshaler = (*NullTime)(nil) + _ json.Unmarshaler = (*NullTime)(nil) + _ sql.Scanner = (*NullTime)(nil) + _ QueryAppender = (*NullTime)(nil) +) + +func (tm NullTime) MarshalJSON() ([]byte, error) { + if tm.IsZero() { + return jsonNull, nil + } + return tm.Time.MarshalJSON() +} + +func (tm *NullTime) UnmarshalJSON(b []byte) error { + if bytes.Equal(b, jsonNull) { + tm.Time = time.Time{} + return nil + } + return tm.Time.UnmarshalJSON(b) +} + +func (tm NullTime) AppendQuery(fmter Formatter, b []byte) ([]byte, error) { + if tm.IsZero() { + return dialect.AppendNull(b), nil + } + return fmter.Dialect().AppendTime(b, tm.Time), nil +} + +func (tm *NullTime) Scan(src interface{}) error { + if src == nil { + tm.Time = time.Time{} + return nil + } + + switch src := src.(type) { + case time.Time: + tm.Time = src + return nil + case string: + newtm, err := internal.ParseTime(src) + if err != nil { + return err + } + tm.Time = newtm + return nil + case []byte: + newtm, err := internal.ParseTime(internal.String(src)) + if err != nil { + return err + } + tm.Time = newtm + return nil + default: + return scanError(ogxNullTimeType, src) + } +} diff --git a/ogx/schema/table.go b/ogx/schema/table.go new file mode 100644 index 00000000..20a0c13a --- /dev/null +++ b/ogx/schema/table.go @@ -0,0 +1,1029 @@ +package schema + +import ( + "database/sql" + "fmt" + "reflect" + "strings" + "sync" + "time" + + "github.com/jinzhu/inflection" + + "gitee.com/chentanyang/ogx/internal" + "gitee.com/chentanyang/ogx/internal/tagparser" +) + +const ( + beforeAppendModelHookFlag internal.Flag = 1 << iota + beforeScanHookFlag + afterScanHookFlag + beforeScanRowHookFlag + afterScanRowHookFlag +) + +var ( + baseModelType = reflect.TypeOf((*BaseModel)(nil)).Elem() + tableNameInflector = inflection.Plural +) + +type BaseModel struct{} + +// SetTableNameInflector overrides the default func that pluralizes +// model name to get table name, e.g. my_article becomes my_articles. +func SetTableNameInflector(fn func(string) string) { + tableNameInflector = fn +} + +// Table represents a SQL table created from Go struct. +type Table struct { + dialect Dialect + + Type reflect.Type + ZeroValue reflect.Value // reflect.Struct + ZeroIface interface{} // struct pointer + + TypeName string + ModelName string + + Name string + SQLName Safe + SQLNameForSelects Safe + Alias string + SQLAlias Safe + + Fields []*Field // PKs + DataFields + PKs []*Field + DataFields []*Field + + fieldsMapMu sync.RWMutex + FieldMap map[string]*Field + + Relations map[string]*Relation + Unique map[string][]*Field + + SoftDeleteField *Field + UpdateSoftDeleteField func(fv reflect.Value, tm time.Time) error + + allFields []*Field // read only + + flags internal.Flag +} + +func newTable(dialect Dialect, typ reflect.Type) *Table { + t := new(Table) + t.dialect = dialect + t.Type = typ + t.ZeroValue = reflect.New(t.Type).Elem() + t.ZeroIface = reflect.New(t.Type).Interface() + t.TypeName = internal.ToExported(t.Type.Name()) + t.ModelName = internal.Underscore(t.Type.Name()) + tableName := tableNameInflector(t.ModelName) + t.setName(tableName) + t.Alias = t.ModelName + t.SQLAlias = t.quoteIdent(t.ModelName) + + hooks := []struct { + typ reflect.Type + flag internal.Flag + }{ + {beforeAppendModelHookType, beforeAppendModelHookFlag}, + + {beforeScanHookType, beforeScanHookFlag}, + {afterScanHookType, afterScanHookFlag}, + + {beforeScanRowHookType, beforeScanRowHookFlag}, + {afterScanRowHookType, afterScanRowHookFlag}, + } + + typ = reflect.PtrTo(t.Type) + for _, hook := range hooks { + if typ.Implements(hook.typ) { + t.flags = t.flags.Set(hook.flag) + } + } + + // Deprecated. + deprecatedHooks := []struct { + typ reflect.Type + flag internal.Flag + msg string + }{ + {beforeScanHookType, beforeScanHookFlag, "rename BeforeScan hook to BeforeScanRow"}, + {afterScanHookType, afterScanHookFlag, "rename AfterScan hook to AfterScanRow"}, + } + for _, hook := range deprecatedHooks { + if typ.Implements(hook.typ) { + internal.Deprecated.Printf("%s: %s", t.TypeName, hook.msg) + t.flags = t.flags.Set(hook.flag) + } + } + + return t +} + +func (t *Table) init1() { + t.initFields() +} + +func (t *Table) init2() { + t.initRelations() +} + +func (t *Table) setName(name string) { + t.Name = name + t.SQLName = t.quoteIdent(name) + t.SQLNameForSelects = t.quoteIdent(name) + if t.SQLAlias == "" { + t.Alias = name + t.SQLAlias = t.quoteIdent(name) + } +} + +func (t *Table) String() string { + return "model=" + t.TypeName +} + +func (t *Table) CheckPKs() error { + if len(t.PKs) == 0 { + return fmt.Errorf("ogx: %s does not have primary keys", t) + } + return nil +} + +func (t *Table) addField(field *Field) { + t.Fields = append(t.Fields, field) + if field.IsPK { + t.PKs = append(t.PKs, field) + } else { + t.DataFields = append(t.DataFields, field) + } + t.FieldMap[field.Name] = field +} + +func (t *Table) removeField(field *Field) { + t.Fields = removeField(t.Fields, field) + if field.IsPK { + t.PKs = removeField(t.PKs, field) + } else { + t.DataFields = removeField(t.DataFields, field) + } + delete(t.FieldMap, field.Name) +} + +func (t *Table) fieldWithLock(name string) *Field { + t.fieldsMapMu.RLock() + field := t.FieldMap[name] + t.fieldsMapMu.RUnlock() + return field +} + +func (t *Table) HasField(name string) bool { + _, ok := t.FieldMap[name] + return ok +} + +func (t *Table) Field(name string) (*Field, error) { + field, ok := t.FieldMap[name] + if !ok { + return nil, fmt.Errorf("ogx: %s does not have column=%s", t, name) + } + return field, nil +} + +func (t *Table) fieldByGoName(name string) *Field { + for _, f := range t.allFields { + if f.GoName == name { + return f + } + } + return nil +} + +func (t *Table) initFields() { + t.Fields = make([]*Field, 0, t.Type.NumField()) + t.FieldMap = make(map[string]*Field, t.Type.NumField()) + t.addFields(t.Type, "", nil) +} + +func (t *Table) addFields(typ reflect.Type, prefix string, index []int) { + for i := 0; i < typ.NumField(); i++ { + f := typ.Field(i) + unexported := f.PkgPath != "" + + if unexported && !f.Anonymous { // unexported + continue + } + if f.Tag.Get("ogx") == "-" { + continue + } + + if f.Anonymous { + if f.Name == "BaseModel" && f.Type == baseModelType { + if len(index) == 0 { + t.processBaseModelField(f) + } + continue + } + + // If field is an embedded struct, add each field of the embedded struct. + fieldType := indirectType(f.Type) + if fieldType.Kind() == reflect.Struct { + t.addFields(fieldType, "", withIndex(index, f.Index)) + + tag := tagparser.Parse(f.Tag.Get("ogx")) + if tag.HasOption("inherit") || tag.HasOption("extend") { + embeddedTable := t.dialect.Tables().Ref(fieldType) + t.TypeName = embeddedTable.TypeName + t.SQLName = embeddedTable.SQLName + t.SQLNameForSelects = embeddedTable.SQLNameForSelects + t.Alias = embeddedTable.Alias + t.SQLAlias = embeddedTable.SQLAlias + t.ModelName = embeddedTable.ModelName + } + continue + } + } + + // If field is not a struct, add it. + // This will also add any embedded non-struct type as a field. + if field := t.newField(f, prefix, index); field != nil { + t.addField(field) + } + } +} + +func (t *Table) processBaseModelField(f reflect.StructField) { + tag := tagparser.Parse(f.Tag.Get("ogx")) + + if isKnownTableOption(tag.Name) { + internal.Warn.Printf( + "%s.%s tag name %q is also an option name, is it a mistake? Try table:%s.", + t.TypeName, f.Name, tag.Name, tag.Name, + ) + } + + for name := range tag.Options { + if !isKnownTableOption(name) { + internal.Warn.Printf("%s.%s has unknown tag option: %q", t.TypeName, f.Name, name) + } + } + + if tag.Name != "" { + t.setName(tag.Name) + } + + if s, ok := tag.Option("table"); ok { + t.setName(s) + } + + if s, ok := tag.Option("select"); ok { + t.SQLNameForSelects = t.quoteTableName(s) + } + + if s, ok := tag.Option("alias"); ok { + t.Alias = s + t.SQLAlias = t.quoteIdent(s) + } +} + +// nolint +func (t *Table) newField(f reflect.StructField, prefix string, index []int) *Field { + tag := tagparser.Parse(f.Tag.Get("ogx")) + + if nextPrefix, ok := tag.Option("embed"); ok { + fieldType := indirectType(f.Type) + if fieldType.Kind() != reflect.Struct { + panic(fmt.Errorf("ogx: embed %s.%s: got %s, wanted reflect.Struct", + t.TypeName, f.Name, fieldType.Kind())) + } + t.addFields(fieldType, prefix+nextPrefix, withIndex(index, f.Index)) + return nil + } + + sqlName := internal.Underscore(f.Name) + if tag.Name != "" && tag.Name != sqlName { + if isKnownFieldOption(tag.Name) { + internal.Warn.Printf( + "%s.%s tag name %q is also an option name, is it a mistake? Try column:%s.", + t.TypeName, f.Name, tag.Name, tag.Name, + ) + } + sqlName = tag.Name + } + if s, ok := tag.Option("column"); ok { + sqlName = s + } + sqlName = prefix + sqlName + + for name := range tag.Options { + if !isKnownFieldOption(name) { + internal.Warn.Printf("%s.%s has unknown tag option: %q", t.TypeName, f.Name, name) + } + } + + index = withIndex(index, f.Index) + if field := t.fieldWithLock(sqlName); field != nil { + if indexEqual(field.Index, index) { + return field + } + t.removeField(field) + } + + field := &Field{ + StructField: f, + IsPtr: f.Type.Kind() == reflect.Ptr, + + Tag: tag, + IndirectType: indirectType(f.Type), + Index: index, + + Name: sqlName, + GoName: f.Name, + SQLName: t.quoteIdent(sqlName), + } + + field.NotNull = tag.HasOption("notnull") + field.NullZero = tag.HasOption("nullzero") + if tag.HasOption("pk") { + field.IsPK = true + field.NotNull = true + } + if tag.HasOption("autoincrement") { + field.AutoIncrement = true + field.NullZero = true + } + if tag.HasOption("identity") { + field.Identity = true + } + + if v, ok := tag.Options["unique"]; ok { + var names []string + if len(v) == 1 { + // Split the value by comma, this will allow multiple names to be specified. + // We can use this to create multiple named unique constraints where a single column + // might be included in multiple constraints. + names = strings.Split(v[0], ",") + } else { + names = v + } + + for _, uniqueName := range names { + if t.Unique == nil { + t.Unique = make(map[string][]*Field) + } + t.Unique[uniqueName] = append(t.Unique[uniqueName], field) + } + } + if s, ok := tag.Option("default"); ok { + field.SQLDefault = s + field.NullZero = true + } + if s, ok := field.Tag.Option("type"); ok { + field.UserSQLType = s + } + field.DiscoveredSQLType = DiscoverSQLType(field.IndirectType) + field.Append = FieldAppender(t.dialect, field) + field.Scan = FieldScanner(t.dialect, field) + field.IsZero = zeroChecker(field.StructField.Type) + + if v, ok := tag.Option("alt"); ok { + t.FieldMap[v] = field + } + + t.allFields = append(t.allFields, field) + if tag.HasOption("scanonly") { + t.FieldMap[field.Name] = field + if field.IndirectType.Kind() == reflect.Struct { + t.inlineFields(field, nil) + } + return nil + } + + if _, ok := tag.Options["soft_delete"]; ok { + t.SoftDeleteField = field + t.UpdateSoftDeleteField = softDeleteFieldUpdater(field) + } + + return field +} + +//--------------------------------------------------------------------------------------- + +func (t *Table) initRelations() { + for i := 0; i < len(t.Fields); { + f := t.Fields[i] + if t.tryRelation(f) { + t.Fields = removeField(t.Fields, f) + t.DataFields = removeField(t.DataFields, f) + } else { + i++ + } + + if f.IndirectType.Kind() == reflect.Struct { + t.inlineFields(f, nil) + } + } +} + +func (t *Table) tryRelation(field *Field) bool { + if rel, ok := field.Tag.Option("rel"); ok { + t.initRelation(field, rel) + return true + } + if field.Tag.HasOption("m2m") { + t.addRelation(t.m2mRelation(field)) + return true + } + + if field.Tag.HasOption("join") { + internal.Warn.Printf( + `%s.%s "join" option must come together with "rel" option`, + t.TypeName, field.GoName, + ) + } + + return false +} + +func (t *Table) initRelation(field *Field, rel string) { + switch rel { + case "belongs-to": + t.addRelation(t.belongsToRelation(field)) + case "has-one": + t.addRelation(t.hasOneRelation(field)) + case "has-many": + t.addRelation(t.hasManyRelation(field)) + default: + panic(fmt.Errorf("ogx: unknown relation=%s on field=%s", rel, field.GoName)) + } +} + +func (t *Table) addRelation(rel *Relation) { + if t.Relations == nil { + t.Relations = make(map[string]*Relation) + } + _, ok := t.Relations[rel.Field.GoName] + if ok { + panic(fmt.Errorf("%s already has %s", t, rel)) + } + t.Relations[rel.Field.GoName] = rel +} + +func (t *Table) belongsToRelation(field *Field) *Relation { + joinTable := t.dialect.Tables().Ref(field.IndirectType) + if err := joinTable.CheckPKs(); err != nil { + panic(err) + } + + rel := &Relation{ + Type: HasOneRelation, + Field: field, + JoinTable: joinTable, + } + + if field.Tag.HasOption("join_on") { + rel.Condition = field.Tag.Options["join_on"] + } + + rel.OnUpdate = "ON UPDATE NO ACTION" + if onUpdate, ok := field.Tag.Options["on_update"]; ok { + if len(onUpdate) > 1 { + panic(fmt.Errorf("ogx: %s belongs-to %s: on_update option must be a single field", t.TypeName, field.GoName)) + } + + rule := strings.ToUpper(onUpdate[0]) + if !isKnownFKRule(rule) { + internal.Warn.Printf("ogx: %s belongs-to %s: unknown on_update rule %s", t.TypeName, field.GoName, rule) + } + + s := fmt.Sprintf("ON UPDATE %s", rule) + rel.OnUpdate = s + } + + rel.OnDelete = "ON DELETE NO ACTION" + if onDelete, ok := field.Tag.Options["on_delete"]; ok { + if len(onDelete) > 1 { + panic(fmt.Errorf("ogx: %s belongs-to %s: on_delete option must be a single field", t.TypeName, field.GoName)) + } + + rule := strings.ToUpper(onDelete[0]) + if !isKnownFKRule(rule) { + internal.Warn.Printf("ogx: %s belongs-to %s: unknown on_delete rule %s", t.TypeName, field.GoName, rule) + } + s := fmt.Sprintf("ON DELETE %s", rule) + rel.OnDelete = s + } + + if join, ok := field.Tag.Options["join"]; ok { + baseColumns, joinColumns := parseRelationJoin(join) + for i, baseColumn := range baseColumns { + joinColumn := joinColumns[i] + + if f := t.fieldWithLock(baseColumn); f != nil { + rel.BaseFields = append(rel.BaseFields, f) + } else { + panic(fmt.Errorf( + "ogx: %s belongs-to %s: %s must have column %s", + t.TypeName, field.GoName, t.TypeName, baseColumn, + )) + } + + if f := joinTable.fieldWithLock(joinColumn); f != nil { + rel.JoinFields = append(rel.JoinFields, f) + } else { + panic(fmt.Errorf( + "ogx: %s belongs-to %s: %s must have column %s", + t.TypeName, field.GoName, t.TypeName, baseColumn, + )) + } + } + return rel + } + + rel.JoinFields = joinTable.PKs + fkPrefix := internal.Underscore(field.GoName) + "_" + for _, joinPK := range joinTable.PKs { + fkName := fkPrefix + joinPK.Name + if fk := t.fieldWithLock(fkName); fk != nil { + rel.BaseFields = append(rel.BaseFields, fk) + continue + } + + if fk := t.fieldWithLock(joinPK.Name); fk != nil { + rel.BaseFields = append(rel.BaseFields, fk) + continue + } + + panic(fmt.Errorf( + "ogx: %s belongs-to %s: %s must have column %s "+ + "(to override, use join:base_column=join_column tag on %s field)", + t.TypeName, field.GoName, t.TypeName, fkName, field.GoName, + )) + } + return rel +} + +func (t *Table) hasOneRelation(field *Field) *Relation { + if err := t.CheckPKs(); err != nil { + panic(err) + } + + joinTable := t.dialect.Tables().Ref(field.IndirectType) + rel := &Relation{ + Type: BelongsToRelation, + Field: field, + JoinTable: joinTable, + } + + if field.Tag.HasOption("join_on") { + rel.Condition = field.Tag.Options["join_on"] + } + + if join, ok := field.Tag.Options["join"]; ok { + baseColumns, joinColumns := parseRelationJoin(join) + for i, baseColumn := range baseColumns { + if f := t.fieldWithLock(baseColumn); f != nil { + rel.BaseFields = append(rel.BaseFields, f) + } else { + panic(fmt.Errorf( + "ogx: %s has-one %s: %s must have column %s", + field.GoName, t.TypeName, joinTable.TypeName, baseColumn, + )) + } + + joinColumn := joinColumns[i] + if f := joinTable.fieldWithLock(joinColumn); f != nil { + rel.JoinFields = append(rel.JoinFields, f) + } else { + panic(fmt.Errorf( + "ogx: %s has-one %s: %s must have column %s", + field.GoName, t.TypeName, joinTable.TypeName, baseColumn, + )) + } + } + return rel + } + + rel.BaseFields = t.PKs + fkPrefix := internal.Underscore(t.ModelName) + "_" + for _, pk := range t.PKs { + fkName := fkPrefix + pk.Name + if f := joinTable.fieldWithLock(fkName); f != nil { + rel.JoinFields = append(rel.JoinFields, f) + continue + } + + if f := joinTable.fieldWithLock(pk.Name); f != nil { + rel.JoinFields = append(rel.JoinFields, f) + continue + } + + panic(fmt.Errorf( + "ogx: %s has-one %s: %s must have column %s "+ + "(to override, use join:base_column=join_column tag on %s field)", + field.GoName, t.TypeName, joinTable.TypeName, fkName, field.GoName, + )) + } + return rel +} + +func (t *Table) hasManyRelation(field *Field) *Relation { + if err := t.CheckPKs(); err != nil { + panic(err) + } + if field.IndirectType.Kind() != reflect.Slice { + panic(fmt.Errorf( + "ogx: %s.%s has-many relation requires slice, got %q", + t.TypeName, field.GoName, field.IndirectType.Kind(), + )) + } + + joinTable := t.dialect.Tables().Ref(indirectType(field.IndirectType.Elem())) + polymorphicValue, isPolymorphic := field.Tag.Option("polymorphic") + rel := &Relation{ + Type: HasManyRelation, + Field: field, + JoinTable: joinTable, + } + + if field.Tag.HasOption("join_on") { + rel.Condition = field.Tag.Options["join_on"] + } + + var polymorphicColumn string + + if join, ok := field.Tag.Options["join"]; ok { + baseColumns, joinColumns := parseRelationJoin(join) + for i, baseColumn := range baseColumns { + joinColumn := joinColumns[i] + + if isPolymorphic && baseColumn == "type" { + polymorphicColumn = joinColumn + continue + } + + if f := t.fieldWithLock(baseColumn); f != nil { + rel.BaseFields = append(rel.BaseFields, f) + } else { + panic(fmt.Errorf( + "ogx: %s has-many %s: %s must have column %s", + t.TypeName, field.GoName, t.TypeName, baseColumn, + )) + } + + if f := joinTable.fieldWithLock(joinColumn); f != nil { + rel.JoinFields = append(rel.JoinFields, f) + } else { + panic(fmt.Errorf( + "ogx: %s has-many %s: %s must have column %s", + t.TypeName, field.GoName, t.TypeName, baseColumn, + )) + } + } + } else { + rel.BaseFields = t.PKs + fkPrefix := internal.Underscore(t.ModelName) + "_" + if isPolymorphic { + polymorphicColumn = fkPrefix + "type" + } + + for _, pk := range t.PKs { + joinColumn := fkPrefix + pk.Name + if fk := joinTable.fieldWithLock(joinColumn); fk != nil { + rel.JoinFields = append(rel.JoinFields, fk) + continue + } + + if fk := joinTable.fieldWithLock(pk.Name); fk != nil { + rel.JoinFields = append(rel.JoinFields, fk) + continue + } + + panic(fmt.Errorf( + "ogx: %s has-many %s: %s must have column %s "+ + "(to override, use join:base_column=join_column tag on the field %s)", + t.TypeName, field.GoName, joinTable.TypeName, joinColumn, field.GoName, + )) + } + } + + if isPolymorphic { + rel.PolymorphicField = joinTable.fieldWithLock(polymorphicColumn) + if rel.PolymorphicField == nil { + panic(fmt.Errorf( + "ogx: %s has-many %s: %s must have polymorphic column %s", + t.TypeName, field.GoName, joinTable.TypeName, polymorphicColumn, + )) + } + + if polymorphicValue == "" { + polymorphicValue = t.ModelName + } + rel.PolymorphicValue = polymorphicValue + } + + return rel +} + +func (t *Table) m2mRelation(field *Field) *Relation { + if field.IndirectType.Kind() != reflect.Slice { + panic(fmt.Errorf( + "ogx: %s.%s m2m relation requires slice, got %q", + t.TypeName, field.GoName, field.IndirectType.Kind(), + )) + } + joinTable := t.dialect.Tables().Ref(indirectType(field.IndirectType.Elem())) + + if err := t.CheckPKs(); err != nil { + panic(err) + } + if err := joinTable.CheckPKs(); err != nil { + panic(err) + } + + m2mTableName, ok := field.Tag.Option("m2m") + if !ok { + panic(fmt.Errorf("ogx: %s must have m2m tag option", field.GoName)) + } + + m2mTable := t.dialect.Tables().ByName(m2mTableName) + if m2mTable == nil { + panic(fmt.Errorf( + "ogx: can't find m2m %s table (use db.RegisterModel)", + m2mTableName, + )) + } + + rel := &Relation{ + Type: ManyToManyRelation, + Field: field, + JoinTable: joinTable, + M2MTable: m2mTable, + } + + if field.Tag.HasOption("join_on") { + rel.Condition = field.Tag.Options["join_on"] + } + + var leftColumn, rightColumn string + + if join, ok := field.Tag.Options["join"]; ok { + left, right := parseRelationJoin(join) + leftColumn = left[0] + rightColumn = right[0] + } else { + leftColumn = t.TypeName + rightColumn = joinTable.TypeName + } + + leftField := m2mTable.fieldByGoName(leftColumn) + if leftField == nil { + panic(fmt.Errorf( + "ogx: %s many-to-many %s: %s must have field %s "+ + "(to override, use tag join:LeftField=RightField on field %s.%s", + t.TypeName, field.GoName, m2mTable.TypeName, leftColumn, t.TypeName, field.GoName, + )) + } + + rightField := m2mTable.fieldByGoName(rightColumn) + if rightField == nil { + panic(fmt.Errorf( + "ogx: %s many-to-many %s: %s must have field %s "+ + "(to override, use tag join:LeftField=RightField on field %s.%s", + t.TypeName, field.GoName, m2mTable.TypeName, rightColumn, t.TypeName, field.GoName, + )) + } + + leftRel := m2mTable.belongsToRelation(leftField) + rel.BaseFields = leftRel.JoinFields + rel.M2MBaseFields = leftRel.BaseFields + + rightRel := m2mTable.belongsToRelation(rightField) + rel.JoinFields = rightRel.JoinFields + rel.M2MJoinFields = rightRel.BaseFields + + return rel +} + +func (t *Table) inlineFields(field *Field, seen map[reflect.Type]struct{}) { + if seen == nil { + seen = map[reflect.Type]struct{}{t.Type: {}} + } + + if _, ok := seen[field.IndirectType]; ok { + return + } + seen[field.IndirectType] = struct{}{} + + joinTable := t.dialect.Tables().Ref(field.IndirectType) + for _, f := range joinTable.allFields { + f = f.Clone() + f.GoName = field.GoName + "_" + f.GoName + f.Name = field.Name + "__" + f.Name + f.SQLName = t.quoteIdent(f.Name) + f.Index = withIndex(field.Index, f.Index) + + t.fieldsMapMu.Lock() + if _, ok := t.FieldMap[f.Name]; !ok { + t.FieldMap[f.Name] = f + } + t.fieldsMapMu.Unlock() + + if f.IndirectType.Kind() != reflect.Struct { + continue + } + + if _, ok := seen[f.IndirectType]; !ok { + t.inlineFields(f, seen) + } + } +} + +//------------------------------------------------------------------------------ + +func (t *Table) Dialect() Dialect { return t.dialect } + +func (t *Table) HasBeforeAppendModelHook() bool { return t.flags.Has(beforeAppendModelHookFlag) } + +func (t *Table) HasBeforeScanRowHook() bool { return t.flags.Has(beforeScanRowHookFlag) } +func (t *Table) HasAfterScanRowHook() bool { return t.flags.Has(afterScanRowHookFlag) } + +//------------------------------------------------------------------------------ + +func (t *Table) AppendNamedArg( + fmter Formatter, b []byte, name string, strct reflect.Value, +) ([]byte, bool) { + if field, ok := t.FieldMap[name]; ok { + return field.AppendValue(fmter, b, strct), true + } + return b, false +} + +func (t *Table) quoteTableName(s string) Safe { + // Don't quote if table name contains placeholder (?) or parentheses. + if strings.IndexByte(s, '?') >= 0 || + strings.IndexByte(s, '(') >= 0 || + strings.IndexByte(s, ')') >= 0 { + return Safe(s) + } + return t.quoteIdent(s) +} + +func (t *Table) quoteIdent(s string) Safe { + return Safe(NewFormatter(t.dialect).AppendIdent(nil, s)) +} + +func isKnownTableOption(name string) bool { + switch name { + case "table", "alias", "select": + return true + } + return false +} + +func isKnownFieldOption(name string) bool { + switch name { + case "column", + "alias", + "type", + "array", + "hstore", + "composite", + "json_use_number", + "msgpack", + "notnull", + "nullzero", + "default", + "unique", + "soft_delete", + "scanonly", + "skipupdate", + + "pk", + "autoincrement", + "rel", + "join", + "join_on", + "on_update", + "on_delete", + "m2m", + "polymorphic", + "identity": + return true + } + return false +} + +func isKnownFKRule(name string) bool { + switch name { + case "CASCADE", + "RESTRICT", + "SET NULL", + "SET DEFAULT": + return true + } + return false +} + +func removeField(fields []*Field, field *Field) []*Field { + for i, f := range fields { + if f == field { + return append(fields[:i], fields[i+1:]...) + } + } + return fields +} + +func parseRelationJoin(join []string) ([]string, []string) { + var ss []string + if len(join) == 1 { + ss = strings.Split(join[0], ",") + } else { + ss = join + } + + baseColumns := make([]string, len(ss)) + joinColumns := make([]string, len(ss)) + for i, s := range ss { + ss := strings.Split(strings.TrimSpace(s), "=") + if len(ss) != 2 { + panic(fmt.Errorf("can't parse relation join: %q", join)) + } + baseColumns[i] = ss[0] + joinColumns[i] = ss[1] + } + return baseColumns, joinColumns +} + +//------------------------------------------------------------------------------ + +func softDeleteFieldUpdater(field *Field) func(fv reflect.Value, tm time.Time) error { + typ := field.StructField.Type + + switch typ { + case timeType: + return func(fv reflect.Value, tm time.Time) error { + ptr := fv.Addr().Interface().(*time.Time) + *ptr = tm + return nil + } + case nullTimeType: + return func(fv reflect.Value, tm time.Time) error { + ptr := fv.Addr().Interface().(*sql.NullTime) + *ptr = sql.NullTime{Time: tm} + return nil + } + case nullIntType: + return func(fv reflect.Value, tm time.Time) error { + ptr := fv.Addr().Interface().(*sql.NullInt64) + *ptr = sql.NullInt64{Int64: tm.UnixNano()} + return nil + } + } + + switch field.IndirectType.Kind() { + case reflect.Int64: + return func(fv reflect.Value, tm time.Time) error { + ptr := fv.Addr().Interface().(*int64) + *ptr = tm.UnixNano() + return nil + } + case reflect.Ptr: + typ = typ.Elem() + default: + return softDeleteFieldUpdaterFallback(field) + } + + switch typ { //nolint:gocritic + case timeType: + return func(fv reflect.Value, tm time.Time) error { + fv.Set(reflect.ValueOf(&tm)) + return nil + } + } + + switch typ.Kind() { //nolint:gocritic + case reflect.Int64: + return func(fv reflect.Value, tm time.Time) error { + utime := tm.UnixNano() + fv.Set(reflect.ValueOf(&utime)) + return nil + } + } + + return softDeleteFieldUpdaterFallback(field) +} + +func softDeleteFieldUpdaterFallback(field *Field) func(fv reflect.Value, tm time.Time) error { + return func(fv reflect.Value, tm time.Time) error { + return field.ScanWithCheck(fv, tm) + } +} + +func withIndex(a, b []int) []int { + dest := make([]int, 0, len(a)+len(b)) + dest = append(dest, a...) + dest = append(dest, b...) + return dest +} diff --git a/ogx/schema/tables.go b/ogx/schema/tables.go new file mode 100644 index 00000000..b6215a14 --- /dev/null +++ b/ogx/schema/tables.go @@ -0,0 +1,151 @@ +package schema + +import ( + "fmt" + "reflect" + "sync" +) + +type tableInProgress struct { + table *Table + + init1Once sync.Once + init2Once sync.Once +} + +func newTableInProgress(table *Table) *tableInProgress { + return &tableInProgress{ + table: table, + } +} + +func (inp *tableInProgress) init1() bool { + var inited bool + inp.init1Once.Do(func() { + inp.table.init1() + inited = true + }) + return inited +} + +func (inp *tableInProgress) init2() bool { + var inited bool + inp.init2Once.Do(func() { + inp.table.init2() + inited = true + }) + return inited +} + +type Tables struct { + dialect Dialect + tables sync.Map + + mu sync.RWMutex + inProgress map[reflect.Type]*tableInProgress +} + +func NewTables(dialect Dialect) *Tables { + return &Tables{ + dialect: dialect, + inProgress: make(map[reflect.Type]*tableInProgress), + } +} + +func (t *Tables) Register(models ...interface{}) { + for _, model := range models { + _ = t.Get(reflect.TypeOf(model).Elem()) + } +} + +func (t *Tables) Get(typ reflect.Type) *Table { + return t.table(typ, false) +} + +func (t *Tables) Ref(typ reflect.Type) *Table { + return t.table(typ, true) +} + +func (t *Tables) table(typ reflect.Type, allowInProgress bool) *Table { + typ = indirectType(typ) + if typ.Kind() != reflect.Struct { + panic(fmt.Errorf("got %s, wanted %s", typ.Kind(), reflect.Struct)) + } + + if v, ok := t.tables.Load(typ); ok { + return v.(*Table) + } + + t.mu.Lock() + + if v, ok := t.tables.Load(typ); ok { + t.mu.Unlock() + return v.(*Table) + } + + var table *Table + + inProgress := t.inProgress[typ] + if inProgress == nil { + table = newTable(t.dialect, typ) + inProgress = newTableInProgress(table) + t.inProgress[typ] = inProgress + } else { + table = inProgress.table + } + + t.mu.Unlock() + + inProgress.init1() + if allowInProgress { + return table + } + + if !inProgress.init2() { + return table + } + + t.mu.Lock() + delete(t.inProgress, typ) + t.tables.Store(typ, table) + t.mu.Unlock() + + t.dialect.OnTable(table) + + for _, field := range table.FieldMap { + if field.UserSQLType == "" { + field.UserSQLType = field.DiscoveredSQLType + } + if field.CreateTableSQLType == "" { + field.CreateTableSQLType = field.UserSQLType + } + } + + return table +} + +func (t *Tables) ByModel(name string) *Table { + var found *Table + t.tables.Range(func(key, value interface{}) bool { + t := value.(*Table) + if t.TypeName == name { + found = t + return false + } + return true + }) + return found +} + +func (t *Tables) ByName(name string) *Table { + var found *Table + t.tables.Range(func(key, value interface{}) bool { + t := value.(*Table) + if t.Name == name { + found = t + return false + } + return true + }) + return found +} diff --git a/ogx/schema/zerochecker.go b/ogx/schema/zerochecker.go new file mode 100644 index 00000000..f088b8c2 --- /dev/null +++ b/ogx/schema/zerochecker.go @@ -0,0 +1,122 @@ +package schema + +import ( + "database/sql/driver" + "reflect" +) + +var isZeroerType = reflect.TypeOf((*isZeroer)(nil)).Elem() + +type isZeroer interface { + IsZero() bool +} + +type IsZeroerFunc func(reflect.Value) bool + +func zeroChecker(typ reflect.Type) IsZeroerFunc { + if typ.Implements(isZeroerType) { + return isZeroInterface + } + + kind := typ.Kind() + + if kind != reflect.Ptr { + ptr := reflect.PtrTo(typ) + if ptr.Implements(isZeroerType) { + return addrChecker(isZeroInterface) + } + } + + switch kind { + case reflect.Array: + if typ.Elem().Kind() == reflect.Uint8 { + return isZeroBytes + } + return isZeroLen + case reflect.String: + return isZeroLen + case reflect.Bool: + return isZeroBool + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return isZeroInt + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return isZeroUint + case reflect.Float32, reflect.Float64: + return isZeroFloat + case reflect.Interface, reflect.Ptr, reflect.Slice, reflect.Map: + return isNil + } + + if typ.Implements(driverValuerType) { + return isZeroDriverValue + } + + return notZero +} + +func addrChecker(fn IsZeroerFunc) IsZeroerFunc { + return func(v reflect.Value) bool { + if !v.CanAddr() { + return false + } + return fn(v.Addr()) + } +} + +func isZeroInterface(v reflect.Value) bool { + if v.Kind() == reflect.Ptr && v.IsNil() { + return true + } + return v.Interface().(isZeroer).IsZero() +} + +func isZeroDriverValue(v reflect.Value) bool { + if v.Kind() == reflect.Ptr { + return v.IsNil() + } + + valuer := v.Interface().(driver.Valuer) + value, err := valuer.Value() + if err != nil { + return false + } + return value == nil +} + +func isZeroLen(v reflect.Value) bool { + return v.Len() == 0 +} + +func isNil(v reflect.Value) bool { + return v.IsNil() +} + +func isZeroBool(v reflect.Value) bool { + return !v.Bool() +} + +func isZeroInt(v reflect.Value) bool { + return v.Int() == 0 +} + +func isZeroUint(v reflect.Value) bool { + return v.Uint() == 0 +} + +func isZeroFloat(v reflect.Value) bool { + return v.Float() == 0 +} + +func isZeroBytes(v reflect.Value) bool { + b := v.Slice(0, v.Len()).Bytes() + for _, c := range b { + if c != 0 { + return false + } + } + return true +} + +func notZero(v reflect.Value) bool { + return false +} diff --git a/ogx/util.go b/ogx/util.go new file mode 100644 index 00000000..414887fb --- /dev/null +++ b/ogx/util.go @@ -0,0 +1,114 @@ +package ogx + +import "reflect" + +func indirect(v reflect.Value) reflect.Value { + switch v.Kind() { + case reflect.Interface: + return indirect(v.Elem()) + case reflect.Ptr: + return v.Elem() + default: + return v + } +} + +func walk(v reflect.Value, index []int, fn func(reflect.Value)) { + v = reflect.Indirect(v) + switch v.Kind() { + case reflect.Slice: + sliceLen := v.Len() + for i := 0; i < sliceLen; i++ { + visitField(v.Index(i), index, fn) + } + default: + visitField(v, index, fn) + } +} + +func visitField(v reflect.Value, index []int, fn func(reflect.Value)) { + v = reflect.Indirect(v) + if len(index) > 0 { + v = v.Field(index[0]) + if v.Kind() == reflect.Ptr && v.IsNil() { + return + } + walk(v, index[1:], fn) + } else { + fn(v) + } +} + +func typeByIndex(t reflect.Type, index []int) reflect.Type { + for _, x := range index { + switch t.Kind() { + case reflect.Ptr: + t = t.Elem() + case reflect.Slice: + t = indirectType(t.Elem()) + } + t = t.Field(x).Type + } + return indirectType(t) +} + +func indirectType(t reflect.Type) reflect.Type { + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t +} + +func sliceElemType(v reflect.Value) reflect.Type { + elemType := v.Type().Elem() + if elemType.Kind() == reflect.Interface && v.Len() > 0 { + return indirect(v.Index(0).Elem()).Type() + } + return indirectType(elemType) +} + +func makeSliceNextElemFunc(v reflect.Value) func() reflect.Value { + if v.Kind() == reflect.Array { + var pos int + return func() reflect.Value { + v := v.Index(pos) + pos++ + return v + } + } + + sliceType := v.Type() + elemType := sliceType.Elem() + + if elemType.Kind() == reflect.Ptr { + elemType = elemType.Elem() + return func() reflect.Value { + if v.Len() < v.Cap() { + v.Set(v.Slice(0, v.Len()+1)) + elem := v.Index(v.Len() - 1) + if elem.IsNil() { + elem.Set(reflect.New(elemType)) + } + return elem.Elem() + } + + elem := reflect.New(elemType) + v.Set(reflect.Append(v, elem)) + return elem.Elem() + } + } + + zero := reflect.Zero(elemType) + return func() reflect.Value { + l := v.Len() + c := v.Cap() + + if l < c { + v.Set(v.Slice(0, l+1)) + return v.Index(l) + } + + v.Set(reflect.Append(v, zero)) + return v.Index(l) + } +} diff --git a/ogx/version.go b/ogx/version.go new file mode 100644 index 00000000..3cd3784e --- /dev/null +++ b/ogx/version.go @@ -0,0 +1,5 @@ +package ogx + +func Version() string { + return "1.0.0" +} -- Gitee