package pg_test import ( "bytes" "context" "crypto/tls" "database/sql" "fmt" "net" "testing" "time" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/go-pg/pg" "github.com/go-pg/pg/orm" ) func TestGinkgo(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "pg") } func pgOptions() *pg.Options { return &pg.Options{ User: "postgres", Database: "postgres", TLSConfig: &tls.Config{ InsecureSkipVerify: true, }, MaxRetries: 1, MinRetryBackoff: -1, DialTimeout: 30 * time.Second, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, PoolSize: 10, MaxConnAge: 10 * time.Second, PoolTimeout: 30 * time.Second, IdleTimeout: 10 * time.Second, IdleCheckFrequency: 100 * time.Millisecond, } } func TestDBString(t *testing.T) { db := pg.Connect(pgOptions()) defer db.Close() wanted := `DB<Addr="localhost:5432">` if db.String() != wanted { t.Fatalf("got %q, wanted %q", db.String(), wanted) } db = db.WithParam("param1", "value1").WithParam("param2", 2) wanted = `DB<Addr="localhost:5432" param1=value1 param2=2>` if db.String() != wanted { t.Fatalf("got %q, wanted %q", db.String(), wanted) } } func TestOnConnect(t *testing.T) { opt := pgOptions() opt.OnConnect = func(db *pg.Conn) error { _, err := db.Exec("SET application_name = 'myapp'") return err } db := pg.Connect(opt) defer db.Close() var name string _, err := db.QueryOne(pg.Scan(&name), "SHOW application_name") if err != nil { t.Fatal(err) } if name != "myapp" { t.Fatalf(`got %q, wanted "myapp"`, name) } } func TestEmptyQuery(t *testing.T) { db := pg.Connect(pgOptions()) defer db.Close() assert := func(err error) { if err == nil { t.Fatal("error expected") } if err.Error() != "pg: query is empty" { t.Fatal(err) } } _, err := db.Exec("") assert(err) _, err = db.Query(pg.Discard, "") assert(err) stmt, err := db.Prepare("") if err != nil { t.Fatal(err) } _, err = stmt.Exec() assert(err) } var _ = Describe("DB", func() { var db *pg.DB var tx *pg.Tx BeforeEach(func() { db = pg.Connect(pgOptions()) var err error tx, err = db.Begin() Expect(err).NotTo(HaveOccurred()) }) AfterEach(func() { Expect(db.Close()).NotTo(HaveOccurred()) }) Describe("Query", func() { It("does not return an error when there are no results", func() { res, err := db.Query(pg.Discard, "SELECT 1 WHERE 1 = 2") Expect(err).NotTo(HaveOccurred()) Expect(res.RowsAffected()).To(Equal(0)) res, err = tx.Query(pg.Discard, "SELECT 1 WHERE 1 = 2") Expect(err).NotTo(HaveOccurred()) Expect(res.RowsAffected()).To(Equal(0)) }) It("selects into embedded struct pointer", func() { type One struct { ID int } type Two struct { *One } two := new(Two) _, err := db.QueryOne(two, "SELECT 1 AS id") Expect(err).NotTo(HaveOccurred()) Expect(two.One.ID).To(Equal(1)) }) }) Describe("QueryOne", func() { It("returns pg.ErrNoRows when there are no results", func() { _, err := db.QueryOne(pg.Discard, "SELECT 1 WHERE 1 = 2") Expect(err).To(Equal(pg.ErrNoRows)) _, err = tx.QueryOne(pg.Discard, "SELECT 1 WHERE 1 = 2") Expect(err).To(Equal(pg.ErrNoRows)) }) }) Describe("Exec", func() { It("does not return an error when there are no results", func() { res, err := db.Exec("SELECT 1 WHERE 1 = 2") Expect(err).NotTo(HaveOccurred()) Expect(res.RowsAffected()).To(Equal(0)) res, err = tx.Exec("SELECT 1 WHERE 1 = 2") Expect(err).NotTo(HaveOccurred()) Expect(res.RowsAffected()).To(Equal(0)) }) }) Describe("ExecOne", func() { It("returns pg.ErrNoRows when there are no results", func() { _, err := db.ExecOne("SELECT 1 WHERE 1 = 2") Expect(err).To(Equal(pg.ErrNoRows)) _, err = tx.ExecOne("SELECT 1 WHERE 1 = 2") Expect(err).To(Equal(pg.ErrNoRows)) }) }) Describe("Prepare", func() { It("returns an error when query can't be prepared", func() { for i := 0; i < 3; i++ { _, err := db.Prepare("totally invalid sql") Expect(err).To(MatchError(`ERROR #42601 syntax error at or near "totally"`)) _, err = db.Exec("SELECT 1") Expect(err).NotTo(HaveOccurred()) } }) }) Describe("Context", func() { It("cancels query when context is cancelled", func() { c := context.Background() c, cancel := context.WithTimeout(c, time.Second) defer cancel() _, err := db.ExecContext(c, "SELECT pg_sleep(10)") Expect(err).To(MatchError(`ERROR #57014 canceling statement due to user request`)) }) }) }) var _ = Describe("DB.Conn", func() { var db *pg.DB BeforeEach(func() { db = pg.Connect(pgOptions()) }) AfterEach(func() { Expect(db.Close()).NotTo(HaveOccurred()) }) It("does not ackquire connection immediately", func() { conn := db.Conn() stats := db.PoolStats() Expect(stats.TotalConns).To(Equal(uint32(0))) err := conn.Close() Expect(err).NotTo(HaveOccurred()) }) It("ackquires connection when used and frees when closed", func() { conn := db.Conn() _, err := conn.Exec("SELECT 1") Expect(err).NotTo(HaveOccurred()) stats := db.PoolStats() Expect(stats.TotalConns).To(Equal(uint32(1))) Expect(stats.IdleConns).To(Equal(uint32(0))) err = conn.Close() Expect(err).NotTo(HaveOccurred()) stats = db.PoolStats() Expect(stats.TotalConns).To(Equal(uint32(1))) Expect(stats.IdleConns).To(Equal(uint32(1))) }) It("supports Tx", func() { conn := db.Conn() tx, err := conn.Begin() Expect(err).NotTo(HaveOccurred()) _, err = tx.Exec("SELECT 1") Expect(err).NotTo(HaveOccurred()) _, err = conn.Exec("SELECT 1") Expect(err).NotTo(HaveOccurred()) err = tx.Commit() Expect(err).NotTo(HaveOccurred()) _, err = conn.Exec("SELECT 1") Expect(err).NotTo(HaveOccurred()) err = conn.Close() Expect(err).NotTo(HaveOccurred()) stats := db.PoolStats() Expect(stats.TotalConns).To(Equal(uint32(1))) Expect(stats.IdleConns).To(Equal(uint32(1))) }) }) var _ = Describe("Time", func() { var tests = []struct { str string wanted time.Time }{ {"0001-01-01 00:00:00+00", time.Time{}}, {"0000-01-01 00:00:00+00", time.Date(0, time.January, 1, 0, 0, 0, 0, time.UTC)}, {"2001-02-03", time.Date(2001, time.February, 3, 0, 0, 0, 0, time.UTC)}, {"2001-02-03 04:05:06", time.Date(2001, time.February, 3, 4, 5, 6, 0, time.UTC)}, {"2001-02-03 04:05:06.000001", time.Date(2001, time.February, 3, 4, 5, 6, 1000, time.UTC)}, {"2001-02-03 04:05:06.00001", time.Date(2001, time.February, 3, 4, 5, 6, 10000, time.UTC)}, {"2001-02-03 04:05:06.0001", time.Date(2001, time.February, 3, 4, 5, 6, 100000, time.UTC)}, {"2001-02-03 04:05:06.001", time.Date(2001, time.February, 3, 4, 5, 6, 1000000, time.UTC)}, {"2001-02-03 04:05:06.01", time.Date(2001, time.February, 3, 4, 5, 6, 10000000, time.UTC)}, {"2001-02-03 04:05:06.1", time.Date(2001, time.February, 3, 4, 5, 6, 100000000, time.UTC)}, {"2001-02-03 04:05:06.12", time.Date(2001, time.February, 3, 4, 5, 6, 120000000, time.UTC)}, {"2001-02-03 04:05:06.123", time.Date(2001, time.February, 3, 4, 5, 6, 123000000, time.UTC)}, {"2001-02-03 04:05:06.1234", time.Date(2001, time.February, 3, 4, 5, 6, 123400000, time.UTC)}, {"2001-02-03 04:05:06.12345", time.Date(2001, time.February, 3, 4, 5, 6, 123450000, time.UTC)}, {"2001-02-03 04:05:06.123456", time.Date(2001, time.February, 3, 4, 5, 6, 123456000, time.UTC)}, {"2001-02-03 04:05:06.123-07", time.Date(2001, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", -7*60*60))}, {"2001-02-03 04:05:06-07", time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -7*60*60))}, {"2001-02-03 04:05:06-07:42", time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+42*60)))}, {"2001-02-03 04:05:06-07:30:09", time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9)))}, {"2001-02-03 04:05:06+07", time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", 7*60*60))}, } var db *pg.DB BeforeEach(func() { db = pg.Connect(pgOptions()) }) AfterEach(func() { Expect(db.Close()).NotTo(HaveOccurred()) }) It("is formatted correctly", func() { for i, test := range tests { var tm time.Time _, err := db.QueryOne(pg.Scan(&tm), "SELECT ?", test.wanted) Expect(err).NotTo(HaveOccurred()) Expect(tm.Unix()).To( Equal(test.wanted.Unix()), "#%d str=%q wanted=%q", i, test.str, test.wanted, ) } }) It("is parsed correctly", func() { for i, test := range tests { var tm time.Time _, err := db.QueryOne(pg.Scan(&tm), "SELECT ?", test.str) Expect(err).NotTo(HaveOccurred()) Expect(tm.Unix()).To( Equal(test.wanted.Unix()), "#%d str=%q wanted=%q", i, test.str, test.wanted, ) } }) }) var _ = Describe("array model", func() { type value struct { Values []int16 `sql:",array"` } var db *pg.DB BeforeEach(func() { db = pg.Connect(pgOptions()) }) AfterEach(func() { Expect(db.Close()).NotTo(HaveOccurred()) }) It("selects values", func() { model := new(value) _, err := db.QueryOne(model, "SELECT ? AS values", pg.Array([]int16{1, 2})) Expect(err).NotTo(HaveOccurred()) Expect(model.Values).To(Equal([]int16{1, 2})) }) It("selects empty values", func() { model := &value{ Values: []int16{1, 2}, } _, err := db.QueryOne(model, "SELECT ? AS values", pg.Array([]int16{})) Expect(err).NotTo(HaveOccurred()) Expect(model.Values).To(BeEmpty()) }) It("selects null values", func() { model := &value{ Values: []int16{1, 2}, } _, err := db.QueryOne(model, "SELECT NULL AS values", pg.Array([]int16{})) Expect(err).NotTo(HaveOccurred()) Expect(model.Values).To(BeEmpty()) }) }) var _ = Describe("slice model", func() { type value struct { Id int } var db *pg.DB BeforeEach(func() { db = pg.Connect(pgOptions()) }) AfterEach(func() { Expect(db.Close()).NotTo(HaveOccurred()) }) It("does not error when there are no rows", func() { ints := make([]int, 1) _, err := db.Query(&ints, "SELECT generate_series(1, 0)") Expect(err).NotTo(HaveOccurred()) Expect(ints).To(BeEmpty()) }) It("does not error when there are no rows", func() { slice := make([]value, 1) _, err := db.Query(&slice, "SELECT generate_series(1, 0)") Expect(err).NotTo(HaveOccurred()) Expect(slice).To(BeEmpty()) }) It("does not error when there are no rows", func() { slice := make([]*value, 1) _, err := db.Query(&slice, "SELECT generate_series(1, 0)") Expect(err).NotTo(HaveOccurred()) Expect(slice).To(BeEmpty()) }) It("supports slice of structs", func() { var slice []value _, err := db.Query(&slice, `SELECT generate_series(1, 3) AS id`) Expect(err).NotTo(HaveOccurred()) Expect(slice).To(Equal([]value{{1}, {2}, {3}})) }) It("supports slice of pointers", func() { var slice []*value _, err := db.Query(&slice, `SELECT generate_series(1, 3) AS id`) Expect(err).NotTo(HaveOccurred()) Expect(slice).To(Equal([]*value{{1}, {2}, {3}})) }) It("supports Ints", func() { var ints pg.Ints _, err := db.Query(&ints, `SELECT generate_series(1, 3)`) Expect(err).NotTo(HaveOccurred()) Expect(ints).To(Equal(pg.Ints{1, 2, 3})) }) It("supports slice of ints", func() { var ints []int _, err := db.Query(&ints, `SELECT generate_series(1, 3)`) Expect(err).NotTo(HaveOccurred()) Expect(ints).To(Equal([]int{1, 2, 3})) }) It("supports slice of time.Time", func() { var times []time.Time _, err := db.Query(×, ` WITH data (time) AS (VALUES (clock_timestamp()), (clock_timestamp())) SELECT time FROM data `) Expect(err).NotTo(HaveOccurred()) Expect(times).To(HaveLen(2)) }) It("resets slice", func() { ints := []int{1, 2, 3} _, err := db.Query(&ints, `SELECT 1`) Expect(err).NotTo(HaveOccurred()) Expect(ints).To(Equal([]int{1})) }) It("resets slice when there are no results", func() { ints := []int{1, 2, 3} _, err := db.Query(&ints, `SELECT 1 WHERE FALSE`) Expect(err).NotTo(HaveOccurred()) Expect(ints).To(BeEmpty()) }) }) var _ = Describe("read/write timeout", func() { var db *pg.DB BeforeEach(func() { opt := pgOptions() opt.ReadTimeout = time.Millisecond db = pg.Connect(opt) }) AfterEach(func() { Expect(db.Close()).NotTo(HaveOccurred()) }) It("slow query timeouts", func() { _, err := db.Exec(`SELECT pg_sleep(1)`) Expect(err.(net.Error).Timeout()).To(BeTrue()) }) Context("WithTimeout", func() { It("slow query passes", func() { _, err := db.WithTimeout(time.Minute).Exec(`SELECT pg_sleep(1)`) Expect(err).NotTo(HaveOccurred()) }) }) }) var _ = Describe("CopyFrom/CopyTo", func() { const n = 1000000 var db *pg.DB BeforeEach(func() { db = pg.Connect(pgOptions()) qs := []string{ "CREATE TEMP TABLE copy_src(n int)", "CREATE TEMP TABLE copy_dst(n int)", fmt.Sprintf("INSERT INTO copy_src SELECT generate_series(1, %d)", n), } for _, q := range qs { _, err := db.Exec(q) Expect(err).NotTo(HaveOccurred()) } }) AfterEach(func() { err := db.Close() Expect(err).NotTo(HaveOccurred()) }) It("copies data from a table and to a table", func() { var buf bytes.Buffer res, err := db.CopyTo(&buf, "COPY copy_src TO STDOUT") Expect(err).NotTo(HaveOccurred()) Expect(res.RowsAffected()).To(Equal(n)) res, err = db.CopyFrom(&buf, "COPY copy_dst FROM STDIN") Expect(err).NotTo(HaveOccurred()) Expect(res.RowsAffected()).To(Equal(n)) st := db.PoolStats() Expect(st.Hits).To(Equal(uint32(4))) Expect(st.Misses).To(Equal(uint32(1))) Expect(st.Timeouts).To(Equal(uint32(0))) Expect(st.TotalConns).To(Equal(uint32(1))) Expect(st.IdleConns).To(Equal(uint32(1))) var count int _, err = db.QueryOne(pg.Scan(&count), "SELECT count(*) FROM copy_dst") Expect(err).NotTo(HaveOccurred()) Expect(count).To(Equal(n)) }) It("copies corrupted data to a table", func() { buf := bytes.NewBufferString("corrupted,data\nrow,two\r\nrow three") res, err := db.CopyFrom(buf, "COPY copy_dst FROM STDIN WITH FORMAT csv") Expect(err).To(MatchError(`ERROR #42601 syntax error at or near "FORMAT"`)) Expect(res).To(BeNil()) st := db.Pool().Stats() Expect(st.Hits).To(Equal(uint32(3))) Expect(st.Misses).To(Equal(uint32(1))) Expect(st.Timeouts).To(Equal(uint32(0))) Expect(st.TotalConns).To(Equal(uint32(1))) Expect(st.IdleConns).To(Equal(uint32(1))) var count int _, err = db.QueryOne(pg.Scan(&count), "SELECT count(*) FROM copy_dst") Expect(err).NotTo(HaveOccurred()) Expect(count).To(Equal(0)) }) }) var _ = Describe("CountEstimate", func() { var db *pg.DB BeforeEach(func() { db = pg.Connect(pgOptions()) }) AfterEach(func() { Expect(db.Close()).NotTo(HaveOccurred()) }) It("works", func() { count, err := db.Model(). TableExpr("generate_series(1, 10)"). CountEstimate(1000) Expect(err).NotTo(HaveOccurred()) Expect(count).To(Equal(10)) }) It("works when there are no results", func() { count, err := db.Model(). TableExpr("generate_series(1, 0)"). CountEstimate(1000) Expect(err).NotTo(HaveOccurred()) Expect(count).To(Equal(0)) }) It("works with GROUP", func() { count, err := db.Model(). TableExpr("generate_series(1, 10)"). Group("generate_series"). CountEstimate(1000) Expect(err).NotTo(HaveOccurred()) Expect(count).To(Equal(10)) }) It("works with GROUP when there are no results", func() { count, err := db.Model(). TableExpr("generate_series(1, 0)"). Group("generate_series"). CountEstimate(1000) Expect(err).NotTo(HaveOccurred()) Expect(count).To(Equal(0)) }) }) var _ = Describe("DB nulls", func() { var db *pg.DB BeforeEach(func() { db = pg.Connect(pgOptions()) _, err := db.Exec("CREATE TEMP TABLE tests (id int, value int)") Expect(err).To(BeNil()) }) AfterEach(func() { Expect(db.Close()).NotTo(HaveOccurred()) }) Describe("sql.NullInt64", func() { type Test struct { Id int Value sql.NullInt64 } It("inserts null value", func() { ins := Test{ Id: 1, } err := db.Insert(&ins) Expect(err).NotTo(HaveOccurred()) sel := Test{ Id: 1, } err = db.Select(&sel) Expect(err).NotTo(HaveOccurred()) Expect(sel.Value.Valid).To(BeFalse()) }) It("inserts non-null value", func() { ins := Test{ Id: 1, Value: sql.NullInt64{ Int64: 2, Valid: true, }, } err := db.Insert(&ins) Expect(err).NotTo(HaveOccurred()) sel := Test{ Id: 1, } err = db.Select(&sel) Expect(err).NotTo(HaveOccurred()) Expect(sel.Value.Valid).To(BeTrue()) Expect(sel.Value.Int64).To(Equal(int64(2))) }) }) Context("nil ptr", func() { type Test struct { Id int Value *int } It("inserts null value", func() { ins := Test{ Id: 1, } err := db.Insert(&ins) Expect(err).NotTo(HaveOccurred()) sel := Test{ Id: 1, } err = db.Select(&sel) Expect(err).NotTo(HaveOccurred()) Expect(sel.Value).To(BeNil()) }) It("inserts non-null value", func() { value := 2 ins := Test{ Id: 1, Value: &value, } err := db.Insert(&ins) Expect(err).NotTo(HaveOccurred()) sel := Test{ Id: 1, } err = db.Select(&sel) Expect(err).NotTo(HaveOccurred()) Expect(sel.Value).NotTo(BeNil()) Expect(*sel.Value).To(Equal(2)) }) }) }) var _ = Describe("DB.Select", func() { var db *pg.DB BeforeEach(func() { db = pg.Connect(pgOptions()) }) AfterEach(func() { Expect(db.Close()).NotTo(HaveOccurred()) }) It("selects bytea", func() { qs := []string{ `CREATE TEMP TABLE tests (col bytea)`, fmt.Sprintf(`INSERT INTO tests VALUES ('\x%x')`, []byte("bytes")), } for _, q := range qs { _, err := db.Exec(q) Expect(err).NotTo(HaveOccurred()) } var col []byte err := db.Model().Table("tests").Column("col").Select(pg.Scan(&col)) Expect(err).NotTo(HaveOccurred()) }) It("selects into embedded struct pointer", func() { type One struct { ID int } type Two struct { *One } err := db.CreateTable((*Two)(nil), &orm.CreateTableOptions{ Temp: true, }) Expect(err).NotTo(HaveOccurred()) err = db.Insert(&Two{ One: &One{ ID: 1, }, }) Expect(err).NotTo(HaveOccurred()) two := new(Two) err = db.Model(two).Where("id = 1").Select() Expect(err).NotTo(HaveOccurred()) Expect(two.One.ID).To(Equal(1)) }) }) var _ = Describe("DB.Insert", func() { var db *pg.DB BeforeEach(func() { db = pg.Connect(pgOptions()) }) AfterEach(func() { Expect(db.Close()).NotTo(HaveOccurred()) }) It("returns an error on nil", func() { err := db.Insert(nil) Expect(err).To(MatchError("pg: Model(nil)")) }) It("returns an error if value is not settable", func() { err := db.Insert(1) Expect(err).To(MatchError("pg: Model(non-pointer int)")) }) It("returns an error if value is not supported", func() { var v int err := db.Insert(&v) Expect(err).To(MatchError("pg: Model(unsupported int)")) }) }) var _ = Describe("DB.Update", func() { var db *pg.DB BeforeEach(func() { db = pg.Connect(pgOptions()) }) AfterEach(func() { Expect(db.Close()).NotTo(HaveOccurred()) }) It("returns an error on nil", func() { err := db.Update(nil) Expect(err).To(MatchError("pg: Model(nil)")) }) It("returns an error if there are no pks", func() { type Test struct{} var test Test err := db.Update(&test) Expect(err).To(MatchError(`pg: model=Test does not have primary keys`)) }) }) var _ = Describe("DB.Delete", func() { var db *pg.DB BeforeEach(func() { db = pg.Connect(pgOptions()) }) AfterEach(func() { Expect(db.Close()).NotTo(HaveOccurred()) }) It("returns an error on nil", func() { err := db.Delete(nil) Expect(err).To(MatchError("pg: Model(nil)")) }) It("returns an error if there are no pks", func() { type Test struct{} var test Test err := db.Delete(&test) Expect(err).To(MatchError(`pg: model=Test does not have primary keys`)) }) It("returns an error if there are no where", func() { var test []struct { Id int } _, err := db.Model(&test).Delete() Expect(err).To(MatchError(`pg: Update and Delete queries require Where clause (try WherePK)`)) }) }) var _ = Describe("errors", func() { var db *pg.DB BeforeEach(func() { db = pg.Connect(pgOptions()) }) AfterEach(func() { Expect(db.Close()).NotTo(HaveOccurred()) }) It("unknown column error", func() { type Test struct { Col1 int } var test Test _, err := db.QueryOne(&test, "SELECT 1 AS col1, 2 AS col2") Expect(err).To(MatchError("pg: can't find column=col2 in model=Test (try discard_unknown_columns)")) Expect(test.Col1).To(Equal(1)) }) It("Scan error", func() { var n1 int _, err := db.QueryOne(pg.Scan(&n1), "SELECT 1, 2") Expect(err).To(MatchError(`pg: no Scan var for column index=1 name="?column?"`)) Expect(n1).To(Equal(1)) }) }) type Genre struct { // tableName is an optional field that specifies custom table name and alias. // By default go-pg generates table name and alias from struct name. tableName struct{} `sql:"genres,alias:genre"` // default values are the same Id int // Id is automatically detected as primary key Name string Rating int `sql:"-"` // - is used to ignore field Books []Book `pg:"many2many:book_genres"` // many to many relation ParentId int Subgenres []Genre `pg:"fk:parent_id"` } func (g Genre) String() string { return fmt.Sprintf("Genre<Id=%d Name=%q>", g.Id, g.Name) } type Image struct { Id int Path string } type Author struct { ID int // both "Id" and "ID" are detected as primary key Name string `sql:",unique"` Books []*Book // has many relation AvatarId int Avatar Image } func (a Author) String() string { return fmt.Sprintf("Author<ID=%d Name=%q>", a.ID, a.Name) } type BookGenre struct { tableName struct{} `sql:"alias:bg"` // custom table alias BookId int `sql:",pk"` // pk tag is used to mark field as primary key Book *Book GenreId int `sql:",pk"` Genre *Genre Genre_Rating int // belongs to and is copied to Genre model } type Book struct { Id int Title string AuthorID int Author Author // has one relation EditorID int Editor *Author // has one relation CreatedAt time.Time `sql:"default:now()"` UpdatedAt time.Time Genres []Genre `pg:"many2many:book_genres"` // many to many relation Translations []Translation // has many relation Comments []Comment `pg:"polymorphic:trackable_"` // has many polymorphic relation } func (b Book) String() string { return fmt.Sprintf("Book<Id=%d Title=%q>", b.Id, b.Title) } func (b *Book) BeforeInsert(c context.Context, db orm.DB) error { if b.CreatedAt.IsZero() { b.CreatedAt = time.Now() } return nil } // BookWithCommentCount is like Book model, but has additional CommentCount // field that is used to select data into it. The use of `pg:",inherit"` tag // is essential here so it inherits internal model properties such as table name. type BookWithCommentCount struct { Book `pg:",inherit"` CommentCount int } type Translation struct { tableName struct{} `sql:",alias:tr"` // custom table alias Id int BookId int `sql:"unique:book_id_lang"` Book *Book // has one relation Lang string `sql:"unique:book_id_lang"` Comments []Comment `pg:",polymorphic:trackable_"` // has many polymorphic relation } type Comment struct { TrackableId int // Book.Id or Translation.Id TrackableType string // "Book" or "Translation" Text string } func createTestSchema(db *pg.DB) error { models := []interface{}{ (*Image)(nil), (*Author)(nil), (*Book)(nil), (*Genre)(nil), (*BookGenre)(nil), (*Translation)(nil), (*Comment)(nil), } for _, model := range models { err := db.DropTable(model, &orm.DropTableOptions{ IfExists: true, Cascade: true, }) if err != nil { return err } err = db.CreateTable(model, nil) if err != nil { return err } } return nil } var _ = Describe("ORM", func() { var db *pg.DB BeforeEach(func() { db = pg.Connect(pgOptions()) err := createTestSchema(db) Expect(err).NotTo(HaveOccurred()) genres := []Genre{{ Id: 1, Name: "genre 1", }, { Id: 2, Name: "genre 2", }, { Id: 3, Name: "subgenre 1", ParentId: 1, }, { Id: 4, Name: "subgenre 2", ParentId: 1, }} err = db.Insert(&genres) Expect(err).NotTo(HaveOccurred()) Expect(genres).To(HaveLen(4)) images := []Image{{ Id: 1, Path: "/path/to/1.jpg", }, { Id: 2, Path: "/path/to/2.jpg", }, { Id: 3, Path: "/path/to/3.jpg", }} err = db.Insert(&images) Expect(err).NotTo(HaveOccurred()) Expect(images).To(HaveLen(3)) authors := []Author{{ ID: 10, Name: "author 1", AvatarId: images[0].Id, }, { ID: 11, Name: "author 2", AvatarId: images[1].Id, }, Author{ ID: 12, Name: "author 3", AvatarId: images[2].Id, }} err = db.Insert(&authors) Expect(err).NotTo(HaveOccurred()) Expect(authors).To(HaveLen(3)) books := []Book{{ Id: 100, Title: "book 1", AuthorID: 10, EditorID: 11, }, { Id: 101, Title: "book 2", AuthorID: 10, EditorID: 12, }, Book{ Id: 102, Title: "book 3", AuthorID: 11, EditorID: 11, }} err = db.Insert(&books) Expect(err).NotTo(HaveOccurred()) Expect(books).To(HaveLen(3)) for _, book := range books { Expect(book.CreatedAt).To(BeTemporally("~", time.Now(), time.Second)) } bookGenres := []BookGenre{{ BookId: 100, GenreId: 1, Genre_Rating: 999, }, { BookId: 100, GenreId: 2, Genre_Rating: 9999, }, { BookId: 101, GenreId: 1, Genre_Rating: 99999, }} err = db.Insert(&bookGenres) Expect(err).NotTo(HaveOccurred()) Expect(bookGenres).To(HaveLen(3)) translations := []Translation{{ Id: 1000, BookId: 100, Lang: "ru", }, { Id: 1001, BookId: 100, Lang: "md", }, { Id: 1002, BookId: 101, Lang: "ua", }} err = db.Insert(&translations) Expect(err).NotTo(HaveOccurred()) Expect(translations).To(HaveLen(3)) comments := []Comment{{ TrackableId: 100, TrackableType: "Book", Text: "comment1", }, { TrackableId: 100, TrackableType: "Book", Text: "comment2", }, { TrackableId: 1000, TrackableType: "Translation", Text: "comment3", }} err = db.Insert(&comments) Expect(err).NotTo(HaveOccurred()) Expect(comments).To(HaveLen(3)) }) AfterEach(func() { Expect(db.Close()).NotTo(HaveOccurred()) }) Describe("relation with no results", func() { It("does not panic", func() { tr := new(Translation) tr.Id = 123 err := db.Insert(tr) Expect(err).NotTo(HaveOccurred()) err = db.Model(tr). Relation("Book.Genres"). Relation("Book.Translations"). Relation("Book.Comments"). WherePK(). Select() Expect(err).NotTo(HaveOccurred()) }) }) Describe("struct model", func() { It("Select returns pg.ErrNoRows", func() { book := new(Book) err := db.Model(book). Where("1 = 2"). Select() Expect(err).To(Equal(pg.ErrNoRows)) }) It("Insert returns pg.ErrNoRows", func() { book := new(Book) err := db.Model(book).First() Expect(err).NotTo(HaveOccurred()) _, err = db.Model(book). OnConflict("DO NOTHING"). Returning("*"). Insert() Expect(err).To(Equal(pg.ErrNoRows)) }) It("Update returns pg.ErrNoRows", func() { book := new(Book) _, err := db.Model(book). Where("1 = 2"). Returning("*"). Update() Expect(err).To(Equal(pg.ErrNoRows)) }) It("Delete returns pg.ErrNoRows", func() { book := new(Book) _, err := db.Model(book). Where("1 = 2"). Returning("*"). Delete() Expect(err).To(Equal(pg.ErrNoRows)) }) It("fetches Book relations", func() { book := new(Book) err := db.Model(book). Column("book.id"). Relation("Author"). Relation("Author.Avatar"). Relation("Editor"). Relation("Editor.Avatar"). Relation("Genres"). Relation("Comments"). Relation("Translations"). Relation("Translations.Comments"). First() Expect(err).NotTo(HaveOccurred()) Expect(book).To(Equal(&Book{ Id: 100, Title: "", Author: Author{ ID: 10, Name: "author 1", AvatarId: 1, Avatar: Image{ Id: 1, Path: "/path/to/1.jpg", }, }, Editor: &Author{ ID: 11, Name: "author 2", AvatarId: 2, Avatar: Image{ Id: 2, Path: "/path/to/2.jpg", }, }, CreatedAt: time.Time{}, Genres: []Genre{ {Id: 1, Name: "genre 1", Rating: 999}, {Id: 2, Name: "genre 2", Rating: 9999}, }, Translations: []Translation{{ Id: 1000, BookId: 100, Lang: "ru", Comments: []Comment{ {TrackableId: 1000, TrackableType: "Translation", Text: "comment3"}, }, }, { Id: 1001, BookId: 100, Lang: "md", Comments: nil, }}, Comments: []Comment{ {TrackableId: 100, TrackableType: "Book", Text: "comment1"}, {TrackableId: 100, TrackableType: "Book", Text: "comment2"}, }, })) }) It("fetches Author relations", func() { var author Author err := db.Model(&author). Column("author.*"). Column("Books.id", "Books.author_id", "Books.editor_id"). Relation("Books.Author"). Relation("Books.Editor"). Relation("Books.Translations"). First() Expect(err).NotTo(HaveOccurred()) Expect(author).To(Equal(Author{ ID: 10, Name: "author 1", AvatarId: 1, Books: []*Book{{ Id: 100, Title: "", AuthorID: 10, Author: Author{ID: 10, Name: "author 1", AvatarId: 1}, EditorID: 11, Editor: &Author{ID: 11, Name: "author 2", AvatarId: 2}, CreatedAt: time.Time{}, Genres: nil, Translations: []Translation{ {Id: 1000, BookId: 100, Book: nil, Lang: "ru", Comments: nil}, {Id: 1001, BookId: 100, Book: nil, Lang: "md", Comments: nil}, }, }, { Id: 101, Title: "", AuthorID: 10, Author: Author{ID: 10, Name: "author 1", AvatarId: 1}, EditorID: 12, Editor: &Author{ID: 12, Name: "author 3", AvatarId: 3}, CreatedAt: time.Time{}, Genres: nil, Translations: []Translation{ {Id: 1002, BookId: 101, Book: nil, Lang: "ua", Comments: nil}, }, }}, })) }) It("fetches Genre relations", func() { var genre Genre err := db.Model(&genre). Column("genre.*"). Relation("Books.id"). Relation("Books.Translations"). First() Expect(err).NotTo(HaveOccurred()) Expect(genre).To(Equal(Genre{ Id: 1, Name: "genre 1", Rating: 0, Books: []Book{{ Id: 100, Translations: []Translation{ {Id: 1000, BookId: 100, Book: nil, Lang: "ru", Comments: nil}, {Id: 1001, BookId: 100, Book: nil, Lang: "md", Comments: nil}, }, }, { Id: 101, Translations: []Translation{ {Id: 1002, BookId: 101, Book: nil, Lang: "ua", Comments: nil}, }, }}, ParentId: 0, Subgenres: nil, })) }) It("fetches Translation relation", func() { var translation Translation err := db.Model(&translation). Column("tr.*"). Relation("Book.id"). Relation("Book.Author"). Relation("Book.Editor"). First() Expect(err).NotTo(HaveOccurred()) Expect(translation).To(Equal(Translation{ Id: 1000, BookId: 100, Book: &Book{ Id: 100, Author: Author{ID: 10, Name: "author 1", AvatarId: 1}, Editor: &Author{ID: 11, Name: "author 2", AvatarId: 2}, }, Lang: "ru", })) }) It("works when there are no results", func() { book := new(Book) err := db.Model(book). Column("book.*"). Relation("Author"). Relation("Genres"). Relation("Comments"). Where("1 = 2"). Select() Expect(err).To(Equal(pg.ErrNoRows)) }) It("supports overriding", func() { book := new(BookWithCommentCount) err := db.Model(book). Column("book.id"). Relation("Author"). Relation("Genres"). ColumnExpr(`(SELECT COUNT(*) FROM comments WHERE trackable_type = 'Book' AND trackable_id = book.id) AS comment_count`). First() Expect(err).NotTo(HaveOccurred()) Expect(book).To(Equal(&BookWithCommentCount{ Book: Book{ Id: 100, Author: Author{ID: 10, Name: "author 1", AvatarId: 1}, Genres: []Genre{ {Id: 1, Name: "genre 1", Rating: 999}, {Id: 2, Name: "genre 2", Rating: 9999}, }, }, CommentCount: 2, })) }) }) Describe("slice model", func() { It("fetches Book relations", func() { var books []Book err := db.Model(&books). Column("book.id"). Relation("Author"). Relation("Author.Avatar"). Relation("Editor"). Relation("Editor.Avatar"). Relation("Genres"). Relation("Comments"). Relation("Translations"). Relation("Translations.Comments"). OrderExpr("book.id ASC"). Select() Expect(err).NotTo(HaveOccurred()) Expect(books).To(Equal([]Book{{ Id: 100, Title: "", AuthorID: 0, Author: Author{ ID: 10, Name: "author 1", AvatarId: 1, Avatar: Image{ Id: 1, Path: "/path/to/1.jpg", }, }, EditorID: 0, Editor: &Author{ ID: 11, Name: "author 2", AvatarId: 2, Avatar: Image{ Id: 2, Path: "/path/to/2.jpg", }, }, Genres: []Genre{ {Id: 1, Name: "genre 1", Rating: 999}, {Id: 2, Name: "genre 2", Rating: 9999}, }, Translations: []Translation{{ Id: 1000, BookId: 100, Lang: "ru", Comments: []Comment{ {TrackableId: 1000, TrackableType: "Translation", Text: "comment3"}, }, }, { Id: 1001, BookId: 100, Lang: "md", Comments: nil, }}, Comments: []Comment{ {TrackableId: 100, TrackableType: "Book", Text: "comment1"}, {TrackableId: 100, TrackableType: "Book", Text: "comment2"}, }, }, { Id: 101, Title: "", AuthorID: 0, Author: Author{ ID: 10, Name: "author 1", AvatarId: 1, Avatar: Image{ Id: 1, Path: "/path/to/1.jpg", }, }, EditorID: 0, Editor: &Author{ ID: 12, Name: "author 3", AvatarId: 3, Avatar: Image{ Id: 3, Path: "/path/to/3.jpg", }, }, Genres: []Genre{ {Id: 1, Name: "genre 1", Rating: 99999}, }, Translations: []Translation{ {Id: 1002, BookId: 101, Lang: "ua"}, }, }, { Id: 102, Title: "", AuthorID: 0, Author: Author{ ID: 11, Name: "author 2", AvatarId: 2, Avatar: Image{ Id: 2, Path: "/path/to/2.jpg", }, }, EditorID: 0, Editor: &Author{ ID: 11, Name: "author 2", AvatarId: 2, Avatar: Image{ Id: 2, Path: "/path/to/2.jpg", }, }, }})) }) It("fetches Genre relations", func() { var genres []Genre err := db.Model(&genres). Column("genre.*"). Relation("Subgenres"). Relation("Books.id"). Relation("Books.Translations"). Where("genre.parent_id IS NULL"). OrderExpr("genre.id"). Select() Expect(err).NotTo(HaveOccurred()) Expect(genres).To(Equal([]Genre{{ Id: 1, Name: "genre 1", Rating: 0, Books: []Book{{ Id: 100, Translations: []Translation{ {Id: 1000, BookId: 100, Book: nil, Lang: "ru", Comments: nil}, {Id: 1001, BookId: 100, Book: nil, Lang: "md", Comments: nil}, }, }, { Id: 101, Translations: []Translation{ {Id: 1002, BookId: 101, Book: nil, Lang: "ua", Comments: nil}, }, }}, ParentId: 0, Subgenres: []Genre{ {Id: 3, Name: "subgenre 1", Rating: 0, Books: nil, ParentId: 1, Subgenres: nil}, {Id: 4, Name: "subgenre 2", Rating: 0, Books: nil, ParentId: 1, Subgenres: nil}, }, }, { Id: 2, Name: "genre 2", Rating: 0, Books: []Book{{ Id: 100, Translations: []Translation{ {Id: 1000, BookId: 100, Book: nil, Lang: "ru", Comments: nil}, {Id: 1001, BookId: 100, Book: nil, Lang: "md", Comments: nil}, }, }}, ParentId: 0, Subgenres: nil, }, })) }) It("fetches Translation relation", func() { var translations []Translation err := db.Model(&translations). Column("tr.*"). Relation("Book.id"). Relation("Book.Author"). Relation("Book.Editor"). Select() Expect(err).NotTo(HaveOccurred()) Expect(translations).To(Equal([]Translation{{ Id: 1000, BookId: 100, Book: &Book{ Id: 100, Author: Author{ID: 10, Name: "author 1", AvatarId: 1}, Editor: &Author{ID: 11, Name: "author 2", AvatarId: 2}, }, Lang: "ru", }, { Id: 1001, BookId: 100, Book: &Book{ Id: 100, Author: Author{ID: 10, Name: "author 1", AvatarId: 1}, Editor: &Author{ID: 11, Name: "author 2", AvatarId: 2}, }, Lang: "md", }, { Id: 1002, BookId: 101, Book: &Book{ Id: 101, Author: Author{ID: 10, Name: "author 1", AvatarId: 1}, Editor: &Author{ID: 12, Name: "author 3", AvatarId: 3}, }, Lang: "ua", }})) }) It("works when there are no results", func() { var books []Book err := db.Model(&books). Column("book.*"). Relation("Author"). Relation("Genres"). Relation("Comments"). Where("1 = 2"). Select() Expect(err).NotTo(HaveOccurred()) Expect(books).To(BeNil()) }) It("supports overriding", func() { var books []BookWithCommentCount err := db.Model(&books). Column("book.id"). Relation("Author"). Relation("Genres"). ColumnExpr(`(SELECT COUNT(*) FROM comments WHERE trackable_type = 'Book' AND trackable_id = book.id) AS comment_count`). OrderExpr("id ASC"). Select() Expect(err).NotTo(HaveOccurred()) Expect(books).To(Equal([]BookWithCommentCount{{ Book: Book{ Id: 100, Author: Author{ID: 10, Name: "author 1", AvatarId: 1}, Genres: []Genre{ {Id: 1, Name: "genre 1", Rating: 999}, {Id: 2, Name: "genre 2", Rating: 9999}, }, }, CommentCount: 2, }, { Book: Book{ Id: 101, Author: Author{ID: 10, Name: "author 1", AvatarId: 1}, Genres: []Genre{ {Id: 1, Name: "genre 1", Rating: 99999}, }, }, CommentCount: 0, }, { Book: Book{ Id: 102, Author: Author{ID: 11, Name: "author 2", AvatarId: 2}, }, CommentCount: 0, }})) }) }) Describe("fetches Book relations", func() { It("supports HasOne, HasMany, HasMany2Many", func() { var books []*Book err := db.Model(&books). Column("book.id"). Relation("Author"). Relation("Editor"). Relation("Translations"). Relation("Genres"). OrderExpr("book.id ASC"). Select() Expect(err).NotTo(HaveOccurred()) Expect(books).To(HaveLen(3)) }) It("fetches Genre relations", func() { var genres []*Genre err := db.Model(&genres). Column("genre.*"). Relation("Subgenres"). Relation("Books.id"). Relation("Books.Translations"). Where("genre.parent_id IS NULL"). OrderExpr("genre.id"). Select() Expect(err).NotTo(HaveOccurred()) Expect(genres).To(HaveLen(2)) }) It("fetches Translation relations", func() { var translations []*Translation err := db.Model(&translations). Column("tr.*"). Relation("Book.id"). Relation("Book.Author"). Relation("Book.Editor"). Select() Expect(err).NotTo(HaveOccurred()) Expect(translations).To(HaveLen(3)) }) It("works when there are no results", func() { var books []*Book err := db.Model(&books). Column("book.*"). Relation("Author"). Relation("Genres"). Relation("Comments"). Where("1 = 2"). Select() Expect(err).NotTo(HaveOccurred()) Expect(books).To(BeNil()) }) It("supports overriding", func() { var books []*BookWithCommentCount err := db.Model(&books). Column("book.id"). Relation("Author"). ColumnExpr(`(SELECT COUNT(*) FROM comments WHERE trackable_type = 'Book' AND trackable_id = book.id) AS comment_count`). OrderExpr("id ASC"). Select() Expect(err).NotTo(HaveOccurred()) Expect(books).To(HaveLen(3)) }) }) Describe("bulk insert", func() { It("returns an error if there is no data", func() { var books []Book err := db.Insert(&books) Expect(err).To(MatchError("pg: can't bulk-insert empty slice []pg_test.Book")) }) It("inserts books", func() { books := []Image{{ Id: 111, Path: "111.jpg", }, { Id: 222, Path: "222.jpg", }} _, err := db.Model(&books).Insert() Expect(err).NotTo(HaveOccurred()) Expect(len(books)).NotTo(BeZero()) }) }) Describe("bulk update", func() { It("returns an error if there is no data", func() { var books []Book _, err := db.Model(&books).Update() Expect(err).To(MatchError("pg: can't bulk-update empty slice []pg_test.Book")) }) It("updates books using Set", func() { var books []Book err := db.Model(&books).Order("id").Select() Expect(err).NotTo(HaveOccurred()) for i := range books { books[i].Title = fmt.Sprintf("censored %d", i) } _, err = db.Model(&books).Set("title = ?title").Update() Expect(err).NotTo(HaveOccurred()) Expect(len(books)).NotTo(BeZero()) books = nil err = db.Model(&books).Order("id").Select() Expect(err).NotTo(HaveOccurred()) for i := range books { Expect(books[i].Title).To(Equal(fmt.Sprintf("censored %d", i))) } }) It("updates books using Set expression", func() { books := []Book{{ Id: 100, Title: " suffix", }, { Id: 101, }} res, err := db.Model(&books). Set("title = book.title || COALESCE(_data.title, '')"). Update() Expect(err).NotTo(HaveOccurred()) Expect(res.RowsAffected()).To(Equal(2)) Expect(len(books)).NotTo(BeZero()) books = nil err = db.Model(&books).Column("id", "title").Order("id").Select() Expect(err).NotTo(HaveOccurred()) Expect(books).To(Equal([]Book{{ Id: 100, Title: "book 1 suffix", }, { Id: 101, Title: "book 2", }, { Id: 102, Title: "book 3", }})) }) It("updates books using Column", func() { var books []Book err := db.Model(&books).Order("id").Select() Expect(err).NotTo(HaveOccurred()) for i := range books { books[i].Title = fmt.Sprintf("censored %d", i) } _, err = db.Model(&books).Column("title").Update() Expect(err).NotTo(HaveOccurred()) Expect(len(books)).NotTo(BeZero()) books = nil err = db.Model(&books).Order("id").Select() Expect(err).NotTo(HaveOccurred()) for i := range books { Expect(books[i].Title).To(Equal(fmt.Sprintf("censored %d", i))) } }) }) Describe("bulk delete", func() { It("returns an error when slice is empty", func() { var books []Book _, err := db.Model(&books).Delete() Expect(err).To(MatchError("pg: Update and Delete queries require Where clause (try WherePK)")) }) It("deletes books", func() { var books []Book err := db.Model(&books).Order("id").Select() Expect(err).NotTo(HaveOccurred()) res, err := db.Model(&books).Delete() Expect(err).NotTo(HaveOccurred()) Expect(res.RowsAffected()).To(Equal(3)) books = make([]Book, 0) n, err := db.Model(&books).Count() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(0)) }) It("deletes ptrs of books", func() { var books []*Book err := db.Model(&books).Order("id").Select() Expect(err).NotTo(HaveOccurred()) res, err := db.Model(&books).Delete() Expect(err).NotTo(HaveOccurred()) Expect(res.RowsAffected()).To(Equal(3)) books = make([]*Book, 0) n, err := db.Model(&books).Count() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(0)) }) }) It("filters by HasOne", func() { var books []Book err := db.Model(&books). Column("book.id"). Relation("Author._"). Where("author.id = 10"). OrderExpr("book.id ASC"). Select() Expect(err).NotTo(HaveOccurred()) Expect(books).To(Equal([]Book{{ Id: 100, }, { Id: 101, }})) }) It("supports filtering HasMany", func() { var book Book err := db.Model(&book). Column("book.id"). Relation("Translations", func(q *orm.Query) (*orm.Query, error) { return q.Where("lang = 'ru'"), nil }). First() Expect(err).NotTo(HaveOccurred()) Expect(book).To(Equal(Book{ Id: 100, Translations: []Translation{ {Id: 1000, BookId: 100, Lang: "ru"}, }, })) }) It("supports filtering HasMany2Many", func() { var book Book err := db.Model(&book). Column("book.id"). Relation("Genres", func(q *orm.Query) (*orm.Query, error) { return q.Where("genre__rating > 999"), nil }). First() Expect(err).NotTo(HaveOccurred()) Expect(book).To(Equal(Book{ Id: 100, Genres: []Genre{ {Id: 2, Name: "genre 2", Rating: 9999}, }, })) }) It("deletes book returning title", func() { book := &Book{ Id: 100, } res, err := db.Model(book).WherePK().Returning("title").Delete() Expect(err).NotTo(HaveOccurred()) Expect(res.RowsAffected()).To(Equal(1)) Expect(book).To(Equal(&Book{ Id: 100, Title: "book 1", })) }) It("deletes books returning id", func() { var ids []int res, err := db.Model((*Book)(nil)).Where("TRUE").Returning("id").Delete(&ids) Expect(err).NotTo(HaveOccurred()) Expect(res.RowsAffected()).To(Equal(3)) Expect(ids).To(Equal([]int{100, 101, 102})) }) It("supports Exec & Query", func() { _, err := db.Model((*Book)(nil)).Exec("DROP TABLE ?TableName CASCADE") Expect(err).NotTo(HaveOccurred()) var num int _, err = db.Model(&Book{}).QueryOne(pg.Scan(&num), "SELECT 1 FROM ?TableName") Expect(err).To(MatchError(`ERROR #42P01 relation "books" does not exist`)) }) It("does not create zero model for null relation", func() { newBook := new(Book) err := db.Insert(newBook) Expect(err).NotTo(HaveOccurred()) book := new(Book) err = db.Model(book). Relation("Editor"). Where("book.id = ?", newBook.Id). Select() Expect(err).NotTo(HaveOccurred()) Expect(book.Editor).To(BeNil()) }) Describe("ForEach", func() { It("works with a struct ptr", func() { q := db.Model((*Book)(nil)). Order("id ASC") var books []Book err := q.Select(&books) Expect(err).NotTo(HaveOccurred()) var count int err = q.ForEach(func(b *Book) error { book := &books[count] Expect(book).To(Equal(b)) count++ return nil }) Expect(err).NotTo(HaveOccurred()) Expect(count).To(Equal(3)) }) It("works with a struct", func() { q := db.Model((*Book)(nil)). Order("id ASC") var books []Book err := q.Select(&books) Expect(err).NotTo(HaveOccurred()) var count int err = q.ForEach(func(b Book) error { book := &books[count] Expect(book).To(Equal(&b)) count++ return nil }) Expect(err).NotTo(HaveOccurred()) Expect(count).To(Equal(3)) }) It("works with a model", func() { q := db.Model((*Book)(nil)). Order("id ASC") var count int err := q.ForEach(func(_ orm.Discard) error { count++ return nil }) Expect(err).NotTo(HaveOccurred()) Expect(count).To(Equal(3)) }) It("works with scalars", func() { q := db.Model((*Book)(nil)). ColumnExpr("id, title"). Order("id ASC") var books []Book err := q.Select(&books) Expect(err).NotTo(HaveOccurred()) var count int err = q.ForEach(func(id int, title string) error { book := &books[count] Expect(id).To(Equal(book.Id)) Expect(title).To(Equal(book.Title)) count++ return nil }) Expect(err).NotTo(HaveOccurred()) Expect(count).To(Equal(3)) }) }) Describe("SelectAndCount", func() { It("selects and counts books", func() { var books []Book count, err := db.Model(&books).SelectAndCount() Expect(err).NotTo(HaveOccurred()) Expect(count).To(Equal(3)) Expect(books).To(HaveLen(3)) }) It("works with Limit=-1", func() { var books []Book count, err := db.Model(&books).Limit(-1).SelectAndCount() Expect(err).NotTo(HaveOccurred()) Expect(count).To(Equal(3)) Expect(books).To(HaveLen(0)) }) }) Describe("Exists", func() { It("returns true for existing rows", func() { var books []Book exists, err := db.Model(&books).Exists() Expect(err).NotTo(HaveOccurred()) Expect(exists).To(Equal(true)) Expect(books).To(HaveLen(0)) }) It("returns false otherwise", func() { var books []Book exists, err := db.Model(&books).Where("id = 0").Exists() Expect(err).NotTo(HaveOccurred()) Expect(exists).To(Equal(false)) Expect(books).To(HaveLen(0)) }) }) }) type SoftDeleteModel struct { Id int DeletedAt time.Time `pg:",soft_delete"` } var _testDB *pg.DB func testDB() *pg.DB { if _testDB == nil { _testDB = pg.Connect(pgOptions()) } return _testDB } var _ = Describe("soft deletes", func() { var db *pg.DB BeforeEach(func() { db = testDB() err := db.CreateTable((*SoftDeleteModel)(nil), &orm.CreateTableOptions{ Temp: true, }) Expect(err).NotTo(HaveOccurred()) model := &SoftDeleteModel{ Id: 1, } err = db.Insert(model) Expect(err).NotTo(HaveOccurred()) err = db.Delete(model) Expect(err).NotTo(HaveOccurred()) }) AfterEach(func() { err := db.DropTable((*SoftDeleteModel)(nil), nil) Expect(err).NotTo(HaveOccurred()) }) It("soft deletes the model", func() { model := new(SoftDeleteModel) err := db.Model(model).Select() Expect(err).To(Equal(pg.ErrNoRows)) n, err := db.Model((*SoftDeleteModel)(nil)).Count() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(0)) }) It("Deleted allows to select the model", func() { model := new(SoftDeleteModel) err := db.Model(model).Deleted().Select() Expect(err).NotTo(HaveOccurred()) Expect(model.Id).To(Equal(1)) Expect(model.DeletedAt).To(BeTemporally("~", time.Now(), 3*time.Second)) n, err := db.Model((*SoftDeleteModel)(nil)).Deleted().Count() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(1)) }) Describe("ForceDelete", func() { BeforeEach(func() { model := &SoftDeleteModel{ Id: 1, } err := db.ForceDelete(model) Expect(err).NotTo(HaveOccurred()) }) It("deletes the model", func() { model := new(SoftDeleteModel) err := db.Model(model).Deleted().Select() Expect(err).To(Equal(pg.ErrNoRows)) n, err := db.Model((*SoftDeleteModel)(nil)).Deleted().Count() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(0)) }) }) })