Go-Back-Skeleton/vendor/github.com/go-pg/pg/db_test.go
2017-09-25 20:20:52 +02:00

1529 lines
36 KiB
Go

package pg_test
import (
"bytes"
"crypto/tls"
"database/sql"
"fmt"
"net"
"testing"
"time"
"github.com/go-pg/pg"
"github.com/go-pg/pg/orm"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
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,
PoolTimeout: 30 * time.Second,
IdleTimeout: 10 * time.Second,
MaxAge: 10 * time.Second,
IdleCheckFrequency: 100 * time.Millisecond,
}
}
func TestDBString(t *testing.T) {
db := pg.Connect(pgOptions())
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.DB) error {
_, err := db.Exec("SET application_name = 'myapp'")
return err
}
db := pg.Connect(opt)
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())
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() {
_, err := db.Query(pg.Discard, "SELECT 1 WHERE 1 = 2")
Expect(err).NotTo(HaveOccurred())
})
It("does not return an error when there are no results", func() {
_, err := tx.Query(pg.Discard, "SELECT 1 WHERE 1 = 2")
Expect(err).NotTo(HaveOccurred())
})
})
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))
})
It("returns pg.ErrNoRows when there are no results", func() {
_, 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() {
_, err := db.Exec("SELECT 1 WHERE 1 = 2")
Expect(err).NotTo(HaveOccurred())
})
It("does not return an error when there are no results", func() {
_, err := tx.Exec("SELECT 1 WHERE 1 = 2")
Expect(err).NotTo(HaveOccurred())
})
})
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))
})
It("returns pg.ErrNoRows when there are no results", func() {
_, err := tx.ExecOne("SELECT 1 WHERE 1 = 2")
Expect(err).To(Equal(pg.ErrNoRows))
})
})
})
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.Local)},
{"2001-02-03 04:05:06.000001", time.Date(2001, time.February, 3, 4, 5, 6, 1000, time.Local)},
{"2001-02-03 04:05:06.00001", time.Date(2001, time.February, 3, 4, 5, 6, 10000, time.Local)},
{"2001-02-03 04:05:06.0001", time.Date(2001, time.February, 3, 4, 5, 6, 100000, time.Local)},
{"2001-02-03 04:05:06.001", time.Date(2001, time.February, 3, 4, 5, 6, 1000000, time.Local)},
{"2001-02-03 04:05:06.01", time.Date(2001, time.February, 3, 4, 5, 6, 10000000, time.Local)},
{"2001-02-03 04:05:06.1", time.Date(2001, time.February, 3, 4, 5, 6, 100000000, time.Local)},
{"2001-02-03 04:05:06.12", time.Date(2001, time.February, 3, 4, 5, 6, 120000000, time.Local)},
{"2001-02-03 04:05:06.123", time.Date(2001, time.February, 3, 4, 5, 6, 123000000, time.Local)},
{"2001-02-03 04:05:06.1234", time.Date(2001, time.February, 3, 4, 5, 6, 123400000, time.Local)},
{"2001-02-03 04:05:06.12345", time.Date(2001, time.February, 3, 4, 5, 6, 123450000, time.Local)},
{"2001-02-03 04:05:06.123456", time.Date(2001, time.February, 3, 4, 5, 6, 123456000, time.Local)},
{"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("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() {
var ints []int
_, err := db.Query(&ints, "SELECT generate_series(1, 0)")
Expect(err).NotTo(HaveOccurred())
Expect(ints).To(BeZero())
})
It("does not error when there are no rows", func() {
var slice []value
_, err := db.Query(&slice, "SELECT generate_series(1, 0)")
Expect(err).NotTo(HaveOccurred())
Expect(slice).To(BeZero())
})
It("does not error when there are no rows", func() {
var slice []*value
_, err := db.Query(&slice, "SELECT generate_series(1, 0)")
Expect(err).NotTo(HaveOccurred())
Expect(slice).To(BeZero())
})
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(&times, `
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.FreeConns).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" (addr="127.0.0.1:5432")`))
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.FreeConns).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())
})
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() {
err := db.Close()
Expect(err).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())
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())
}
})
AfterEach(func() {
err := db.Close()
Expect(err).NotTo(HaveOccurred())
})
It("selects bytea", func() {
var col []byte
err := db.Model().Table("tests").Column("col").Select(pg.Scan(&col))
Expect(err).NotTo(HaveOccurred())
})
})
var _ = Describe("DB.Insert", func() {
var db *pg.DB
BeforeEach(func() {
db = pg.Connect(pgOptions())
})
AfterEach(func() {
err := db.Close()
Expect(err).NotTo(HaveOccurred())
})
It("returns an error on nil", func() {
err := db.Insert(nil)
Expect(err).To(MatchError("pg: Model(nil)"))
})
It("returns an errors if value is not settable", func() {
err := db.Insert(1)
Expect(err).To(MatchError("pg: Model(non-pointer int)"))
})
It("returns an errors 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() {
err := db.Close()
Expect(err).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(`model=Test does not have primary keys`))
})
})
var _ = Describe("DB.Delete", func() {
var db *pg.DB
BeforeEach(func() {
db = pg.Connect(pgOptions())
})
AfterEach(func() {
err := db.Close()
Expect(err).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(`model=Test does not have primary keys`))
})
})
var _ = Describe("errors", func() {
var db *pg.DB
BeforeEach(func() {
db = pg.Connect(pgOptions())
})
AfterEach(func() {
err := db.Close()
Expect(err).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"))
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"` // fk specifies prefix for foreign key (ParentId)
}
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
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(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:",override"` tag
// is essential here and it overrides internal model properties such as table name.
type BookWithCommentCount struct {
Book `pg:",override"`
CommentCount int
}
type Translation struct {
tableName struct{} `sql:",alias:tr"` // custom table alias
Id int
BookId int
Book *Book // has one relation
Lang string
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 {
tables := []interface{}{
&Image{},
&Author{},
&Book{},
&Genre{},
&BookGenre{},
&Translation{},
&Comment{},
}
for _, table := range tables {
err := db.DropTable(table, &orm.DropTableOptions{
IfExists: true,
Cascade: true,
})
if err != nil {
return err
}
err = db.CreateTable(table, 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))
})
It("multi updates", 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))
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",
}}))
})
Describe("struct model", func() {
It("fetches Book relations", func() {
var book Book
err := db.Model(&book).
Column(
"book.id",
"Author", "Author.Avatar", "Editor", "Editor.Avatar",
"Genres", "Comments", "Translations", "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.*",
"Books.id", "Books.author_id", "Books.editor_id",
"Books.Author", "Books.Editor",
"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.*", "Books.id", "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.*", "Book.id", "Book.Author", "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() {
var book Book
err := db.Model(&book).
Column("book.*", "Author", "Genres", "Comments").
Where("1 = 2").
Select()
Expect(err).To(Equal(pg.ErrNoRows))
})
It("supports overriding", func() {
var book BookWithCommentCount
err := db.Model(&book).
Column("book.id", "Author", "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",
"Author", "Author.Avatar", "Editor", "Editor.Avatar",
"Genres", "Comments", "Translations", "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.*", "Subgenres", "Books.id", "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.*", "Book.id", "Book.Author", "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.*", "Author", "Genres", "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", "Author", "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", "Author", "Editor", "Translations", "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.*", "Subgenres", "Books.id", "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.*", "Book.id", "Book.Author", "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.*", "Author", "Genres", "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", "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))
})
})
It("filters by HasOne", func() {
var books []Book
err := db.Model(&books).
Column("book.id", "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", "Translations").
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", "Genres").
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).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{}).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{}).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 (addr="127.0.0.1:5432")`))
})
})