Add sqly for handle sql dependency between package
This commit is contained in:
parent
9212318c23
commit
51c3ab87d9
94
shows.go
94
shows.go
@ -2,38 +2,63 @@ package shows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/sqly"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/users"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/odwrtw/polochon/lib"
|
||||
)
|
||||
|
||||
const showsCreate = `
|
||||
CREATE TABLE shows (
|
||||
id SERIAL PRIMARY KEY,
|
||||
imdbid text NOT NULL UNIQUE,
|
||||
title text NOT NULL,
|
||||
updated timestamp DEFAULT current_timestamp,
|
||||
created timestamp DEFAULT current_timestamp
|
||||
);
|
||||
`
|
||||
var Schema = sqly.Schema{
|
||||
Require: []sqly.Schema{
|
||||
users.Schema,
|
||||
},
|
||||
Tables: []sqly.SchemaTable{
|
||||
sqly.SchemaTable{
|
||||
Name: "shows",
|
||||
Sql: `
|
||||
CREATE TABLE shows (
|
||||
id SERIAL PRIMARY KEY,
|
||||
imdbid text NOT NULL UNIQUE,
|
||||
title text NOT NULL,
|
||||
updated timestamp DEFAULT current_timestamp,
|
||||
created timestamp DEFAULT current_timestamp
|
||||
);
|
||||
`},
|
||||
sqly.SchemaTable{
|
||||
Name: "episodes",
|
||||
Sql: `
|
||||
CREATE TABLE episodes (
|
||||
id SERIAL PRIMARY KEY,
|
||||
shows_id integer REFERENCES shows (id) ON DELETE CASCADE,
|
||||
title text NOT NULL,
|
||||
season integer NOT NULL,
|
||||
episode integer NOT NULL,
|
||||
updated timestamp DEFAULT current_timestamp,
|
||||
created timestamp DEFAULT current_timestamp
|
||||
);
|
||||
`},
|
||||
},
|
||||
Drop: `
|
||||
DROP TABLE episodes;
|
||||
DROP TABLE shows;
|
||||
`,
|
||||
}
|
||||
|
||||
const (
|
||||
addShowQuery = `INSERT INTO shows (imdbid, title) VALUES (:imdbid, :title) RETURNING id;`
|
||||
getShowQuery = `SELECT * FROM shows WHERE imdbid=$1;`
|
||||
deleteShowQuery = `DELETE FROM shows WHERE id=$1;`
|
||||
|
||||
addEpisodeQuery = `INSERT INTO episodes (shows_id, title, season, episode) VALUES ($1,$2,$3,$4);`
|
||||
getEpisodesQuery = `SELECT title, season, episode FROM episodes WHERE shows_id=$1;`
|
||||
)
|
||||
|
||||
// BaseTable have to be embeded in all your struct which reflect a table
|
||||
type BaseTable struct {
|
||||
Updated time.Time
|
||||
Created time.Time
|
||||
}
|
||||
|
||||
type Show struct {
|
||||
ID int
|
||||
sqly.BaseTable
|
||||
polochon.Show
|
||||
BaseTable
|
||||
Episodes []*Episode
|
||||
}
|
||||
|
||||
func Get(db *sqlx.DB, imdbID string) (*Show, error) {
|
||||
@ -55,6 +80,23 @@ func (s *Show) Add(db *sqlx.DB) error {
|
||||
r.Scan(&id)
|
||||
}
|
||||
s.ID = id
|
||||
|
||||
// When add a show to database use polochon episode details
|
||||
// so s.Show.Episodes
|
||||
for _, pEp := range s.Show.Episodes {
|
||||
err = s.addEpisode(db, pEp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Show) addEpisode(db *sqlx.DB, pEpisode *polochon.ShowEpisode) error {
|
||||
_, err := db.Exec(addEpisodeQuery, s.ID, pEpisode.Title, pEpisode.Season, pEpisode.Episode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -69,3 +111,19 @@ func (s *Show) Delete(db *sqlx.DB) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Show) GetEpisodes(db *sqlx.DB) error {
|
||||
// When retrive episode's info from database populate the s.Episodes member
|
||||
// and not s.Show.Episodes
|
||||
err := db.Select(&s.Episodes, getEpisodesQuery, s.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Episode struct {
|
||||
sqly.BaseTable
|
||||
polochon.ShowEpisode
|
||||
}
|
||||
|
@ -1,22 +1,19 @@
|
||||
package shows
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/sqltest"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/sqly"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/odwrtw/polochon/lib"
|
||||
)
|
||||
|
||||
const drop = `
|
||||
DROP TABLE shows;
|
||||
`
|
||||
|
||||
const showNFO1 = `
|
||||
<tvshow>
|
||||
<title>Marvel's Jessica Jones</title>
|
||||
@ -32,6 +29,43 @@ const showNFO1 = `
|
||||
</tvshow>
|
||||
`
|
||||
|
||||
const showNFO1E1 = `
|
||||
<episodedetails>
|
||||
<title>AKA Ladies Night</title>
|
||||
<showtitle>Marvel's Jessica Jones</showtitle>
|
||||
<season>1</season>
|
||||
<episode>1</episode>
|
||||
<uniqueid>5311261</uniqueid>
|
||||
<aired>2015-11-20</aired>
|
||||
<premiered>2015-11-20</premiered>
|
||||
<plot>Jessica Jones is hired to find a pretty NYU student who's vanished, but it turns out to be more than a simple missing persons case.</plot>
|
||||
<runtime>60</runtime>
|
||||
<thumb>http://thetvdb.com/banners/episodes/284190/5311261.jpg</thumb>
|
||||
<rating>7.5</rating>
|
||||
<showimdbid>tt2357547</showimdbid>
|
||||
<showtvdbid>284190</showtvdbid>
|
||||
<episodeimdbid>tt4162058</episodeimdbid>
|
||||
</episodedetails>
|
||||
`
|
||||
const showNFO1E2 = `
|
||||
<episodedetails>
|
||||
<title>AKA Crush Syndrome </title>
|
||||
<showtitle>Marvel's Jessica Jones</showtitle>
|
||||
<season>1</season>
|
||||
<episode>2</episode>
|
||||
<uniqueid>5311262</uniqueid>
|
||||
<aired>2015-11-20</aired>
|
||||
<premiered>2015-11-20</premiered>
|
||||
<plot>Jessica vows to prove Hope's innocence, even though it means tracking down a terrifying figure from her own past.</plot>
|
||||
<runtime>60</runtime>
|
||||
<thumb>http://thetvdb.com/banners/episodes/284190/5311262.jpg</thumb>
|
||||
<rating>7.7</rating>
|
||||
<showimdbid>tt2357547</showimdbid>
|
||||
<showtvdbid>284190</showtvdbid>
|
||||
<episodeimdbid>tt4162062</episodeimdbid>
|
||||
</episodedetails>
|
||||
`
|
||||
|
||||
var db *sqlx.DB
|
||||
|
||||
func init() {
|
||||
@ -44,20 +78,34 @@ func init() {
|
||||
fmt.Printf("Unavailable PG tests:\n %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = sqly.InitDB(db)
|
||||
if err != nil {
|
||||
fmt.Printf("Unavailable PG tests:\n %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddRemoveShow(t *testing.T) {
|
||||
sqltest.RunWithSchema(db, showsCreate, drop, t, func(db *sqlx.DB, t *testing.T) {
|
||||
sqly.RunWithSchema(db, Schema, t, func(db *sqlx.DB, t *testing.T) {
|
||||
nfo := strings.NewReader(showNFO1)
|
||||
s := &polochon.Show{}
|
||||
polochon.ReadNFO(nfo, s)
|
||||
|
||||
nfo = strings.NewReader(showNFO1E1)
|
||||
ep1 := &polochon.ShowEpisode{}
|
||||
polochon.ReadNFO(nfo, ep1)
|
||||
|
||||
nfo = strings.NewReader(showNFO1E2)
|
||||
ep2 := &polochon.ShowEpisode{}
|
||||
polochon.ReadNFO(nfo, ep2)
|
||||
|
||||
s.Episodes = append(s.Episodes, ep1)
|
||||
s.Episodes = append(s.Episodes, ep2)
|
||||
|
||||
err := polochon.ReadNFO(nfo, s)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
show := Show{Show: *s}
|
||||
|
||||
err = show.Add(db)
|
||||
err := show.Add(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -67,6 +115,14 @@ func TestAddRemoveShow(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = show1.GetEpisodes(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(show1.Episodes) != 2 {
|
||||
t.Fatalf("Unexpected number of episodes: %d", len(show1.Episodes))
|
||||
}
|
||||
|
||||
err = show1.Delete(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -76,7 +132,7 @@ func TestAddRemoveShow(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("We expect an error here, the show didn't exist anymore")
|
||||
}
|
||||
if err.Error() != "sql: no rows in result set" {
|
||||
if err != sql.ErrNoRows {
|
||||
t.Fatalf("Unexpected error: %q", err)
|
||||
}
|
||||
if show2 != nil {
|
||||
|
115
sqly/sqly.go
Normal file
115
sqly/sqly.go
Normal file
@ -0,0 +1,115 @@
|
||||
package sqly
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
const initDBQuery = `
|
||||
CREATE OR REPLACE FUNCTION update_modified_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN NEW.updated = now(); RETURN NEW; END; $$ language 'plpgsql';
|
||||
`
|
||||
|
||||
// InitDB add some function to the database and template
|
||||
func InitDB(db *sqlx.DB) error {
|
||||
err := MultiExec(db, initDBQuery)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BaseTable have to be embeded in all your struct which reflect a table
|
||||
type BaseTable struct {
|
||||
ID int
|
||||
Updated time.Time
|
||||
Created time.Time
|
||||
}
|
||||
|
||||
type SchemaTable struct {
|
||||
Name string
|
||||
Sql string
|
||||
}
|
||||
|
||||
type Schema struct {
|
||||
// Create contains all tables, the key is the name and the
|
||||
// value the sql
|
||||
Tables []SchemaTable
|
||||
|
||||
// Drop contains the drop sql
|
||||
Drop string
|
||||
|
||||
// Require add schema before create and delete after
|
||||
Require []Schema
|
||||
}
|
||||
|
||||
func (s Schema) Create(db *sqlx.DB) error {
|
||||
for _, sch := range s.Require {
|
||||
err := sch.Create(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, table := range s.Tables {
|
||||
_, err := db.Exec(table.Sql)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s\n%s", err, table.Sql)
|
||||
}
|
||||
trigger := fmt.Sprintf("CREATE TRIGGER update_%s BEFORE UPDATE ON %s FOR EACH ROW EXECUTE PROCEDURE update_modified_column();", table.Name, table.Name)
|
||||
_, err = db.Exec(trigger)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s\n%s", err, trigger)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s Schema) Delete(db *sqlx.DB) error {
|
||||
for _, sch := range s.Require {
|
||||
err := sch.Delete(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err := MultiExec(db, s.Drop)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func MultiExec(e sqlx.Execer, query string) error {
|
||||
stmts := strings.Split(query, ";\n")
|
||||
if len(strings.Trim(stmts[len(stmts)-1], " \n\t\r")) == 0 {
|
||||
stmts = stmts[:len(stmts)-1]
|
||||
}
|
||||
for _, s := range stmts {
|
||||
_, err := e.Exec(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s\n%s", err, s)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func RunWithSchema(db *sqlx.DB, schema Schema, t *testing.T, test func(db *sqlx.DB, t *testing.T)) {
|
||||
defer func() {
|
||||
err := schema.Delete(db)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
err := schema.Create(db)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err.Error())
|
||||
}
|
||||
|
||||
test(db, t)
|
||||
}
|
@ -2,35 +2,41 @@ package users
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/random"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/sqly"
|
||||
)
|
||||
|
||||
const usersCreate = `
|
||||
CREATE TABLE users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name text NOT NULL UNIQUE,
|
||||
updated timestamp DEFAULT current_timestamp,
|
||||
created timestamp DEFAULT current_timestamp
|
||||
);
|
||||
var Schema = sqly.Schema{
|
||||
Tables: []sqly.SchemaTable{
|
||||
sqly.SchemaTable{
|
||||
Name: "users",
|
||||
Sql: `
|
||||
CREATE TABLE users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name text NOT NULL UNIQUE,
|
||||
updated timestamp DEFAULT current_timestamp,
|
||||
created timestamp DEFAULT current_timestamp
|
||||
);`},
|
||||
sqly.SchemaTable{
|
||||
Name: "tokens",
|
||||
Sql: `
|
||||
CREATE TABLE tokens (
|
||||
id SERIAL,
|
||||
value text NOT NULL UNIQUE,
|
||||
users_id integer REFERENCES users (id) ON DELETE CASCADE,
|
||||
updated timestamp DEFAULT current_timestamp,
|
||||
created timestamp DEFAULT current_timestamp
|
||||
);
|
||||
`},
|
||||
},
|
||||
Drop: `
|
||||
DROP TABLE tokens;
|
||||
DROP TABLE users;
|
||||
`,
|
||||
}
|
||||
|
||||
CREATE TABLE tokens (
|
||||
id SERIAL,
|
||||
value text NOT NULL UNIQUE,
|
||||
users_id integer REFERENCES users (id) ON DELETE CASCADE,
|
||||
updated timestamp DEFAULT current_timestamp,
|
||||
created timestamp DEFAULT current_timestamp
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_modified_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN NEW.updated = now(); RETURN NEW; END; $$ language 'plpgsql';
|
||||
|
||||
CREATE TRIGGER update_users BEFORE UPDATE ON users FOR EACH ROW EXECUTE PROCEDURE update_modified_column();
|
||||
CREATE TRIGGER update_tokens BEFORE UPDATE ON tokens FOR EACH ROW EXECUTE PROCEDURE update_modified_column();
|
||||
`
|
||||
const (
|
||||
addUserQuery = `INSERT INTO users (name) VALUES ($1) RETURNING id;`
|
||||
getUserQuery = `SELECT * FROM users WHERE name=$1;`
|
||||
@ -43,23 +49,15 @@ const (
|
||||
deleteTokenQuery = `DELETE FROM tokens WHERE users_id=$1 AND value=$2;`
|
||||
)
|
||||
|
||||
// BaseTable have to be embeded in all your struct which reflect a table
|
||||
type BaseTable struct {
|
||||
Updated time.Time
|
||||
Created time.Time
|
||||
}
|
||||
|
||||
// User represents an user
|
||||
type User struct {
|
||||
BaseTable
|
||||
ID int
|
||||
sqly.BaseTable
|
||||
Name string
|
||||
}
|
||||
|
||||
// Token represents a token
|
||||
type Token struct {
|
||||
BaseTable
|
||||
ID int
|
||||
sqly.BaseTable
|
||||
Value string
|
||||
}
|
||||
|
||||
|
@ -5,19 +5,12 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/sqltest"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/sqly"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
const drop = `
|
||||
DROP TABLE tokens;
|
||||
DROP TABLE users;
|
||||
|
||||
DROP FUNCTION update_modified_column();
|
||||
`
|
||||
|
||||
var db *sqlx.DB
|
||||
|
||||
func init() {
|
||||
@ -30,10 +23,16 @@ func init() {
|
||||
fmt.Printf("Unavailable PG tests:\n %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = sqly.InitDB(db)
|
||||
if err != nil {
|
||||
fmt.Printf("Unavailable PG tests:\n %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUser(t *testing.T) {
|
||||
sqltest.RunWithSchema(db, usersCreate, drop, t, func(db *sqlx.DB, t *testing.T) {
|
||||
sqly.RunWithSchema(db, Schema, t, func(db *sqlx.DB, t *testing.T) {
|
||||
|
||||
// Add a new user
|
||||
u := &User{Name: "plop"}
|
||||
@ -101,7 +100,7 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTokenAddDelete(t *testing.T) {
|
||||
sqltest.RunWithSchema(db, usersCreate, drop, t, func(db *sqlx.DB, t *testing.T) {
|
||||
sqly.RunWithSchema(db, Schema, t, func(db *sqlx.DB, t *testing.T) {
|
||||
// Add a new user
|
||||
u := &User{Name: "plop"}
|
||||
err := u.Add(db)
|
||||
@ -161,7 +160,7 @@ func TestTokenAddDelete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTokenCheck(t *testing.T) {
|
||||
sqltest.RunWithSchema(db, usersCreate, drop, t, func(db *sqlx.DB, t *testing.T) {
|
||||
sqly.RunWithSchema(db, Schema, t, func(db *sqlx.DB, t *testing.T) {
|
||||
u := &User{Name: "plop"}
|
||||
u.Add(db)
|
||||
token, err := u.NewToken(db)
|
||||
@ -187,7 +186,7 @@ func TestTokenCheck(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAutoUpdateCols(t *testing.T) {
|
||||
sqltest.RunWithSchema(db, usersCreate, drop, t, func(db *sqlx.DB, t *testing.T) {
|
||||
sqly.RunWithSchema(db, Schema, t, func(db *sqlx.DB, t *testing.T) {
|
||||
u := &User{Name: "plop"}
|
||||
u.Add(db)
|
||||
u.Name = "toto"
|
||||
|
Loading…
x
Reference in New Issue
Block a user