From 7789580408b304987636d47b3f001706d6bdc6c7 Mon Sep 17 00:00:00 2001 From: Danny Hadley Date: Mon, 11 Dec 2017 14:11:34 -0500 Subject: [PATCH 1/4] correcting postgres placeholder indexed param calculations closes GH-75 --- examples/library/data/genres.sql | 7 - examples/library/data/postgres.sql | 15 + examples/library/main.go | 16 +- examples/library/models/author_test.go | 6 +- examples/library/models/book_test.go | 6 +- examples/library/models/genre_test.go | 349 +++++++++++---------- examples/library/models/multi_auto.go | 8 + examples/library/models/mutli_auto_test.go | 151 +++++++++ marlow/blueprint.go | 2 +- marlow/constants/config.go | 3 + marlow/createable.go | 49 +-- marlow/queryable.go | 2 +- marlow/record.go | 7 +- marlow/store.go | 23 +- marlow/store_test.go | 4 +- 15 files changed, 443 insertions(+), 205 deletions(-) delete mode 100644 examples/library/data/genres.sql create mode 100644 examples/library/data/postgres.sql create mode 100644 examples/library/models/multi_auto.go create mode 100644 examples/library/models/mutli_auto_test.go diff --git a/examples/library/data/genres.sql b/examples/library/data/genres.sql deleted file mode 100644 index 97e2670..0000000 --- a/examples/library/data/genres.sql +++ /dev/null @@ -1,7 +0,0 @@ -drop table if exists genres; - -create table genres ( - id SERIAL, - name TEXT, - parent_id INTEGER -); diff --git a/examples/library/data/postgres.sql b/examples/library/data/postgres.sql new file mode 100644 index 0000000..9310bfe --- /dev/null +++ b/examples/library/data/postgres.sql @@ -0,0 +1,15 @@ +drop table if exists genres; + +create table genres ( + id SERIAL, + name TEXT, + parent_id INTEGER +); + +drop table if exists multi_auto; + +create table multi_auto ( + id SERIAL, + status TEXT DEFAULT 'pending' NOT NULL, + name TEXT +); diff --git a/examples/library/main.go b/examples/library/main.go index 2c145a8..ae8454a 100644 --- a/examples/library/main.go +++ b/examples/library/main.go @@ -3,6 +3,7 @@ package main import "os" import "log" import "fmt" +import "bytes" import _ "github.com/mattn/go-sqlite3" import "database/sql" import "github.com/dadleyy/marlow/examples/library/data" @@ -110,7 +111,9 @@ func main() { NameLike: []string{"danny"}, }) - authorStore := models.NewAuthorStore(db) + queryLog := new(bytes.Buffer) + + authorStore := models.NewAuthorStore(db, queryLog) a, e := authorStore.FindAuthors(&models.AuthorBlueprint{ ID: []int{1, 2, 3}, @@ -124,7 +127,16 @@ func main() { log.Printf("found author name[%s]", author.Name) } - bookStore := models.NewBookStore(db) + queryLog.Reset() + + bookStore := models.NewBookStore(db, queryLog) + + if _, e := bookStore.CreateBooks(models.Book{Title: "AwesomeBook"}); e != nil { + log.Fatalf("unable to create book: %v", e) + } + + log.Printf("%v", queryLog.String()) + b, e := bookStore.FindBooks(&models.BookBlueprint{ ID: []int{1, 2}, }) diff --git a/examples/library/models/author_test.go b/examples/library/models/author_test.go index 8dce8cb..3bb086b 100644 --- a/examples/library/models/author_test.go +++ b/examples/library/models/author_test.go @@ -1,7 +1,9 @@ package models import "os" +import "io" import "fmt" +import "bytes" import "strings" import "testing" import _ "github.com/mattn/go-sqlite3" @@ -37,6 +39,7 @@ func Test_Author(t *testing.T) { g := goblin.Goblin(t) var db *sql.DB var store AuthorStore + var queryLog io.Writer dbFile := "author-testing.db" generatedAuthorCount := 150 @@ -149,7 +152,8 @@ func Test_Author(t *testing.T) { }) g.BeforeEach(func() { - store = NewAuthorStore(db) + queryLog = new(bytes.Buffer) + store = NewAuthorStore(db, queryLog) }) g.After(func() { diff --git a/examples/library/models/book_test.go b/examples/library/models/book_test.go index f24a078..6dc2515 100644 --- a/examples/library/models/book_test.go +++ b/examples/library/models/book_test.go @@ -1,7 +1,9 @@ package models import "os" +import "io" import "fmt" +import "bytes" import "strings" import "testing" import _ "github.com/mattn/go-sqlite3" @@ -35,6 +37,7 @@ func addBookRow(db *sql.DB, values ...[]string) error { func Test_Book(t *testing.T) { var db *sql.DB var store BookStore + var queryLog io.Writer g := goblin.Goblin(t) testBookCount := 150 @@ -77,7 +80,8 @@ func Test_Book(t *testing.T) { }) g.BeforeEach(func() { - store = NewBookStore(db) + queryLog = new(bytes.Buffer) + store = NewBookStore(db, queryLog) }) g.After(func() { diff --git a/examples/library/models/genre_test.go b/examples/library/models/genre_test.go index 7f10dad..f9481b7 100644 --- a/examples/library/models/genre_test.go +++ b/examples/library/models/genre_test.go @@ -2,6 +2,7 @@ package models import "os" import "fmt" +import "bytes" import "testing" import _ "github.com/lib/pq" import "database/sql" @@ -14,6 +15,7 @@ func Test_Genre(t *testing.T) { g.Describe("genre record test suite (postgres)", func() { var db *sql.DB var store GenreStore + var queryLog *bytes.Buffer g.Before(func() { var e error @@ -32,223 +34,244 @@ func Test_Genre(t *testing.T) { g.Assert(e).Equal(nil) }) - g.BeforeEach(func() { - schema, e := data.Asset("data/genres.sql") - g.Assert(e).Equal(nil) - _, e = db.Exec(string(schema)) - g.Assert(e).Equal(nil) - store = NewGenreStore(db) - }) + g.Describe("with a populated store", func() { - g.Describe("genre blueprint test suite", func() { - g.It("supports ors between string like clauses when inclusive", func() { - a := fmt.Sprintf("%s", &GenreBlueprint{NameLike: []string{"hi", "bye"}, Inclusive: true}) - g.Assert(a).Equal("WHERE genres.name LIKE $1 OR genres.name LIKE $2") + g.BeforeEach(func() { + schema, e := data.Asset("data/postgres.sql") + g.Assert(e).Equal(nil) + _, e = db.Exec(string(schema)) + g.Assert(e).Equal(nil) }) - g.It("uses the postgres dialect for blueprint string in params", func() { - s := fmt.Sprintf("%s", &GenreBlueprint{Name: []string{"horror", "comedy"}}) - g.Assert(s).Equal("WHERE genres.name IN ($1,$2)") - }) + g.Describe("without a logger", func() { + g.BeforeEach(func() { + store = NewGenreStore(db, nil) + }) - g.It("uses the postgres dialect for blueprint int in params", func() { - s := fmt.Sprintf("%s", &GenreBlueprint{ID: []uint{0, 10}}) - g.Assert(s).Equal("WHERE genres.id IN ($1,$2)") + g.It("still allows consumer to use the store", func() { + id, e := store.CreateGenres(Genre{Name: "Loggerless Genre"}) + g.Assert(e).Equal(nil) + _, e = store.DeleteGenres(&GenreBlueprint{ID: []uint{uint(id)}}) + g.Assert(e).Equal(nil) + }) }) - g.It("uses the postgres dialect for blueprint int range params", func() { - s := fmt.Sprintf("%s", &GenreBlueprint{IDRange: []uint{0, 10}}) - g.Assert(s).Equal("WHERE (genres.id > $1 AND genres.id < $2)") + g.BeforeEach(func() { + queryLog = new(bytes.Buffer) + store = NewGenreStore(db, queryLog) }) - g.It("uses the postgres dialect for blueprint string like params", func() { - s := fmt.Sprintf("%s", &GenreBlueprint{NameLike: []string{"danny"}}) - g.Assert(s).Equal("WHERE genres.name LIKE $1") - }) - }) + g.Describe("genre blueprint test suite", func() { + g.It("supports ors between string like clauses when inclusive", func() { + a := fmt.Sprintf("%s", &GenreBlueprint{NameLike: []string{"hi", "bye"}, Inclusive: true}) + g.Assert(a).Equal("WHERE genres.name LIKE $1 OR genres.name LIKE $2") + }) - g.It("allows user to create genres", func() { - id, e := store.CreateGenres([]Genre{ - {Name: "Comedy"}, - {Name: "Literature"}, - {Name: "Science Fiction", ParentID: sql.NullInt64{Valid: true, Int64: 10}}, - }...) - g.Assert(e).Equal(nil) + g.It("uses the postgres dialect for blueprint string in params", func() { + s := fmt.Sprintf("%s", &GenreBlueprint{Name: []string{"horror", "comedy"}}) + g.Assert(s).Equal("WHERE genres.name IN ($1,$2)") + }) - results, e := store.SelectNames(&GenreBlueprint{ - ID: []uint{uint(id)}, - }) + g.It("uses the postgres dialect for blueprint int in params", func() { + s := fmt.Sprintf("%s", &GenreBlueprint{ID: []uint{0, 10}}) + g.Assert(s).Equal("WHERE genres.id IN ($1,$2)") + }) - g.Assert(e).Equal(nil) - g.Assert(len(results)).Equal(1) - g.Assert(results[0]).Equal("Science Fiction") - }) + g.It("uses the postgres dialect for blueprint int range params", func() { + s := fmt.Sprintf("%s", &GenreBlueprint{IDRange: []uint{0, 10}}) + g.Assert(s).Equal("WHERE (genres.id > $1 AND genres.id < $2)") + }) - g.Describe("having created some genres", func() { - var lastID int64 + g.It("uses the postgres dialect for blueprint string like params", func() { + s := fmt.Sprintf("%s", &GenreBlueprint{NameLike: []string{"danny"}}) + g.Assert(s).Equal("WHERE genres.name LIKE $1") + }) + }) - g.BeforeEach(func() { - var e error - lastID, e = store.CreateGenres([]Genre{ - {Name: "Romance"}, + g.It("allows user to create genres", func() { + id, e := store.CreateGenres([]Genre{ {Name: "Comedy"}, {Name: "Literature"}, {Name: "Science Fiction", ParentID: sql.NullInt64{Valid: true, Int64: 10}}, - {Name: "History", ParentID: sql.NullInt64{Valid: true, Int64: 10}}, - {Name: "Western European History", ParentID: sql.NullInt64{Valid: true, Int64: 10}}, - {Name: "Eastern European History", ParentID: sql.NullInt64{Valid: true, Int64: 10}}, - {Name: "South American History", ParentID: sql.NullInt64{Valid: true, Int64: 10}}, - {Name: "North American History", ParentID: sql.NullInt64{Valid: true, Int64: 10}}, }...) g.Assert(e).Equal(nil) - }) + g.Assert(queryLog.Len() > 0).Equal(true) - g.It("supports or-ing clauses when blueprint set to be inclusive", func() { - genres, e := store.FindGenres(&GenreBlueprint{ - NameLike: []string{"%European%", "%American%"}, - Inclusive: true, + results, e := store.SelectNames(&GenreBlueprint{ + ID: []uint{uint(id)}, }) - g.Assert(e).Equal(nil) - g.Assert(len(genres)).Equal(4) - }) - g.It("supports selecting parent ids", func() { - parents, e := store.SelectParentIDs(&GenreBlueprint{ - ID: []uint{uint(lastID)}, - }) g.Assert(e).Equal(nil) - g.Assert(len(parents)).Equal(1) - g.Assert(parents[0].Valid).Equal(true) - g.Assert(parents[0].Int64).Equal(10) + g.Assert(len(results)).Equal(1) + g.Assert(results[0]).Equal("Science Fiction") }) - g.It("allows counting w/ empty NullInt64 blueprint (NOT NULL)", func() { - c, e := store.CountGenres(&GenreBlueprint{ - ParentID: []sql.NullInt64{}, + g.Describe("having created some genres", func() { + var lastID int64 + + g.BeforeEach(func() { + var e error + lastID, e = store.CreateGenres([]Genre{ + {Name: "Romance"}, + {Name: "Comedy"}, + {Name: "Literature"}, + {Name: "Science Fiction", ParentID: sql.NullInt64{Valid: true, Int64: 10}}, + {Name: "History", ParentID: sql.NullInt64{Valid: true, Int64: 10}}, + {Name: "Western European History", ParentID: sql.NullInt64{Valid: true, Int64: 10}}, + {Name: "Eastern European History", ParentID: sql.NullInt64{Valid: true, Int64: 10}}, + {Name: "South American History", ParentID: sql.NullInt64{Valid: true, Int64: 10}}, + {Name: "North American History", ParentID: sql.NullInt64{Valid: true, Int64: 10}}, + }...) + g.Assert(e).Equal(nil) }) - g.Assert(e).Equal(nil) - g.Assert(c).Equal(6) - }) - g.It("allows counting w/ valid NullInt64 blueprint", func() { - var p sql.NullInt64 - p.Scan(10) - children, e := store.CountGenres(&GenreBlueprint{ - ParentID: []sql.NullInt64{p}, + g.It("supports or-ing clauses when blueprint set to be inclusive", func() { + genres, e := store.FindGenres(&GenreBlueprint{ + NameLike: []string{"%European%", "%American%"}, + Inclusive: true, + }) + g.Assert(e).Equal(nil) + g.Assert(len(genres)).Equal(4) }) - g.Assert(e).Equal(nil) - g.Assert(children).Equal(6) - }) - g.It("allows counting w/ nil NullInt64 blueprint", func() { - var p sql.NullInt64 - p.Scan(nil) - orphans, e := store.CountGenres(&GenreBlueprint{ - ParentID: []sql.NullInt64{p}, + g.It("supports selecting parent ids", func() { + parents, e := store.SelectParentIDs(&GenreBlueprint{ + ID: []uint{uint(lastID)}, + }) + g.Assert(e).Equal(nil) + g.Assert(len(parents)).Equal(1) + g.Assert(parents[0].Valid).Equal(true) + g.Assert(parents[0].Int64).Equal(10) }) - g.Assert(e).Equal(nil) - g.Assert(orphans).Equal(3) - }) - g.It("allows selecting by genre name like", func() { - ids, e := store.SelectIDs(&GenreBlueprint{ - NameLike: []string{"%%Fiction%%"}, + g.It("allows counting w/ empty NullInt64 blueprint (NOT NULL)", func() { + c, e := store.CountGenres(&GenreBlueprint{ + ParentID: []sql.NullInt64{}, + }) + g.Assert(e).Equal(nil) + g.Assert(c).Equal(6) }) - g.Assert(e).Equal(nil) - g.Assert(len(ids)).Equal(1) - }) - g.It("allows finding by id range (with offset and limit)", func() { - genres, e := store.FindGenres(&GenreBlueprint{ - IDRange: []uint{0, 10}, - Offset: 1, - Limit: 1, + g.It("allows counting w/ valid NullInt64 blueprint", func() { + var p sql.NullInt64 + p.Scan(10) + children, e := store.CountGenres(&GenreBlueprint{ + ParentID: []sql.NullInt64{p}, + }) + g.Assert(e).Equal(nil) + g.Assert(children).Equal(6) }) - g.Assert(e).Equal(nil) - g.Assert(len(genres)).Equal(1) - g.Assert(genres[0].ID).Equal(uint(2)) - }) - g.It("allows finding by id range", func() { - genres, e := store.FindGenres(&GenreBlueprint{ - IDRange: []uint{0, 10}, + g.It("allows counting w/ nil NullInt64 blueprint", func() { + var p sql.NullInt64 + p.Scan(nil) + orphans, e := store.CountGenres(&GenreBlueprint{ + ParentID: []sql.NullInt64{p}, + }) + g.Assert(e).Equal(nil) + g.Assert(orphans).Equal(3) }) - g.Assert(e).Equal(nil) - g.Assert(len(genres)).Equal(9) - }) - g.It("allows updating the genre name", func() { - bp := &GenreBlueprint{ID: []uint{1}} - _, e, _ := store.UpdateGenreName("Politics", bp) - g.Assert(e).Equal(nil) + g.It("allows selecting by genre name like", func() { + ids, e := store.SelectIDs(&GenreBlueprint{ + NameLike: []string{"%%Fiction%%"}, + }) + g.Assert(e).Equal(nil) + g.Assert(len(ids)).Equal(1) + }) - names, e := store.SelectNames(bp) - g.Assert(e).Equal(nil) - g.Assert(len(names)).Equal(1) - g.Assert(names[0]).Equal("Politics") - }) + g.It("allows finding by id range (with offset and limit)", func() { + genres, e := store.FindGenres(&GenreBlueprint{ + IDRange: []uint{0, 10}, + Offset: 1, + Limit: 1, + }) + g.Assert(e).Equal(nil) + g.Assert(len(genres)).Equal(1) + g.Assert(genres[0].ID).Equal(uint(2)) + }) - g.It("allows deleting a newly created genre", func() { - id, e := store.CreateGenres(Genre{ - Name: "Comic Books", + g.It("allows finding by id range", func() { + genres, e := store.FindGenres(&GenreBlueprint{ + IDRange: []uint{0, 10}, + }) + g.Assert(e).Equal(nil) + g.Assert(len(genres)).Equal(9) }) - g.Assert(e).Equal(nil) - _, e = store.DeleteGenres(&GenreBlueprint{ - ID: []uint{uint(id)}, + g.It("allows updating the genre name", func() { + bp := &GenreBlueprint{ID: []uint{1}} + _, e, _ := store.UpdateGenreName("Politics", bp) + g.Assert(e).Equal(nil) + + names, e := store.SelectNames(bp) + g.Assert(e).Equal(nil) + g.Assert(len(names)).Equal(1) + g.Assert(names[0]).Equal("Politics") }) - g.Assert(e).Equal(nil) - c, e := store.CountGenres(&GenreBlueprint{ - Name: []string{"Comic Books"}, + g.It("allows deleting a newly created genre", func() { + id, e := store.CreateGenres(Genre{ + Name: "Comic Books", + }) + g.Assert(e).Equal(nil) + + _, e = store.DeleteGenres(&GenreBlueprint{ + ID: []uint{uint(id)}, + }) + g.Assert(e).Equal(nil) + + c, e := store.CountGenres(&GenreBlueprint{ + Name: []string{"Comic Books"}, + }) + g.Assert(e).Equal(nil) + g.Assert(c).Equal(0) }) - g.Assert(e).Equal(nil) - g.Assert(c).Equal(0) - }) - g.It("allows updating the genre parent id", func() { - var p sql.NullInt64 - p.Scan(100) + g.It("allows updating the genre parent id", func() { + var p sql.NullInt64 + p.Scan(100) - bp := &GenreBlueprint{ID: []uint{1}} - _, e, _ := store.UpdateGenreParentID(&p, bp) - g.Assert(e).Equal(nil) + bp := &GenreBlueprint{ID: []uint{1}} + _, e, _ := store.UpdateGenreParentID(&p, bp) + g.Assert(e).Equal(nil) - ids, e := store.SelectParentIDs(bp) + ids, e := store.SelectParentIDs(bp) - g.Assert(e).Equal(nil) - g.Assert(len(ids)).Equal(1) - g.Assert(ids[0].Valid).Equal(true) - g.Assert(ids[0].Int64).Equal(100) + g.Assert(e).Equal(nil) + g.Assert(len(ids)).Equal(1) + g.Assert(ids[0].Valid).Equal(true) + g.Assert(ids[0].Int64).Equal(100) - p.Scan(nil) - _, e, _ = store.UpdateGenreParentID(&p, bp) - g.Assert(e).Equal(nil) + p.Scan(nil) + _, e, _ = store.UpdateGenreParentID(&p, bp) + g.Assert(e).Equal(nil) - ids, e = store.SelectParentIDs(bp) - g.Assert(e).Equal(nil) - g.Assert(len(ids)).Equal(1) - g.Assert(ids[0].Valid).Equal(false) - }) + ids, e = store.SelectParentIDs(bp) + g.Assert(e).Equal(nil) + g.Assert(len(ids)).Equal(1) + g.Assert(ids[0].Valid).Equal(false) + }) - g.It("allows updating the genre id", func() { - bp := &GenreBlueprint{Name: []string{"Science Fiction"}} - _, e, _ := store.UpdateGenreID(1337, bp) - g.Assert(e).Equal(nil) + g.It("allows updating the genre id", func() { + bp := &GenreBlueprint{Name: []string{"Science Fiction"}} + _, e, _ := store.UpdateGenreID(1337, bp) + g.Assert(e).Equal(nil) - ids, e := store.SelectIDs(bp) - g.Assert(e).Equal(nil) - g.Assert(len(ids)).Equal(1) - g.Assert(ids[0]).Equal(uint(1337)) + ids, e := store.SelectIDs(bp) + g.Assert(e).Equal(nil) + g.Assert(len(ids)).Equal(1) + g.Assert(ids[0]).Equal(uint(1337)) + }) }) - }) - g.It("allows the consumer to select genres by name like", func() { - _, e := store.CountGenres(&GenreBlueprint{ - Name: []string{"horror"}, - IDRange: []uint{0, 10}, + g.It("allows the consumer to select genres by name like", func() { + _, e := store.CountGenres(&GenreBlueprint{ + Name: []string{"horror"}, + IDRange: []uint{0, 10}, + }) + g.Assert(e).Equal(nil) }) - g.Assert(e).Equal(nil) }) }) } diff --git a/examples/library/models/multi_auto.go b/examples/library/models/multi_auto.go new file mode 100644 index 0000000..ba040a9 --- /dev/null +++ b/examples/library/models/multi_auto.go @@ -0,0 +1,8 @@ +package models + +type MultiAuto struct { + table bool `marlow:"tableName=multi_auto&dialect=postgres&primaryKey=id"` + ID uint `marlow:"column=id&autoIncrement=true"` + Status string `marlow:"column=status&autoIncrement=true"` + Name string `marlow:"column=name"` +} diff --git a/examples/library/models/mutli_auto_test.go b/examples/library/models/mutli_auto_test.go new file mode 100644 index 0000000..3564e5b --- /dev/null +++ b/examples/library/models/mutli_auto_test.go @@ -0,0 +1,151 @@ +package models + +import "os" +import "fmt" +import "testing" +import _ "github.com/lib/pq" +import "database/sql" +import "github.com/franela/goblin" +import "github.com/dadleyy/marlow/examples/library/data" + +func Test_MultiAuto(t *testing.T) { + g := goblin.Goblin(t) + + var db *sql.DB + var store MultiAutoStore + + g.Describe("Model with mutliple auto increment columns", func() { + g.Before(func() { + var e error + config := struct { + username string + database string + port string + }{"postgres", "marlow_test", "5432"} + + if port := os.Getenv("PG_PORT"); len(port) > 0 { + config.port = port + } + + constr := fmt.Sprintf("user=%s dbname=%s port=%s sslmode=disable", config.username, config.database, config.port) + db, e = sql.Open("postgres", constr) + g.Assert(e).Equal(nil) + }) + + g.BeforeEach(func() { + schema, e := data.Asset("data/postgres.sql") + g.Assert(e).Equal(nil) + _, e = db.Exec(string(schema)) + g.Assert(e).Equal(nil) + store = NewMultiAutoStore(db, nil) + }) + + g.It("allows the consumer to create multiple records, respecting prepared index params", func() { + _, e := store.CreateMultiAutos([]MultiAuto{ + {Name: "first"}, + {Name: "second"}, + }...) + g.Assert(e).Equal(nil) + }) + + g.Describe("updates", func() { + var updateBlueprint *MultiAutoBlueprint + + g.BeforeEach(func() { + id, e := store.CreateMultiAutos(MultiAuto{Name: "updater"}) + g.Assert(e).Equal(nil) + updateBlueprint = &MultiAutoBlueprint{ID: []uint{uint(id)}} + }) + + g.It("allows consumer to update status", func() { + _, e, _ := store.UpdateMultiAutoStatus("updated", updateBlueprint) + g.Assert(e).Equal(nil) + }) + + g.It("allows consumer to update name", func() { + _, e, _ := store.UpdateMultiAutoName("updated", updateBlueprint) + g.Assert(e).Equal(nil) + }) + + g.It("allows consumer to update id", func() { + _, e, _ := store.UpdateMultiAutoID(2000, updateBlueprint) + g.Assert(e).Equal(nil) + }) + }) + + g.It("allows consumer to delete mutli auto records", func() { + id, e := store.CreateMultiAutos(MultiAuto{Name: "to-delete"}) + g.Assert(e).Equal(nil) + _, e = store.DeleteMultiAutos(&MultiAutoBlueprint{ID: []uint{uint(id)}}) + g.Assert(e).Equal(nil) + }) + + g.Describe("selections", func() { + g.BeforeEach(func() { + autos := make([]MultiAuto, 100) + + for i := 0; i < 100; i++ { + autos[i] = MultiAuto{ + Name: fmt.Sprintf("auto-%d", i), + Status: "pending", + } + } + + _, e := store.CreateMultiAutos(autos...) + g.Assert(e).Equal(nil) + }) + + g.It("allows the consumer to query the records (by status)", func() { + _, e := store.FindMultiAutos(&MultiAutoBlueprint{Status: []string{"pending"}}) + g.Assert(e).Equal(nil) + }) + + g.It("allows the consumer to query the records (by status like)", func() { + _, e := store.FindMultiAutos(&MultiAutoBlueprint{StatusLike: []string{"%%pending%%"}}) + g.Assert(e).Equal(nil) + }) + + g.It("allows the consumer to query the records (by name like)", func() { + _, e := store.FindMultiAutos(&MultiAutoBlueprint{NameLike: []string{"%%-1%%"}}) + g.Assert(e).Equal(nil) + }) + + g.It("allows the consumer to query the records (by name)", func() { + _, e := store.FindMultiAutos(&MultiAutoBlueprint{Name: []string{"first"}}) + g.Assert(e).Equal(nil) + }) + + g.It("allows the consumer to query the records (by id)", func() { + _, e := store.FindMultiAutos(&MultiAutoBlueprint{ID: []uint{1}}) + g.Assert(e).Equal(nil) + }) + + g.It("allows the consumer to query the records (w limit)", func() { + _, e := store.FindMultiAutos(&MultiAutoBlueprint{Limit: 1}) + g.Assert(e).Equal(nil) + }) + + g.It("allows the consumer to count mutli autos", func() { + a, e := store.CountMultiAutos(&MultiAutoBlueprint{Limit: 1}) + g.Assert(e).Equal(nil) + g.Assert(a > 0).Equal(true) + }) + + g.It("allows the consumer to select names", func() { + _, e := store.SelectNames(&MultiAutoBlueprint{Limit: 1}) + g.Assert(e).Equal(nil) + }) + + g.It("allows the consumer to select statuses", func() { + _, e := store.SelectStatuses(&MultiAutoBlueprint{Limit: 1}) + g.Assert(e).Equal(nil) + }) + + g.It("allows the consumer to select ids", func() { + _, e := store.SelectIDs(&MultiAutoBlueprint{Limit: 1}) + g.Assert(e).Equal(nil) + }) + }) + + }) +} diff --git a/marlow/blueprint.go b/marlow/blueprint.go index 822277c..15e9cef 100644 --- a/marlow/blueprint.go +++ b/marlow/blueprint.go @@ -82,7 +82,7 @@ func writeBlueprint(destination io.Writer, record marlowRecord) error { wg.Done() }() - for _, f := range record.fieldList() { + for _, f := range record.fieldList(nil) { name, config := f.name, record.fields[f.name] fieldGenerators := fieldMethods(record, name, config, methodReceiver) diff --git a/marlow/constants/config.go b/marlow/constants/config.go index 3544f75..9391ff9 100644 --- a/marlow/constants/config.go +++ b/marlow/constants/config.go @@ -1,6 +1,9 @@ package constants const ( + // StoreLoggerField is the internal field on stores for the io.Writer log stream + StoreLoggerField = "logger" + // PrimaryKeyColumnConfigOption specifies the primary key on the record PrimaryKeyColumnConfigOption = "primaryKey" diff --git a/marlow/createable.go b/marlow/createable.go index 569463b..577756e 100644 --- a/marlow/createable.go +++ b/marlow/createable.go @@ -2,7 +2,6 @@ package marlow import "io" import "fmt" -import "sort" import "net/url" import "strings" import "github.com/gedex/inflector" @@ -75,33 +74,27 @@ func newCreateableGenerator(record marlowRecord) io.Reader { return gosrc.Returns("0", writing.Nil) }, symbols.recordParam) - columnList := make([]string, 0, len(record.fields)) + columns := make([]string, 0, len(record.fields)) placeholders := make([]string, 0, len(record.fields)) - fieldLookup := make(map[string]string, len(record.fields)) index := 1 - for field, c := range record.fields { - columnName := c.Get(constants.ColumnConfigOption) - - if c.Get(constants.ColumnAutoIncrementFlag) != "" { - continue - } + fields := record.fieldList(func(config url.Values) bool { + return config.Get(constants.ColumnAutoIncrementFlag) == "" + }) - columnList = append(columnList, columnName) + for _, field := range fields { placeholder := "?" if record.dialect() == "postgres" { - fmtStr := "fmt.Sprintf(\"$%%d\", (%s*(%d-1))+%d)" - placeholder = fmt.Sprintf(fmtStr, symbols.recordIndex, len(record.fields), index) + fmtStr := "fmt.Sprintf(\"$%%d\", (%s*%d)+%d)" + placeholder = fmt.Sprintf(fmtStr, symbols.recordIndex, len(fields), index) } + columns = append(columns, strings.Split(field.column, ".")[1]) placeholders = append(placeholders, placeholder) - fieldLookup[columnName] = field index++ } - sort.Strings(columnList) - gosrc.Println("%s := make([]string, 0, len(%s))", symbols.statementPlaceholderList, symbols.recordParam) gosrc.Println("%s := make([]interface{}, 0, len(%s))", symbols.statementValueList, symbols.recordParam) @@ -112,11 +105,16 @@ func newCreateableGenerator(record marlowRecord) io.Reader { gosrc.Println("%s := %s", symbols.rowValueString, writing.StringSliceLiteral(placeholders)) } - fieldReferences := make([]string, 0, len(columnList)) + fieldReferences := make([]string, 0, len(placeholders)) + + for _, field := range fields { + config := record.fields[field.name] + + if config.Get(constants.ColumnAutoIncrementFlag) != "" { + continue + } - for _, columnName := range columnList { - field := fieldLookup[columnName] - fieldReferences = append(fieldReferences, fmt.Sprintf("%s.%s", symbols.singleRecord, field)) + fieldReferences = append(fieldReferences, fmt.Sprintf("%s.%s", symbols.singleRecord, field.name)) } gosrc.Println( @@ -136,13 +134,12 @@ func newCreateableGenerator(record marlowRecord) io.Reader { gosrc.Println("%s := new(bytes.Buffer)", symbols.queryBuffer) - insertStatement := fmt.Sprintf("INSERT INTO %s (%s) VALUES %%s;", record.table(), strings.Join(columnList, ",")) + insertStatement := fmt.Sprintf("INSERT INTO %s (%s) VALUES %%s;", record.table(), strings.Join(columns, ",")) if record.dialect() == "postgres" { template := "INSERT INTO %s (%s) VALUES %%s RETURNING %s;" - columns := strings.Join(columnList, ",") primary := record.primaryKeyColumn() - insertStatement = fmt.Sprintf(template, record.table(), columns, primary) + insertStatement = fmt.Sprintf(template, record.table(), strings.Join(columns, ","), primary) } gosrc.Println( @@ -152,6 +149,14 @@ func newCreateableGenerator(record marlowRecord) io.Reader { symbols.statementPlaceholderList, ) + gosrc.Println( + "fmt.Fprintf(%s.%s, \"%%s | values(%%v)\", %s.String(), %s)", + scope.Get("receiver"), + constants.StoreLoggerField, + symbols.queryBuffer, + symbols.statementValueList, + ) + gosrc.Println( "%s, %s := %s.Prepare(%s.String())", symbols.statement, diff --git a/marlow/queryable.go b/marlow/queryable.go index 0f03276..9e41550 100644 --- a/marlow/queryable.go +++ b/marlow/queryable.go @@ -60,7 +60,7 @@ func finder(record marlowRecord) io.Reader { returns := []string{symbols.recordSlice, "error"} - fieldList := record.fieldList() + fieldList := record.fieldList(nil) defaultLimit := record.config.Get(constants.DefaultLimitConfigOption) if defaultLimit == "" { diff --git a/marlow/record.go b/marlow/record.go index fc0eb40..4397d42 100644 --- a/marlow/record.go +++ b/marlow/record.go @@ -18,11 +18,16 @@ type marlowRecord struct { storeChannel chan writing.FuncDecl } -func (r *marlowRecord) fieldList() fieldList { +func (r *marlowRecord) fieldList(filter func(url.Values) bool) fieldList { list := make(fieldList, 0, len(r.fields)) for name, c := range r.fields { column := fmt.Sprintf("%s.%s", r.table(), c.Get(constants.ColumnConfigOption)) + + if filter != nil && filter(c) != true { + continue + } + list = append(list, field{name: name, column: column}) } diff --git a/marlow/store.go b/marlow/store.go index e78cb01..a7e54a2 100644 --- a/marlow/store.go +++ b/marlow/store.go @@ -5,12 +5,14 @@ import "fmt" import "strings" import "net/url" import "github.com/dadleyy/marlow/marlow/writing" +import "github.com/dadleyy/marlow/marlow/constants" func writeStore(destination io.Writer, record marlowRecord, storeMethods map[string]writing.FuncDecl) error { out := writing.NewGoWriter(destination) e := out.WithStruct(record.store(), func(url.Values) error { out.Println("*sql.DB") + out.Println("%s io.Writer", constants.StoreLoggerField) return nil }) @@ -19,18 +21,29 @@ func writeStore(destination io.Writer, record marlowRecord, storeMethods map[str } symbols := struct { - dbParam string - }{"_db"} + dbParam string + queryLogger string + }{"_db", "_logger"} params := []writing.FuncParam{ {Type: "*sql.DB", Symbol: symbols.dbParam}, + {Type: "io.Writer", Symbol: symbols.queryLogger}, } returns := []string{record.external()} e = out.WithFunc(fmt.Sprintf("New%s", record.external()), params, returns, func(url.Values) error { - out.Println("return &%s{%s}", record.store(), symbols.dbParam) - return nil + out.WithIf("%s == nil", func(url.Values) error { + return out.Println("%s, _ = os.Open(os.DevNull)", symbols.queryLogger) + }, symbols.queryLogger) + + return out.Println( + "return &%s{DB: %s, %s: %s}", + record.store(), + symbols.dbParam, + constants.StoreLoggerField, + symbols.queryLogger, + ) }) if e != nil { @@ -56,7 +69,7 @@ func writeStore(destination io.Writer, record marlowRecord, storeMethods map[str return nil }) - record.registerImports("database/sql") + record.registerImports("database/sql", "io", "os") return e } diff --git a/marlow/store_test.go b/marlow/store_test.go index 06e779d..9f9d85f 100644 --- a/marlow/store_test.go +++ b/marlow/store_test.go @@ -96,8 +96,10 @@ func Test_StoreGenerator(t *testing.T) { g.It("injects fmt and sql packages into import stream", func() { io.Copy(scaffold.output, scaffold.g()) scaffold.close() - g.Assert(len(scaffold.received)).Equal(1) g.Assert(scaffold.received["database/sql"]).Equal(true) + g.Assert(scaffold.received["io"]).Equal(true) + g.Assert(scaffold.received["os"]).Equal(true) + g.Assert(len(scaffold.received)).Equal(3) }) g.It("writes valid golang code if store name is present", func() { From 861e5e68ec9f4e75248ef364f911dc45ab9ded39 Mon Sep 17 00:00:00 2001 From: Danny Hadley Date: Mon, 11 Dec 2017 14:14:03 -0500 Subject: [PATCH 2/4] chore(hound) hound ci fixes --- examples/library/models/multi_auto.go | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/library/models/multi_auto.go b/examples/library/models/multi_auto.go index ba040a9..6496502 100644 --- a/examples/library/models/multi_auto.go +++ b/examples/library/models/multi_auto.go @@ -1,5 +1,6 @@ package models +// MultiAuto represents a record w/ mutliple auto-increment directives on a postgres model. type MultiAuto struct { table bool `marlow:"tableName=multi_auto&dialect=postgres&primaryKey=id"` ID uint `marlow:"column=id&autoIncrement=true"` From a9d5322a6e6e699865b4381ab0db1a8436a43a55 Mon Sep 17 00:00:00 2001 From: Danny Hadley Date: Mon, 11 Dec 2017 14:17:53 -0500 Subject: [PATCH 3/4] chore(travis) removing stdout compilation to save travis time --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3236b3a..c7c34d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,6 @@ script: - make - make test - make test-example - - go run marlowc/main.go -input examples/library/models -stdout=true - make example - pushd ./examples/library && ./library && popd after_success: From 1e40d5dcd0b80384b9ea2b22ab7a0591e3e172d2 Mon Sep 17 00:00:00 2001 From: Danny Hadley Date: Mon, 11 Dec 2017 14:20:08 -0500 Subject: [PATCH 4/4] chore(make) minor tweaks to makefile --- Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a059079..7f41cf0 100644 --- a/Makefile +++ b/Makefile @@ -52,6 +52,8 @@ LIBRARY_EXAMPLE_COVERAGE_REPORT=$(LIBRARY_COVERAGE_OUTPUT_DIR)/library.coverage. LIBRARY_EXAMPLE_COVERAGE_DISTRIBUTABLE=$(LIBRARY_COVERAGE_OUTPUT_DIR)/library.coverage.html LIBRARY_EXAMPLE_TEST_FLAGS=-covermode=atomic $(TEST_VERBOSITY) -coverprofile=$(LIBRARY_EXAMPLE_COVERAGE_REPORT) +.PHONY: all lint test test-example clean clean-example + all: $(EXE) $(EXE): $(VENDOR_DIR) $(GO_SRC) $(LIB_SRC) @@ -78,7 +80,9 @@ $(VENDOR_DIR): $(GO) get -v -u github.com/golang/lint/golint $(GLIDE) install -example: $(LIBRARY_EXAMPLE_SRC) $(LIBRARY_EXAMPLE_MAIN) $(EXE) +example: $(LIBRARY_EXAMPLE_EXE) + +$(LIBRARY_EXAMPLE_EXE): $(LIBRARY_EXAMPLE_SRC) $(LIBRARY_EXAMPLE_MAIN) $(EXE) $(GO) get -v github.com/lib/pq $(GO) get -v github.com/mattn/go-sqlite3 $(GO) install -v -x github.com/mattn/go-sqlite3