From 5b9bb24e1c3626da40cd072d4768d69980746867 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Tue, 1 Sep 2020 23:29:21 +0700 Subject: [PATCH 1/4] REL Schema Migration POC --- Makefile | 6 +- cmd/db/main.go | 60 ++++ db/migrate/20202806225100_create_todos.rb | 13 - db/migrations/20202806225100_create_todos.go | 24 ++ db/migrations/20203006230600_create_scores.go | 20 ++ db/migrations/20203006230700_create_points.go | 24 ++ go.mod | 2 +- go.sum | 8 + vendor/github.com/Fs02/rel/.travis.yml | 2 + vendor/github.com/Fs02/rel/CONTRIBUTING.md | 12 + vendor/github.com/Fs02/rel/Gopkg.lock | 6 +- vendor/github.com/Fs02/rel/Gopkg.toml | 4 + vendor/github.com/Fs02/rel/README.md | 5 + vendor/github.com/Fs02/rel/adapter.go | 6 +- .../Fs02/rel/adapter/postgres/postgres.go | 65 +++- .../Fs02/rel/adapter/sql/adapter.go | 184 +++++----- .../github.com/Fs02/rel/adapter/sql/buffer.go | 6 +- .../Fs02/rel/adapter/sql/builder.go | 322 +++++++++++++++--- .../github.com/Fs02/rel/adapter/sql/config.go | 76 +++++ .../github.com/Fs02/rel/adapter/sql/util.go | 62 ++++ vendor/github.com/Fs02/rel/association.go | 6 +- vendor/github.com/Fs02/rel/collection.go | 70 +++- vendor/github.com/Fs02/rel/column.go | 145 ++++++++ vendor/github.com/Fs02/rel/document.go | 98 ++++-- vendor/github.com/Fs02/rel/filter_query.go | 101 ++++++ vendor/github.com/Fs02/rel/go.mod | 2 +- vendor/github.com/Fs02/rel/go.sum | 13 +- vendor/github.com/Fs02/rel/index.go | 62 ++++ vendor/github.com/Fs02/rel/iterator.go | 46 ++- vendor/github.com/Fs02/rel/key.go | 92 +++++ vendor/github.com/Fs02/rel/map.go | 9 +- .../github.com/Fs02/rel/migrator/migrator.go | 164 +++++++++ vendor/github.com/Fs02/rel/mutation.go | 7 +- vendor/github.com/Fs02/rel/query.go | 19 +- vendor/github.com/Fs02/rel/reltest/delete.go | 2 +- .../Fs02/rel/reltest/nop_adapter.go | 8 +- .../github.com/Fs02/rel/reltest/repository.go | 2 +- vendor/github.com/Fs02/rel/repository.go | 163 ++++----- vendor/github.com/Fs02/rel/schema.go | 143 ++++++++ vendor/github.com/Fs02/rel/structset.go | 19 +- vendor/github.com/Fs02/rel/table.go | 189 ++++++++++ vendor/modules.txt | 3 +- 42 files changed, 1922 insertions(+), 348 deletions(-) create mode 100644 cmd/db/main.go delete mode 100644 db/migrate/20202806225100_create_todos.rb create mode 100644 db/migrations/20202806225100_create_todos.go create mode 100644 db/migrations/20203006230600_create_scores.go create mode 100644 db/migrations/20203006230700_create_points.go create mode 100644 vendor/github.com/Fs02/rel/CONTRIBUTING.md create mode 100644 vendor/github.com/Fs02/rel/adapter/sql/config.go create mode 100644 vendor/github.com/Fs02/rel/column.go create mode 100644 vendor/github.com/Fs02/rel/index.go create mode 100644 vendor/github.com/Fs02/rel/key.go create mode 100644 vendor/github.com/Fs02/rel/migrator/migrator.go create mode 100644 vendor/github.com/Fs02/rel/schema.go create mode 100644 vendor/github.com/Fs02/rel/table.go diff --git a/Makefile b/Makefile index f0fc372..8bf8b72 100644 --- a/Makefile +++ b/Makefile @@ -5,12 +5,10 @@ export DEPLOY ?= api all: build start bundle: bundle install --path bundle -db-create: bundle - bundle exec rake db:create db-migrate: bundle - bundle exec rake db:migrate + export $$(cat .env | grep -v ^\# | xargs) && go run cmd/db/main.go migrate db-rollback: bundle - bundle exec rake db:rollback + export $$(cat .env | grep -v ^\# | xargs) && go run cmd/db/main.go rollback gen: go generate ./... build: gen diff --git a/cmd/db/main.go b/cmd/db/main.go new file mode 100644 index 0000000..3de8783 --- /dev/null +++ b/cmd/db/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "context" + "fmt" + "os" + + "github.com/Fs02/go-todo-backend/db/migrations" + "github.com/Fs02/rel" + "github.com/Fs02/rel/adapter/postgres" + "github.com/Fs02/rel/migrator" + _ "github.com/lib/pq" + "go.uber.org/zap" +) + +var ( + logger, _ = zap.NewProduction(zap.Fields(zap.String("type", "main"))) + shutdowns []func() error +) + +func main() { + var ( + ctx = context.Background() + dsn = fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable", + os.Getenv("POSTGRESQL_USERNAME"), + os.Getenv("POSTGRESQL_PASSWORD"), + os.Getenv("POSTGRESQL_HOST"), + os.Getenv("POSTGRESQL_PORT"), + os.Getenv("POSTGRESQL_DATABASE")) + ) + + adapter, err := postgres.Open(dsn) + if err != nil { + logger.Fatal(err.Error(), zap.Error(err)) + } + + var ( + op string + repo = rel.New(adapter) + m = migrator.New(repo) + ) + + // There will be a command line like go test for this in the future. + m.Register(20202806225100, migrations.MigrateCreateTodos, migrations.RollbackCreateTodos) + m.Register(20203006230600, migrations.MigrateCreateScores, migrations.RollbackCreateScores) + m.Register(20203006230700, migrations.MigrateCreatePoints, migrations.RollbackCreatePoints) + + if len(os.Args) > 1 { + op = os.Args[1] + } + + switch op { + case "migrate", "up": + m.Migrate(ctx) + case "rollback", "down": + m.Rollback(ctx) + default: + logger.Fatal("command not recognized", zap.String("command", op)) + } +} diff --git a/db/migrate/20202806225100_create_todos.rb b/db/migrate/20202806225100_create_todos.rb deleted file mode 100644 index 04c96e2..0000000 --- a/db/migrate/20202806225100_create_todos.rb +++ /dev/null @@ -1,13 +0,0 @@ -class CreateTodos < ActiveRecord::Migration[5.2] - def change - create_table :todos do |t| - t.datetime :created_at - t.datetime :updated_at - t.string :title - t.boolean :completed - t.integer :order - - t.index :order - end - end -end diff --git a/db/migrations/20202806225100_create_todos.go b/db/migrations/20202806225100_create_todos.go new file mode 100644 index 0000000..8a36544 --- /dev/null +++ b/db/migrations/20202806225100_create_todos.go @@ -0,0 +1,24 @@ +package migrations + +import ( + "github.com/Fs02/rel" +) + +// MigrateCreateTodos definition +func MigrateCreateTodos(schema *rel.Schema) { + schema.CreateTable("todos", func(t *rel.Table) { + t.ID("id") + t.DateTime("created_at") + t.DateTime("updated_at") + t.String("title") + t.Bool("completed") + t.Int("order") + }) + + schema.CreateIndex("todos", "order", []string{"order"}) +} + +// RollbackCreateTodos definition +func RollbackCreateTodos(schema *rel.Schema) { + schema.DropTable("todos") +} diff --git a/db/migrations/20203006230600_create_scores.go b/db/migrations/20203006230600_create_scores.go new file mode 100644 index 0000000..a61f0f6 --- /dev/null +++ b/db/migrations/20203006230600_create_scores.go @@ -0,0 +1,20 @@ +package migrations + +import ( + "github.com/Fs02/rel" +) + +// MigrateCreateScores definition +func MigrateCreateScores(schema *rel.Schema) { + schema.CreateTable("scores", func(t *rel.Table) { + t.ID("id") + t.DateTime("created_at") + t.DateTime("updated_at") + t.Int("total_point") + }) +} + +// RollbackCreateScores definition +func RollbackCreateScores(schema *rel.Schema) { + schema.DropTable("scores") +} diff --git a/db/migrations/20203006230700_create_points.go b/db/migrations/20203006230700_create_points.go new file mode 100644 index 0000000..b44fb05 --- /dev/null +++ b/db/migrations/20203006230700_create_points.go @@ -0,0 +1,24 @@ +package migrations + +import ( + "github.com/Fs02/rel" +) + +// MigrateCreatePoints definition +func MigrateCreatePoints(schema *rel.Schema) { + schema.CreateTable("points", func(t *rel.Table) { + t.ID("id") + t.DateTime("created_at") + t.DateTime("updated_at") + t.Int("name") + t.Int("count") + t.Int("score_id", rel.Unsigned(true)) + + t.ForeignKey("score_id", "scores", "id") + }) +} + +// RollbackCreatePoints definition +func RollbackCreatePoints(schema *rel.Schema) { + schema.DropTable("points") +} diff --git a/go.mod b/go.mod index 7286b2b..7edee72 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Fs02/go-todo-backend go 1.14 require ( - github.com/Fs02/rel v0.5.0 + github.com/Fs02/rel v0.6.1-0.20200901120822-3b34832d3a9e github.com/azer/snakecase v1.0.0 // indirect github.com/go-chi/chi v3.3.2+incompatible github.com/goware/cors v1.1.1 diff --git a/go.sum b/go.sum index bfd2552..cb49428 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,10 @@ github.com/Fs02/go-paranoid v0.0.0-20190122110906-018c1ac5124a h1:wYjvXrzEmkEe3k github.com/Fs02/go-paranoid v0.0.0-20190122110906-018c1ac5124a/go.mod h1:mUYWV9DG75bJ33LZlW1Je3MW64017zkfUFCf+QnCJs0= github.com/Fs02/rel v0.5.0 h1:z0SpMTSFTWfZAl+KV5mTEcsyMzPDYWcUmuVRFMWMzMo= github.com/Fs02/rel v0.5.0/go.mod h1:MeJHkZO46QVfjagPSnYX/noguNDOqNK2LLGse77asxw= +github.com/Fs02/rel v0.6.1-0.20200901120822-3b34832d3a9e h1:rBA7GTr0XUKGuOutCgUZ4mZ3i2eSCwkuE2KPWdf/3og= +github.com/Fs02/rel v0.6.1-0.20200901120822-3b34832d3a9e/go.mod h1:HCzzNmcDNX0IShoePaXQ/srKElubko6qQ3NHzxkagF4= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/azer/snakecase v0.0.0-20161028114325-c818dddafb5c h1:7zL0ljVI6ads5EFvx+Oq+uompnFBMJqtbuHvyobbJ1Q= github.com/azer/snakecase v0.0.0-20161028114325-c818dddafb5c/go.mod h1:iApMeoHF0YlMPzCwqH/d59E3w2s8SeO4rGK+iGClS8Y= github.com/azer/snakecase v1.0.0 h1:Gr9hfYVh6U96aUoGEbJK400H9KTiz6yCIYk3EN8n9hY= @@ -32,6 +36,7 @@ github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-sqlite3 v1.6.0 h1:TDwTWbeII+88Qy55nWlof0DclgAtI4LqGujkYMzmQII= github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.2/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -63,10 +68,13 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 h1:eDrdRpKgkcCqKZQwyZRyeFZgfqt37SL7Kv3tok06cKE= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/vendor/github.com/Fs02/rel/.travis.yml b/vendor/github.com/Fs02/rel/.travis.yml index 94a701d..b01a7d8 100644 --- a/vendor/github.com/Fs02/rel/.travis.yml +++ b/vendor/github.com/Fs02/rel/.travis.yml @@ -46,6 +46,8 @@ jobs: - go: "1.11.x" <<: *godep_build - go: "1.12.x" + env: + - GO111MODULE=on <<: *gomod_build - go: "1.13.x" <<: *gomod_build diff --git a/vendor/github.com/Fs02/rel/CONTRIBUTING.md b/vendor/github.com/Fs02/rel/CONTRIBUTING.md new file mode 100644 index 0000000..01b1f33 --- /dev/null +++ b/vendor/github.com/Fs02/rel/CONTRIBUTING.md @@ -0,0 +1,12 @@ +# How to contribute + +I'm really glad you're reading this, because we need volunteer developers to help this project come to fruition. + +Here's some way you on how you can contribute to this project: + +- Report any bug, feature request and questions using issues. +- Contribute directly to the development, don't hestitate to take any task available on [projects](https://github.com/Fs02/rel/projects) page. You can use issues if you need further discussion and help about the implementation. +- Improvement to the documentation is always welcomed. +- Star and let the world know about this project. + +Thanks :heart: :heart: :heart: diff --git a/vendor/github.com/Fs02/rel/Gopkg.lock b/vendor/github.com/Fs02/rel/Gopkg.lock index df3a092..604c4cd 100644 --- a/vendor/github.com/Fs02/rel/Gopkg.lock +++ b/vendor/github.com/Fs02/rel/Gopkg.lock @@ -54,12 +54,12 @@ version = "v1.3.0" [[projects]] - digest = "1:ee0845ea64262e3d1a6e2eab768fcb2008a0c8e571b7a3bebea554a1c031aeeb" + digest = "1:6a60be4b683dfa1d3e222b6ef96da4d3406280019731c6c1c23bd60cfb7928fe" name = "github.com/mattn/go-sqlite3" packages = ["."] pruneopts = "UT" - revision = "6c771bb9887719704b210e87e934f08be014bdb1" - version = "v1.6.0" + revision = "862b95943f99f3b40e317a79d41c27ac4b742011" + version = "v1.14.2" [[projects]] digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" diff --git a/vendor/github.com/Fs02/rel/Gopkg.toml b/vendor/github.com/Fs02/rel/Gopkg.toml index d7072c2..e129f8c 100644 --- a/vendor/github.com/Fs02/rel/Gopkg.toml +++ b/vendor/github.com/Fs02/rel/Gopkg.toml @@ -28,3 +28,7 @@ [prune] go-tests = true unused-packages = true + +[[constraint]] + name = "github.com/mattn/go-sqlite3" + version = "1.14.2" diff --git a/vendor/github.com/Fs02/rel/README.md b/vendor/github.com/Fs02/rel/README.md index 7a940fc..339a206 100644 --- a/vendor/github.com/Fs02/rel/README.md +++ b/vendor/github.com/Fs02/rel/README.md @@ -16,6 +16,7 @@ REL is golang orm-ish database layer for layered architecture. It's testable and - Elegant, yet extendable query builder with mix of syntactic sugar. - Supports Eager loading. - Supports nested transactions. +- Composite Primary Key. - Multi adapter. - Soft Deletion. - Pagination. @@ -31,6 +32,10 @@ go get github.com/Fs02/rel - Guides [https://fs02.github.io/rel](https://fs02.github.io/rel) +## Examples + +- [go-todo-backend](https://github.com/Fs02/go-todo-backend) - Todo Backend + ## License Released under the [MIT License](https://github.com/Fs02/rel/blob/master/LICENSE) diff --git a/vendor/github.com/Fs02/rel/adapter.go b/vendor/github.com/Fs02/rel/adapter.go index 4f5c28e..bda5b80 100644 --- a/vendor/github.com/Fs02/rel/adapter.go +++ b/vendor/github.com/Fs02/rel/adapter.go @@ -10,12 +10,14 @@ type Adapter interface { Ping(ctx context.Context) error Aggregate(ctx context.Context, query Query, mode string, field string) (int, error) Query(ctx context.Context, query Query) (Cursor, error) - Insert(ctx context.Context, query Query, mutates map[string]Mutate) (interface{}, error) - InsertAll(ctx context.Context, query Query, fields []string, bulkMutates []map[string]Mutate) ([]interface{}, error) + Insert(ctx context.Context, query Query, primaryField string, mutates map[string]Mutate) (interface{}, error) + InsertAll(ctx context.Context, query Query, primaryField string, fields []string, bulkMutates []map[string]Mutate) ([]interface{}, error) Update(ctx context.Context, query Query, mutates map[string]Mutate) (int, error) Delete(ctx context.Context, query Query) (int, error) Begin(ctx context.Context) (Adapter, error) Commit(ctx context.Context) error Rollback(ctx context.Context) error + + Apply(ctx context.Context, migration Migration) error } diff --git a/vendor/github.com/Fs02/rel/adapter/postgres/postgres.go b/vendor/github.com/Fs02/rel/adapter/postgres/postgres.go index a0b5daf..5e954af 100644 --- a/vendor/github.com/Fs02/rel/adapter/postgres/postgres.go +++ b/vendor/github.com/Fs02/rel/adapter/postgres/postgres.go @@ -15,6 +15,7 @@ package postgres import ( "context" db "database/sql" + "time" "github.com/Fs02/rel" "github.com/Fs02/rel/adapter/sql" @@ -25,20 +26,26 @@ type Adapter struct { *sql.Adapter } -var _ rel.Adapter = (*Adapter)(nil) +var ( + _ rel.Adapter = (*Adapter)(nil) + + // Config for postgres adapter. + Config = sql.Config{ + Placeholder: "$", + EscapeChar: "\"", + Ordinal: true, + InsertDefaultValues: true, + ErrorFunc: errorFunc, + MapColumnFunc: mapColumnFunc, + } +) -// New is postgres adapter constructor. +// New postgres adapter using existing connection. func New(database *db.DB) *Adapter { return &Adapter{ Adapter: &sql.Adapter{ - Config: &sql.Config{ - Placeholder: "$", - EscapeChar: "\"", - Ordinal: true, - InsertDefaultValues: true, - ErrorFunc: errorFunc, - }, - DB: database, + Config: Config, + DB: database, }, } } @@ -50,10 +57,10 @@ func Open(dsn string) (*Adapter, error) { } // Insert inserts a record to database and returns its id. -func (adapter *Adapter) Insert(ctx context.Context, query rel.Query, mutates map[string]rel.Mutate) (interface{}, error) { +func (adapter *Adapter) Insert(ctx context.Context, query rel.Query, primaryField string, mutates map[string]rel.Mutate) (interface{}, error) { var ( id int64 - statement, args = sql.NewBuilder(adapter.Config).Returning("id").Insert(query.Table, mutates) + statement, args = sql.NewBuilder(adapter.Config).Returning(primaryField).Insert(query.Table, mutates) rows, err = adapter.query(ctx, statement, args) ) @@ -66,10 +73,10 @@ func (adapter *Adapter) Insert(ctx context.Context, query rel.Query, mutates map } // InsertAll inserts multiple records to database and returns its ids. -func (adapter *Adapter) InsertAll(ctx context.Context, query rel.Query, fields []string, bulkMutates []map[string]rel.Mutate) ([]interface{}, error) { +func (adapter *Adapter) InsertAll(ctx context.Context, query rel.Query, primaryField string, fields []string, bulkMutates []map[string]rel.Mutate) ([]interface{}, error) { var ( ids []interface{} - statement, args = sql.NewBuilder(adapter.Config).Returning("id").InsertAll(query.Table, fields, bulkMutates) + statement, args = sql.NewBuilder(adapter.Config).Returning(primaryField).InsertAll(query.Table, fields, bulkMutates) rows, err = adapter.query(ctx, statement, args) ) @@ -144,3 +151,33 @@ func errorFunc(err error) error { return err } } + +func mapColumnFunc(column *rel.Column) (string, int, int) { + var ( + typ string + m, n int + ) + + // postgres specific + column.Unsigned = false + if column.Default == "" { + column.Default = nil + } + + switch column.Type { + case rel.ID: + typ = "SERIAL NOT NULL PRIMARY KEY" + case rel.DateTime: + typ = "TIMESTAMPTZ" + if t, ok := column.Default.(time.Time); ok { + column.Default = t.Format("2006-01-02 15:04:05") + } + case rel.Int, rel.BigInt, rel.Text: + column.Limit = 0 + typ, m, n = sql.MapColumn(column) + default: + typ, m, n = sql.MapColumn(column) + } + + return typ, m, n +} diff --git a/vendor/github.com/Fs02/rel/adapter/sql/adapter.go b/vendor/github.com/Fs02/rel/adapter/sql/adapter.go index b8b277d..8158134 100644 --- a/vendor/github.com/Fs02/rel/adapter/sql/adapter.go +++ b/vendor/github.com/Fs02/rel/adapter/sql/adapter.go @@ -10,20 +10,10 @@ import ( "github.com/Fs02/rel" ) -// Config holds configuration for adapter. -type Config struct { - Placeholder string - Ordinal bool - InsertDefaultValues bool - EscapeChar string - ErrorFunc func(error) error - IncrementFunc func(Adapter) int -} - // Adapter definition for database database. type Adapter struct { Instrumenter rel.Instrumenter - Config *Config + Config Config DB *sql.DB Tx *sql.Tx savepoint int @@ -32,42 +22,42 @@ type Adapter struct { var _ rel.Adapter = (*Adapter)(nil) // Close database connection. -func (adapter *Adapter) Close() error { - return adapter.DB.Close() +func (a *Adapter) Close() error { + return a.DB.Close() } // Instrumentation set instrumenter for this adapter. -func (adapter *Adapter) Instrumentation(instrumenter rel.Instrumenter) { - adapter.Instrumenter = instrumenter +func (a *Adapter) Instrumentation(instrumenter rel.Instrumenter) { + a.Instrumenter = instrumenter } // Instrument call instrumenter, if no instrumenter is set, this will be a no op. -func (adapter *Adapter) Instrument(ctx context.Context, op string, message string) func(err error) { - if adapter.Instrumenter != nil { - return adapter.Instrumenter(ctx, op, message) +func (a *Adapter) Instrument(ctx context.Context, op string, message string) func(err error) { + if a.Instrumenter != nil { + return a.Instrumenter(ctx, op, message) } return func(err error) {} } // Ping database. -func (adapter *Adapter) Ping(ctx context.Context) error { - return adapter.DB.PingContext(ctx) +func (a *Adapter) Ping(ctx context.Context) error { + return a.DB.PingContext(ctx) } // Aggregate record using given query. -func (adapter *Adapter) Aggregate(ctx context.Context, query rel.Query, mode string, field string) (int, error) { +func (a *Adapter) Aggregate(ctx context.Context, query rel.Query, mode string, field string) (int, error) { var ( err error out sql.NullInt64 - statement, args = NewBuilder(adapter.Config).Aggregate(query, mode, field) + statement, args = NewBuilder(a.Config).Aggregate(query, mode, field) ) - finish := adapter.Instrument(ctx, "adapter-aggregate", statement) - if adapter.Tx != nil { - err = adapter.Tx.QueryRowContext(ctx, statement, args...).Scan(&out) + finish := a.Instrument(ctx, "adapter-aggregate", statement) + if a.Tx != nil { + err = a.Tx.QueryRowContext(ctx, statement, args...).Scan(&out) } else { - err = adapter.DB.QueryRowContext(ctx, statement, args...).Scan(&out) + err = a.DB.QueryRowContext(ctx, statement, args...).Scan(&out) } finish(err) @@ -75,34 +65,34 @@ func (adapter *Adapter) Aggregate(ctx context.Context, query rel.Query, mode str } // Query performs query operation. -func (adapter *Adapter) Query(ctx context.Context, query rel.Query) (rel.Cursor, error) { +func (a *Adapter) Query(ctx context.Context, query rel.Query) (rel.Cursor, error) { var ( - statement, args = NewBuilder(adapter.Config).Find(query) + statement, args = NewBuilder(a.Config).Find(query) ) - finish := adapter.Instrument(ctx, "adapter-query", statement) - rows, err := adapter.query(ctx, statement, args) + finish := a.Instrument(ctx, "adapter-query", statement) + rows, err := a.query(ctx, statement, args) finish(err) - return &Cursor{rows}, adapter.Config.ErrorFunc(err) + return &Cursor{rows}, a.Config.ErrorFunc(err) } -func (adapter *Adapter) query(ctx context.Context, statement string, args []interface{}) (*sql.Rows, error) { - if adapter.Tx != nil { - return adapter.Tx.QueryContext(ctx, statement, args...) +func (a *Adapter) query(ctx context.Context, statement string, args []interface{}) (*sql.Rows, error) { + if a.Tx != nil { + return a.Tx.QueryContext(ctx, statement, args...) } - return adapter.DB.QueryContext(ctx, statement, args...) + return a.DB.QueryContext(ctx, statement, args...) } // Exec performs exec operation. -func (adapter *Adapter) Exec(ctx context.Context, statement string, args []interface{}) (int64, int64, error) { - finish := adapter.Instrument(ctx, "adapter-exec", statement) - res, err := adapter.exec(ctx, statement, args) +func (a *Adapter) Exec(ctx context.Context, statement string, args []interface{}) (int64, int64, error) { + finish := a.Instrument(ctx, "adapter-exec", statement) + res, err := a.exec(ctx, statement, args) finish(err) if err != nil { - return 0, 0, adapter.Config.ErrorFunc(err) + return 0, 0, a.Config.ErrorFunc(err) } lastID, _ := res.LastInsertId() @@ -111,28 +101,28 @@ func (adapter *Adapter) Exec(ctx context.Context, statement string, args []inter return lastID, rowCount, nil } -func (adapter *Adapter) exec(ctx context.Context, statement string, args []interface{}) (sql.Result, error) { - if adapter.Tx != nil { - return adapter.Tx.ExecContext(ctx, statement, args...) +func (a *Adapter) exec(ctx context.Context, statement string, args []interface{}) (sql.Result, error) { + if a.Tx != nil { + return a.Tx.ExecContext(ctx, statement, args...) } - return adapter.DB.ExecContext(ctx, statement, args...) + return a.DB.ExecContext(ctx, statement, args...) } // Insert inserts a record to database and returns its id. -func (adapter *Adapter) Insert(ctx context.Context, query rel.Query, mutates map[string]rel.Mutate) (interface{}, error) { +func (a *Adapter) Insert(ctx context.Context, query rel.Query, primaryField string, mutates map[string]rel.Mutate) (interface{}, error) { var ( - statement, args = NewBuilder(adapter.Config).Insert(query.Table, mutates) - id, _, err = adapter.Exec(ctx, statement, args) + statement, args = NewBuilder(a.Config).Insert(query.Table, mutates) + id, _, err = a.Exec(ctx, statement, args) ) return id, err } // InsertAll inserts all record to database and returns its ids. -func (adapter *Adapter) InsertAll(ctx context.Context, query rel.Query, fields []string, bulkMutates []map[string]rel.Mutate) ([]interface{}, error) { - statement, args := NewBuilder(adapter.Config).InsertAll(query.Table, fields, bulkMutates) - id, _, err := adapter.Exec(ctx, statement, args) +func (a *Adapter) InsertAll(ctx context.Context, query rel.Query, primaryField string, fields []string, bulkMutates []map[string]rel.Mutate) ([]interface{}, error) { + statement, args := NewBuilder(a.Config).InsertAll(query.Table, fields, bulkMutates) + id, _, err := a.Exec(ctx, statement, args) if err != nil { return nil, err } @@ -142,8 +132,8 @@ func (adapter *Adapter) InsertAll(ctx context.Context, query rel.Query, fields [ inc = 1 ) - if adapter.Config.IncrementFunc != nil { - inc = adapter.Config.IncrementFunc(*adapter) + if a.Config.IncrementFunc != nil { + inc = a.Config.IncrementFunc(*a) } if inc < 0 { @@ -151,101 +141,129 @@ func (adapter *Adapter) InsertAll(ctx context.Context, query rel.Query, fields [ inc *= -1 } - for i := range ids { - ids[i] = id + int64(i*inc) + if primaryField != "" { + counter := 0 + for i := range ids { + if mut, ok := bulkMutates[i][primaryField]; ok { + ids[i] = mut.Value + id = toInt64(ids[i]) + counter = 1 + } else { + ids[i] = id + int64(counter*inc) + counter++ + } + } } return ids, nil } // Update updates a record in database. -func (adapter *Adapter) Update(ctx context.Context, query rel.Query, mutates map[string]rel.Mutate) (int, error) { +func (a *Adapter) Update(ctx context.Context, query rel.Query, mutates map[string]rel.Mutate) (int, error) { var ( - statement, args = NewBuilder(adapter.Config).Update(query.Table, mutates, query.WhereQuery) - _, updatedCount, err = adapter.Exec(ctx, statement, args) + statement, args = NewBuilder(a.Config).Update(query.Table, mutates, query.WhereQuery) + _, updatedCount, err = a.Exec(ctx, statement, args) ) return int(updatedCount), err } // Delete deletes all results that match the query. -func (adapter *Adapter) Delete(ctx context.Context, query rel.Query) (int, error) { +func (a *Adapter) Delete(ctx context.Context, query rel.Query) (int, error) { var ( - statement, args = NewBuilder(adapter.Config).Delete(query.Table, query.WhereQuery) - _, deletedCount, err = adapter.Exec(ctx, statement, args) + statement, args = NewBuilder(a.Config).Delete(query.Table, query.WhereQuery) + _, deletedCount, err = a.Exec(ctx, statement, args) ) return int(deletedCount), err } // Begin begins a new transaction. -func (adapter *Adapter) Begin(ctx context.Context) (rel.Adapter, error) { +func (a *Adapter) Begin(ctx context.Context) (rel.Adapter, error) { var ( tx *sql.Tx savepoint int err error ) - finish := adapter.Instrument(ctx, "adapter-begin", "begin transaction") + finish := a.Instrument(ctx, "adapter-begin", "begin transaction") - if adapter.Tx != nil { - tx = adapter.Tx - savepoint = adapter.savepoint + 1 - _, _, err = adapter.Exec(ctx, "SAVEPOINT s"+strconv.Itoa(savepoint)+";", []interface{}{}) + if a.Tx != nil { + tx = a.Tx + savepoint = a.savepoint + 1 + _, _, err = a.Exec(ctx, "SAVEPOINT s"+strconv.Itoa(savepoint)+";", []interface{}{}) } else { - tx, err = adapter.DB.BeginTx(ctx, nil) + tx, err = a.DB.BeginTx(ctx, nil) } finish(err) return &Adapter{ - Instrumenter: adapter.Instrumenter, - Config: adapter.Config, + Instrumenter: a.Instrumenter, + Config: a.Config, Tx: tx, savepoint: savepoint, }, err } // Commit commits current transaction. -func (adapter *Adapter) Commit(ctx context.Context) error { +func (a *Adapter) Commit(ctx context.Context) error { var err error - finish := adapter.Instrument(ctx, "adapter-commit", "commit transaction") + finish := a.Instrument(ctx, "adapter-commit", "commit transaction") - if adapter.Tx == nil { + if a.Tx == nil { err = errors.New("unable to commit outside transaction") - } else if adapter.savepoint > 0 { - _, _, err = adapter.Exec(ctx, "RELEASE SAVEPOINT s"+strconv.Itoa(adapter.savepoint)+";", []interface{}{}) + } else if a.savepoint > 0 { + _, _, err = a.Exec(ctx, "RELEASE SAVEPOINT s"+strconv.Itoa(a.savepoint)+";", []interface{}{}) } else { - err = adapter.Tx.Commit() + err = a.Tx.Commit() } finish(err) - return adapter.Config.ErrorFunc(err) + return a.Config.ErrorFunc(err) } // Rollback revert current transaction. -func (adapter *Adapter) Rollback(ctx context.Context) error { +func (a *Adapter) Rollback(ctx context.Context) error { var err error - finish := adapter.Instrument(ctx, "adapter-rollback", "rollback transaction") + finish := a.Instrument(ctx, "adapter-rollback", "rollback transaction") - if adapter.Tx == nil { + if a.Tx == nil { err = errors.New("unable to rollback outside transaction") - } else if adapter.savepoint > 0 { - _, _, err = adapter.Exec(ctx, "ROLLBACK TO SAVEPOINT s"+strconv.Itoa(adapter.savepoint)+";", []interface{}{}) + } else if a.savepoint > 0 { + _, _, err = a.Exec(ctx, "ROLLBACK TO SAVEPOINT s"+strconv.Itoa(a.savepoint)+";", []interface{}{}) } else { - err = adapter.Tx.Rollback() + err = a.Tx.Rollback() } finish(err) - return adapter.Config.ErrorFunc(err) + return a.Config.ErrorFunc(err) +} + +// Apply table. +func (a *Adapter) Apply(ctx context.Context, migration rel.Migration) error { + var ( + statement string + builder = NewBuilder(a.Config) + ) + + switch v := migration.(type) { + case rel.Table: + statement = builder.Table(v) + case rel.Index: + statement = builder.Index(v) + } + + _, _, err := a.Exec(ctx, statement, nil) + return err } // New initialize adapter without db. -func New(config *Config) *Adapter { +func New(config Config) *Adapter { adapter := &Adapter{ Config: config, } diff --git a/vendor/github.com/Fs02/rel/adapter/sql/buffer.go b/vendor/github.com/Fs02/rel/adapter/sql/buffer.go index 5d165ab..191b625 100644 --- a/vendor/github.com/Fs02/rel/adapter/sql/buffer.go +++ b/vendor/github.com/Fs02/rel/adapter/sql/buffer.go @@ -1,12 +1,12 @@ package sql import ( - "bytes" + "strings" ) // Buffer used to strings buffer and argument of the query. type Buffer struct { - bytes.Buffer + strings.Builder Arguments []interface{} } @@ -17,6 +17,6 @@ func (b *Buffer) Append(args ...interface{}) { // Reset buffer. func (b *Buffer) Reset() { - b.Buffer.Reset() + b.Builder.Reset() b.Arguments = nil } diff --git a/vendor/github.com/Fs02/rel/adapter/sql/builder.go b/vendor/github.com/Fs02/rel/adapter/sql/builder.go index 8bb6043..1813f13 100644 --- a/vendor/github.com/Fs02/rel/adapter/sql/builder.go +++ b/vendor/github.com/Fs02/rel/adapter/sql/builder.go @@ -1,6 +1,7 @@ package sql import ( + "encoding/json" "strconv" "strings" "sync" @@ -15,11 +16,262 @@ var fieldCache sync.Map // Builder defines information of query b. type Builder struct { - config *Config + config Config returnField string count int } +// Table generates query for table creation and modification. +func (b *Builder) Table(table rel.Table) string { + var buffer Buffer + + switch table.Op { + case rel.SchemaCreate: + b.createTable(&buffer, table) + case rel.SchemaAlter: + b.alterTable(&buffer, table) + case rel.SchemaRename: + buffer.WriteString("ALTER TABLE ") + buffer.WriteString(Escape(b.config, table.Name)) + buffer.WriteString(" RENAME TO ") + buffer.WriteString(Escape(b.config, table.Rename)) + buffer.WriteByte(';') + case rel.SchemaDrop: + buffer.WriteString("DROP TABLE ") + + if table.Optional { + buffer.WriteString("IF EXISTS ") + } + + buffer.WriteString(Escape(b.config, table.Name)) + buffer.WriteByte(';') + } + + return buffer.String() +} + +func (b *Builder) createTable(buffer *Buffer, table rel.Table) { + buffer.WriteString("CREATE TABLE ") + + if table.Optional { + buffer.WriteString("IF NOT EXISTS ") + } + + buffer.WriteString(Escape(b.config, table.Name)) + buffer.WriteString(" (") + + for i, def := range table.Definitions { + if i > 0 { + buffer.WriteString(", ") + } + switch v := def.(type) { + case rel.Column: + b.column(buffer, v) + case rel.Key: + b.key(buffer, v) + case rel.Raw: + buffer.WriteString(string(v)) + } + } + + buffer.WriteByte(')') + b.options(buffer, table.Options) + buffer.WriteByte(';') +} + +func (b *Builder) alterTable(buffer *Buffer, table rel.Table) { + for _, def := range table.Definitions { + buffer.WriteString("ALTER TABLE ") + buffer.WriteString(Escape(b.config, table.Name)) + buffer.WriteByte(' ') + + switch v := def.(type) { + case rel.Column: + switch v.Op { + case rel.SchemaCreate: + buffer.WriteString("ADD COLUMN ") + b.column(buffer, v) + case rel.SchemaRename: + // Add Change + buffer.WriteString("RENAME COLUMN ") + buffer.WriteString(Escape(b.config, v.Name)) + buffer.WriteString(" TO ") + buffer.WriteString(Escape(b.config, v.Rename)) + case rel.SchemaDrop: + buffer.WriteString("DROP COLUMN ") + buffer.WriteString(Escape(b.config, v.Name)) + } + case rel.Key: + // TODO: Rename and Drop, PR welcomed. + switch v.Op { + case rel.SchemaCreate: + buffer.WriteString("ADD ") + b.key(buffer, v) + } + } + + b.options(buffer, table.Options) + buffer.WriteByte(';') + } +} + +func (b *Builder) column(buffer *Buffer, column rel.Column) { + var ( + typ, m, n = b.config.MapColumnFunc(&column) + ) + + buffer.WriteString(Escape(b.config, column.Name)) + buffer.WriteByte(' ') + buffer.WriteString(typ) + + if m != 0 { + buffer.WriteByte('(') + buffer.WriteString(strconv.Itoa(m)) + + if n != 0 { + buffer.WriteByte(',') + buffer.WriteString(strconv.Itoa(n)) + } + + buffer.WriteByte(')') + } + + if column.Unsigned { + buffer.WriteString(" UNSIGNED") + } + + if column.Unique { + buffer.WriteString(" UNIQUE") + } + + if column.Required { + buffer.WriteString(" NOT NULL") + } + + if column.Default != nil { + buffer.WriteString(" DEFAULT ") + switch v := column.Default.(type) { + case string: + // TODO: single quote only required by postgres. + buffer.WriteByte('\'') + buffer.WriteString(v) + buffer.WriteByte('\'') + default: + // TODO: improve + bytes, _ := json.Marshal(column.Default) + buffer.Write(bytes) + } + } + + b.options(buffer, column.Options) +} + +func (b *Builder) key(buffer *Buffer, key rel.Key) { + var ( + typ = string(key.Type) + ) + + buffer.WriteString(typ) + + if key.Name != "" { + buffer.WriteByte(' ') + buffer.WriteString(Escape(b.config, key.Name)) + } + + buffer.WriteString(" (") + for i, col := range key.Columns { + if i > 0 { + buffer.WriteString(", ") + } + buffer.WriteString(Escape(b.config, col)) + } + buffer.WriteString(")") + + if key.Type == rel.ForeignKey { + buffer.WriteString(" REFERENCES ") + buffer.WriteString(Escape(b.config, key.Reference.Table)) + + buffer.WriteString(" (") + for i, col := range key.Reference.Columns { + if i > 0 { + buffer.WriteString(", ") + } + buffer.WriteString(Escape(b.config, col)) + } + buffer.WriteString(")") + + if onDelete := key.Reference.OnDelete; onDelete != "" { + buffer.WriteString(" ON DELETE ") + buffer.WriteString(onDelete) + } + + if onUpdate := key.Reference.OnUpdate; onUpdate != "" { + buffer.WriteString(" ON UPDATE ") + buffer.WriteString(onUpdate) + } + } + + b.options(buffer, key.Options) +} + +// Index generates query for index. +func (b *Builder) Index(index rel.Index) string { + var buffer Buffer + + switch index.Op { + case rel.SchemaCreate: + buffer.WriteString("CREATE ") + if index.Unique { + buffer.WriteString("UNIQUE ") + } + buffer.WriteString("INDEX ") + + if index.Optional { + buffer.WriteString("IF NOT EXISTS ") + } + + buffer.WriteString(Escape(b.config, index.Name)) + buffer.WriteString(" ON ") + buffer.WriteString(Escape(b.config, index.Table)) + + buffer.WriteString(" (") + for i, col := range index.Columns { + if i > 0 { + buffer.WriteString(", ") + } + buffer.WriteString(Escape(b.config, col)) + } + buffer.WriteString(")") + case rel.SchemaDrop: + buffer.WriteString("DROP INDEX ") + + if index.Optional { + buffer.WriteString("IF EXISTS ") + } + + buffer.WriteString(Escape(b.config, index.Name)) + + if b.config.DropIndexOnTable { + buffer.WriteString(" ON ") + buffer.WriteString(Escape(b.config, index.Table)) + } + } + + b.options(&buffer, index.Options) + buffer.WriteByte(';') + + return buffer.String() +} + +func (b *Builder) options(buffer *Buffer, options string) { + if options == "" { + return + } + + buffer.WriteByte(' ') + buffer.WriteString(options) +} + // Find generates query for select. func (b *Builder) Find(query rel.Query) (string, []interface{}) { if query.SQLQuery.Statement != "" { @@ -47,13 +299,13 @@ func (b *Builder) Aggregate(query rel.Query, mode string, field string) (string, buffer.WriteString("SELECT ") buffer.WriteString(mode) buffer.WriteByte('(') - buffer.WriteString(b.escape(field)) + buffer.WriteString(Escape(b.config, field)) buffer.WriteString(") AS ") buffer.WriteString(mode) for _, f := range query.GroupQuery.Fields { buffer.WriteByte(',') - buffer.WriteString(b.escape(f)) + buffer.WriteString(Escape(b.config, f)) } b.query(&buffer, query) @@ -90,7 +342,7 @@ func (b *Builder) Insert(table string, mutates map[string]rel.Mutate) (string, [ ) buffer.WriteString("INSERT INTO ") - buffer.WriteString(b.escape(table)) + buffer.WriteString(Escape(b.config, table)) if count == 0 && b.config.InsertDefaultValues { buffer.WriteString(" DEFAULT VALUES") @@ -219,14 +471,14 @@ func (b *Builder) Update(table string, mutates map[string]rel.Mutate, filter rel for field, mut := range mutates { switch mut.Type { case rel.ChangeSetOp: - buffer.WriteString(b.escape(field)) + buffer.WriteString(Escape(b.config, field)) buffer.WriteByte('=') buffer.WriteString(b.ph()) buffer.Append(mut.Value) case rel.ChangeIncOp: - buffer.WriteString(b.escape(field)) + buffer.WriteString(Escape(b.config, field)) buffer.WriteByte('=') - buffer.WriteString(b.escape(field)) + buffer.WriteString(Escape(b.config, field)) buffer.WriteByte('+') buffer.WriteString(b.ph()) buffer.Append(mut.Value) @@ -284,7 +536,7 @@ func (b *Builder) fields(buffer *Buffer, distinct bool, fields []string) { l := len(fields) - 1 for i, f := range fields { - buffer.WriteString(b.escape(f)) + buffer.WriteString(Escape(b.config, f)) if i < l { buffer.WriteByte(',') @@ -306,8 +558,8 @@ func (b *Builder) join(buffer *Buffer, table string, joins []rel.JoinQuery) { for _, join := range joins { var ( - from = b.escape(join.From) - to = b.escape(join.To) + from = Escape(b.config, join.From) + to = Escape(b.config, join.To) ) // TODO: move this to core functionality, and infer join condition using assoc data. @@ -350,7 +602,7 @@ func (b *Builder) groupBy(buffer *Buffer, fields []string) { l := len(fields) - 1 for i, f := range fields { - buffer.WriteString(b.escape(f)) + buffer.WriteString(Escape(b.config, f)) if i < l { buffer.WriteByte(',') @@ -379,7 +631,7 @@ func (b *Builder) orderBy(buffer *Buffer, orders []rel.SortQuery) { buffer.WriteString(" ORDER BY") for i, order := range orders { buffer.WriteByte(' ') - buffer.WriteString(b.escape(order.Field)) + buffer.WriteString(Escape(b.config, order.Field)) if order.Asc() { buffer.WriteString(" ASC") @@ -422,21 +674,21 @@ func (b *Builder) filter(buffer *Buffer, filter rel.FilterQuery) { rel.FilterGteOp: b.buildComparison(buffer, filter) case rel.FilterNilOp: - buffer.WriteString(b.escape(filter.Field)) + buffer.WriteString(Escape(b.config, filter.Field)) buffer.WriteString(" IS NULL") case rel.FilterNotNilOp: - buffer.WriteString(b.escape(filter.Field)) + buffer.WriteString(Escape(b.config, filter.Field)) buffer.WriteString(" IS NOT NULL") case rel.FilterInOp, rel.FilterNinOp: b.buildInclusion(buffer, filter) case rel.FilterLikeOp: - buffer.WriteString(b.escape(filter.Field)) + buffer.WriteString(Escape(b.config, filter.Field)) buffer.WriteString(" LIKE ") buffer.WriteString(b.ph()) buffer.Append(filter.Value) case rel.FilterNotLikeOp: - buffer.WriteString(b.escape(filter.Field)) + buffer.WriteString(Escape(b.config, filter.Field)) buffer.WriteString(" NOT LIKE ") buffer.WriteString(b.ph()) buffer.Append(filter.Value) @@ -471,7 +723,7 @@ func (b *Builder) build(buffer *Buffer, op string, inner []rel.FilterQuery) { } func (b *Builder) buildComparison(buffer *Buffer, filter rel.FilterQuery) { - buffer.WriteString(b.escape(filter.Field)) + buffer.WriteString(Escape(b.config, filter.Field)) switch filter.Type { case rel.FilterEqOp: @@ -497,7 +749,7 @@ func (b *Builder) buildInclusion(buffer *Buffer, filter rel.FilterQuery) { values = filter.Value.([]interface{}) ) - buffer.WriteString(b.escape(filter.Field)) + buffer.WriteString(Escape(b.config, filter.Field)) if filter.Type == rel.FilterInOp { buffer.WriteString(" IN (") @@ -523,38 +775,6 @@ func (b *Builder) ph() string { return b.config.Placeholder } -type fieldCacheKey struct { - field string - escape string -} - -func (b *Builder) escape(field string) string { - if b.config.EscapeChar == "" || field == "*" { - return field - } - - key := fieldCacheKey{field: field, escape: b.config.EscapeChar} - escapedField, ok := fieldCache.Load(key) - if ok { - return escapedField.(string) - } - - if len(field) > 0 && field[0] == UnescapeCharacter { - escapedField = field[1:] - } else if start, end := strings.IndexRune(field, '('), strings.IndexRune(field, ')'); start >= 0 && end >= 0 && end > start { - escapedField = field[:start+1] + b.escape(field[start+1:end]) + field[end:] - } else if strings.HasSuffix(field, "*") { - escapedField = b.config.EscapeChar + strings.Replace(field, ".", b.config.EscapeChar+".", 1) - } else { - escapedField = b.config.EscapeChar + - strings.Replace(field, ".", b.config.EscapeChar+"."+b.config.EscapeChar, 1) + - b.config.EscapeChar - } - - fieldCache.Store(key, escapedField) - return escapedField.(string) -} - // Returning append returning to insert rel. func (b *Builder) Returning(field string) *Builder { b.returnField = field @@ -562,7 +782,7 @@ func (b *Builder) Returning(field string) *Builder { } // NewBuilder create new SQL builder. -func NewBuilder(config *Config) *Builder { +func NewBuilder(config Config) *Builder { return &Builder{ config: config, } diff --git a/vendor/github.com/Fs02/rel/adapter/sql/config.go b/vendor/github.com/Fs02/rel/adapter/sql/config.go new file mode 100644 index 0000000..95f8068 --- /dev/null +++ b/vendor/github.com/Fs02/rel/adapter/sql/config.go @@ -0,0 +1,76 @@ +package sql + +import ( + "time" + + "github.com/Fs02/rel" +) + +// Config holds configuration for adapter. +type Config struct { + Placeholder string + Ordinal bool + InsertDefaultValues bool + DropIndexOnTable bool + EscapeChar string + ErrorFunc func(error) error + IncrementFunc func(Adapter) int + IndexToSQL func(config Config, buffer *Buffer, index rel.Index) bool + MapColumnFunc func(column *rel.Column) (string, int, int) +} + +// MapColumn func. +func MapColumn(column *rel.Column) (string, int, int) { + var ( + typ string + m, n int + timeLayout = "2006-01-02 15:04:05" + ) + + switch column.Type { + case rel.ID: + typ = "INT UNSIGNED AUTO_INCREMENT PRIMARY KEY" + case rel.Bool: + typ = "BOOL" + case rel.Int: + typ = "INT" + m = column.Limit + case rel.BigInt: + typ = "BIGINT" + m = column.Limit + case rel.Float: + typ = "FLOAT" + m = column.Precision + case rel.Decimal: + typ = "DECIMAL" + m = column.Precision + n = column.Scale + case rel.String: + typ = "VARCHAR" + m = column.Limit + if m == 0 { + m = 255 + } + case rel.Text: + typ = "TEXT" + m = column.Limit + case rel.Date: + typ = "DATE" + timeLayout = "2006-01-02" + case rel.DateTime: + typ = "DATETIME" + case rel.Time: + typ = "TIME" + timeLayout = "15:04:05" + case rel.Timestamp: + typ = "TIMESTAMP" + default: + typ = string(column.Type) + } + + if t, ok := column.Default.(time.Time); ok { + column.Default = t.Format(timeLayout) + } + + return typ, m, n +} diff --git a/vendor/github.com/Fs02/rel/adapter/sql/util.go b/vendor/github.com/Fs02/rel/adapter/sql/util.go index 4be6913..a9b39ea 100644 --- a/vendor/github.com/Fs02/rel/adapter/sql/util.go +++ b/vendor/github.com/Fs02/rel/adapter/sql/util.go @@ -17,3 +17,65 @@ func ExtractString(s, left, right string) string { return s[start+len(left) : end] } + +type fieldCacheKey struct { + field string + escape string +} + +// Escape field or table name. +func Escape(config Config, field string) string { + if config.EscapeChar == "" || field == "*" { + return field + } + + key := fieldCacheKey{field: field, escape: config.EscapeChar} + escapedField, ok := fieldCache.Load(key) + if ok { + return escapedField.(string) + } + + if len(field) > 0 && field[0] == UnescapeCharacter { + escapedField = field[1:] + } else if start, end := strings.IndexRune(field, '('), strings.IndexRune(field, ')'); start >= 0 && end >= 0 && end > start { + escapedField = field[:start+1] + Escape(config, field[start+1:end]) + field[end:] + } else if strings.HasSuffix(field, "*") { + escapedField = config.EscapeChar + strings.Replace(field, ".", config.EscapeChar+".", 1) + } else { + escapedField = config.EscapeChar + + strings.Replace(field, ".", config.EscapeChar+"."+config.EscapeChar, 1) + + config.EscapeChar + } + + fieldCache.Store(key, escapedField) + return escapedField.(string) +} + +func toInt64(i interface{}) int64 { + var result int64 + + switch s := i.(type) { + case int: + result = int64(s) + case int64: + result = s + case int32: + result = int64(s) + case int16: + result = int64(s) + case int8: + result = int64(s) + case uint: + result = int64(s) + case uint64: + result = int64(s) + case uint32: + result = int64(s) + case uint16: + result = int64(s) + case uint8: + result = int64(s) + } + + return result +} diff --git a/vendor/github.com/Fs02/rel/association.go b/vendor/github.com/Fs02/rel/association.go index 09fc53a..1b6e8a2 100644 --- a/vendor/github.com/Fs02/rel/association.go +++ b/vendor/github.com/Fs02/rel/association.go @@ -62,17 +62,15 @@ func (a Association) Document() (*Document, bool) { var ( doc = NewDocument(rv) - id = doc.PrimaryValue() ) - return doc, !isZero(id) + return doc, doc.Persisted() default: var ( doc = NewDocument(rv.Addr()) - id = doc.PrimaryValue() ) - return doc, !isZero(id) + return doc, doc.Persisted() } } diff --git a/vendor/github.com/Fs02/rel/collection.go b/vendor/github.com/Fs02/rel/collection.go index 15efd6a..cb022ab 100644 --- a/vendor/github.com/Fs02/rel/collection.go +++ b/vendor/github.com/Fs02/rel/collection.go @@ -58,45 +58,81 @@ func (c Collection) tableName() string { return tableName(rt) } -// PrimaryField column name of this collection. -func (c Collection) PrimaryField() string { +// PrimaryFields column name of this collection. +func (c Collection) PrimaryFields() []string { if p, ok := c.v.(primary); ok { - return p.PrimaryField() + return p.PrimaryFields() } - if c.data.primaryField == "" { + if len(c.data.primaryField) == 0 { panic("rel: failed to infer primary key for type " + c.rt.String()) } return c.data.primaryField } -// PrimaryValue of collection. +// PrimaryField column name of this document. +// panic if document uses composite key. +func (c Collection) PrimaryField() string { + if fields := c.PrimaryFields(); len(fields) == 1 { + return fields[0] + } + + panic("rel: composite primary key is not supported") +} + +// PrimaryValues of collection. // Returned value will be interface of slice interface. -func (c Collection) PrimaryValue() interface{} { +func (c Collection) PrimaryValues() []interface{} { if p, ok := c.v.(primary); ok { - return p.PrimaryValue() + return p.PrimaryValues() } var ( - index = c.data.primaryIndex - ids = make([]interface{}, c.rv.Len()) + index = c.data.primaryIndex + pValues = make([]interface{}, len(c.PrimaryFields())) ) - for i := 0; i < len(ids); i++ { + if index != nil { + for i := range index { + var ( + values = make([]interface{}, c.rv.Len()) + ) + + for j := range values { + values[j] = c.rv.Index(j).Field(index[i]).Interface() + } + + pValues[i] = values + } + } else { + // using interface. var ( - fv = c.rv.Index(i) + tmp = make([][]interface{}, len(pValues)) ) - if index == -2 { - // using interface - ids[i] = fv.Interface().(primary).PrimaryValue() - } else { - ids[i] = fv.Field(index).Interface() + for i := 0; i < c.rv.Len(); i++ { + for j, id := range c.rv.Index(i).Interface().(primary).PrimaryValues() { + tmp[j] = append(tmp[j], id) + } } + + for i := range tmp { + pValues[i] = tmp[i] + } + } + + return pValues +} + +// PrimaryValue of this document. +// panic if document uses composite key. +func (c Collection) PrimaryValue() interface{} { + if values := c.PrimaryValues(); len(values) == 1 { + return values[0] } - return ids + panic("rel: composite primary key is not supported") } // Get an element from the underlying slice as a document. diff --git a/vendor/github.com/Fs02/rel/column.go b/vendor/github.com/Fs02/rel/column.go new file mode 100644 index 0000000..89d01c5 --- /dev/null +++ b/vendor/github.com/Fs02/rel/column.go @@ -0,0 +1,145 @@ +package rel + +// ColumnType definition. +type ColumnType string + +const ( + // ID ColumnType. + ID ColumnType = "ID" + // Bool ColumnType. + Bool ColumnType = "BOOL" + // Int ColumnType. + Int ColumnType = "INT" + // BigInt ColumnType. + BigInt ColumnType = "BIGINT" + // Float ColumnType. + Float ColumnType = "FLOAT" + // Decimal ColumnType. + Decimal ColumnType = "DECIMAL" + // String ColumnType. + String ColumnType = "STRING" + // Text ColumnType. + Text ColumnType = "TEXT" + // Date ColumnType. + Date ColumnType = "DATE" + // DateTime ColumnType. + DateTime ColumnType = "DATETIME" + // Time ColumnType. + Time ColumnType = "TIME" + // Timestamp ColumnType. + Timestamp ColumnType = "TIMESTAMP" +) + +// Column definition. +type Column struct { + Op SchemaOp + Name string + Type ColumnType + Rename string + Unique bool + Required bool + Unsigned bool + Limit int + Precision int + Scale int + Default interface{} + Options string +} + +func (Column) internalTableDefinition() {} + +func createColumn(name string, typ ColumnType, options []ColumnOption) Column { + column := Column{ + Op: SchemaCreate, + Name: name, + Type: typ, + } + + applyColumnOptions(&column, options) + return column +} + +func renameColumn(name string, newName string, options []ColumnOption) Column { + column := Column{ + Op: SchemaRename, + Name: name, + Rename: newName, + } + + applyColumnOptions(&column, options) + return column +} + +func dropColumn(name string, options []ColumnOption) Column { + column := Column{ + Op: SchemaDrop, + Name: name, + } + + applyColumnOptions(&column, options) + return column +} + +// ColumnOption interface. +// Available options are: Nil, Unsigned, Limit, Precision, Scale, Default, Comment, Options. +type ColumnOption interface { + applyColumn(column *Column) +} + +func applyColumnOptions(column *Column, options []ColumnOption) { + for i := range options { + options[i].applyColumn(column) + } +} + +// Unique set column as unique. +type Unique bool + +func (r Unique) applyColumn(column *Column) { + column.Unique = bool(r) +} + +func (r Unique) applyIndex(index *Index) { + index.Unique = bool(r) +} + +// Required disallows nil values in the column. +type Required bool + +func (r Required) applyColumn(column *Column) { + column.Required = bool(r) +} + +// Unsigned sets integer column to be unsigned. +type Unsigned bool + +func (u Unsigned) applyColumn(column *Column) { + column.Unsigned = bool(u) +} + +// Precision defines the precision for the decimal fields, representing the total number of digits in the number. +type Precision int + +func (p Precision) applyColumn(column *Column) { + column.Precision = int(p) +} + +// Scale Defines the scale for the decimal fields, representing the number of digits after the decimal point. +type Scale int + +func (s Scale) applyColumn(column *Column) { + column.Scale = int(s) +} + +type defaultValue struct { + value interface{} +} + +func (d defaultValue) applyColumn(column *Column) { + column.Default = d.value +} + +// Default allows to set a default value on the column.). +func Default(def interface{}) ColumnOption { + return defaultValue{value: def} +} diff --git a/vendor/github.com/Fs02/rel/document.go b/vendor/github.com/Fs02/rel/document.go index 0377f6e..0242f3a 100644 --- a/vendor/github.com/Fs02/rel/document.go +++ b/vendor/github.com/Fs02/rel/document.go @@ -46,13 +46,13 @@ type table interface { } type primary interface { - PrimaryField() string - PrimaryValue() interface{} + PrimaryFields() []string + PrimaryValues() []interface{} } type primaryData struct { - field string - index int + field []string + index []int } type documentData struct { @@ -61,8 +61,8 @@ type documentData struct { belongsTo []string hasOne []string hasMany []string - primaryField string - primaryIndex int + primaryField []string + primaryIndex []int flag DocumentFlag } @@ -89,30 +89,73 @@ func (d Document) Table() string { return tableName(d.rt) } +// PrimaryFields column name of this document. +func (d Document) PrimaryFields() []string { + if p, ok := d.v.(primary); ok { + return p.PrimaryFields() + } + + if len(d.data.primaryField) == 0 { + panic("rel: failed to infer primary key for type " + d.rt.String()) + } + + return d.data.primaryField +} + // PrimaryField column name of this document. +// panic if document uses composite key. func (d Document) PrimaryField() string { + if fields := d.PrimaryFields(); len(fields) == 1 { + return fields[0] + } + + panic("rel: composite primary key is not supported") +} + +// PrimaryValues of this document. +func (d Document) PrimaryValues() []interface{} { if p, ok := d.v.(primary); ok { - return p.PrimaryField() + return p.PrimaryValues() } - if d.data.primaryField == "" { + if len(d.data.primaryIndex) == 0 { panic("rel: failed to infer primary key for type " + d.rt.String()) } - return d.data.primaryField + var ( + pValues = make([]interface{}, len(d.data.primaryIndex)) + ) + + for i := range pValues { + pValues[i] = d.rv.Field(d.data.primaryIndex[i]).Interface() + } + + return pValues } // PrimaryValue of this document. +// panic if document uses composite key. func (d Document) PrimaryValue() interface{} { - if p, ok := d.v.(primary); ok { - return p.PrimaryValue() + if values := d.PrimaryValues(); len(values) == 1 { + return values[0] } - if d.data.primaryIndex < 0 { - panic("rel: failed to infer primary key for type " + d.rt.String()) + panic("rel: composite primary key is not supported") +} + +// Persisted returns true if document primary key is not zero. +func (d Document) Persisted() bool { + var ( + pValues = d.PrimaryValues() + ) + + for i := range pValues { + if !isZero(pValues[i]) { + return true + } } - return d.rv.Field(d.data.primaryIndex).Interface() + return false } // Index returns map of column name and it's struct index. @@ -370,7 +413,7 @@ func extractDocumentData(rt reflect.Type, skipAssoc bool) documentData { name = fieldName(sf) ) - if name == "" { + if c := sf.Name[0]; c < 'A' || c > 'Z' || name == "" { continue } @@ -393,7 +436,7 @@ func extractDocumentData(rt reflect.Type, skipAssoc bool) documentData { // struct without primary key is a field // TODO: test by scanner/valuer instead? - if pk, _ := searchPrimary(typ); pk == "" { + if pk, _ := searchPrimary(typ); len(pk) == 0 { data.fields = append(data.fields, name) continue } @@ -453,15 +496,16 @@ func fieldName(sf reflect.StructField) string { return snakecase.SnakeCase(sf.Name) } -func searchPrimary(rt reflect.Type) (string, int) { +func searchPrimary(rt reflect.Type) ([]string, []int) { if result, cached := primariesCache.Load(rt); cached { p := result.(primaryData) return p.field, p.index } var ( - field = "" - index = -1 + field []string + index []int + fallbackIndex = -1 ) if rt.Implements(rtPrimary) { @@ -469,26 +513,30 @@ func searchPrimary(rt reflect.Type) (string, int) { v = reflect.Zero(rt).Interface().(primary) ) - field = v.PrimaryField() - index = -2 // special index to mark interface usage + field = v.PrimaryFields() + // index kept nil to mark interface usage } else { for i := 0; i < rt.NumField(); i++ { sf := rt.Field(i) if tag := sf.Tag.Get("db"); strings.HasSuffix(tag, ",primary") { - index = i - field = fieldName(sf) + index = append(index, i) + field = append(field, fieldName(sf)) continue } // check fallback for id field if strings.EqualFold("id", sf.Name) { - index = i - field = "id" + fallbackIndex = i } } } + if len(field) == 0 && fallbackIndex >= 0 { + field = []string{"id"} + index = []int{fallbackIndex} + } + primariesCache.Store(rt, primaryData{ field: field, index: index, diff --git a/vendor/github.com/Fs02/rel/filter_query.go b/vendor/github.com/Fs02/rel/filter_query.go index ad2e6a2..66c4fc4 100644 --- a/vendor/github.com/Fs02/rel/filter_query.go +++ b/vendor/github.com/Fs02/rel/filter_query.go @@ -1,5 +1,9 @@ package rel +import ( + "errors" +) + // FilterOp defines enumeration of all supported filter types. type FilterOp int @@ -496,3 +500,100 @@ func FilterFragment(expr string, values ...interface{}) FilterQuery { Value: values, } } + +func filterDocument(doc *Document) FilterQuery { + var ( + pFields = doc.PrimaryFields() + pValues = doc.PrimaryValues() + ) + + return filterDocumentPrimary(pFields, pValues, FilterEqOp) +} + +func filterDocumentPrimary(pFields []string, pValues []interface{}, op FilterOp) FilterQuery { + var filter FilterQuery + + for i := range pFields { + filter = filter.And(FilterQuery{ + Type: op, + Field: pFields[i], + Value: pValues[i], + }) + } + + return filter + +} + +func filterCollection(col *Collection) FilterQuery { + var ( + pFields = col.PrimaryFields() + pValues = col.PrimaryValues() + length = col.Len() + ) + + return filterCollectionPrimary(pFields, pValues, length) +} + +func filterCollectionPrimary(pFields []string, pValues []interface{}, length int) FilterQuery { + var filter FilterQuery + + if len(pFields) == 1 { + filter = In(pFields[0], pValues[0].([]interface{})...) + } else { + var ( + andFilters = make([]FilterQuery, length) + ) + + for i := range pValues { + var ( + values = pValues[i].([]interface{}) + ) + + for j := range values { + andFilters[j] = andFilters[j].AndEq(pFields[i], values[j]) + } + } + + filter = Or(andFilters...) + } + + return filter +} + +func filterBelongsTo(assoc Association) (FilterQuery, error) { + var ( + rValue = assoc.ReferenceValue() + fValue = assoc.ForeignValue() + filter = Eq(assoc.ForeignField(), fValue) + ) + + if rValue != fValue { + return filter, ConstraintError{ + Key: assoc.ReferenceField(), + Type: ForeignKeyConstraint, + Err: errors.New("rel: inconsistent belongs to ref and fk"), + } + } + + return filter, nil +} + +func filterHasOne(assoc Association, asssocDoc *Document) (FilterQuery, error) { + var ( + fField = assoc.ForeignField() + fValue = assoc.ForeignValue() + rValue = assoc.ReferenceValue() + filter = filterDocument(asssocDoc).AndEq(fField, rValue) + ) + + if rValue != fValue { + return filter, ConstraintError{ + Key: fField, + Type: ForeignKeyConstraint, + Err: errors.New("rel: inconsistent has one ref and fk"), + } + } + + return filter, nil +} diff --git a/vendor/github.com/Fs02/rel/go.mod b/vendor/github.com/Fs02/rel/go.mod index 4836ea4..26f403e 100644 --- a/vendor/github.com/Fs02/rel/go.mod +++ b/vendor/github.com/Fs02/rel/go.mod @@ -7,7 +7,7 @@ require ( github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a github.com/kr/pretty v0.1.0 // indirect github.com/lib/pq v1.3.0 - github.com/mattn/go-sqlite3 v1.6.0 + github.com/mattn/go-sqlite3 v1.14.2 github.com/stretchr/objx v0.2.0 // indirect github.com/stretchr/testify v1.4.0 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect diff --git a/vendor/github.com/Fs02/rel/go.sum b/vendor/github.com/Fs02/rel/go.sum index e2a1fee..235f62c 100644 --- a/vendor/github.com/Fs02/rel/go.sum +++ b/vendor/github.com/Fs02/rel/go.sum @@ -1,5 +1,7 @@ github.com/Fs02/go-paranoid v0.0.0-20190122110906-018c1ac5124a h1:wYjvXrzEmkEe3kNQXUd2Nzt/EO28kqebKsUWjXH9Opk= github.com/Fs02/go-paranoid v0.0.0-20190122110906-018c1ac5124a/go.mod h1:mUYWV9DG75bJ33LZlW1Je3MW64017zkfUFCf+QnCJs0= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/azer/snakecase v0.0.0-20161028114325-c818dddafb5c h1:7zL0ljVI6ads5EFvx+Oq+uompnFBMJqtbuHvyobbJ1Q= github.com/azer/snakecase v0.0.0-20161028114325-c818dddafb5c/go.mod h1:iApMeoHF0YlMPzCwqH/d59E3w2s8SeO4rGK+iGClS8Y= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= @@ -17,8 +19,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/mattn/go-sqlite3 v1.6.0 h1:TDwTWbeII+88Qy55nWlof0DclgAtI4LqGujkYMzmQII= -github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.2 h1:A2EQLwjYf/hfYaM20FVjs1UewCTTFR7RmjEHkLjldIA= +github.com/mattn/go-sqlite3 v1.14.2/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= 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 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= @@ -29,6 +31,13 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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= diff --git a/vendor/github.com/Fs02/rel/index.go b/vendor/github.com/Fs02/rel/index.go new file mode 100644 index 0000000..6193cb3 --- /dev/null +++ b/vendor/github.com/Fs02/rel/index.go @@ -0,0 +1,62 @@ +package rel + +// Index definition. +type Index struct { + Op SchemaOp + Table string + Name string + Unique bool + Columns []string + Optional bool + Options string +} + +func (Index) internalMigration() {} + +func createIndex(table string, name string, columns []string, options []IndexOption) Index { + index := Index{ + Op: SchemaCreate, + Table: table, + Name: name, + Columns: columns, + } + + applyIndexOptions(&index, options) + return index +} + +func createUniqueIndex(table string, name string, columns []string, options []IndexOption) Index { + index := createIndex(table, name, columns, options) + index.Unique = true + return index +} + +func dropIndex(table string, name string, options []IndexOption) Index { + index := Index{ + Op: SchemaDrop, + Table: table, + Name: name, + } + + applyIndexOptions(&index, options) + return index +} + +// IndexOption interface. +// Available options are: Comment, Options. +type IndexOption interface { + applyIndex(index *Index) +} + +func applyIndexOptions(index *Index, options []IndexOption) { + for i := range options { + options[i].applyIndex(index) + } +} + +// Name option for defining custom index name. +type Name string + +func (n Name) applyKey(key *Key) { + key.Name = string(n) +} diff --git a/vendor/github.com/Fs02/rel/iterator.go b/vendor/github.com/Fs02/rel/iterator.go index 12bb80a..d1c9706 100644 --- a/vendor/github.com/Fs02/rel/iterator.go +++ b/vendor/github.com/Fs02/rel/iterator.go @@ -16,31 +16,43 @@ type IteratorOption interface { apply(*iterator) } -// BatchSize specifies the size of iterator batch. Defaults to 1000. -type BatchSize int +type batchSize int -func (bs BatchSize) apply(i *iterator) { +func (bs batchSize) apply(i *iterator) { i.batchSize = int(bs) } +// BatchSize specifies the size of iterator batch. Defaults to 1000. +func BatchSize(size int) IteratorOption { + return batchSize(size) +} + +type start []interface{} + +func (s start) apply(i *iterator) { + i.start = s +} + // Start specfies the primary value to start from (inclusive). -type Start int +func Start(id ...interface{}) IteratorOption { + return start(id) +} -func (s Start) apply(i *iterator) { - i.start = int(s) +type finish []interface{} + +func (f finish) apply(i *iterator) { + i.finish = f } // Finish specfies the primary value to finish at (inclusive). -type Finish int - -func (f Finish) apply(i *iterator) { - i.finish = int(f) +func Finish(id ...interface{}) IteratorOption { + return finish(id) } type iterator struct { ctx context.Context - start interface{} - finish interface{} + start []interface{} + finish []interface{} batchSize int current int query Query @@ -113,15 +125,15 @@ func (i *iterator) init(record interface{}) { i.query.Table = doc.Table() } - if i.start != nil { - i.query = i.query.Where(Gte(doc.PrimaryField(), i.start)) + if len(i.start) > 0 { + i.query = i.query.Where(filterDocumentPrimary(doc.PrimaryFields(), i.start, FilterGteOp)) } - if i.finish != nil { - i.query = i.query.Where(Lte(doc.PrimaryField(), i.finish)) + if len(i.finish) > 0 { + i.query = i.query.Where(filterDocumentPrimary(doc.PrimaryFields(), i.finish, FilterLteOp)) } - i.query = i.query.SortAsc(doc.PrimaryField()) + i.query = i.query.SortAsc(doc.PrimaryFields()...) } func newIterator(ctx context.Context, adapter Adapter, query Query, options []IteratorOption) Iterator { diff --git a/vendor/github.com/Fs02/rel/key.go b/vendor/github.com/Fs02/rel/key.go new file mode 100644 index 0000000..53dc8e2 --- /dev/null +++ b/vendor/github.com/Fs02/rel/key.go @@ -0,0 +1,92 @@ +package rel + +// KeyType definition. +type KeyType string + +const ( + // PrimaryKey KeyType. + PrimaryKey KeyType = "PRIMARY KEY" + // ForeignKey KeyType. + ForeignKey KeyType = "FOREIGN KEY" + // UniqueKey KeyType. + UniqueKey = "UNIQUE" +) + +// ForeignKeyReference definition. +type ForeignKeyReference struct { + Table string + Columns []string + OnDelete string + OnUpdate string +} + +// Key definition. +type Key struct { + Op SchemaOp + Name string + Type KeyType + Columns []string + Rename string + Reference ForeignKeyReference + Options string +} + +func (Key) internalTableDefinition() {} + +func createKeys(columns []string, typ KeyType, options []KeyOption) Key { + key := Key{ + Op: SchemaCreate, + Columns: columns, + Type: typ, + } + + applyKeyOptions(&key, options) + return key +} + +func createPrimaryKeys(columns []string, options []KeyOption) Key { + return createKeys(columns, PrimaryKey, options) +} + +func createForeignKey(column string, refTable string, refColumn string, options []KeyOption) Key { + key := Key{ + Op: SchemaCreate, + Type: ForeignKey, + Columns: []string{column}, + Reference: ForeignKeyReference{ + Table: refTable, + Columns: []string{refColumn}, + }, + } + + applyKeyOptions(&key, options) + return key +} + +// TODO: Rename and Drop, PR welcomed. + +// KeyOption interface. +// Available options are: Comment, Options. +type KeyOption interface { + applyKey(key *Key) +} + +func applyKeyOptions(key *Key, options []KeyOption) { + for i := range options { + options[i].applyKey(key) + } +} + +// OnDelete option for foreign key. +type OnDelete string + +func (od OnDelete) applyKey(key *Key) { + key.Reference.OnDelete = string(od) +} + +// OnUpdate option for foreign key. +type OnUpdate string + +func (ou OnUpdate) applyKey(key *Key) { + key.Reference.OnUpdate = string(ou) +} diff --git a/vendor/github.com/Fs02/rel/map.go b/vendor/github.com/Fs02/rel/map.go index cd423c5..25f2a84 100644 --- a/vendor/github.com/Fs02/rel/map.go +++ b/vendor/github.com/Fs02/rel/map.go @@ -73,9 +73,12 @@ func applyMaps(maps []Map, assoc Association) ([]Mutation, []interface{}) { deletedIDs []interface{} muts = make([]Mutation, len(maps)) col, _ = assoc.Collection() - pField = col.PrimaryField() - pIndex = make(map[interface{}]int) - pValues = col.PrimaryValue().([]interface{}) + ) + + var ( + pField = col.PrimaryField() + pIndex = make(map[interface{}]int) + pValues = col.PrimaryValue().([]interface{}) ) for i, v := range pValues { diff --git a/vendor/github.com/Fs02/rel/migrator/migrator.go b/vendor/github.com/Fs02/rel/migrator/migrator.go new file mode 100644 index 0000000..6e14e35 --- /dev/null +++ b/vendor/github.com/Fs02/rel/migrator/migrator.go @@ -0,0 +1,164 @@ +package migrator + +import ( + "context" + "fmt" + "sort" + "time" + + "github.com/Fs02/rel" +) + +const versionTable = "rel_schema_versions" + +type version struct { + ID int + Version int + CreatedAt time.Time + UpdatedAt time.Time + + up rel.Schema + down rel.Schema + applied bool +} + +func (version) Table() string { + return versionTable +} + +type versions []version + +func (v versions) Len() int { + return len(v) +} + +func (v versions) Less(i, j int) bool { + return v[i].Version < v[j].Version +} + +func (v versions) Swap(i, j int) { + v[i], v[j] = v[j], v[i] +} + +// Migrator is a migration manager that handles migration logic. +type Migrator struct { + repo rel.Repository + versions versions + versionTableExists bool +} + +// Register a migration. +func (m *Migrator) Register(v int, up func(schema *rel.Schema), down func(schema *rel.Schema)) { + var upSchema, downSchema rel.Schema + + up(&upSchema) + down(&downSchema) + + m.versions = append(m.versions, version{Version: v, up: upSchema, down: downSchema}) +} + +func (m Migrator) buildVersionTableDefinition() rel.Table { + var schema rel.Schema + schema.CreateTableIfNotExists(versionTable, func(t *rel.Table) { + t.ID("id") + t.BigInt("version", rel.Unsigned(true), rel.Unique(true)) + t.DateTime("created_at") + t.DateTime("updated_at") + }) + + return schema.Migrations[0].(rel.Table) +} + +func (m *Migrator) sync(ctx context.Context) { + var ( + versions versions + vi int + adapter = m.repo.Adapter(ctx).(rel.Adapter) + ) + + if !m.versionTableExists { + check(adapter.Apply(ctx, m.buildVersionTableDefinition())) + m.versionTableExists = true + } + + m.repo.MustFindAll(ctx, &versions, rel.NewSortAsc("version")) + sort.Sort(m.versions) + + fmt.Println(versions) + + for i := range m.versions { + if vi < len(versions) && m.versions[i].Version == versions[vi].Version { + m.versions[i].ID = versions[vi].ID + m.versions[i].applied = true + vi++ + } else { + m.versions[i].applied = false + } + } + + if vi != len(versions) { + panic(fmt.Sprint("rel: missing local migration: ", versions[vi].Version)) + } +} + +// Migrate to the latest schema version. +func (m *Migrator) Migrate(ctx context.Context) { + m.sync(ctx) + + for _, v := range m.versions { + if v.applied { + continue + } + + err := m.repo.Transaction(ctx, func(ctx context.Context) error { + m.repo.MustInsert(ctx, &version{Version: v.Version}) + m.run(ctx, v.up.Migrations) + return nil + }) + + check(err) + } +} + +// Rollback migration 1 step. +func (m *Migrator) Rollback(ctx context.Context) { + m.sync(ctx) + + for i := range m.versions { + v := m.versions[len(m.versions)-i-1] + if !v.applied { + continue + } + + err := m.repo.Transaction(ctx, func(ctx context.Context) error { + m.repo.MustDelete(ctx, &v) + m.run(ctx, v.down.Migrations) + return nil + }) + + check(err) + + // only rollback one version. + return + } +} + +func (m *Migrator) run(ctx context.Context, migrations []rel.Migration) { + adapter := m.repo.Adapter(ctx).(rel.Adapter) + for _, migration := range migrations { + // TODO: exec script + check(adapter.Apply(ctx, migration)) + } + +} + +// New migrationr. +func New(repo rel.Repository) Migrator { + return Migrator{repo: repo} +} + +func check(err error) { + if err != nil { + panic(err) + } +} diff --git a/vendor/github.com/Fs02/rel/mutation.go b/vendor/github.com/Fs02/rel/mutation.go index 47e75da..5e4f92c 100644 --- a/vendor/github.com/Fs02/rel/mutation.go +++ b/vendor/github.com/Fs02/rel/mutation.go @@ -42,7 +42,7 @@ func Apply(doc *Document, mutators ...Mutator) Mutation { // AssocMutation represents mutation for association. type AssocMutation struct { Mutations []Mutation - DeletedIDs []interface{} + DeletedIDs []interface{} // This is array of single id, and doesn't support composite primary key. } // Mutation represents value to be inserted or updated to database. @@ -218,6 +218,11 @@ func (r Reload) Apply(doc *Document, mutation *Mutation) { mutation.Reload = r } +// Build query. +func (r Reload) Build(query *Query) { + query.ReloadQuery = r +} + // Cascade enable or disable updating associations. // Default to true. type Cascade bool diff --git a/vendor/github.com/Fs02/rel/query.go b/vendor/github.com/Fs02/rel/query.go index b88aa7b..5aaffe8 100644 --- a/vendor/github.com/Fs02/rel/query.go +++ b/vendor/github.com/Fs02/rel/query.go @@ -36,6 +36,8 @@ func Build(table string, queriers ...Querier) Query { q.Build(&query) case Unscoped: q.Build(&query) + case Reload: + q.Build(&query) case SQLQuery: q.Build(&query) } @@ -61,6 +63,7 @@ type Query struct { LimitQuery Limit LockQuery Lock UnscopedQuery Unscoped + ReloadQuery Reload SQLQuery SQLQuery } @@ -101,6 +104,8 @@ func (q Query) Build(query *Query) { if q.LockQuery != "" { query.LockQuery = q.LockQuery } + + query.ReloadQuery = q.ReloadQuery } } @@ -257,6 +262,12 @@ func (q Query) Unscoped() Query { return q } +// Reload force reloading association on preload. +func (q Query) Reload() Query { + q.ReloadQuery = true + return q +} + // Select query create a query with chainable syntax, using select as the starting point. func Select(fields ...string) Query { return Query{ @@ -325,7 +336,9 @@ func (o Offset) Build(query *Query) { query.OffsetQuery = o } -// Limit query. +// Limit options. +// When passed as query, it limits returned result from database. +// When passed as column option, it sets the maximum size of the string/text/binary/integer columns. type Limit int // Build query. @@ -333,6 +346,10 @@ func (l Limit) Build(query *Query) { query.LimitQuery = l } +func (l Limit) applyColumn(column *Column) { + column.Limit = int(l) +} + // Lock query. // This query will be ignored if used outside of transaction. type Lock string diff --git a/vendor/github.com/Fs02/rel/reltest/delete.go b/vendor/github.com/Fs02/rel/reltest/delete.go index ce0daf8..c783e94 100644 --- a/vendor/github.com/Fs02/rel/reltest/delete.go +++ b/vendor/github.com/Fs02/rel/reltest/delete.go @@ -27,6 +27,6 @@ func (d *Delete) ForType(typ string) *Delete { // ExpectDelete to be called. func ExpectDelete(r *Repository, options []rel.Cascade) *Delete { return &Delete{ - Expect: newExpect(r, "Delete", []interface{}{mock.Anything, mock.Anything, options}, []interface{}{nil}), + Expect: newExpect(r, "Delete", []interface{}{r.ctxData, mock.Anything, options}, []interface{}{nil}), } } diff --git a/vendor/github.com/Fs02/rel/reltest/nop_adapter.go b/vendor/github.com/Fs02/rel/reltest/nop_adapter.go index 1c70eb3..27c21c0 100644 --- a/vendor/github.com/Fs02/rel/reltest/nop_adapter.go +++ b/vendor/github.com/Fs02/rel/reltest/nop_adapter.go @@ -33,11 +33,11 @@ func (na *nopAdapter) Delete(ctx context.Context, query rel.Query) (int, error) return 1, nil } -func (na *nopAdapter) Insert(ctx context.Context, query rel.Query, mutates map[string]rel.Mutate) (interface{}, error) { +func (na *nopAdapter) Insert(ctx context.Context, query rel.Query, primaryField string, mutates map[string]rel.Mutate) (interface{}, error) { return 1, nil } -func (na *nopAdapter) InsertAll(ctx context.Context, query rel.Query, fields []string, bulkMutates []map[string]rel.Mutate) ([]interface{}, error) { +func (na *nopAdapter) InsertAll(ctx context.Context, query rel.Query, primaryField string, fields []string, bulkMutates []map[string]rel.Mutate) ([]interface{}, error) { var ( ids = make([]interface{}, len(bulkMutates)) ) @@ -61,6 +61,10 @@ func (na *nopAdapter) Update(ctx context.Context, query rel.Query, mutates map[s return 1, nil } +func (na *nopAdapter) Apply(ctx context.Context, migration rel.Migration) error { + return nil +} + type nopCursor struct { count int } diff --git a/vendor/github.com/Fs02/rel/reltest/repository.go b/vendor/github.com/Fs02/rel/reltest/repository.go index 17fb868..3fe770d 100644 --- a/vendor/github.com/Fs02/rel/reltest/repository.go +++ b/vendor/github.com/Fs02/rel/reltest/repository.go @@ -20,7 +20,7 @@ var _ rel.Repository = (*Repository)(nil) // Adapter provides a mock function with given fields: func (r *Repository) Adapter(ctx context.Context) rel.Adapter { - return nil + return r.repo.Adapter(ctx) } // Instrumentation provides a mock function with given fields: instrumenter diff --git a/vendor/github.com/Fs02/rel/repository.go b/vendor/github.com/Fs02/rel/repository.go index 621cf34..aca3536 100644 --- a/vendor/github.com/Fs02/rel/repository.go +++ b/vendor/github.com/Fs02/rel/repository.go @@ -258,7 +258,8 @@ func (r repository) Insert(ctx context.Context, record interface{}, mutators ... func (r repository) insert(cw contextWrapper, doc *Document, mutation Mutation) error { var ( - pField = doc.PrimaryField() + pField string + pFields = doc.PrimaryFields() queriers = Build(doc.Table()) ) @@ -268,19 +269,29 @@ func (r repository) insert(cw contextWrapper, doc *Document, mutation Mutation) } } - pValue, err := cw.adapter.Insert(cw.ctx, queriers, mutation.Mutates) + if len(pFields) == 1 { + pField = pFields[0] + } + + pValue, err := cw.adapter.Insert(cw.ctx, queriers, pField, mutation.Mutates) if err != nil { return mutation.ErrorFunc.transform(err) } + // update primary value + if pField != "" { + doc.SetValue(pField, pValue) + } + if mutation.Reload { + var ( + filter = filterDocument(doc) + ) + // fetch record - if err := r.find(cw, doc, queriers.Where(Eq(pField, pValue))); err != nil { + if err := r.find(cw, doc, queriers.Where(filter)); err != nil { return err } - } else { - // update primary value - doc.SetValue(pField, pValue) } if mutation.Cascade { @@ -335,7 +346,8 @@ func (r repository) insertAll(cw contextWrapper, col *Collection, mutation []Mut } var ( - pField = col.PrimaryField() + pField string + pFields = col.PrimaryFields() queriers = Build(col.Table()) fields = make([]string, 0, len(mutation[0].Mutates)) fieldMap = make(map[string]struct{}, len(mutation[0].Mutates)) @@ -353,14 +365,20 @@ func (r repository) insertAll(cw contextWrapper, col *Collection, mutation []Mut bulkMutates[i] = mutation[i].Mutates } - ids, err := cw.adapter.InsertAll(cw.ctx, queriers, fields, bulkMutates) + if len(pFields) == 1 { + pField = pFields[0] + } + + ids, err := cw.adapter.InsertAll(cw.ctx, queriers, pField, fields, bulkMutates) if err != nil { return mutation[0].ErrorFunc.transform(err) } // apply ids - for i, id := range ids { - col.Get(i).SetValue(pField, id) + if pField != "" { + for i, id := range ids { + col.Get(i).SetValue(pField, id) + } } return nil @@ -379,18 +397,17 @@ func (r repository) Update(ctx context.Context, record interface{}, mutators ... var ( cw = fetchContext(ctx, r.rootAdapter) doc = NewDocument(record) - pField = doc.PrimaryField() - pValue = doc.PrimaryValue() + filter = filterDocument(doc) mutation = Apply(doc, mutators...) ) if !mutation.IsAssocEmpty() && mutation.Cascade == true { return r.transaction(cw, func(cw contextWrapper) error { - return r.update(cw, doc, mutation, Eq(pField, pValue)) + return r.update(cw, doc, mutation, filter) }) } - return r.update(cw, doc, mutation, Eq(pField, pValue)) + return r.update(cw, doc, mutation, filter) } func (r repository) update(cw contextWrapper, doc *Document, mutation Mutation, filter FilterQuery) error { @@ -452,7 +469,7 @@ func (r repository) saveBelongsTo(cw contextWrapper, doc *Document, mutation *Mu ) if loaded { - filter, err := r.buildBelongsToFilter(assoc) + filter, err := filterBelongsTo(assoc) if err != nil { return err } @@ -478,24 +495,6 @@ func (r repository) saveBelongsTo(cw contextWrapper, doc *Document, mutation *Mu return nil } -func (r repository) buildBelongsToFilter(assoc Association) (FilterQuery, error) { - var ( - rValue = assoc.ReferenceValue() - fValue = assoc.ForeignValue() - filter = Eq(assoc.ForeignField(), fValue) - ) - - if rValue != fValue { - return filter, ConstraintError{ - Key: assoc.ReferenceField(), - Type: ForeignKeyConstraint, - Err: errors.New("rel: inconsistent belongs to ref and fk"), - } - } - - return filter, nil -} - // TODO: suppprt deletion func (r repository) saveHasOne(cw contextWrapper, doc *Document, mutation *Mutation) error { for _, field := range doc.HasOne() { @@ -511,7 +510,7 @@ func (r repository) saveHasOne(cw contextWrapper, doc *Document, mutation *Mutat ) if loaded { - filter, err := r.buildHasOneFilter(assoc, assocDoc) + filter, err := filterHasOne(assoc, assocDoc) if err != nil { return err } @@ -537,27 +536,6 @@ func (r repository) saveHasOne(cw contextWrapper, doc *Document, mutation *Mutat return nil } -func (r repository) buildHasOneFilter(assoc Association, asssocDoc *Document) (FilterQuery, error) { - var ( - fField = assoc.ForeignField() - fValue = assoc.ForeignValue() - rValue = assoc.ReferenceValue() - pField = asssocDoc.PrimaryField() - pValue = asssocDoc.PrimaryValue() - filter = Eq(pField, pValue).AndEq(fField, rValue) - ) - - if rValue != fValue { - return filter, ConstraintError{ - Key: fField, - Type: ForeignKeyConstraint, - Err: errors.New("rel: inconsistent has one ref and fk"), - } - } - - return filter, nil -} - // saveHasMany expects has many mutation to be ordered the same as the recrods in collection. func (r repository) saveHasMany(cw contextWrapper, doc *Document, mutation *Mutation, insertion bool) error { for _, field := range doc.HasMany() { @@ -604,13 +582,14 @@ func (r repository) saveHasMany(cw contextWrapper, doc *Document, mutation *Muta for i := range muts { var ( assocDoc = col.Get(i) - pValue = assocDoc.PrimaryValue() ) - if !isZero(pValue) { + // When deleted IDs is nil, it's assumed that association will be replaced. + // hence any update request is ignored here. + if deletedIDs != nil && !isZero(assocDoc.PrimaryValue()) { var ( fValue, _ = assocDoc.Value(fField) - filter = Eq(pField, pValue).AndEq(fField, rValue) + filter = filterDocument(assocDoc).AndEq(fField, rValue) ) if rValue != fValue { @@ -691,8 +670,6 @@ func (r repository) Delete(ctx context.Context, record interface{}, options ...C var ( cw = fetchContext(ctx, r.rootAdapter) doc = NewDocument(record) - pField = doc.PrimaryField() - pValue = doc.PrimaryValue() cascade = Cascade(false) ) @@ -702,11 +679,11 @@ func (r repository) Delete(ctx context.Context, record interface{}, options ...C if cascade { return r.transaction(cw, func(cw contextWrapper) error { - return r.delete(cw, doc, Eq(pField, pValue), cascade) + return r.delete(cw, doc, filterDocument(doc), cascade) }) } - return r.delete(cw, doc, Eq(pField, pValue), cascade) + return r.delete(cw, doc, filterDocument(doc), cascade) } func (r repository) delete(cw contextWrapper, doc *Document, filter FilterQuery, cascade Cascade) error { @@ -747,7 +724,7 @@ func (r repository) deleteBelongsTo(cw contextWrapper, doc *Document, cascade Ca ) if loaded { - filter, err := r.buildBelongsToFilter(assoc) + filter, err := filterBelongsTo(assoc) if err != nil { return err } @@ -769,7 +746,7 @@ func (r repository) deleteHasOne(cw contextWrapper, doc *Document, cascade Casca ) if loaded { - filter, err := r.buildHasOneFilter(assoc, assocDoc) + filter, err := filterHasOne(assoc, assocDoc) if err != nil { return err } @@ -792,12 +769,10 @@ func (r repository) deleteHasMany(cw contextWrapper, doc *Document) error { if loaded { var ( - table = col.Table() - pField = col.PrimaryField() - pValues = col.PrimaryValue().([]interface{}) - fField = assoc.ForeignField() - rValue = assoc.ReferenceValue() - filter = Eq(fField, rValue).AndIn(pField, pValues...) + table = col.Table() + fField = assoc.ForeignField() + rValue = assoc.ReferenceValue() + filter = Eq(fField, rValue).And(filterCollection(col)) ) if _, err := r.deleteAll(cw, col.data.flag, Build(table, filter)); err != nil { @@ -844,6 +819,8 @@ func (r repository) deleteAll(cw contextWrapper, flag DocumentFlag, query Query) } // Preload loads association with given query. +// If association is already loaded, this will do nothing. +// To force preloading even though association is already loaeded, add `Reload(true)` as query. func (r repository) Preload(ctx context.Context, records interface{}, field string, queriers ...Querier) error { finish := r.instrument(ctx, "rel-preload", "preloading associations") defer finish(nil) @@ -867,25 +844,16 @@ func (r repository) Preload(ctx context.Context, records interface{}, field stri } var ( - targets, table, keyField, keyType, ddata = r.mapPreloadTargets(sl, path) + targets, table, keyField, keyType, ddata, loaded = r.mapPreloadTargets(sl, path) + ids = r.targetIDs(targets) + query = Build(table, append(queriers, In(keyField, ids...))...) ) - if len(targets) == 0 { + if len(targets) == 0 || loaded && !bool(query.ReloadQuery) { return nil } var ( - ids = make([]interface{}, len(targets)) - i = 0 - ) - - for key := range targets { - ids[i] = key - i++ - } - - var ( - query = Build(table, append(queriers, In(keyField, ids...))...) cur, err = cw.adapter.Query(cw.ctx, r.withDefaultScope(ddata, query)) ) @@ -905,7 +873,7 @@ func (r repository) MustPreload(ctx context.Context, records interface{}, field must(r.Preload(ctx, records, field, queriers...)) } -func (r repository) mapPreloadTargets(sl slice, path []string) (map[interface{}][]slice, string, string, reflect.Type, documentData) { +func (r repository) mapPreloadTargets(sl slice, path []string) (map[interface{}][]slice, string, string, reflect.Type, documentData, bool) { type frame struct { index int doc *Document @@ -916,6 +884,7 @@ func (r repository) mapPreloadTargets(sl slice, path []string) (map[interface{}] keyField string keyType reflect.Type ddata documentData + loaded = true mapTarget = make(map[interface{}][]slice) stack = make([]frame, sl.Len()) ) @@ -936,8 +905,9 @@ func (r repository) mapPreloadTargets(sl slice, path []string) (map[interface{}] if top.index == len(path)-1 { var ( - target slice - ref = assocs.ReferenceValue() + target slice + targetLoaded bool + ref = assocs.ReferenceValue() ) if ref == nil { @@ -945,13 +915,14 @@ func (r repository) mapPreloadTargets(sl slice, path []string) (map[interface{}] } if assocs.Type() == HasMany { - target, _ = assocs.Collection() + target, targetLoaded = assocs.Collection() } else { - target, _ = assocs.Document() + target, targetLoaded = assocs.Document() } target.Reset() mapTarget[ref] = append(mapTarget[ref], target) + loaded = loaded && targetLoaded if table == "" { table = target.Table() @@ -995,7 +966,21 @@ func (r repository) mapPreloadTargets(sl slice, path []string) (map[interface{}] } - return mapTarget, table, keyField, keyType, ddata + return mapTarget, table, keyField, keyType, ddata, loaded +} + +func (r repository) targetIDs(targets map[interface{}][]slice) []interface{} { + var ( + ids = make([]interface{}, len(targets)) + i = 0 + ) + + for key := range targets { + ids[i] = key + i++ + } + + return ids } func (r repository) withDefaultScope(ddata documentData, query Query) Query { diff --git a/vendor/github.com/Fs02/rel/schema.go b/vendor/github.com/Fs02/rel/schema.go new file mode 100644 index 0000000..7ca727c --- /dev/null +++ b/vendor/github.com/Fs02/rel/schema.go @@ -0,0 +1,143 @@ +package rel + +// SchemaOp type. +type SchemaOp uint8 + +const ( + // SchemaCreate operation. + SchemaCreate SchemaOp = iota + // SchemaAlter operation. + SchemaAlter + // SchemaRename operation. + SchemaRename + // SchemaDrop operation. + SchemaDrop +) + +// Migration definition. +type Migration interface { + internalMigration() +} + +// Schema builder. +type Schema struct { + Migrations []Migration +} + +func (s *Schema) add(migration Migration) { + s.Migrations = append(s.Migrations, migration) +} + +// CreateTable with name and its definition. +func (s *Schema) CreateTable(name string, fn func(t *Table), options ...TableOption) { + table := createTable(name, options) + fn(&table) + s.add(table) +} + +// CreateTableIfNotExists with name and its definition. +func (s *Schema) CreateTableIfNotExists(name string, fn func(t *Table), options ...TableOption) { + table := createTableIfNotExists(name, options) + fn(&table) + s.add(table) +} + +// AlterTable with name and its definition. +func (s *Schema) AlterTable(name string, fn func(t *AlterTable), options ...TableOption) { + table := alterTable(name, options) + fn(&table) + s.add(table.Table) +} + +// RenameTable by name. +func (s *Schema) RenameTable(name string, newName string, options ...TableOption) { + s.add(renameTable(name, newName, options)) +} + +// DropTable by name. +func (s *Schema) DropTable(name string, options ...TableOption) { + s.add(dropTable(name, options)) +} + +// DropTableIfExists by name. +func (s *Schema) DropTableIfExists(name string, options ...TableOption) { + s.add(dropTableIfExists(name, options)) +} + +// AddColumn with name and type. +func (s *Schema) AddColumn(table string, name string, typ ColumnType, options ...ColumnOption) { + at := alterTable(table, nil) + at.Column(name, typ, options...) + s.add(at.Table) +} + +// RenameColumn by name. +func (s *Schema) RenameColumn(table string, name string, newName string, options ...ColumnOption) { + at := alterTable(table, nil) + at.RenameColumn(name, newName, options...) + s.add(at.Table) +} + +// DropColumn by name. +func (s *Schema) DropColumn(table string, name string, options ...ColumnOption) { + at := alterTable(table, nil) + at.DropColumn(name, options...) + s.add(at.Table) +} + +// CreateIndex for columns on a table. +func (s *Schema) CreateIndex(table string, name string, column []string, options ...IndexOption) { + s.add(createIndex(table, name, column, options)) +} + +// CreateUniqueIndex for columns on a table. +func (s *Schema) CreateUniqueIndex(table string, name string, column []string, options ...IndexOption) { + s.add(createUniqueIndex(table, name, column, options)) +} + +// DropIndex by name. +func (s *Schema) DropIndex(table string, name string, options ...IndexOption) { + s.add(dropIndex(table, name, options)) +} + +// Exec queries using repo. +// Useful for data migration. +// func (s *Schema) Exec(func(repo rel.Repository) error) { +// } + +// Options options for table, column and index. +type Options string + +func (o Options) applyTable(table *Table) { + table.Options = string(o) +} + +func (o Options) applyColumn(column *Column) { + column.Options = string(o) +} + +func (o Options) applyIndex(index *Index) { + index.Options = string(o) +} + +func (o Options) applyKey(key *Key) { + key.Options = string(o) +} + +// Optional option. +// when used with create table, will create table only if it's not exists. +// when used with drop table, will drop table only if it's exists. +type Optional bool + +func (o Optional) applyTable(table *Table) { + table.Optional = bool(o) +} + +func (o Optional) applyIndex(index *Index) { + index.Optional = bool(o) +} + +// Raw string +type Raw string + +func (r Raw) internalTableDefinition() {} diff --git a/vendor/github.com/Fs02/rel/structset.go b/vendor/github.com/Fs02/rel/structset.go index cfac2eb..9531b83 100644 --- a/vendor/github.com/Fs02/rel/structset.go +++ b/vendor/github.com/Fs02/rel/structset.go @@ -20,14 +20,12 @@ type Structset struct { // Apply mutation. func (s Structset) Apply(doc *Document, mut *Mutation) { var ( - pField = s.doc.PrimaryField() - t = now().Truncate(time.Second) + pFields = s.doc.PrimaryFields() + t = now().Truncate(time.Second) ) for _, field := range s.doc.Fields() { switch field { - case pField: - continue case "created_at", "inserted_at": if doc.Flag(HasCreatedAt) { if value, ok := doc.Value(field); ok && value.(time.Time).IsZero() { @@ -42,7 +40,12 @@ func (s Structset) Apply(doc *Document, mut *Mutation) { } } - s.applyValue(doc, mut, field) + if len(pFields) == 1 && pFields[0] == field { + // allow setting primary key as long as it's not zero. + s.applyValue(doc, mut, field, true) + } else { + s.applyValue(doc, mut, field, s.skipZero) + } } if mut.Cascade { @@ -58,9 +61,9 @@ func (s Structset) set(doc *Document, mut *Mutation, field string, value interfa mut.Add(Set(field, value)) } -func (s Structset) applyValue(doc *Document, mut *Mutation, field string) { +func (s Structset) applyValue(doc *Document, mut *Mutation, field string, skipZero bool) { if value, ok := s.doc.Value(field); ok { - if s.skipZero && isZero(value) { + if skipZero && isZero(value) { return } @@ -103,7 +106,6 @@ func (s Structset) buildAssocMany(field string, mut *Mutation) { var ( col, _ = assoc.Collection() - pField = col.PrimaryField() muts = make([]Mutation, col.Len()) ) @@ -113,7 +115,6 @@ func (s Structset) buildAssocMany(field string, mut *Mutation) { ) muts[i] = Apply(doc, newStructset(doc, s.skipZero)) - doc.SetValue(pField, nil) // reset id, since it'll be reinserted. } mut.SetAssoc(field, muts...) diff --git a/vendor/github.com/Fs02/rel/table.go b/vendor/github.com/Fs02/rel/table.go new file mode 100644 index 0000000..231789c --- /dev/null +++ b/vendor/github.com/Fs02/rel/table.go @@ -0,0 +1,189 @@ +package rel + +// TableDefinition interface. +type TableDefinition interface { + internalTableDefinition() +} + +// Table definition. +type Table struct { + Op SchemaOp + Name string + Rename string + Definitions []TableDefinition + Optional bool + Options string +} + +// Column defines a column with name and type. +func (t *Table) Column(name string, typ ColumnType, options ...ColumnOption) { + t.Definitions = append(t.Definitions, createColumn(name, typ, options)) +} + +// ID defines a column with name and ID type. +// the resulting database type will depends on database. +func (t *Table) ID(name string, options ...ColumnOption) { + t.Column(name, ID, options...) +} + +// Bool defines a column with name and Bool type. +func (t *Table) Bool(name string, options ...ColumnOption) { + t.Column(name, Bool, options...) +} + +// Int defines a column with name and Int type. +func (t *Table) Int(name string, options ...ColumnOption) { + t.Column(name, Int, options...) +} + +// BigInt defines a column with name and BigInt type. +func (t *Table) BigInt(name string, options ...ColumnOption) { + t.Column(name, BigInt, options...) +} + +// Float defines a column with name and Float type. +func (t *Table) Float(name string, options ...ColumnOption) { + t.Column(name, Float, options...) +} + +// Decimal defines a column with name and Decimal type. +func (t *Table) Decimal(name string, options ...ColumnOption) { + t.Column(name, Decimal, options...) +} + +// String defines a column with name and String type. +func (t *Table) String(name string, options ...ColumnOption) { + t.Column(name, String, options...) +} + +// Text defines a column with name and Text type. +func (t *Table) Text(name string, options ...ColumnOption) { + t.Column(name, Text, options...) +} + +// Date defines a column with name and Date type. +func (t *Table) Date(name string, options ...ColumnOption) { + t.Column(name, Date, options...) +} + +// DateTime defines a column with name and DateTime type. +func (t *Table) DateTime(name string, options ...ColumnOption) { + t.Column(name, DateTime, options...) +} + +// Time defines a column with name and Time type. +func (t *Table) Time(name string, options ...ColumnOption) { + t.Column(name, Time, options...) +} + +// Timestamp defines a column with name and Timestamp type. +func (t *Table) Timestamp(name string, options ...ColumnOption) { + t.Column(name, Timestamp, options...) +} + +// PrimaryKey defines a primary key for table. +func (t *Table) PrimaryKey(column string, options ...KeyOption) { + t.PrimaryKeys([]string{column}, options...) +} + +// PrimaryKeys defines composite primary keys for table. +func (t *Table) PrimaryKeys(columns []string, options ...KeyOption) { + t.Definitions = append(t.Definitions, createPrimaryKeys(columns, options)) +} + +// ForeignKey defines foreign key index. +func (t *Table) ForeignKey(column string, refTable string, refColumn string, options ...KeyOption) { + t.Definitions = append(t.Definitions, createForeignKey(column, refTable, refColumn, options)) +} + +// Unique defines an unique key for columns. +func (t *Table) Unique(columns []string, options ...KeyOption) { + t.Definitions = append(t.Definitions, createKeys(columns, UniqueKey, options)) +} + +// Fragment defines anything using sql fragment. +func (t *Table) Fragment(fragment string) { + t.Definitions = append(t.Definitions, Raw(fragment)) +} + +func (t Table) internalMigration() {} + +// AlterTable Migrator. +type AlterTable struct { + Table +} + +// RenameColumn to a new name. +func (at *AlterTable) RenameColumn(name string, newName string, options ...ColumnOption) { + at.Definitions = append(at.Definitions, renameColumn(name, newName, options)) +} + +// DropColumn from this table. +func (at *AlterTable) DropColumn(name string, options ...ColumnOption) { + at.Definitions = append(at.Definitions, dropColumn(name, options)) +} + +func createTable(name string, options []TableOption) Table { + table := Table{ + Op: SchemaCreate, + Name: name, + } + + applyTableOptions(&table, options) + return table +} + +func createTableIfNotExists(name string, options []TableOption) Table { + table := createTable(name, options) + table.Optional = true + return table +} + +func alterTable(name string, options []TableOption) AlterTable { + table := Table{ + Op: SchemaAlter, + Name: name, + } + + applyTableOptions(&table, options) + return AlterTable{Table: table} +} + +func renameTable(name string, newName string, options []TableOption) Table { + table := Table{ + Op: SchemaRename, + Name: name, + Rename: newName, + } + + applyTableOptions(&table, options) + return table +} + +func dropTable(name string, options []TableOption) Table { + table := Table{ + Op: SchemaDrop, + Name: name, + } + + applyTableOptions(&table, options) + return table +} + +func dropTableIfExists(name string, options []TableOption) Table { + table := dropTable(name, options) + table.Optional = true + return table +} + +// TableOption interface. +// Available options are: Comment, Options. +type TableOption interface { + applyTable(table *Table) +} + +func applyTableOptions(table *Table, options []TableOption) { + for i := range options { + options[i].applyTable(table) + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 721a83c..64a17ec 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,8 +1,9 @@ -# github.com/Fs02/rel v0.5.0 +# github.com/Fs02/rel v0.6.1-0.20200901120822-3b34832d3a9e ## explicit github.com/Fs02/rel github.com/Fs02/rel/adapter/postgres github.com/Fs02/rel/adapter/sql +github.com/Fs02/rel/migrator github.com/Fs02/rel/reltest github.com/Fs02/rel/where # github.com/azer/snakecase v1.0.0 From 6e747c57e7b1d8ee7a74b63af87e56ac98d3b46f Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Wed, 2 Sep 2020 00:07:45 +0700 Subject: [PATCH 2/4] bump rel --- go.mod | 2 +- go.sum | 2 ++ vendor/github.com/Fs02/rel/migrator/migrator.go | 2 +- vendor/modules.txt | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7edee72..9587735 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Fs02/go-todo-backend go 1.14 require ( - github.com/Fs02/rel v0.6.1-0.20200901120822-3b34832d3a9e + github.com/Fs02/rel v0.6.1-0.20200901164852-1f41be8177cc github.com/azer/snakecase v1.0.0 // indirect github.com/go-chi/chi v3.3.2+incompatible github.com/goware/cors v1.1.1 diff --git a/go.sum b/go.sum index cb49428..a17928e 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/Fs02/rel v0.5.0 h1:z0SpMTSFTWfZAl+KV5mTEcsyMzPDYWcUmuVRFMWMzMo= github.com/Fs02/rel v0.5.0/go.mod h1:MeJHkZO46QVfjagPSnYX/noguNDOqNK2LLGse77asxw= github.com/Fs02/rel v0.6.1-0.20200901120822-3b34832d3a9e h1:rBA7GTr0XUKGuOutCgUZ4mZ3i2eSCwkuE2KPWdf/3og= github.com/Fs02/rel v0.6.1-0.20200901120822-3b34832d3a9e/go.mod h1:HCzzNmcDNX0IShoePaXQ/srKElubko6qQ3NHzxkagF4= +github.com/Fs02/rel v0.6.1-0.20200901164852-1f41be8177cc h1:zuHgP4mhT1XaOnhj90siqJRg0VQb8uDPOfgeq6YoD28= +github.com/Fs02/rel v0.6.1-0.20200901164852-1f41be8177cc/go.mod h1:HCzzNmcDNX0IShoePaXQ/srKElubko6qQ3NHzxkagF4= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/azer/snakecase v0.0.0-20161028114325-c818dddafb5c h1:7zL0ljVI6ads5EFvx+Oq+uompnFBMJqtbuHvyobbJ1Q= diff --git a/vendor/github.com/Fs02/rel/migrator/migrator.go b/vendor/github.com/Fs02/rel/migrator/migrator.go index 6e14e35..426db63 100644 --- a/vendor/github.com/Fs02/rel/migrator/migrator.go +++ b/vendor/github.com/Fs02/rel/migrator/migrator.go @@ -61,7 +61,7 @@ func (m Migrator) buildVersionTableDefinition() rel.Table { var schema rel.Schema schema.CreateTableIfNotExists(versionTable, func(t *rel.Table) { t.ID("id") - t.BigInt("version", rel.Unsigned(true), rel.Unique(true)) + t.BigInt("version", rel.Unique(true)) t.DateTime("created_at") t.DateTime("updated_at") }) diff --git a/vendor/modules.txt b/vendor/modules.txt index 64a17ec..d94a709 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,4 @@ -# github.com/Fs02/rel v0.6.1-0.20200901120822-3b34832d3a9e +# github.com/Fs02/rel v0.6.1-0.20200901164852-1f41be8177cc ## explicit github.com/Fs02/rel github.com/Fs02/rel/adapter/postgres From de505feccf27524dc1c89a1e2eaa1ba4b833e9d2 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Fri, 4 Sep 2020 00:29:29 +0700 Subject: [PATCH 3/4] bump rel master --- go.mod | 2 +- go.sum | 13 +- vendor/github.com/Fs02/rel/README.md | 2 +- .../Fs02/rel/adapter/sql/adapter.go | 2 + vendor/github.com/Fs02/rel/collection.go | 1 - vendor/github.com/Fs02/rel/column.go | 64 -------- vendor/github.com/Fs02/rel/context_wrapper.go | 4 - vendor/github.com/Fs02/rel/document.go | 2 - vendor/github.com/Fs02/rel/key.go | 26 ---- .../github.com/Fs02/rel/migrator/migrator.go | 11 +- .../Fs02/rel/reltest/nop_adapter.go | 1 - vendor/github.com/Fs02/rel/schema.go | 44 +----- vendor/github.com/Fs02/rel/schema_options.go | 146 ++++++++++++++++++ vendor/github.com/Fs02/rel/table.go | 12 -- vendor/github.com/Fs02/rel/util.go | 2 +- vendor/modules.txt | 2 +- 16 files changed, 169 insertions(+), 165 deletions(-) create mode 100644 vendor/github.com/Fs02/rel/schema_options.go diff --git a/go.mod b/go.mod index 9587735..b601349 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Fs02/go-todo-backend go 1.14 require ( - github.com/Fs02/rel v0.6.1-0.20200901164852-1f41be8177cc + github.com/Fs02/rel v0.6.1-0.20200903172418-3af9e733867d github.com/azer/snakecase v1.0.0 // indirect github.com/go-chi/chi v3.3.2+incompatible github.com/goware/cors v1.1.1 diff --git a/go.sum b/go.sum index a17928e..662a6b3 100644 --- a/go.sum +++ b/go.sum @@ -2,13 +2,11 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Fs02/go-paranoid v0.0.0-20190122110906-018c1ac5124a h1:wYjvXrzEmkEe3kNQXUd2Nzt/EO28kqebKsUWjXH9Opk= github.com/Fs02/go-paranoid v0.0.0-20190122110906-018c1ac5124a/go.mod h1:mUYWV9DG75bJ33LZlW1Je3MW64017zkfUFCf+QnCJs0= -github.com/Fs02/rel v0.5.0 h1:z0SpMTSFTWfZAl+KV5mTEcsyMzPDYWcUmuVRFMWMzMo= -github.com/Fs02/rel v0.5.0/go.mod h1:MeJHkZO46QVfjagPSnYX/noguNDOqNK2LLGse77asxw= -github.com/Fs02/rel v0.6.1-0.20200901120822-3b34832d3a9e h1:rBA7GTr0XUKGuOutCgUZ4mZ3i2eSCwkuE2KPWdf/3og= -github.com/Fs02/rel v0.6.1-0.20200901120822-3b34832d3a9e/go.mod h1:HCzzNmcDNX0IShoePaXQ/srKElubko6qQ3NHzxkagF4= -github.com/Fs02/rel v0.6.1-0.20200901164852-1f41be8177cc h1:zuHgP4mhT1XaOnhj90siqJRg0VQb8uDPOfgeq6YoD28= -github.com/Fs02/rel v0.6.1-0.20200901164852-1f41be8177cc/go.mod h1:HCzzNmcDNX0IShoePaXQ/srKElubko6qQ3NHzxkagF4= +github.com/Fs02/rel v0.6.1-0.20200903172418-3af9e733867d h1:mkYNjwXWyLde8hiktED74sdyEMfmJLG6KkQ2sKzMLQg= +github.com/Fs02/rel v0.6.1-0.20200903172418-3af9e733867d/go.mod h1:HCzzNmcDNX0IShoePaXQ/srKElubko6qQ3NHzxkagF4= +github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/azer/snakecase v0.0.0-20161028114325-c818dddafb5c h1:7zL0ljVI6ads5EFvx+Oq+uompnFBMJqtbuHvyobbJ1Q= github.com/azer/snakecase v0.0.0-20161028114325-c818dddafb5c/go.mod h1:iApMeoHF0YlMPzCwqH/d59E3w2s8SeO4rGK+iGClS8Y= @@ -36,8 +34,7 @@ github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-sqlite3 v1.6.0 h1:TDwTWbeII+88Qy55nWlof0DclgAtI4LqGujkYMzmQII= -github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.2 h1:A2EQLwjYf/hfYaM20FVjs1UewCTTFR7RmjEHkLjldIA= github.com/mattn/go-sqlite3 v1.14.2/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/vendor/github.com/Fs02/rel/README.md b/vendor/github.com/Fs02/rel/README.md index 339a206..27ca215 100644 --- a/vendor/github.com/Fs02/rel/README.md +++ b/vendor/github.com/Fs02/rel/README.md @@ -20,7 +20,7 @@ REL is golang orm-ish database layer for layered architecture. It's testable and - Multi adapter. - Soft Deletion. - Pagination. - +- Schema Migration. ## Install diff --git a/vendor/github.com/Fs02/rel/adapter/sql/adapter.go b/vendor/github.com/Fs02/rel/adapter/sql/adapter.go index 8158134..542798e 100644 --- a/vendor/github.com/Fs02/rel/adapter/sql/adapter.go +++ b/vendor/github.com/Fs02/rel/adapter/sql/adapter.go @@ -256,6 +256,8 @@ func (a *Adapter) Apply(ctx context.Context, migration rel.Migration) error { statement = builder.Table(v) case rel.Index: statement = builder.Index(v) + case rel.Raw: + statement = string(v) } _, _, err := a.Exec(ctx, statement, nil) diff --git a/vendor/github.com/Fs02/rel/collection.go b/vendor/github.com/Fs02/rel/collection.go index cb022ab..d8913cd 100644 --- a/vendor/github.com/Fs02/rel/collection.go +++ b/vendor/github.com/Fs02/rel/collection.go @@ -18,7 +18,6 @@ type Collection struct { rv reflect.Value rt reflect.Type data documentData - index map[interface{}]int swapper func(i, j int) } diff --git a/vendor/github.com/Fs02/rel/column.go b/vendor/github.com/Fs02/rel/column.go index 89d01c5..a903520 100644 --- a/vendor/github.com/Fs02/rel/column.go +++ b/vendor/github.com/Fs02/rel/column.go @@ -79,67 +79,3 @@ func dropColumn(name string, options []ColumnOption) Column { applyColumnOptions(&column, options) return column } - -// ColumnOption interface. -// Available options are: Nil, Unsigned, Limit, Precision, Scale, Default, Comment, Options. -type ColumnOption interface { - applyColumn(column *Column) -} - -func applyColumnOptions(column *Column, options []ColumnOption) { - for i := range options { - options[i].applyColumn(column) - } -} - -// Unique set column as unique. -type Unique bool - -func (r Unique) applyColumn(column *Column) { - column.Unique = bool(r) -} - -func (r Unique) applyIndex(index *Index) { - index.Unique = bool(r) -} - -// Required disallows nil values in the column. -type Required bool - -func (r Required) applyColumn(column *Column) { - column.Required = bool(r) -} - -// Unsigned sets integer column to be unsigned. -type Unsigned bool - -func (u Unsigned) applyColumn(column *Column) { - column.Unsigned = bool(u) -} - -// Precision defines the precision for the decimal fields, representing the total number of digits in the number. -type Precision int - -func (p Precision) applyColumn(column *Column) { - column.Precision = int(p) -} - -// Scale Defines the scale for the decimal fields, representing the number of digits after the decimal point. -type Scale int - -func (s Scale) applyColumn(column *Column) { - column.Scale = int(s) -} - -type defaultValue struct { - value interface{} -} - -func (d defaultValue) applyColumn(column *Column) { - column.Default = d.value -} - -// Default allows to set a default value on the column.). -func Default(def interface{}) ColumnOption { - return defaultValue{value: def} -} diff --git a/vendor/github.com/Fs02/rel/context_wrapper.go b/vendor/github.com/Fs02/rel/context_wrapper.go index 664ca28..ae1dee6 100644 --- a/vendor/github.com/Fs02/rel/context_wrapper.go +++ b/vendor/github.com/Fs02/rel/context_wrapper.go @@ -6,10 +6,6 @@ import ( type contextKey int8 -type contextData struct { - adapter Adapter -} - type contextWrapper struct { ctx context.Context adapter Adapter diff --git a/vendor/github.com/Fs02/rel/document.go b/vendor/github.com/Fs02/rel/document.go index 0242f3a..c48a806 100644 --- a/vendor/github.com/Fs02/rel/document.go +++ b/vendor/github.com/Fs02/rel/document.go @@ -33,8 +33,6 @@ const ( var ( tablesCache sync.Map primariesCache sync.Map - fieldsCache sync.Map - typesCache sync.Map documentDataCache sync.Map rtTime = reflect.TypeOf(time.Time{}) rtTable = reflect.TypeOf((*table)(nil)).Elem() diff --git a/vendor/github.com/Fs02/rel/key.go b/vendor/github.com/Fs02/rel/key.go index 53dc8e2..9d15157 100644 --- a/vendor/github.com/Fs02/rel/key.go +++ b/vendor/github.com/Fs02/rel/key.go @@ -64,29 +64,3 @@ func createForeignKey(column string, refTable string, refColumn string, options } // TODO: Rename and Drop, PR welcomed. - -// KeyOption interface. -// Available options are: Comment, Options. -type KeyOption interface { - applyKey(key *Key) -} - -func applyKeyOptions(key *Key, options []KeyOption) { - for i := range options { - options[i].applyKey(key) - } -} - -// OnDelete option for foreign key. -type OnDelete string - -func (od OnDelete) applyKey(key *Key) { - key.Reference.OnDelete = string(od) -} - -// OnUpdate option for foreign key. -type OnUpdate string - -func (ou OnUpdate) applyKey(key *Key) { - key.Reference.OnUpdate = string(ou) -} diff --git a/vendor/github.com/Fs02/rel/migrator/migrator.go b/vendor/github.com/Fs02/rel/migrator/migrator.go index 426db63..0b27d91 100644 --- a/vendor/github.com/Fs02/rel/migrator/migrator.go +++ b/vendor/github.com/Fs02/rel/migrator/migrator.go @@ -61,7 +61,7 @@ func (m Migrator) buildVersionTableDefinition() rel.Table { var schema rel.Schema schema.CreateTableIfNotExists(versionTable, func(t *rel.Table) { t.ID("id") - t.BigInt("version", rel.Unique(true)) + t.BigInt("version", rel.Unsigned(true), rel.Unique(true)) t.DateTime("created_at") t.DateTime("updated_at") }) @@ -84,8 +84,6 @@ func (m *Migrator) sync(ctx context.Context) { m.repo.MustFindAll(ctx, &versions, rel.NewSortAsc("version")) sort.Sort(m.versions) - fmt.Println(versions) - for i := range m.versions { if vi < len(versions) && m.versions[i].Version == versions[vi].Version { m.versions[i].ID = versions[vi].ID @@ -146,8 +144,11 @@ func (m *Migrator) Rollback(ctx context.Context) { func (m *Migrator) run(ctx context.Context, migrations []rel.Migration) { adapter := m.repo.Adapter(ctx).(rel.Adapter) for _, migration := range migrations { - // TODO: exec script - check(adapter.Apply(ctx, migration)) + if fn, ok := migration.(rel.Do); ok { + check(fn(m.repo)) + } else { + check(adapter.Apply(ctx, migration)) + } } } diff --git a/vendor/github.com/Fs02/rel/reltest/nop_adapter.go b/vendor/github.com/Fs02/rel/reltest/nop_adapter.go index 27c21c0..b7ea8cc 100644 --- a/vendor/github.com/Fs02/rel/reltest/nop_adapter.go +++ b/vendor/github.com/Fs02/rel/reltest/nop_adapter.go @@ -7,7 +7,6 @@ import ( ) type nopAdapter struct { - count int } func (na *nopAdapter) Instrumentation(instrumenter rel.Instrumenter) { diff --git a/vendor/github.com/Fs02/rel/schema.go b/vendor/github.com/Fs02/rel/schema.go index 7ca727c..2a0a1a8 100644 --- a/vendor/github.com/Fs02/rel/schema.go +++ b/vendor/github.com/Fs02/rel/schema.go @@ -100,44 +100,12 @@ func (s *Schema) DropIndex(table string, name string, options ...IndexOption) { s.add(dropIndex(table, name, options)) } -// Exec queries using repo. -// Useful for data migration. -// func (s *Schema) Exec(func(repo rel.Repository) error) { -// } - -// Options options for table, column and index. -type Options string - -func (o Options) applyTable(table *Table) { - table.Options = string(o) -} - -func (o Options) applyColumn(column *Column) { - column.Options = string(o) +// Exec queries. +func (s *Schema) Exec(raw Raw) { + s.add(raw) } -func (o Options) applyIndex(index *Index) { - index.Options = string(o) +// Do migration using golang codes. +func (s *Schema) Do(fn Do) { + s.add(fn) } - -func (o Options) applyKey(key *Key) { - key.Options = string(o) -} - -// Optional option. -// when used with create table, will create table only if it's not exists. -// when used with drop table, will drop table only if it's exists. -type Optional bool - -func (o Optional) applyTable(table *Table) { - table.Optional = bool(o) -} - -func (o Optional) applyIndex(index *Index) { - index.Optional = bool(o) -} - -// Raw string -type Raw string - -func (r Raw) internalTableDefinition() {} diff --git a/vendor/github.com/Fs02/rel/schema_options.go b/vendor/github.com/Fs02/rel/schema_options.go new file mode 100644 index 0000000..dcd2617 --- /dev/null +++ b/vendor/github.com/Fs02/rel/schema_options.go @@ -0,0 +1,146 @@ +package rel + +// TableOption interface. +// Available options are: Comment, Options. +type TableOption interface { + applyTable(table *Table) +} + +func applyTableOptions(table *Table, options []TableOption) { + for i := range options { + options[i].applyTable(table) + } +} + +// ColumnOption interface. +// Available options are: Nil, Unsigned, Limit, Precision, Scale, Default, Comment, Options. +type ColumnOption interface { + applyColumn(column *Column) +} + +func applyColumnOptions(column *Column, options []ColumnOption) { + for i := range options { + options[i].applyColumn(column) + } +} + +// KeyOption interface. +// Available options are: Comment, Options. +type KeyOption interface { + applyKey(key *Key) +} + +func applyKeyOptions(key *Key, options []KeyOption) { + for i := range options { + options[i].applyKey(key) + } +} + +// Unique set column as unique. +type Unique bool + +func (r Unique) applyColumn(column *Column) { + column.Unique = bool(r) +} + +func (r Unique) applyIndex(index *Index) { + index.Unique = bool(r) +} + +// Required disallows nil values in the column. +type Required bool + +func (r Required) applyColumn(column *Column) { + column.Required = bool(r) +} + +// Unsigned sets integer column to be unsigned. +type Unsigned bool + +func (u Unsigned) applyColumn(column *Column) { + column.Unsigned = bool(u) +} + +// Precision defines the precision for the decimal fields, representing the total number of digits in the number. +type Precision int + +func (p Precision) applyColumn(column *Column) { + column.Precision = int(p) +} + +// Scale Defines the scale for the decimal fields, representing the number of digits after the decimal point. +type Scale int + +func (s Scale) applyColumn(column *Column) { + column.Scale = int(s) +} + +type defaultValue struct { + value interface{} +} + +func (d defaultValue) applyColumn(column *Column) { + column.Default = d.value +} + +// Default allows to set a default value on the column.). +func Default(def interface{}) ColumnOption { + return defaultValue{value: def} +} + +// OnDelete option for foreign key. +type OnDelete string + +func (od OnDelete) applyKey(key *Key) { + key.Reference.OnDelete = string(od) +} + +// OnUpdate option for foreign key. +type OnUpdate string + +func (ou OnUpdate) applyKey(key *Key) { + key.Reference.OnUpdate = string(ou) +} + +// Options options for table, column and index. +type Options string + +func (o Options) applyTable(table *Table) { + table.Options = string(o) +} + +func (o Options) applyColumn(column *Column) { + column.Options = string(o) +} + +func (o Options) applyIndex(index *Index) { + index.Options = string(o) +} + +func (o Options) applyKey(key *Key) { + key.Options = string(o) +} + +// Optional option. +// when used with create table, will create table only if it's not exists. +// when used with drop table, will drop table only if it's exists. +type Optional bool + +func (o Optional) applyTable(table *Table) { + table.Optional = bool(o) +} + +func (o Optional) applyIndex(index *Index) { + index.Optional = bool(o) +} + +// Raw string +type Raw string + +func (r Raw) internalMigration() {} +func (r Raw) internalTableDefinition() {} + +// Do used internally for schema migration. +type Do func(Repository) error + +func (d Do) internalMigration() {} diff --git a/vendor/github.com/Fs02/rel/table.go b/vendor/github.com/Fs02/rel/table.go index 231789c..54c5c10 100644 --- a/vendor/github.com/Fs02/rel/table.go +++ b/vendor/github.com/Fs02/rel/table.go @@ -175,15 +175,3 @@ func dropTableIfExists(name string, options []TableOption) Table { table.Optional = true return table } - -// TableOption interface. -// Available options are: Comment, Options. -type TableOption interface { - applyTable(table *Table) -} - -func applyTableOptions(table *Table, options []TableOption) { - for i := range options { - options[i].applyTable(table) - } -} diff --git a/vendor/github.com/Fs02/rel/util.go b/vendor/github.com/Fs02/rel/util.go index f10fb09..400e5e5 100644 --- a/vendor/github.com/Fs02/rel/util.go +++ b/vendor/github.com/Fs02/rel/util.go @@ -37,7 +37,7 @@ func isZero(value interface{}) bool { case nil: zero = true case bool: - zero = v == false + zero = !v case string: zero = v == "" case int: diff --git a/vendor/modules.txt b/vendor/modules.txt index d94a709..dc093f2 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,4 @@ -# github.com/Fs02/rel v0.6.1-0.20200901164852-1f41be8177cc +# github.com/Fs02/rel v0.6.1-0.20200903172418-3af9e733867d ## explicit github.com/Fs02/rel github.com/Fs02/rel/adapter/postgres From b205668103c54a923066c76d7c88616b57a74c10 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Fri, 4 Sep 2020 00:33:24 +0700 Subject: [PATCH 4/4] remove ruby migration files --- .bundle/config | 2 - .gitignore | 1 - Gemfile | 12 ---- Gemfile.lock | 80 ---------------------- Makefile | 6 +- Rakefile | 23 ------- db/README.md | 4 +- db/config.yml | 20 ------ db/migrate/20203006230600_create_scores.rb | 9 --- db/migrate/20203006230700_create_points.rb | 12 ---- db/schema.rb | 42 ------------ 11 files changed, 3 insertions(+), 208 deletions(-) delete mode 100644 .bundle/config delete mode 100644 Gemfile delete mode 100644 Gemfile.lock delete mode 100644 Rakefile delete mode 100644 db/config.yml delete mode 100644 db/migrate/20203006230600_create_scores.rb delete mode 100644 db/migrate/20203006230700_create_points.rb delete mode 100644 db/schema.rb diff --git a/.bundle/config b/.bundle/config deleted file mode 100644 index bc0c601..0000000 --- a/.bundle/config +++ /dev/null @@ -1,2 +0,0 @@ ---- -BUNDLE_PATH: "bundle" diff --git a/.gitignore b/.gitignore index 0a12151..67742d6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ .env bin !bin/README.md -bundle diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 764f085..0000000 --- a/Gemfile +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -# gems's source -source 'http://rubygems.org/' - -gem 'dotenv' -gem 'standalone_migrations' -gem 'postgresql' - -## uncomment if using mysql with lhm -# gem 'mysql2' -# gem 'lhm' diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index b376cb7..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,80 +0,0 @@ -GEM - remote: http://rubygems.org/ - specs: - actionpack (6.0.3.2) - actionview (= 6.0.3.2) - activesupport (= 6.0.3.2) - rack (~> 2.0, >= 2.0.8) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actionview (6.0.3.2) - activesupport (= 6.0.3.2) - builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - activemodel (6.0.3.2) - activesupport (= 6.0.3.2) - activerecord (6.0.3.2) - activemodel (= 6.0.3.2) - activesupport (= 6.0.3.2) - activesupport (6.0.3.2) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - builder (3.2.4) - concurrent-ruby (1.1.6) - crass (1.0.6) - dotenv (2.7.5) - erubi (1.9.0) - i18n (1.8.3) - concurrent-ruby (~> 1.0) - loofah (2.6.0) - crass (~> 1.0.2) - nokogiri (>= 1.5.9) - method_source (1.0.0) - mini_portile2 (2.4.0) - minitest (5.14.1) - nokogiri (1.10.9) - mini_portile2 (~> 2.4.0) - pg (1.2.3) - postgresql (1.0.0) - pg - rack (2.2.3) - rack-test (1.1.0) - rack (>= 1.0, < 3) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) - nokogiri (>= 1.6) - rails-html-sanitizer (1.3.0) - loofah (~> 2.3) - railties (6.0.3.2) - actionpack (= 6.0.3.2) - activesupport (= 6.0.3.2) - method_source - rake (>= 0.8.7) - thor (>= 0.20.3, < 2.0) - rake (13.0.1) - standalone_migrations (6.0.0) - activerecord (>= 4.2.7, < 6.1.0, != 5.2.3.rc1, != 5.2.3) - railties (>= 4.2.7, < 6.1.0, != 5.2.3.rc1, != 5.2.3) - rake (>= 10.0) - thor (1.0.1) - thread_safe (0.3.6) - tzinfo (1.2.7) - thread_safe (~> 0.1) - zeitwerk (2.3.0) - -PLATFORMS - ruby - -DEPENDENCIES - dotenv - postgresql - standalone_migrations - -BUNDLED WITH - 1.17.2 diff --git a/Makefile b/Makefile index 8bf8b72..b0b1e1d 100644 --- a/Makefile +++ b/Makefile @@ -3,11 +3,9 @@ export DOCKER_REGISTRY ?= docker.pkg.github.com/fs02/go-todo-backend export DEPLOY ?= api all: build start -bundle: - bundle install --path bundle -db-migrate: bundle +db-migrate: export $$(cat .env | grep -v ^\# | xargs) && go run cmd/db/main.go migrate -db-rollback: bundle +db-rollback: export $$(cat .env | grep -v ^\# | xargs) && go run cmd/db/main.go rollback gen: go generate ./... diff --git a/Rakefile b/Rakefile deleted file mode 100644 index 1aba295..0000000 --- a/Rakefile +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env rake - -require 'yaml' -require 'dotenv' -require 'standalone_migrations' - -Dotenv.load - -# rake task for database migrations -StandaloneMigrations::Tasks.load_tasks - -## uncomment if using mysql with lhm -# require 'lhm' -# # ignore LHM (lhma and lhmn) -# ActiveRecord::SchemaDumper.ignore_tables << /^lhma_/ -# ActiveRecord::SchemaDumper.ignore_tables << /^lhmn_/ - -# namespace :lhm do -# desc "LHM Clean Up" -# task cleanup: :environment do -# Lhm.cleanup(:run) -# end -# end diff --git a/db/README.md b/db/README.md index e6e983a..8e32446 100644 --- a/db/README.md +++ b/db/README.md @@ -1,5 +1,3 @@ # db -Contains file required for building db schema. `schema.rb` is useful when your project already running for a long time and running migration file one by one takes a lot of time, which then you can just load the schema directly which is faster. - -The reason I'm using ruby based solution is because there's no golang based solution that can generate schema and allows working on multiple branch with different not yet merged migration without having to rollback and migrate when switching branch. +Contains file required for building db schema. diff --git a/db/config.yml b/db/config.yml deleted file mode 100644 index f69f441..0000000 --- a/db/config.yml +++ /dev/null @@ -1,20 +0,0 @@ -default: &default - adapter: postgresql - reconnect: true - encoding: utf8 - username: <%= ENV['POSTGRESQL_USERNAME'] %> - password: <%= ENV['POSTGRESQL_PASSWORD'] %> - host: <%= ENV['POSTGRESQL_HOST'] || '127.0.0.1' %> - port: <%= ENV['POSTGRESQL_PORT'] %> - -development: - <<: *default - database: <%= ENV['POSTGRESQL_DATABASE'] %> - -test: &test - <<: *default - database: <%= ENV['POSTGRESQL_DATABASE_TEST'] %> - -production: - <<: *default - database: <%= ENV['POSTGRESQL_DATABASE'] %> diff --git a/db/migrate/20203006230600_create_scores.rb b/db/migrate/20203006230600_create_scores.rb deleted file mode 100644 index 0a9ca78..0000000 --- a/db/migrate/20203006230600_create_scores.rb +++ /dev/null @@ -1,9 +0,0 @@ -class CreateScores < ActiveRecord::Migration[5.2] - def change - create_table :scores do |t| - t.datetime :created_at - t.datetime :updated_at - t.integer :total_point - end - end -end diff --git a/db/migrate/20203006230700_create_points.rb b/db/migrate/20203006230700_create_points.rb deleted file mode 100644 index 1008a90..0000000 --- a/db/migrate/20203006230700_create_points.rb +++ /dev/null @@ -1,12 +0,0 @@ -class CreatePoints < ActiveRecord::Migration[5.2] - def change - create_table :points do |t| - t.datetime :created_at - t.datetime :updated_at - t.string :name - t.integer :count - - t.belongs_to :score - end - end -end diff --git a/db/schema.rb b/db/schema.rb deleted file mode 100644 index e051f65..0000000 --- a/db/schema.rb +++ /dev/null @@ -1,42 +0,0 @@ -# This file is auto-generated from the current state of the database. Instead -# of editing this file, please use the migrations feature of Active Record to -# incrementally modify your database, and then regenerate this schema definition. -# -# This file is the source Rails uses to define your schema when running `rails -# db:schema:load`. When creating a new database, `rails db:schema:load` tends to -# be faster and is potentially less error prone than running all of your -# migrations from scratch. Old migrations may fail to apply correctly if those -# migrations use external dependencies or application code. -# -# It's strongly recommended that you check this file into your version control system. - -ActiveRecord::Schema.define(version: 2020_30_06_230700) do - - # These are extensions that must be enabled in order to support this database - enable_extension "plpgsql" - - create_table "points", force: :cascade do |t| - t.datetime "created_at" - t.datetime "updated_at" - t.string "name" - t.integer "count" - t.bigint "score_id" - t.index ["score_id"], name: "index_points_on_score_id" - end - - create_table "scores", force: :cascade do |t| - t.datetime "created_at" - t.datetime "updated_at" - t.integer "total_point" - end - - create_table "todos", force: :cascade do |t| - t.datetime "created_at" - t.datetime "updated_at" - t.string "title" - t.boolean "completed" - t.integer "order" - t.index ["order"], name: "index_todos_on_order" - end - -end