589 lines
15 KiB
Go
589 lines
15 KiB
Go
//
|
|
// orm_test.go
|
|
// Copyright (C) 2023 tiglog <me@tiglog.com>
|
|
//
|
|
// Distributed under terms of the MIT license.
|
|
//
|
|
|
|
package orm_test
|
|
|
|
import (
|
|
"database/sql"
|
|
"testing"
|
|
|
|
"git.hexq.cn/tiglog/golib/gdb/orm"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
type AuthorEmail struct {
|
|
ID int64
|
|
Email string
|
|
}
|
|
|
|
func (a AuthorEmail) ConfigureEntity(e *orm.EntityConfigurator) {
|
|
e.
|
|
Table("emails").
|
|
Connection("default").
|
|
BelongsTo(&Post{}, orm.BelongsToConfig{})
|
|
}
|
|
|
|
type HeaderPicture struct {
|
|
ID int64
|
|
PostID int64
|
|
Link string
|
|
}
|
|
|
|
func (h HeaderPicture) ConfigureEntity(e *orm.EntityConfigurator) {
|
|
e.Table("header_pictures").BelongsTo(&Post{}, orm.BelongsToConfig{})
|
|
}
|
|
|
|
type Post struct {
|
|
ID int64
|
|
BodyText string
|
|
CreatedAt sql.NullTime
|
|
UpdatedAt sql.NullTime
|
|
DeletedAt sql.NullTime
|
|
}
|
|
|
|
func (p Post) ConfigureEntity(e *orm.EntityConfigurator) {
|
|
e.Field("BodyText").ColumnName("body")
|
|
e.Field("ID").ColumnName("id")
|
|
e.
|
|
Table("posts").
|
|
HasMany(Comment{}, orm.HasManyConfig{}).
|
|
HasOne(HeaderPicture{}, orm.HasOneConfig{}).
|
|
HasOne(AuthorEmail{}, orm.HasOneConfig{}).
|
|
BelongsToMany(Category{}, orm.BelongsToManyConfig{IntermediateTable: "post_categories"})
|
|
|
|
}
|
|
|
|
func (p *Post) Categories() ([]Category, error) {
|
|
return orm.BelongsToMany[Category](p).All()
|
|
}
|
|
|
|
func (p *Post) Comments() *orm.QueryBuilder[Comment] {
|
|
return orm.HasMany[Comment](p)
|
|
}
|
|
|
|
type Comment struct {
|
|
ID int64
|
|
PostID int64
|
|
Body string
|
|
}
|
|
|
|
func (c Comment) ConfigureEntity(e *orm.EntityConfigurator) {
|
|
e.Table("comments").BelongsTo(&Post{}, orm.BelongsToConfig{})
|
|
}
|
|
|
|
func (c *Comment) Post() (Post, error) {
|
|
return orm.BelongsTo[Post](c).Get()
|
|
}
|
|
|
|
type Category struct {
|
|
ID int64
|
|
Title string
|
|
}
|
|
|
|
func (c Category) ConfigureEntity(e *orm.EntityConfigurator) {
|
|
e.Table("categories").BelongsToMany(Post{}, orm.BelongsToManyConfig{IntermediateTable: "post_categories"})
|
|
}
|
|
|
|
func (c Category) Posts() ([]Post, error) {
|
|
return orm.BelongsToMany[Post](c).All()
|
|
}
|
|
|
|
// enough models let's test
|
|
// Entities is mandatory
|
|
// Errors should be carried
|
|
|
|
func setup() error {
|
|
db, err := sql.Open("sqlite3", ":memory:")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY, body text, created_at TIMESTAMP, updated_at TIMESTAMP, deleted_at TIMESTAMP)`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS emails (id INTEGER PRIMARY KEY, post_id INTEGER, email text)`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS header_pictures (id INTEGER PRIMARY KEY, post_id INTEGER, link text)`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS comments (id INTEGER PRIMARY KEY, post_id INTEGER, body text)`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS categories (id INTEGER PRIMARY KEY, title text)`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS post_categories (post_id INTEGER, category_id INTEGER, PRIMARY KEY(post_id, category_id))`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return orm.SetupConnections(orm.ConnectionConfig{
|
|
Name: "default",
|
|
DB: db,
|
|
Dialect: orm.Dialects.SQLite3,
|
|
Entities: []orm.Entity{&Post{}, &Comment{}, &Category{}, &HeaderPicture{}},
|
|
DatabaseValidations: true,
|
|
})
|
|
}
|
|
|
|
func TestFind(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
err = orm.InsertAll(&Post{
|
|
BodyText: "my body for insert",
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
post, err := orm.Find[Post](1)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "my body for insert", post.BodyText)
|
|
assert.Equal(t, int64(1), post.ID)
|
|
}
|
|
|
|
func TestInsert(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
post := &Post{
|
|
BodyText: "my body for insert",
|
|
}
|
|
err = orm.Insert(post)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(1), post.ID)
|
|
var p Post
|
|
assert.NoError(t,
|
|
orm.GetConnection("default").DB.QueryRow(`SELECT id, body FROM posts where id = ?`, 1).Scan(&p.ID, &p.BodyText))
|
|
|
|
assert.Equal(t, "my body for insert", p.BodyText)
|
|
}
|
|
func TestInsertAll(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
post1 := &Post{
|
|
BodyText: "Body1",
|
|
}
|
|
post2 := &Post{
|
|
BodyText: "Body2",
|
|
}
|
|
|
|
post3 := &Post{
|
|
BodyText: "Body3",
|
|
}
|
|
|
|
err = orm.InsertAll(post1, post2, post3)
|
|
assert.NoError(t, err)
|
|
var counter int
|
|
assert.NoError(t, orm.GetConnection("default").DB.QueryRow(`SELECT count(id) FROM posts`).Scan(&counter))
|
|
assert.Equal(t, 3, counter)
|
|
|
|
}
|
|
func TestUpdateORM(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
post := &Post{
|
|
BodyText: "my body for insert",
|
|
}
|
|
err = orm.Insert(post)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(1), post.ID)
|
|
|
|
post.BodyText += " update text"
|
|
assert.NoError(t, orm.Update(post))
|
|
|
|
var body string
|
|
assert.NoError(t,
|
|
orm.GetConnection("default").DB.QueryRow(`SELECT body FROM posts where id = ?`, post.ID).Scan(&body))
|
|
|
|
assert.Equal(t, "my body for insert update text", body)
|
|
}
|
|
|
|
func TestDeleteORM(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
post := &Post{
|
|
BodyText: "my body for insert",
|
|
}
|
|
err = orm.Insert(post)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(1), post.ID)
|
|
|
|
assert.NoError(t, orm.Delete(post))
|
|
|
|
var count int
|
|
assert.NoError(t,
|
|
orm.GetConnection("default").DB.QueryRow(`SELECT count(id) FROM posts where id = ?`, post.ID).Scan(&count))
|
|
|
|
assert.Equal(t, 0, count)
|
|
}
|
|
func TestAdd_HasMany(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
post := &Post{
|
|
BodyText: "my body for insert",
|
|
}
|
|
err = orm.Insert(post)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(1), post.ID)
|
|
|
|
err = orm.Add(post, []orm.Entity{
|
|
Comment{
|
|
Body: "comment 1",
|
|
},
|
|
Comment{
|
|
Body: "comment 2",
|
|
},
|
|
}...)
|
|
// orm.Query(qm.WhereBetween())
|
|
assert.NoError(t, err)
|
|
var count int
|
|
assert.NoError(t, orm.GetConnection("default").DB.QueryRow(`SELECT COUNT(id) FROM comments`).Scan(&count))
|
|
assert.Equal(t, 2, count)
|
|
|
|
comment, err := orm.Find[Comment](1)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, int64(1), comment.PostID)
|
|
|
|
}
|
|
|
|
func TestAdd_ManyToMany(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
post := &Post{
|
|
BodyText: "my body for insert",
|
|
}
|
|
err = orm.Insert(post)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(1), post.ID)
|
|
|
|
err = orm.Add(post, []orm.Entity{
|
|
&Category{
|
|
Title: "cat 1",
|
|
},
|
|
&Category{
|
|
Title: "cat 2",
|
|
},
|
|
}...)
|
|
assert.NoError(t, err)
|
|
var count int
|
|
assert.NoError(t, orm.GetConnection("default").DB.QueryRow(`SELECT COUNT(post_id) FROM post_categories`).Scan(&count))
|
|
assert.Equal(t, 2, count)
|
|
assert.NoError(t, orm.GetConnection("default").DB.QueryRow(`SELECT COUNT(id) FROM categories`).Scan(&count))
|
|
assert.Equal(t, 2, count)
|
|
|
|
categories, err := post.Categories()
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, 2, len(categories))
|
|
assert.Equal(t, int64(1), categories[0].ID)
|
|
assert.Equal(t, int64(2), categories[1].ID)
|
|
|
|
}
|
|
|
|
func TestSave(t *testing.T) {
|
|
t.Run("save should insert", func(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
post := &Post{
|
|
BodyText: "1",
|
|
}
|
|
assert.NoError(t, orm.Save(post))
|
|
assert.Equal(t, int64(1), post.ID)
|
|
})
|
|
|
|
t.Run("save should update", func(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
|
|
post := &Post{
|
|
BodyText: "1",
|
|
}
|
|
assert.NoError(t, orm.Save(post))
|
|
assert.Equal(t, int64(1), post.ID)
|
|
|
|
post.BodyText += "2"
|
|
assert.NoError(t, orm.Save(post))
|
|
|
|
myPost, err := orm.Find[Post](1)
|
|
assert.NoError(t, err)
|
|
|
|
assert.EqualValues(t, post.BodyText, myPost.BodyText)
|
|
})
|
|
|
|
}
|
|
|
|
func TestHasMany(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
|
|
post := &Post{
|
|
BodyText: "first post",
|
|
}
|
|
assert.NoError(t, orm.Save(post))
|
|
assert.Equal(t, int64(1), post.ID)
|
|
|
|
assert.NoError(t, orm.Save(&Comment{
|
|
PostID: post.ID,
|
|
Body: "comment 1",
|
|
}))
|
|
assert.NoError(t, orm.Save(&Comment{
|
|
PostID: post.ID,
|
|
Body: "comment 2",
|
|
}))
|
|
|
|
comments, err := orm.HasMany[Comment](post).All()
|
|
assert.NoError(t, err)
|
|
|
|
assert.Len(t, comments, 2)
|
|
|
|
assert.Equal(t, post.ID, comments[0].PostID)
|
|
assert.Equal(t, post.ID, comments[1].PostID)
|
|
}
|
|
|
|
func TestBelongsTo(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
|
|
post := &Post{
|
|
BodyText: "first post",
|
|
}
|
|
assert.NoError(t, orm.Save(post))
|
|
assert.Equal(t, int64(1), post.ID)
|
|
|
|
comment := &Comment{
|
|
PostID: post.ID,
|
|
Body: "comment 1",
|
|
}
|
|
assert.NoError(t, orm.Save(comment))
|
|
|
|
post2, err := orm.BelongsTo[Post](comment).Get()
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, post.BodyText, post2.BodyText)
|
|
}
|
|
|
|
func TestHasOne(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
|
|
post := &Post{
|
|
BodyText: "first post",
|
|
}
|
|
assert.NoError(t, orm.Save(post))
|
|
assert.Equal(t, int64(1), post.ID)
|
|
|
|
headerPicture := &HeaderPicture{
|
|
PostID: post.ID,
|
|
Link: "google",
|
|
}
|
|
assert.NoError(t, orm.Save(headerPicture))
|
|
|
|
c1, err := orm.HasOne[HeaderPicture](post).Get()
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, headerPicture.PostID, c1.PostID)
|
|
}
|
|
|
|
func TestBelongsToMany(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
|
|
post := &Post{
|
|
BodyText: "first Post",
|
|
}
|
|
|
|
assert.NoError(t, orm.Save(post))
|
|
assert.Equal(t, int64(1), post.ID)
|
|
|
|
category := &Category{
|
|
Title: "first category",
|
|
}
|
|
assert.NoError(t, orm.Save(category))
|
|
assert.Equal(t, int64(1), category.ID)
|
|
|
|
_, _, err = orm.ExecRaw[Category](`INSERT INTO post_categories (post_id, category_id) VALUES (?,?)`, post.ID, category.ID)
|
|
assert.NoError(t, err)
|
|
|
|
categories, err := orm.BelongsToMany[Category](post).All()
|
|
assert.NoError(t, err)
|
|
|
|
assert.Len(t, categories, 1)
|
|
}
|
|
|
|
func TestSchematic(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
|
|
orm.Schematic()
|
|
}
|
|
|
|
func TestAddProperty(t *testing.T) {
|
|
t.Run("having pk value", func(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
|
|
post := &Post{
|
|
BodyText: "first post",
|
|
}
|
|
|
|
assert.NoError(t, orm.Save(post))
|
|
assert.EqualValues(t, 1, post.ID)
|
|
|
|
err = orm.Add(post, &Comment{PostID: post.ID, Body: "firstComment"})
|
|
assert.NoError(t, err)
|
|
|
|
var comment Comment
|
|
assert.NoError(t, orm.GetConnection("default").
|
|
DB.
|
|
QueryRow(`SELECT id, post_id, body FROM comments WHERE post_id=?`, post.ID).
|
|
Scan(&comment.ID, &comment.PostID, &comment.Body))
|
|
|
|
assert.EqualValues(t, post.ID, comment.PostID)
|
|
})
|
|
t.Run("not having PK value", func(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
|
|
post := &Post{
|
|
BodyText: "first post",
|
|
}
|
|
assert.NoError(t, orm.Save(post))
|
|
assert.EqualValues(t, 1, post.ID)
|
|
|
|
err = orm.Add(post, &AuthorEmail{Email: "myemail"})
|
|
assert.NoError(t, err)
|
|
|
|
emails, err := orm.QueryRaw[AuthorEmail](`SELECT id, email FROM emails WHERE post_id=?`, post.ID)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, []AuthorEmail{{ID: 1, Email: "myemail"}}, emails)
|
|
})
|
|
}
|
|
|
|
func TestQuery(t *testing.T) {
|
|
t.Run("querying single row", func(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
|
|
assert.NoError(t, orm.Save(&Post{BodyText: "body 1"}))
|
|
// post, err := orm.Query[Post]().Where("id", 1).First()
|
|
post, err := orm.Query[Post]().WherePK(1).First().Get()
|
|
assert.NoError(t, err)
|
|
assert.EqualValues(t, "body 1", post.BodyText)
|
|
assert.EqualValues(t, 1, post.ID)
|
|
|
|
})
|
|
t.Run("querying multiple rows", func(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
|
|
assert.NoError(t, orm.Save(&Post{BodyText: "body 1"}))
|
|
assert.NoError(t, orm.Save(&Post{BodyText: "body 2"}))
|
|
assert.NoError(t, orm.Save(&Post{BodyText: "body 3"}))
|
|
posts, err := orm.Query[Post]().All()
|
|
assert.NoError(t, err)
|
|
assert.Len(t, posts, 3)
|
|
assert.Equal(t, "body 1", posts[0].BodyText)
|
|
})
|
|
|
|
t.Run("updating a row using query interface", func(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
|
|
assert.NoError(t, orm.Save(&Post{BodyText: "body 1"}))
|
|
|
|
affected, err := orm.Query[Post]().Where("id", 1).Set("body", "body jadid").Update()
|
|
assert.NoError(t, err)
|
|
assert.EqualValues(t, 1, affected)
|
|
|
|
post, err := orm.Find[Post](1)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "body jadid", post.BodyText)
|
|
})
|
|
|
|
t.Run("deleting a row using query interface", func(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
|
|
assert.NoError(t, orm.Save(&Post{BodyText: "body 1"}))
|
|
|
|
affected, err := orm.Query[Post]().WherePK(1).Delete()
|
|
assert.NoError(t, err)
|
|
assert.EqualValues(t, 1, affected)
|
|
count, err := orm.Query[Post]().WherePK(1).Count().Get()
|
|
assert.NoError(t, err)
|
|
assert.EqualValues(t, 0, count)
|
|
})
|
|
|
|
t.Run("count", func(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
|
|
count, err := orm.Query[Post]().WherePK(1).Count().Get()
|
|
assert.NoError(t, err)
|
|
assert.EqualValues(t, 0, count)
|
|
})
|
|
|
|
t.Run("latest", func(t *testing.T) {
|
|
err := setup()
|
|
assert.NoError(t, err)
|
|
|
|
assert.NoError(t, orm.Save(&Post{BodyText: "body 1"}))
|
|
assert.NoError(t, orm.Save(&Post{BodyText: "body 2"}))
|
|
|
|
post, err := orm.Query[Post]().Latest().Get()
|
|
assert.NoError(t, err)
|
|
|
|
assert.EqualValues(t, "body 2", post.BodyText)
|
|
})
|
|
}
|
|
|
|
func TestSetup(t *testing.T) {
|
|
t.Run("tables are out of sync", func(t *testing.T) {
|
|
db, err := sql.Open("sqlite3", ":memory:")
|
|
// _, err = db.Exec(`CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY, body text, created_at TIMESTAMP, updated_at TIMESTAMP, deleted_at TIMESTAMP)`)
|
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS emails (id INTEGER PRIMARY KEY, post_id INTEGER, email text)`)
|
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS header_pictures (id INTEGER PRIMARY KEY, post_id INTEGER, link text)`)
|
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS comments (id INTEGER PRIMARY KEY, post_id INTEGER, body text)`)
|
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS categories (id INTEGER PRIMARY KEY, title text)`)
|
|
// _, err = db.Exec(`CREATE TABLE IF NOT EXISTS post_categories (post_id INTEGER, category_id INTEGER, PRIMARY KEY(post_id, category_id))`)
|
|
|
|
err = orm.SetupConnections(orm.ConnectionConfig{
|
|
Name: "default",
|
|
DB: db,
|
|
Dialect: orm.Dialects.SQLite3,
|
|
Entities: []orm.Entity{&Post{}, &Comment{}, &Category{}, &HeaderPicture{}},
|
|
DatabaseValidations: true,
|
|
})
|
|
assert.Error(t, err)
|
|
|
|
})
|
|
t.Run("schemas are wrong", func(t *testing.T) {
|
|
db, err := sql.Open("sqlite3", ":memory:")
|
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY, body text, created_at TIMESTAMP, updated_at TIMESTAMP, deleted_at TIMESTAMP)`)
|
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS emails (id INTEGER PRIMARY KEY, post_id INTEGER, email text)`)
|
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS header_pictures (id INTEGER PRIMARY KEY, post_id INTEGER, link text)`)
|
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS comments (id INTEGER PRIMARY KEY, body text)`) // missing post_id
|
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS categories (id INTEGER PRIMARY KEY, title text)`)
|
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS post_categories (post_id INTEGER, category_id INTEGER, PRIMARY KEY(post_id, category_id))`)
|
|
|
|
err = orm.SetupConnections(orm.ConnectionConfig{
|
|
Name: "default",
|
|
DB: db,
|
|
Dialect: orm.Dialects.SQLite3,
|
|
Entities: []orm.Entity{&Post{}, &Comment{}, &Category{}, &HeaderPicture{}},
|
|
DatabaseValidations: true,
|
|
})
|
|
assert.Error(t, err)
|
|
|
|
})
|
|
}
|