├── LICENSE ├── README.md ├── db.go ├── mongo.go ├── redis.go └── sql.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Gregory Roseberry (greg@toki.waseda.jp) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Update from 2022 2 | 3 | This project was made in the wild west of pre-stdlib context days to explore what was possible. Since then, we've all agreed that putting databases in your context is generally a bad idea. Transactions are arguably OK. Regardless, you probably shouldn't use this. 4 | 5 | ## db [![GoDoc](https://godoc.org/github.com/guregu/db?status.svg)](https://godoc.org/github.com/guregu/db) 6 | `import "github.com/guregu/db"` 7 | 8 | db is a simple helper for using [x/net/context](https://blog.golang.org/context) with various databases. With db you can give each of your connections a name and shove it in your context. Later you can use that name to retrieve the connection. Feel free to fork this and add your favorite drivers. 9 | 10 | ### Example 11 | First we make a context with our DB connection. Then we use [kami](https://github.com/guregu/kami) to set up a web server and pass that context to every request. From within the request, we retrieve the DB connection and send a query. 12 | 13 | ```go 14 | package main 15 | 16 | import ( 17 | "fmt" 18 | "net/http" 19 | 20 | "github.com/guregu/db" 21 | "github.com/guregu/kami" 22 | "golang.org/x/net/context" 23 | 24 | _ "github.com/go-sql-driver/mysql" 25 | ) 26 | 27 | func main() { 28 | ctx := context.Background() 29 | ctx = db.OpenSQL(ctx, "main", "mysql", "root:hunter2@unix(/tmp/mysql.sock)/myCoolDB") 30 | defer db.Close(ctx) // closes all DB connections 31 | kami.Context = ctx 32 | 33 | kami.Get("/hello/:name", hello) 34 | kami.Serve() 35 | } 36 | 37 | func hello(ctx context.Context, w http.ResponseWriter, r *http.Request) { 38 | mainDB := db.SQL(ctx, "main") // *sql.DB 39 | var greeting string 40 | mainDB.QueryRow("SELECT content FROM greetings WHERE name = ?", kami.Param(ctx, "name")).Scan(&greeting) 41 | fmt.Fprint(w, greeting) 42 | } 43 | ``` 44 | 45 | ### License 46 | BSD 47 | -------------------------------------------------------------------------------- /db.go: -------------------------------------------------------------------------------- 1 | // Package db provides a simple way to store and retrieve database connections using x/net/context. 2 | package db 3 | 4 | import "golang.org/x/net/context" 5 | 6 | type indexkey int 7 | 8 | const ( 9 | sqlIndex indexkey = iota + 1 10 | redisIndex 11 | mongoIndex 12 | ) 13 | 14 | // Close closes all connections of all kinds and returns a new context without them. 15 | func Close(ctx context.Context) context.Context { 16 | ctx = CloseSQLAll(ctx) 17 | ctx = CloseRedisAll(ctx) 18 | ctx = CloseMongoDBAll(ctx) 19 | return ctx 20 | } 21 | -------------------------------------------------------------------------------- /mongo.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "golang.org/x/net/context" 5 | "gopkg.in/mgo.v2" 6 | ) 7 | 8 | type mongokey string 9 | 10 | // Mongo retrieves the *mgo.Session with the given name or nil. 11 | func MongoDB(ctx context.Context, name string) *mgo.Session { 12 | db, _ := ctx.Value(mongokey(name)).(*mgo.Session) 13 | return db 14 | } 15 | 16 | // WithMongoDB returns a new context containing the given *mgo.Session 17 | func WithMongoDB(ctx context.Context, name string, db *mgo.Session) context.Context { 18 | key := mongokey(name) 19 | if idx := mongoIndexFrom(ctx); idx != nil { 20 | idx[key] = db 21 | } else { 22 | idx = map[mongokey]*mgo.Session{key: db} 23 | ctx = withMongoIndex(ctx, idx) 24 | } 25 | return context.WithValue(ctx, mongokey(name), db) 26 | } 27 | 28 | // OpenMongoDB opens a Mongo connection and returns a new context or panics. 29 | func OpenMongoDB(ctx context.Context, name, url string) context.Context { 30 | db, err := mgo.Dial(url) 31 | if err != nil { 32 | panic(err) 33 | } 34 | return WithMongoDB(ctx, name, db) 35 | } 36 | 37 | // CloseMongoDB closes the specified Mongo connection, panciking if Close returns an error. 38 | // CloseMongoDB will do nothing if the given Mongo connection does not exist. 39 | func CloseMongoDB(ctx context.Context, name string) context.Context { 40 | db := MongoDB(ctx, name) 41 | if db == nil { 42 | return ctx 43 | } 44 | 45 | db.Close() 46 | return removeMongo(ctx, name) 47 | } 48 | 49 | // CloseMongoDBAll closes all open Mongo connections and returns a new context without them. 50 | func CloseMongoDBAll(ctx context.Context) context.Context { 51 | if idx := mongoIndexFrom(ctx); idx != nil { 52 | for name, _ := range idx { 53 | ctx = CloseMongoDB(ctx, string(name)) 54 | } 55 | } 56 | return ctx 57 | } 58 | 59 | func removeMongo(ctx context.Context, name string) context.Context { 60 | key := mongokey(name) 61 | if idx := mongoIndexFrom(ctx); idx != nil { 62 | delete(idx, key) 63 | } 64 | return context.WithValue(ctx, key, nil) 65 | } 66 | 67 | func mongoIndexFrom(ctx context.Context) map[mongokey]*mgo.Session { 68 | idx, _ := ctx.Value(mongoIndex).(map[mongokey]*mgo.Session) 69 | return idx 70 | } 71 | 72 | func withMongoIndex(ctx context.Context, idx map[mongokey]*mgo.Session) context.Context { 73 | return context.WithValue(ctx, mongoIndex, idx) 74 | } 75 | -------------------------------------------------------------------------------- /redis.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "golang.org/x/net/context" 5 | "gopkg.in/redis.v2" 6 | ) 7 | 8 | type rediskey string 9 | 10 | // Redis retrieves the *redis.Client with the given name or nil. 11 | func Redis(ctx context.Context, name string) *redis.Client { 12 | client, _ := ctx.Value(rediskey(name)).(*redis.Client) 13 | return client 14 | } 15 | 16 | // WithRedis returns a new context containing the given *redis.Client 17 | func WithRedis(ctx context.Context, name string, client *redis.Client) context.Context { 18 | key := rediskey(name) 19 | if idx := redisIndexFrom(ctx); idx != nil { 20 | idx[key] = client 21 | } else { 22 | idx = map[rediskey]*redis.Client{key: client} 23 | ctx = withRedisIndex(ctx, idx) 24 | } 25 | return context.WithValue(ctx, rediskey(name), client) 26 | } 27 | 28 | // OpenRedis opens a Redis connection and returns a new context or panics. 29 | func OpenRedis(ctx context.Context, name string, options *redis.Options) context.Context { 30 | client := redis.NewClient(options) 31 | return WithRedis(ctx, name, client) 32 | } 33 | 34 | // OpenFailoverRedis opens a failover Redis connection and returns a new context or panics. 35 | func OpenFailoverRedis(ctx context.Context, name string, options *redis.FailoverOptions) context.Context { 36 | client := redis.NewFailoverClient(options) 37 | return WithRedis(ctx, name, client) 38 | } 39 | 40 | // CloseRedis closes the specified Redis connection, panciking if Close returns an error. 41 | // CloseRedis will do nothing if the given Redis connection does not exist. 42 | func CloseRedis(ctx context.Context, name string) context.Context { 43 | client := Redis(ctx, name) 44 | if client == nil { 45 | return ctx 46 | } 47 | 48 | if err := client.Close(); err != nil { 49 | panic(err) 50 | } 51 | return removeRedis(ctx, name) 52 | } 53 | 54 | // CloseRedisAll closes all open Redis connections and returns a new context without them. 55 | func CloseRedisAll(ctx context.Context) context.Context { 56 | if idx := redisIndexFrom(ctx); idx != nil { 57 | for name, _ := range idx { 58 | ctx = CloseRedis(ctx, string(name)) 59 | } 60 | } 61 | return ctx 62 | } 63 | 64 | func removeRedis(ctx context.Context, name string) context.Context { 65 | key := rediskey(name) 66 | if idx := redisIndexFrom(ctx); idx != nil { 67 | delete(idx, key) 68 | } 69 | return context.WithValue(ctx, key, nil) 70 | } 71 | 72 | func redisIndexFrom(ctx context.Context) map[rediskey]*redis.Client { 73 | idx, _ := ctx.Value(redisIndex).(map[rediskey]*redis.Client) 74 | return idx 75 | } 76 | 77 | func withRedisIndex(ctx context.Context, idx map[rediskey]*redis.Client) context.Context { 78 | return context.WithValue(ctx, redisIndex, idx) 79 | } 80 | -------------------------------------------------------------------------------- /sql.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql" 5 | 6 | "golang.org/x/net/context" 7 | ) 8 | 9 | type sqlkey string 10 | 11 | // SQL retrieves the *sql.DB with the given name or nil. 12 | func SQL(ctx context.Context, name string) *sql.DB { 13 | db, _ := ctx.Value(sqlkey(name)).(*sql.DB) 14 | return db 15 | } 16 | 17 | // WithSQL returns a new context containing the given *sql.DB 18 | func WithSQL(ctx context.Context, name string, db *sql.DB) context.Context { 19 | key := sqlkey(name) 20 | if idx := sqlIndexFrom(ctx); idx != nil { 21 | idx[key] = db 22 | } else { 23 | idx = map[sqlkey]*sql.DB{key: db} 24 | ctx = withSQLIndex(ctx, idx) 25 | } 26 | return context.WithValue(ctx, sqlkey(name), db) 27 | } 28 | 29 | // OpenSQL opens a SQL connection and returns a new context or panics. 30 | func OpenSQL(ctx context.Context, name, driver, dataSource string) context.Context { 31 | db, err := sql.Open(driver, dataSource) 32 | if err != nil { 33 | panic(err) 34 | } 35 | return WithSQL(ctx, name, db) 36 | } 37 | 38 | // CloseSQL closes the specified SQL connection, panciking if Close returns an error. 39 | // CloseSQL will do nothing if the given SQL connection does not exist. 40 | func CloseSQL(ctx context.Context, name string) context.Context { 41 | db := SQL(ctx, name) 42 | if db == nil { 43 | return ctx 44 | } 45 | 46 | if err := db.Close(); err != nil { 47 | panic(err) 48 | } 49 | return removeSQL(ctx, name) 50 | } 51 | 52 | // CloseSQLAll closes all open SQL connections and returns a new context without them. 53 | func CloseSQLAll(ctx context.Context) context.Context { 54 | if idx := sqlIndexFrom(ctx); idx != nil { 55 | for name, _ := range idx { 56 | ctx = CloseSQL(ctx, string(name)) 57 | } 58 | } 59 | return ctx 60 | } 61 | 62 | func removeSQL(ctx context.Context, name string) context.Context { 63 | key := sqlkey(name) 64 | if idx := sqlIndexFrom(ctx); idx != nil { 65 | delete(idx, key) 66 | } 67 | return context.WithValue(ctx, key, nil) 68 | } 69 | 70 | func sqlIndexFrom(ctx context.Context) map[sqlkey]*sql.DB { 71 | idx, _ := ctx.Value(sqlIndex).(map[sqlkey]*sql.DB) 72 | return idx 73 | } 74 | 75 | func withSQLIndex(ctx context.Context, idx map[sqlkey]*sql.DB) context.Context { 76 | return context.WithValue(ctx, sqlIndex, idx) 77 | } 78 | --------------------------------------------------------------------------------