ã€ã³ã¿ãŒãã§ã€ã¹ã«åºã¥ãGolangããŒã¿ããŒã¹ã¯ã©ã€ã¢ã³ããžã§ãã¬ãŒã¿ãŒ ã

ããŒã¿ããŒã¹ãæäœããããã«ãGolangã¯database/sql
ããã±ãŒãžãæäŸãdatabase/sql
ãããã¯ããªã¬ãŒã·ã§ãã«ããŒã¿ããŒã¹ããã°ã©ãã³ã°ã€ã³ã¿ãŒãã§ã€ã¹ã®æœè±¡åã§ãã äžæ¹ã§ã¯ãããã±ãŒãžã«ã¯ãæ¥ç¶ããŒã«ã®ç®¡çãæºåæžã¿ã¹ããŒãã¡ã³ãããã©ã³ã¶ã¯ã·ã§ã³ãããã³ããŒã¿ããŒã¹ã¯ãšãªã€ã³ã¿ãŒãã§ã€ã¹ã®æäœã®ããã®åŒ·åãªæ©èœãå«ãŸããŠããŸãã äžæ¹ãããŒã¿ããŒã¹ãšããåãããããã«ã¯ãWebã¢ããªã±ãŒã·ã§ã³ã«åãã¿ã€ãã®ã³ãŒããçžåœéæžãå¿
èŠããããŸãã go-gad / salã©ã€ãã©ãªã¯ã説æãããŠããã€ã³ã¿ãŒãã§ã€ã¹ã«åºã¥ããŠåãã¿ã€ãã®ã³ãŒããçæãããšãã圢ã§ãœãªã¥ãŒã·ã§ã³ãæäŸããŸãã
ããæ°
çŸåšãORMã®åœ¢åŒã§ãœãªã¥ãŒã·ã§ã³ãæäŸããååãªæ°ã®ã©ã€ãã©ãªãã¯ãšãªãæ§ç¯ããããã®ãã«ããŒãããŒã¿ããŒã¹ã¹ããŒãã«åºã¥ããŠãã«ããŒãçæããã©ã€ãã©ãªããããŸãã
æ°å¹Žåã«Golangèšèªã«åãæ¿ãããšããç§ã¯ãã§ã«ããŸããŸãªèšèªã®ããŒã¿ããŒã¹ãæäœããçµéšããããŸããã ActiveRecordãªã©ã®ORMã䜿çšããå Žåãšäœ¿çšããªãå Žåã æããæãã¿ãžãšé²ã¿ãæ°è¡ã®ã³ãŒãã远å ããããšã§åé¡ãªããGolangã®ããŒã¿ããŒã¹ãšå¯Ÿè©±ããããšã§ããªããžããªãã¿ãŒã³ã«äŒŒããã®ãæãä»ããŸããã ããŒã¿ããŒã¹ãæäœããã€ã³ã¿ãŒãã§ã€ã¹ã«ã€ããŠèª¬æããæšæºã®db.Queryãrow.Scanã䜿çšããŠå®è£
ããŸãã 远å ã®ã©ãããŒã䜿çšããã®ã¯æå³ããããŸããã§ãããäžéæã§ã泚æãåèµ·ããŸãã
SQLèšèªèªäœã¯ããã§ã«ããã°ã©ã ãšãªããžããªå
ã®ããŒã¿ã®éã®æœè±¡åã§ãã ããŒã¿ã¹ããŒã ãèšè¿°ããŠãããè€éãªã¯ãšãªãäœæããããšããããšã¯ãåžžã«éè«ççã«æããŸããã ãã®å Žåã®å¿çæ§é ã¯ãããŒã¿ã¹ããŒã ãšã¯ç°ãªããŸãã å¥çŽã¯ãããŒã¿ã¹ããŒãã¬ãã«ã§ã¯ãªããèŠæ±ããã³å¿çã¬ãã«ã§èšè¿°ããå¿
èŠãããããšãããããŸããã APIãªã¯ãšã¹ããšã¬ã¹ãã³ã¹ã®ããŒã¿æ§é ã説æãããšããWebéçºã§ãã®ã¢ãããŒãã䜿çšããŸãã RESTful JSONãŸãã¯gRPCã䜿çšããŠãµãŒãã¹ã«ã¢ã¯ã»ã¹ããå ŽåããµãŒãã¹å
ã®ãšã³ãã£ãã£ã®ããŒã¿ã¹ããŒãã§ã¯ãªããJSONã¹ããŒããŸãã¯Protobufã䜿çšããŠèŠæ±ããã³å¿çã¬ãã«ã§ã³ã³ãã©ã¯ãã宣èšããŸãã
ã€ãŸããããŒã¿ããŒã¹ãšã®å¯Ÿè©±ã¯ãåæ§ã®æ¹æ³ã«ãªããŸããã
type User struct { ID int64 Name string } type Store interface { FindUser(id int64) (*User, error) } type Postgres struct { DB *sql.DB } func (pg *Postgres) FindUser(id int64) (*User, error) { var resp User err := pg.DB.QueryRow("SELECT id, name FROM users WHERE id=$1", id).Scan(&resp.ID, &resp.Name) if err != nil { return nil, err } return &resp, nil } func HanlderFindUser(s Store, id int) (*User, error) {
ããã«ãããããã°ã©ã ãäºæž¬å¯èœã«ãªããŸãã ããããæ£çŽã«èšã£ãŠãããã¯è©©äººã®å€¢ã§ã¯ãããŸããã ãã€ã©ãŒãã¬ãŒãã³ãŒãã®éãæžãããŠãã¯ãšãªãæ§æããããŒã¿æ§é ã«ããŒã¿ã远å ãã倿°ãã€ã³ãã£ã³ã°ã䜿çšããŸãã ç®çã®ãŠãŒãã£ãªãã£ã»ãããæºããã¹ãèŠä»¶ã®ãªã¹ããäœæããããšããŸããã
å¿
èŠæ¡ä»¶
- ã€ã³ã¿ãŒãã§ã€ã¹ã®åœ¢ã§ã®çžäºäœçšã®èª¬æã
- ã€ã³ã¿ãŒãã§ãŒã¹ã¯ãèŠæ±ãšå¿çã®ã¡ãœãããšã¡ãã»ãŒãžã«ãã£ãŠèšè¿°ãããŸãã
- ãã€ã³ã倿°ãšæºåæžã¿ã¹ããŒãã¡ã³ãã®ãµããŒãã
- ååä»ãåŒæ°ã®ãµããŒãã
- ããŒã¿ããŒã¹å¿çãã¡ãã»ãŒãžããŒã¿æ§é ã®ãã£ãŒã«ãã«ãªã³ã¯ããŸãã
- éå®åããŒã¿æ§é ïŒé
åãjsonïŒã®ãµããŒãã
- ãã©ã³ã¶ã¯ã·ã§ã³ã䜿çšããééçãªäœæ¥ã
- ããã«ãŠã§ã¢ã®ãã€ãã£ããµããŒãã
ã€ã³ã¿ãŒãã§ã€ã¹ã䜿çšããŠãããŒã¿ããŒã¹ãšã®å¯Ÿè©±ã®å®è£
ãæœè±¡åããŸãã ããã«ããããªããžããªãªã©ã®èšèšãã¿ãŒã³ã«äŒŒããã®ãå®è£
ã§ããŸãã äžèšã®äŸã§ã¯ãStoreã€ã³ã¿ãŒãã§ãŒã¹ã«ã€ããŠèª¬æããŸããã ããã§ãäŸåé¢ä¿ãšããŠäœ¿çšã§ããŸãã ãã¹ã段éã§ã¯ããã®ã€ã³ã¿ãŒãã§ã€ã¹ã«åºã¥ããŠçæãããã¹ã¿ããæž¡ãããšãã§ãã補åã§ã¯Postgresæ§é ã«åºã¥ããå®è£
ã䜿çšããŸãã
åã€ã³ã¿ãŒãã§ã€ã¹ã¡ãœããã¯ã1ã€ã®ããŒã¿ããŒã¹ã¯ãšãªãèšè¿°ããŸãã ã¡ãœããã®å
¥åããã³åºåãã©ã¡ãŒã¿ãŒã¯ãèŠæ±ã®ã³ã³ãã©ã¯ãã®äžéšã§ããå¿
èŠããããŸãã ã¯ãšãªæååã¯ãå
¥åãã©ã¡ãŒã¿ã«å¿ããŠãã©ãŒãããã§ããå¿
èŠããããŸãã ããã¯ãè€éãªãµã³ããªã³ã°æ¡ä»¶ã§ã¯ãšãªãã³ã³ãã€ã«ããå Žåã«ç¹ã«åœãŠã¯ãŸããŸãã
ã¯ãšãªãã³ã³ãã€ã«ãããšãã眮æãšå€æ°ãã€ã³ãã£ã³ã°ã䜿çšããŸãã ããšãã°ãPostgreSQLã§ã¯ãå€ã®ä»£ããã«$1
ãèšè¿°ããã¯ãšãªãšãšãã«åŒæ°ã®é
åãæž¡ããŸãã æåã®åŒæ°ã¯ã倿ãããã¯ãšãªã®å€ãšããŠäœ¿çšãããŸãã æºåãããåŒã®ãµããŒãã«ããããããã®åãåŒã®ã¹ãã¬ãŒãžã®æŽçã«ã€ããŠå¿é
ããå¿
èŠããªããªããŸãã ããŒã¿ããŒã¹/ SQLã©ã€ãã©ãªã¯ãæºåãããåŒããµããŒãããããã®åŒ·åãªããŒã«ãæäŸããããèªäœãæ¥ç¶ããŒã«ãéããããæ¥ç¶ãåŠçããŸãã ãã ãããŠãŒã¶ãŒåŽã§ã¯ãæºåãããåŒããã©ã³ã¶ã¯ã·ã§ã³ã§åå©çšããããã«è¿œå ã®ã¢ã¯ã·ã§ã³ãå¿
èŠã§ãã
PostgreSQLãMySQLãªã©ã®ããŒã¿ããŒã¹ã¯ã眮æãšå€æ°ãã€ã³ãã£ã³ã°ã䜿çšããããã«ç°ãªãæ§æã䜿çšããŸãã PostgreSQLã¯$1
ã $2
ã...ãšãã圢åŒã䜿çšã?
å€ã®å Žæã«é¢ä¿ãªãã ããŒã¿ããŒã¹/ SQLã©ã€ãã©ãªã¯ãååä»ãåŒæ°https://golang.org/pkg/database/sql/#NamedArgã®æ±çšåœ¢åŒãææ¡ããŸããã 䜿çšäŸïŒ
db.ExecContext(ctx, `DELETE FROM orders WHERE created_at < @end`, sql.Named("end", endTime))
ãã®åœ¢åŒã®ãµããŒãã¯ãPostgreSQLãŸãã¯MySQLãœãªã¥ãŒã·ã§ã³ãšæ¯èŒããŠäœ¿çšããããšããå§ãããŸãã
ãœãããŠã§ã¢ãã©ã€ããŒãåŠçããããŒã¿ããŒã¹ããã®å¿çã¯ã次ã®ããã«æ¡ä»¶ä»ãã§è¡šãããšãã§ããŸãã
dev > SELECT * FROM rubrics; id | created_at | title | url
ã€ã³ã¿ãŒãã§ã€ã¹ã¬ãã«ã§ã®ãŠãŒã¶ãŒã®èгç¹ãããåºåãã©ã¡ãŒã¿ãŒã次ã®åœ¢åŒã®æ§é äœã®é
åãšããŠèšè¿°ãããšäŸ¿å©ã§ãã
type GetRubricsResp struct { ID int CreatedAt time.Time Title string URL string }
次ã«ã resp.ID
ãªã©ã«id
å€ãresp.ID
ããŸãã äžè¬ã«ããã®æ©èœã¯ã»ãšãã©ã®ããŒãºã«å¯Ÿå¿ããŸãã
å
éšããŒã¿æ§é ãä»ããŠã¡ãã»ãŒãžã宣èšãããšããéæšæºã®ããŒã¿åããµããŒãããæ¹æ³ã®åé¡ãçããŸãã ããšãã°ãé
åã PostgreSQLã§äœæ¥ãããšãã«github.com/lib/pqãã©ã€ããŒã䜿çšããå Žåãã¯ãšãªåŒæ°ãæž¡ããšãããŸãã¯å¿çãã¹ãã£ã³ãããšãã«pq.Array(&x)
ãªã©ã®è£å©é¢æ°ã䜿çšã§ããŸãã ããã¥ã¡ã³ãã®äŸïŒ
db.Query(`SELECT * FROM t WHERE id = ANY($1)`, pq.Array([]int{235, 401})) var x []sql.NullInt64 db.QueryRow('SELECT ARRAY[235, 401]').Scan(pq.Array(&x))
ãããã£ãŠãããŒã¿æ§é ãæºåããæ¹æ³ãå¿
èŠã§ãã
ã€ã³ã¿ãŒãã§ãŒã¹ã¡ãœããã®ãããããå®è¡ããå ŽåãããŒã¿ããŒã¹æ¥ç¶ã¯*sql.DB
圢åŒã§äœ¿çšã§ã*sql.DB
ã åäžã®ãã©ã³ã¶ã¯ã·ã§ã³å
ã§è€æ°ã®ã¡ãœãããå®è¡ããå¿
èŠãããå Žåã¯ã远å ã®åŒæ°ãæž¡ãã®ã§ã¯ãªãããã©ã³ã¶ã¯ã·ã§ã³ã®å€éšã§ã®äœæ¥ãšåæ§ã®ã¢ãããŒãã§ééçãªæ©èœã䜿çšããŸãã
ã€ã³ã¿ãŒãã§ã€ã¹å®è£
ã䜿çšããå ŽåãããŒã«ããããçµã¿èŸŒãããšãéèŠã§ãã ããšãã°ããã¹ãŠã®ãªã¯ãšã¹ããèšé²ããŸãã ããŒã«ãããã¯ãèŠæ±å€æ°ãå¿çãšã©ãŒãã©ã³ã¿ã€ã ãã€ã³ã¿ãŒãã§ã€ã¹ã¡ãœããåã«ã¢ã¯ã»ã¹ããå¿
èŠããããŸãã
ã»ãšãã©ã®å ŽåãèŠä»¶ã¯ãããŒã¿ããŒã¹ãæäœããããã®ã·ããªãªã®äœç³»åãšããŠçå®ãããŸããã
解決çïŒgo-gad / sal
å®åã³ãŒããåŠçãã1ã€ã®æ¹æ³ã¯ããããçæããããšã§ãã 幞ããGolangã«ã¯ãã®https://blog.golang.org/generateã®ããŒã«ãšäŸããããŸã ã GoMockã®https://github.com/golang/mockã¢ãããŒãã¯ãã€ã³ã¿ãŒãã§ãŒã¹åæããªãã¬ã¯ã·ã§ã³ã䜿çšããŠå®è¡ãããäžä»£ã®ã¢ãŒããã¯ãã£ãœãªã¥ãŒã·ã§ã³ãšããŠæ¡çšãããŸããã ãã®ã¢ãããŒãã«åºã¥ããŠãèŠä»¶ã«åŸã£ãŠãã€ã³ã¿ãŒãã§ã€ã¹å®è£
ã³ãŒããçæããäžé£ã®è£å©æ©èœãæäŸããsalgenãŠãŒãã£ãªãã£ãšsalã©ã€ãã©ãªãäœæãããŸããã
ãã®ãœãªã¥ãŒã·ã§ã³ã®äœ¿çšãéå§ããã«ã¯ãããŒã¿ããŒã¹ãšã®å¯Ÿè©±å±€ã®åäœãèšè¿°ããã€ã³ã¿ãŒãã§ã€ã¹ãèšè¿°ããå¿
èŠããããŸãã åŒæ°ã®ã»ããã§go:generate
ãã£ã¬ã¯ãã£ããæå®ãã go:generate
ãéå§ããŸãã ã³ã³ã¹ãã©ã¯ã¿ãŒãšäžé£ã®ãã€ã©ãŒãã¬ãŒãã³ãŒããçšæãããŠãããããã«äœ¿çšã§ããŸãã
package repo import "context"
ã€ã³ã¿ãŒãã§ãŒã¹
ãã¹ãŠã¯ãã€ã³ã¿ãŒãã§ã€ã¹ãšgo generate
ãŠãŒãã£ãªãã£ã®ç¹å¥ãªã³ãã³ãã宣èšããããšããå§ãŸããŸãã
ããã§ã¯ã Store
ã€ã³ã¿ãŒãã§ãŒã¹ã®å Žåãã³ã³ãœãŒã«ãŠãŒãã£ãªãã£salgen
ãããã±ãŒãžããåŒã³åºããã2ã€ã®ãªãã·ã§ã³ãš2ã€ã®åŒæ°ãããããšã説æããŸãã æåã®ãªãã·ã§ã³-destination
ã¯ãçæãããã³ãŒããæžã蟌ãŸãããã¡ã€ã«ã決å®ããŸãã 2çªç®ã®ãªãã·ã§ã³-package
ã¯ãçæãããå®è£
ã®ã©ã€ãã©ãªã®ãã«ãã¹ïŒã€ã³ããŒããã¹ïŒãå®çŸ©ããŸãã 以äžã¯2ã€ã®åŒæ°ã§ãã æåã¯ã€ã³ã¿ãŒãã§ãŒã¹ãé
眮ãããŠããå®å
šãªããã±ãŒãžãã¹ïŒ github.com/go-gad/sal/examples/profile/storage
ïŒãèšè¿°ãã2çªç®ã¯ã€ã³ã¿ãŒãã§ãŒã¹åèªäœã瀺ããŸãã go generate
ã®ã³ãã³ãã¯ã©ãã«ã§ãé
眮ã§ããããšã«æ³šæããŠãã ãããå¿
ãããã¿ãŒã²ããã€ã³ã¿ãŒãã§ã€ã¹ã®æšªã«ããå¿
èŠã¯ãããŸããã
go generate
ã³ãã³ããå®è¡ããåŸã New
ãã¬ãã£ãã¯ã¹ãã€ã³ã¿ãŒãã§ã€ã¹åã«è¿œå ããããšã§ååãäœæãããã³ã³ã¹ãã©ã¯ã¿ãŒãååŸããŸãã ã³ã³ã¹ãã©ã¯ã¿ãŒã¯ã sal.QueryHandler
ã€ã³ã¿ãŒãã§ãŒã¹ã«å¯Ÿå¿ããå¿
é ãã©ã¡ãŒã¿ãŒãsal.QueryHandler
ãŸãã
type QueryHandler interface { QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) }
ãã®ã€ã³ã¿ãŒãã§ã€ã¹ã¯ã *sql.DB
ãªããžã§ã¯ãã«å¯Ÿå¿ããŸãã
connStr := "user=pqgotest dbname=pqgotest sslmode=verify-full" db, err := sql.Open("postgres", connStr) client := storage.NewStore(db)
æ¹æ³
ã€ã³ã¿ãŒãã§ã€ã¹ã¡ãœããã¯ã䜿çšå¯èœãªããŒã¿ããŒã¹ã¯ãšãªã®ã»ãããæ±ºå®ããŸãã
type Store interface { CreateAuthor(ctx context.Context, req CreateAuthorReq) (CreateAuthorResp, error) GetAuthors(ctx context.Context, req GetAuthorsReq) ([]*GetAuthorsResp, error) UpdateAuthor(ctx context.Context, req *UpdateAuthorReq) error }
- åŒæ°ã®æ°ã¯åžžã«å³å¯ã«2ã§ãã
- æåã®åŒæ°ã¯ã³ã³ããã¹ãã§ãã
- 2çªç®ã®åŒæ°ã«ã¯ã倿°ããã€ã³ãããããã®ããŒã¿ãå«ãŸããã¯ãšãªæååãå®çŸ©ããŸãã
- æåã®åºåãã©ã¡ãŒã¿ãŒã¯ããªããžã§ã¯ãããªããžã§ã¯ãã®é
åããŸãã¯äžåšã§ãã
- æåŸã®åºåãã©ã¡ãŒã¿ãŒã¯åžžã«ãšã©ãŒã§ãã
æåã®åŒæ°ã¯åžžã«context.Context
ãªããžã§ã¯ãã§ãã ãã®ã³ã³ããã¹ãã¯ãããŒã¿ããŒã¹ãšããŒã«ããããåŒã³åºããšãã«æž¡ãããŸãã 2çªç®ã®åŒæ°ã«ã¯ãåºæ¬åstruct
ïŒãŸãã¯struct
ãžã®ãã€ã³ã¿ãŒïŒãæã€ãã©ã¡ãŒã¿ãŒãå¿
èŠã§ãã ãã©ã¡ãŒã¿ãŒã¯ã次ã®ã€ã³ã¿ãŒãã§ãŒã¹ãæºããå¿
èŠããããŸãã
type Queryer interface { Query() string }
Query()
ã¡ãœããã¯ãããŒã¿ããŒã¹ã¯ãšãªãå®è¡ããåã«åŒã³åºãããŸãã çµæã®æååã¯ãããŒã¿ããŒã¹åºæã®åœ¢åŒã«å€æãããŸãã ã€ãŸããPostgreSQLã®å Žåã &req.End
ã¯$1
ã«çœ®ãæããããå€&req.End
ãåŒæ°ã®é
åã«æž¡ãããŸã
åºåãã©ã¡ãŒã¿ãŒã«å¿ããŠãã©ã®ã¡ãœããïŒQuery / ExecïŒãåŒã³åºãããããæ±ºå®ãããŸãã
- æåã®ãã©ã¡ãŒã¿ãŒãåºæ¬åã®
struct
ïŒãŸãã¯struct
ãžã®ãã€ã³ã¿ãŒïŒã®å Žåã QueryContext
ã¡ãœãããåŒã³åºãããŸãã ããŒã¿ããŒã¹ããã®å¿çã«åäžã®è¡ãå«ãŸããŠããªãå Žåã sql.ErrNoRows
ãšã©ãŒãsql.ErrNoRows
ãŸãã ã€ãŸããåäœã¯db.QueryRow
䌌ãŠãdb.QueryRow
ã - æåã®ãã©ã¡ãŒã¿ãŒãåºæ¬ã¿ã€ã
slice
å Žåã QueryContext
ã¡ãœãããåŒã³åºãããŸãã ããŒã¿ããŒã¹ããã®å¿çã«è¡ãå«ãŸããŠããªãå Žåã空ã®ãªã¹ããè¿ãããŸãã ãªã¹ãé
ç®ã®åºæ¬åã¯stuct
ïŒãŸãã¯struct
ãžã®ãã€ã³ã¿ãŒïŒã§ãªããã°ãªããŸããã - åºåãã©ã¡ãŒã¿ãŒã
error
ã¿ã€ãã®ãã©ã¡ãŒã¿ãŒã§ããå Žåã ExecContext
ã¡ãœãããåŒã³åºãããŸãã
æºåãããã¹ããŒãã¡ã³ã
çæãããã³ãŒãã¯ãæºåãããåŒããµããŒãããŸãã æºåãããåŒã¯ãã£ãã·ã¥ãããŸãã åŒã®æåã®æºååŸããã£ãã·ã¥ãããŸãã ããŒã¿ããŒã¹/ SQLã©ã€ãã©ãªèªäœã¯ãéããããæ¥ç¶ã®åŠçãå«ããæºåãããåŒãç®çã®ããŒã¿ããŒã¹æ¥ç¶ã«ééçã«é©çšãããããšãä¿èšŒããŸãã é çªã«ã go-gad/sal
ã©ã€ãã©ãªã¯ããã©ã³ã¶ã¯ã·ã§ã³ã®ã³ã³ããã¹ãã§æºåãããåŒãåå©çšããŸãã æºåãããåŒãå®è¡ããããšãåŒæ°ã¯éçºè
ã«ééçãªå€æ°ãã€ã³ãã£ã³ã°ã䜿çšããŠæž¡ãããŸãã
go-gad/sal
ã©ã€ãã©ãªåŽã§ååä»ãåŒæ°ããµããŒãããããã«ããªã¯ãšã¹ãã¯ããŒã¿ããŒã¹ã«é©ãããã¥ãŒã«å€æãããŸãã çŸåšãPostgreSQLã®å€æãµããŒãããããŸãã ã¯ãšãªãªããžã§ã¯ãã®ãã£ãŒã«ãåã¯ãååä»ãåŒæ°ã®ä»£ããã«äœ¿çšãããŸãã ãªããžã§ã¯ããã£ãŒã«ãåã®ä»£ããã«å¥ã®ååãæå®ããã«ã¯ãæ§é äœãã£ãŒã«ãã«sql
ã¿ã°ã䜿çšããå¿
èŠããããŸãã äŸãèããŠã¿ãŸãããïŒ
type DeleteOrdersRequest struct { UserID int64 `sql:"user_id"` CreateAt time.Time `sql:"created_at"` } func (r * DeleteOrdersRequest) Query() string { return `DELETE FROM orders WHERE user_id=@user_id AND created_at<@end` }
ã¯ãšãªæååã倿ããã察å¿ããŒãã«ãšå€æ°ãã€ã³ãã£ã³ã°ã䜿çšããŠãã¯ãšãªå®è¡åŒæ°ã«ãªã¹ããæž¡ãããŸãã
æ§é äœãèŠæ±ã®åŒæ°ãšå¿çã¡ãã»ãŒãžã«ãããããŸã
go-gad/sal
ã©ã€ãã©ãªã¯ãããŒã¿ããŒã¹ã®å¿çè¡ãšå¿çæ§é ãããŒãã«ã®åãšæ§é ãã£ãŒã«ãã®é¢é£ä»ããåŠçããŸãã
type GetRubricsReq struct {} func (r GetRubricReq) Query() string { return `SELECT * FROM rubrics` } type Rubric struct { ID int64 `sql:"id"` CreateAt time.Time `sql:"created_at"` Title string `sql:"title"` } type GetRubricsResp []*Rubric type Store interface { GetRubrics(ctx context.Context, req GetRubricsReq) (GetRubricsResp, error) }
ããŒã¿ããŒã¹ã®å¿çãæ¬¡ã®å ŽåïŒ
dev > SELECT * FROM rubrics; id | created_at | title
次ã«ãGetRubricsRespãªã¹ããè¿ãããŸãããã®èŠçŽ ã¯Rubricãã€ã³ã¿ãŒã«ãªããã¿ã°åã«å¯Ÿå¿ããåã®å€ããã£ãŒã«ãã«å
¥åãããŸãã
ããŒã¿ããŒã¹å¿çã«åãååã®åãå«ãŸããŠããå Žåã察å¿ããæ§é ãã£ãŒã«ãã宣èšé ã«éžæãããŸãã
dev > select * from rubrics, subrubrics; id | title | id | title
type Rubric struct { ID int64 `sql:"id"` Title string `sql:"title"` } type Subrubric struct { ID int64 `sql:"id"` Title string `sql:"title"` } type GetCategoryResp struct { Rubric Subrubric }
éæšæºã®ããŒã¿å
database/sql
ããã±ãŒãžã¯ãåºæ¬çãªããŒã¿åïŒæååãæ°å€ïŒã®ãµããŒããæäŸããŸãã èŠæ±ãŸãã¯å¿çã§é
åãjsonãªã©ã®ããŒã¿åãåŠçããã«ã¯ã driver.Valuer
ããã³sql.Scanner
ããµããŒãããå¿
èŠãããsql.Scanner
ã ããŸããŸãªãã©ã€ããŒå®è£
ã«ã¯ãç¹å¥ãªãã«ããŒé¢æ°ããããŸãã ããšãã°ã lib/pq.Array
ïŒ https://godoc.org/github.com/lib/pq#Array ïŒïŒ
func Array(a interface{}) interface { driver.Valuer sql.Scanner }
ããã©ã«ãã§ã¯ããã¥ãŒæ§é ãã£ãŒã«ãã®go-gad/sql
ã©ã€ãã©ãª
type DeleteAuthrosReq struct { Tags []int64 `sql:"tags"` }
å€&req.Tags
ã䜿çšããŸãã æ§é ãsal.ProcessRower
ã€ã³ã¿ãŒãã§ãŒã¹ãæºããå Žåã
type ProcessRower interface { ProcessRow(rowMap RowMap) }
ãã®åŸã䜿çšå€ã調æŽã§ããŸã
func (r *DeleteAuthorsReq) ProcessRow(rowMap sal.RowMap) { rowMap.Set("tags", pq.Array(r.Tags)) } func (r *DeleteAuthorsReq) Query() string { return `DELETE FROM authors WHERE tags=ANY(@tags::UUID[])` }
ãã®ãã³ãã©ãŒã¯ãèŠæ±ããã³å¿çã®åŒæ°ã«äœ¿çšã§ããŸãã å¿çã®ãªã¹ãã®å Žåãã¡ãœããã¯ãªã¹ãé
ç®ã«å±ããŠããå¿
èŠããããŸãã
ååŒ
ãã©ã³ã¶ã¯ã·ã§ã³ããµããŒãããã«ã¯ãã€ã³ã¿ãŒãã§ãŒã¹ïŒã¹ãã¢ïŒã次ã®ã¡ãœããã§æ¡åŒµããå¿
èŠããããŸãã
type Store interface { BeginTx(ctx context.Context, opts *sql.TxOptions) (Store, error) sal.Txer ...
ã¡ãœããã®å®è£
ãçæãããŸãã BeginTx
ã¡ãœããã¯ãçŸåšã®sal.QueryHandler
ãªããžã§ã¯ãããã®æ¥ç¶ã䜿çšããŠããã©ã³ã¶ã¯ã·ã§ã³db.BeginTx(...)
ãéããŸãã Store
ã€ã³ã¿ãŒãã§ãŒã¹ã®æ°ããå®è£
ãªããžã§ã¯ããè¿ããŸãããåä¿¡ãã*sql.Tx
ãªããžã§ã¯ãã*sql.Tx
ãšããŠäœ¿çšããŸã
ããã«ãŠã§ã¢
ããŒã«ãåã蟌ãããã®ããã¯ãçšæãããŠããŸãã
type BeforeQueryFunc func(ctx context.Context, query string, req interface{}) (context.Context, FinalizerFunc) type FinalizerFunc func(ctx context.Context, err error)
BeforeQueryFunc
ããã¯ã¯ã db.PrepareContext
ãŸãã¯db.Query
ããåã«db.PrepareContext
db.Query
ãŸãã ã€ãŸããããã°ã©ã ã®éå§æãæºåãããåŒãã£ãã·ã¥ã空ã®å Žåã store.GetAuthors
åŒã³åºããããšã BeforeQueryFunc
ããã¯ã2ååŒã³åºãããŸãã BeforeQueryFunc
ããã¯ã¯FinalizerFunc
ããã¯ãè¿ãããšãã§ããŸãããã®ã¡ãœããã¯ã defer
ã䜿çšããŠããŠãŒã¶ãŒã¡ãœããïŒãã®å Žåã¯store.GetAuthors
ïŒãçµäºããåã«åŒã³åºãããŸãã
ããã¯ã®å®è¡æã«ãã³ã³ããã¹ãã«ã¯æ¬¡ã®å€ãæã€ãµãŒãã¹ããŒãå
¥åãããŸãã
ctx.Value(sal.ContextKeyTxOpened)
ããŒã«å€ã¯ãã¡ãœããããã©ã³ã¶ã¯ã·ã§ã³ã®ã³ã³ããã¹ãã§åŒã³åºããããã©ãããæ±ºå®ããŸããctx.Value(sal.ContextKeyOperationType)
ãæäœã¿ã€ãã®ã¹ããªã³ã°å€ã "QueryRow"
ã "Query"
ã "Exec"
ã "Commit"
ãªã©ctx.Value(sal.ContextKeyMethodName)
"GetAuthors"
ãªã©ã®ã€ã³ã¿ãŒãã§ã€ã¹ã¡ãœããctx.Value(sal.ContextKeyMethodName)
æååå€ã
åŒæ°ãšããŠã BeforeQueryFunc
ããã¯ã¯ãã¯ãšãªã®sqlæååãšãŠãŒã¶ãŒã¯ãšãªã¡ãœããã®req
åŒæ°ãåãå
¥ããŸãã FinalizerFunc
ããã¯ã¯ã err
倿°ãåŒæ°ãšããŠåãåããŸãã
beforeHook := func(ctx context.Context, query string, req interface{}) (context.Context, sal.FinalizerFunc) { start := time.Now() return ctx, func(ctx context.Context, err error) { log.Printf( "%q > Opeartion %q: %q with req %#v took [%v] inTx[%v] Error: %+v", ctx.Value(sal.ContextKeyMethodName), ctx.Value(sal.ContextKeyOperationType), query, req, time.Since(start), ctx.Value(sal.ContextKeyTxOpened), err, ) } } client := NewStore(db, sal.BeforeQuery(beforeHook))
åºåäŸïŒ
"CreateAuthor" > Opeartion "Prepare": "INSERT INTO authors (Name, Desc, CreatedAt) VALUES($1, $2, now()) RETURNING ID, CreatedAt" with req <nil> took [50.819µs] inTx[false] Error: <nil> "CreateAuthor" > Opeartion "QueryRow": "INSERT INTO authors (Name, Desc, CreatedAt) VALUES(@Name, @Desc, now()) RETURNING ID, CreatedAt" with req bookstore.CreateAuthorReq{BaseAuthor:bookstore.BaseAuthor{Name:"foo", Desc:"Bar"}} took [150.994µs] inTx[false] Error: <nil>
次ã¯äœã§ãã
- MySQLã®ãã€ã³ã倿°ãšæºåãããåŒã®ãµããŒãã
- å¿çã調æŽããRowAppenderããã¯ã
Exec.Result
ã®å€ãè¿ããŸãã