├── README.md ├── sqlt.go └── sqlt_context.go /README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://godoc.org/github.com/albert-widi/sqlt?status.svg)](https://godoc.org/github.com/albert-widi/sqlt) 2 | 3 | # sqlt 4 | 5 | Sqlt is a wrapper package for `jmoiron/sqlx` 6 | 7 | This wrapper build based on `tsenart/nap` master-slave and its `load-balancing` configuration with some modification 8 | 9 | Since this package is just a wrapper, you can use it 100% like `nap` but with the taste of `sqlx` 10 | 11 | Usage 12 | ------ 13 | 14 | To connect to database, you need an appended connection string with `;` delimeter, but there is some notes: 15 | * First connection will always be considered as `master` connection 16 | * Another connection will be considered as `slave` 17 | 18 | ```go 19 | databaseCon := "con1;" + "con2;" + "con3" 20 | db, err := sqlt.Open("postgres", databaseCon) 21 | ``` 22 | 23 | Or 24 | 25 | ```go 26 | databaseCon := "con1" 27 | db, err := sqlt.Open("postgres", databaseCon) 28 | ``` 29 | 30 | Query Example: 31 | 32 | ```go 33 | row := db.QueryRow(query, args) 34 | ``` 35 | 36 | ```go 37 | rows, err := db.Query(query, args) 38 | ``` 39 | 40 | ```go 41 | err := db.Select(&struct, query, args) 42 | ``` 43 | 44 | ```go 45 | err := db.Get(&struct, query, args) 46 | ``` 47 | 48 | `preapre` and `preparex` for `sql` and `sqlx` are supported 49 | 50 | use `preparex` to enable `ScanStruct` 51 | 52 | ```go 53 | statement := db.Prepare(query) 54 | rows, err := statement.Query(param) 55 | row := statement.QueryRows(param).Scan(&var) 56 | ``` 57 | 58 | ```go 59 | statement := db.Preparex(query) 60 | rows, err := statement.Query(param) 61 | row := statement.QueryRows(param).ScanStruct(&struct) 62 | ``` 63 | 64 | Complete example: 65 | 66 | ```go 67 | package main 68 | 69 | // lib/pq or go-sql-driver needed to import database driver 70 | import ( 71 | _"github.com/lib/pq" 72 | _ "github.com/go-sql-driver/mysql" 73 | "github.com/albert-widi/sqlt" 74 | "log" 75 | ) 76 | 77 | func main() { 78 | masterDSN := "user:password@tcp(master_ip:master_port)/database_name" 79 | slave1DSN := "user:password@tcp(slave_ip:slave_port)/database_name" 80 | slave2DSN := "user:password@tcp(slave2_ip:slave2_port)/database_name" 81 | 82 | dsn := masterDSN + ";" + slave1DSN + ";" + slave2DSN 83 | 84 | db, err = sqlt.Open("mysql", dsn) 85 | if err != nil { 86 | log.Println("Error ", err.Error()) 87 | } 88 | 89 | // do something using db connection 90 | } 91 | ``` 92 | 93 | Heartbeat/Watcher 94 | ------ 95 | 96 | SQLT provide a mechanism to switch between slaves if something bad happen. If no slaves is available then all connection will go to master. 97 | 98 | The watcher will auto-reconnect to your slaves if possible and start to `load-balancing` itself. 99 | 100 | To watch all slaves connection, use `DoHeartbeat`. This will ping your database every second and make auto-reconnect if needed. 101 | 102 | ```go 103 | databaseCon := "con1;" + "con2;" + "con3" 104 | db, err := sqlt.Open("postgres", databaseCon) 105 | 106 | if err != nil { 107 | return err 108 | } 109 | 110 | //this will automatically ping the database and watch the connection 111 | db.DoHeartBeat() 112 | ``` 113 | 114 | Don't forget to stop the heartbeat when your application stop, because it(goroutine) will most likely leak if you forgot to close it. 115 | 116 | ```go 117 | db.StopBeat() 118 | ``` 119 | 120 | Database status 121 | ------ 122 | 123 | You can also get the database status, for example: 124 | 125 | ```go 126 | databaseCon := "con1;" + "con2;" + "con3" 127 | db, err := sqlt.OpenWithName("postgres", databaseCon, "order") 128 | 129 | if err != nil { 130 | return err 131 | } 132 | 133 | //this will automatically ping the database and watch the connection 134 | db.DoHeartBeat() 135 | 136 | //this will return database status in JSON 137 | status, _ := db.GetStatus() 138 | ``` 139 | 140 | Output: 141 | 142 | ```go 143 | DbStatus { 144 | Name: "order", 145 | Connected: true, 146 | LastActive: "21 September 2016", 147 | Error: nil, 148 | } 149 | ``` 150 | 151 | 152 | ---------------------------------- 153 | 154 | 3rd party references: 155 | * https://github.com/jmoiron/sqlx 156 | * https://github.com/tsenart/nap 157 | -------------------------------------------------------------------------------- /sqlt.go: -------------------------------------------------------------------------------- 1 | package sqlt 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "errors" 7 | "fmt" 8 | "sync" 9 | "sync/atomic" 10 | "time" 11 | 12 | "github.com/jmoiron/sqlx" 13 | ) 14 | 15 | // Error list 16 | var ( 17 | ErrNoConnectionDetected = errors.New("No connection detected") 18 | ) 19 | 20 | // DB struct wrapper for sqlx connection 21 | type DB struct { 22 | sqlxdb []*sqlx.DB 23 | activedb []int 24 | inactivedb []int 25 | driverName string 26 | groupName string 27 | length int 28 | count uint64 29 | // for stats 30 | stats []DbStatus 31 | heartBeat bool 32 | stopBeat chan bool 33 | lastBeat string 34 | } 35 | 36 | // DbStatus for status response 37 | type DbStatus struct { 38 | Name string `json:"name"` 39 | Connected bool `json:"connected"` 40 | LastActive string `json:"last_active"` 41 | Error interface{} `json:"error"` 42 | } 43 | 44 | type statusResponse struct { 45 | Dbs interface{} `json:"db_list"` 46 | Heartbeat bool `json:"heartbeat"` 47 | Lastbeat string `json:"last_beat"` 48 | } 49 | 50 | const defaultGroupName = "sqlt_open" 51 | 52 | var dbLengthMutex = &sync.Mutex{} 53 | 54 | func openConnection(driverName, sources string, groupName string) (*DB, error) { 55 | db, err := open(context.Background(), driverName, sources, groupName) 56 | if err != nil { 57 | return nil, err 58 | } 59 | return db, db.Ping() 60 | } 61 | 62 | // Open connection to database 63 | func Open(driverName, sources string) (*DB, error) { 64 | return openConnection(driverName, sources, "") 65 | } 66 | 67 | // OpenWithName open the connection and set connection group name 68 | func OpenWithName(driverName, sources string, name string) (*DB, error) { 69 | return openConnection(driverName, sources, name) 70 | } 71 | 72 | // GetStatus return database status 73 | func (db *DB) GetStatus() ([]DbStatus, error) { 74 | if len(db.stats) == 0 { 75 | return db.stats, ErrNoConnectionDetected 76 | } 77 | 78 | // if heartbeat is not enabled, ping to get status before send status 79 | if !db.heartBeat { 80 | db.Ping() 81 | } 82 | return db.stats, nil 83 | } 84 | 85 | // DoHeartBeat will automatically spawn a goroutines to ping your database every one second, use this carefully 86 | func (db *DB) DoHeartBeat() { 87 | if !db.heartBeat { 88 | ticker := time.NewTicker(time.Second * 2) 89 | db.stopBeat = make(chan bool) 90 | go func() { 91 | for { 92 | select { 93 | case <-ticker.C: 94 | db.Ping() 95 | db.lastBeat = time.Now().Format(time.RFC1123) 96 | case <-db.stopBeat: 97 | return 98 | } 99 | } 100 | }() 101 | } 102 | db.heartBeat = true 103 | } 104 | 105 | // StopBeat will stop heartbeat, exit from goroutines 106 | func (db *DB) StopBeat() { 107 | if !db.heartBeat { 108 | return 109 | } 110 | db.stopBeat <- true 111 | } 112 | 113 | // Ping database 114 | func (db *DB) Ping() error { 115 | var err error 116 | 117 | if !db.heartBeat { 118 | for _, val := range db.sqlxdb { 119 | err = val.Ping() 120 | if err != nil { 121 | return err 122 | } 123 | } 124 | return err 125 | } 126 | 127 | for i := 0; i < len(db.activedb); i++ { 128 | val := db.activedb[i] 129 | err = db.sqlxdb[val].Ping() 130 | name := db.stats[val].Name 131 | 132 | if err != nil { 133 | if db.length <= 1 { 134 | return err 135 | } 136 | 137 | db.stats[val].Connected = false 138 | db.activedb = append(db.activedb[:i], db.activedb[i+1:]...) 139 | i-- 140 | db.inactivedb = append(db.inactivedb, val) 141 | db.stats[val].Error = errors.New(name + ": " + err.Error()) 142 | dbLengthMutex.Lock() 143 | db.length-- 144 | dbLengthMutex.Unlock() 145 | } else { 146 | db.stats[val].Connected = true 147 | db.stats[val].LastActive = time.Now().Format(time.RFC1123) 148 | db.stats[val].Error = nil 149 | } 150 | } 151 | 152 | for i := 0; i < len(db.inactivedb); i++ { 153 | val := db.inactivedb[i] 154 | err = db.sqlxdb[val].Ping() 155 | name := db.stats[val].Name 156 | 157 | if err != nil { 158 | db.stats[val].Connected = false 159 | db.stats[val].Error = errors.New(name + ": " + err.Error()) 160 | } else { 161 | db.stats[val].Connected = true 162 | db.inactivedb = append(db.inactivedb[:i], db.inactivedb[i+1:]...) 163 | i-- 164 | db.activedb = append(db.activedb, val) 165 | db.stats[val].LastActive = time.Now().Format(time.RFC1123) 166 | db.stats[val].Error = nil 167 | dbLengthMutex.Lock() 168 | db.length++ 169 | dbLengthMutex.Unlock() 170 | } 171 | } 172 | return err 173 | } 174 | 175 | // Prepare return sql stmt 176 | func (db *DB) Prepare(query string) (*Stmt, error) { 177 | var err error 178 | stmt := new(Stmt) 179 | stmts := make([]*sql.Stmt, len(db.sqlxdb)) 180 | 181 | for i := range db.sqlxdb { 182 | stmts[i], err = db.sqlxdb[i].Prepare(query) 183 | 184 | if err != nil { 185 | return nil, err 186 | } 187 | } 188 | stmt.db = db 189 | stmt.stmts = stmts 190 | return stmt, nil 191 | } 192 | 193 | // Preparex sqlx stmt 194 | func (db *DB) Preparex(query string) (*Stmtx, error) { 195 | var err error 196 | stmts := make([]*sqlx.Stmt, len(db.sqlxdb)) 197 | 198 | for i := range db.sqlxdb { 199 | stmts[i], err = db.sqlxdb[i].Preparex(query) 200 | 201 | if err != nil { 202 | return nil, err 203 | } 204 | } 205 | 206 | return &Stmtx{db: db, stmts: stmts}, nil 207 | } 208 | 209 | // SetMaxOpenConnections to set max connections 210 | func (db *DB) SetMaxOpenConnections(max int) { 211 | for i := range db.sqlxdb { 212 | db.sqlxdb[i].SetMaxOpenConns(max) 213 | } 214 | } 215 | 216 | // SetConnMaxLifetime sets the maximum amount of time a connection may be reused. 217 | // Expired connections may be closed lazily before reuse. 218 | // If d <= 0, connections are reused forever. 219 | func (db *DB) SetConnMaxLifetime(d time.Duration) { 220 | for i := range db.sqlxdb { 221 | db.sqlxdb[i].SetConnMaxLifetime(d) 222 | } 223 | } 224 | 225 | // Slave return slave database 226 | func (db *DB) Slave() *sqlx.DB { 227 | return db.sqlxdb[db.slave()] 228 | } 229 | 230 | // Master return master database 231 | func (db *DB) Master() *sqlx.DB { 232 | return db.sqlxdb[0] 233 | } 234 | 235 | // Query queries the database and returns an *sql.Rows. 236 | func (db *DB) Query(query string, args ...interface{}) (*sql.Rows, error) { 237 | r, err := db.sqlxdb[db.slave()].Query(query, args...) 238 | return r, err 239 | } 240 | 241 | // QueryRow queries the database and returns an *sqlx.Row. 242 | func (db *DB) QueryRow(query string, args ...interface{}) *sql.Row { 243 | rows := db.sqlxdb[db.slave()].QueryRow(query, args...) 244 | return rows 245 | } 246 | 247 | // Queryx queries the database and returns an *sqlx.Rows. 248 | func (db *DB) Queryx(query string, args ...interface{}) (*sqlx.Rows, error) { 249 | r, err := db.sqlxdb[db.slave()].Queryx(query, args...) 250 | return r, err 251 | } 252 | 253 | // QueryRowx queries the database and returns an *sqlx.Row. 254 | func (db *DB) QueryRowx(query string, args ...interface{}) *sqlx.Row { 255 | rows := db.sqlxdb[db.slave()].QueryRowx(query, args...) 256 | return rows 257 | } 258 | 259 | // Exec using master db 260 | func (db *DB) Exec(query string, args ...interface{}) (sql.Result, error) { 261 | return db.sqlxdb[0].Exec(query, args...) 262 | } 263 | 264 | // MustExec (panic) runs MustExec using master database. 265 | func (db *DB) MustExec(query string, args ...interface{}) sql.Result { 266 | return db.sqlxdb[0].MustExec(query, args...) 267 | } 268 | 269 | // Select using slave db. 270 | func (db *DB) Select(dest interface{}, query string, args ...interface{}) error { 271 | return db.sqlxdb[db.slave()].Select(dest, query, args...) 272 | } 273 | 274 | // SelectMaster using master db. 275 | func (db *DB) SelectMaster(dest interface{}, query string, args ...interface{}) error { 276 | return db.sqlxdb[0].Select(dest, query, args...) 277 | } 278 | 279 | // Get using slave. 280 | func (db *DB) Get(dest interface{}, query string, args ...interface{}) error { 281 | return db.sqlxdb[db.slave()].Get(dest, query, args...) 282 | } 283 | 284 | // GetMaster using master. 285 | func (db *DB) GetMaster(dest interface{}, query string, args ...interface{}) error { 286 | return db.sqlxdb[0].Get(dest, query, args...) 287 | } 288 | 289 | // NamedExec using master db. 290 | func (db *DB) NamedExec(query string, arg interface{}) (sql.Result, error) { 291 | return db.sqlxdb[0].NamedExec(query, arg) 292 | } 293 | 294 | // Begin sql transaction 295 | func (db *DB) Begin() (*sql.Tx, error) { 296 | return db.sqlxdb[0].Begin() 297 | } 298 | 299 | // Beginx sqlx transaction 300 | func (db *DB) Beginx() (*sqlx.Tx, error) { 301 | return db.sqlxdb[0].Beginx() 302 | } 303 | 304 | // MustBegin starts a transaction, and panics on error. Returns an *sqlx.Tx instead 305 | // of an *sql.Tx. 306 | func (db *DB) MustBegin() *sqlx.Tx { 307 | tx, err := db.sqlxdb[0].Beginx() 308 | if err != nil { 309 | panic(err) 310 | } 311 | return tx 312 | } 313 | 314 | // Rebind query 315 | func (db *DB) Rebind(query string) string { 316 | return db.sqlxdb[db.slave()].Rebind(query) 317 | } 318 | 319 | // RebindMaster will rebind query for master 320 | func (db *DB) RebindMaster(query string) string { 321 | return db.sqlxdb[0].Rebind(query) 322 | } 323 | 324 | // Close closes all database connections 325 | func (db *DB) Close() error { 326 | for _, val := range db.sqlxdb { 327 | err := val.Close() 328 | if err != nil { 329 | return err 330 | } 331 | } 332 | return nil 333 | } 334 | 335 | // SetMaxIdleConns sets the maximum number of connections in the idle 336 | // connection pool for all connections 337 | func (db *DB) SetMaxIdleConns(n int) { 338 | for _, val := range db.sqlxdb { 339 | val.SetMaxIdleConns(n) 340 | } 341 | } 342 | 343 | // Stmt implement sql stmt 344 | type Stmt struct { 345 | db *DB 346 | stmts []*sql.Stmt 347 | } 348 | 349 | // Exec will always go to production 350 | func (st *Stmt) Exec(args ...interface{}) (sql.Result, error) { 351 | return st.stmts[0].Exec(args...) 352 | } 353 | 354 | // Query will always go to slave 355 | func (st *Stmt) Query(args ...interface{}) (*sql.Rows, error) { 356 | return st.stmts[st.db.slave()].Query(args...) 357 | } 358 | 359 | // QueryMaster will use master db 360 | func (st *Stmt) QueryMaster(args ...interface{}) (*sql.Rows, error) { 361 | return st.stmts[0].Query(args...) 362 | } 363 | 364 | // QueryRow will always go to slave 365 | func (st *Stmt) QueryRow(args ...interface{}) *sql.Row { 366 | return st.stmts[st.db.slave()].QueryRow(args...) 367 | } 368 | 369 | // QueryRowMaster will use master db 370 | func (st *Stmt) QueryRowMaster(args ...interface{}) *sql.Row { 371 | return st.stmts[0].QueryRow(args...) 372 | } 373 | 374 | // Close stmt 375 | func (st *Stmt) Close() error { 376 | for i := range st.stmts { 377 | err := st.stmts[i].Close() 378 | 379 | if err != nil { 380 | return err 381 | } 382 | } 383 | return nil 384 | } 385 | 386 | // Stmtx implement sqlx stmt 387 | type Stmtx struct { 388 | db *DB 389 | stmts []*sqlx.Stmt 390 | } 391 | 392 | // Close all dbs connection 393 | func (st *Stmtx) Close() error { 394 | for i := range st.stmts { 395 | err := st.stmts[i].Close() 396 | 397 | if err != nil { 398 | return err 399 | } 400 | } 401 | return nil 402 | } 403 | 404 | // Exec will always go to production 405 | func (st *Stmtx) Exec(args ...interface{}) (sql.Result, error) { 406 | return st.stmts[0].Exec(args...) 407 | 408 | } 409 | 410 | // Query will always go to slave 411 | func (st *Stmtx) Query(args ...interface{}) (*sql.Rows, error) { 412 | return st.stmts[st.db.slave()].Query(args...) 413 | } 414 | 415 | // QueryMaster will use master db 416 | func (st *Stmtx) QueryMaster(args ...interface{}) (*sql.Rows, error) { 417 | return st.stmts[0].Query(args...) 418 | } 419 | 420 | // QueryRow will always go to slave 421 | func (st *Stmtx) QueryRow(args ...interface{}) *sql.Row { 422 | return st.stmts[st.db.slave()].QueryRow(args...) 423 | } 424 | 425 | // QueryRowMaster will use master db 426 | func (st *Stmtx) QueryRowMaster(args ...interface{}) *sql.Row { 427 | return st.stmts[0].QueryRow(args...) 428 | } 429 | 430 | // MustExec using master database 431 | func (st *Stmtx) MustExec(args ...interface{}) sql.Result { 432 | return st.stmts[0].MustExec(args...) 433 | } 434 | 435 | // Queryx will always go to slave 436 | func (st *Stmtx) Queryx(args ...interface{}) (*sqlx.Rows, error) { 437 | return st.stmts[st.db.slave()].Queryx(args...) 438 | } 439 | 440 | // QueryRowx will always go to slave 441 | func (st *Stmtx) QueryRowx(args ...interface{}) *sqlx.Row { 442 | return st.stmts[st.db.slave()].QueryRowx(args...) 443 | } 444 | 445 | // QueryRowxMaster will always go to master 446 | func (st *Stmtx) QueryRowxMaster(args ...interface{}) *sqlx.Row { 447 | return st.stmts[0].QueryRowx(args...) 448 | } 449 | 450 | // Get will always go to slave 451 | func (st *Stmtx) Get(dest interface{}, args ...interface{}) error { 452 | return st.stmts[st.db.slave()].Get(dest, args...) 453 | } 454 | 455 | // GetMaster will always go to master 456 | func (st *Stmtx) GetMaster(dest interface{}, args ...interface{}) error { 457 | return st.stmts[0].Get(dest, args...) 458 | } 459 | 460 | // Select will always go to slave 461 | func (st *Stmtx) Select(dest interface{}, args ...interface{}) error { 462 | return st.stmts[st.db.slave()].Select(dest, args...) 463 | } 464 | 465 | // SelectMaster will always go to master 466 | func (st *Stmtx) SelectMaster(dest interface{}, args ...interface{}) error { 467 | return st.stmts[0].Select(dest, args...) 468 | } 469 | 470 | // slave 471 | func (db *DB) slave() int { 472 | dbLengthMutex.Lock() 473 | defer dbLengthMutex.Unlock() 474 | if db.length <= 1 { 475 | return 0 476 | } 477 | 478 | slave := int(1 + (atomic.AddUint64(&db.count, 1) % uint64(db.length-1))) 479 | active := db.activedb[slave] 480 | return active 481 | } 482 | 483 | //InitMocking initialize the dbconnection mocking 484 | func InitMocking(dbConn *sql.DB, slaveAmount int) *DB { 485 | 486 | db := &DB{ 487 | sqlxdb: make([]*sqlx.DB, slaveAmount+1), 488 | stats: make([]DbStatus, slaveAmount+1), 489 | } 490 | 491 | for i := 0; i <= slaveAmount; i++ { 492 | db.sqlxdb[i] = sqlx.NewDb(dbConn, "postgres") 493 | name := fmt.Sprintf("slave-%d", i) 494 | if i == 0 { 495 | name = "master" 496 | } 497 | 498 | db.stats[i] = DbStatus{ 499 | Name: name, 500 | Connected: true, 501 | LastActive: time.Now().String(), 502 | } 503 | db.activedb = append(db.activedb, i) 504 | } 505 | 506 | db.driverName = "postgres" 507 | db.groupName = "sqlt-open" 508 | db.length = len(db.sqlxdb) 509 | return db 510 | } 511 | -------------------------------------------------------------------------------- /sqlt_context.go: -------------------------------------------------------------------------------- 1 | package sqlt 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "errors" 7 | "strconv" 8 | "strings" 9 | "time" 10 | 11 | "github.com/jmoiron/sqlx" 12 | ) 13 | 14 | func open(ctx context.Context, driverName, sources string, groupName string) (*DB, error) { 15 | var err error 16 | 17 | conns := strings.Split(sources, ";") 18 | connsLength := len(conns) 19 | 20 | // check if no source is available 21 | if connsLength < 1 { 22 | return nil, errors.New("No sources found") 23 | } 24 | 25 | db := &DB{ 26 | sqlxdb: make([]*sqlx.DB, connsLength), 27 | stats: make([]DbStatus, connsLength), 28 | } 29 | db.length = connsLength 30 | db.driverName = driverName 31 | 32 | for i := range conns { 33 | db.sqlxdb[i], err = sqlx.Open(driverName, conns[i]) 34 | if err != nil { 35 | db.inactivedb = append(db.inactivedb, i) 36 | return nil, err 37 | } 38 | constatus := true 39 | 40 | // set the name 41 | name := "" 42 | if i == 0 { 43 | name = "master" 44 | } else { 45 | name = "slave-" + strconv.Itoa(i) 46 | } 47 | 48 | status := DbStatus{ 49 | Name: name, 50 | Connected: constatus, 51 | LastActive: time.Now().String(), 52 | } 53 | 54 | db.stats[i] = status 55 | db.activedb = append(db.activedb, i) 56 | } 57 | 58 | // set the default group name 59 | db.groupName = defaultGroupName 60 | if groupName != "" { 61 | db.groupName = groupName 62 | } 63 | return db, err 64 | } 65 | 66 | func openContextConnection(ctx context.Context, driverName, sources string, groupName string) (*DB, error) { 67 | // ping database to retrieve error 68 | db, err := open(ctx, driverName, sources, groupName) 69 | if err != nil { 70 | return nil, err 71 | } 72 | return db, db.PingContext(ctx) 73 | } 74 | 75 | // OpenWithContext opening connection with context 76 | func OpenWithContext(ctx context.Context, driver, sources string) (*DB, error) { 77 | return openContextConnection(ctx, driver, sources, "") 78 | } 79 | 80 | // PingContext database 81 | func (db *DB) PingContext(ctx context.Context) error { 82 | var err error 83 | 84 | if !db.heartBeat { 85 | for _, val := range db.sqlxdb { 86 | err = val.PingContext(ctx) 87 | if err != nil { 88 | return err 89 | } 90 | } 91 | return err 92 | } 93 | 94 | for i := 0; i < len(db.activedb); i++ { 95 | val := db.activedb[i] 96 | err = db.sqlxdb[val].PingContext(ctx) 97 | name := db.stats[val].Name 98 | 99 | if err != nil { 100 | if db.length <= 1 { 101 | return err 102 | } 103 | 104 | db.stats[val].Connected = false 105 | db.activedb = append(db.activedb[:i], db.activedb[i+1:]...) 106 | i-- 107 | db.inactivedb = append(db.inactivedb, val) 108 | db.stats[val].Error = errors.New(name + ": " + err.Error()) 109 | dbLengthMutex.Lock() 110 | db.length-- 111 | dbLengthMutex.Unlock() 112 | } else { 113 | db.stats[val].Connected = true 114 | db.stats[val].LastActive = time.Now().Format(time.RFC1123) 115 | db.stats[val].Error = nil 116 | } 117 | } 118 | 119 | for i := 0; i < len(db.inactivedb); i++ { 120 | val := db.inactivedb[i] 121 | err = db.sqlxdb[val].PingContext(ctx) 122 | name := db.stats[val].Name 123 | 124 | if err != nil { 125 | db.stats[val].Connected = false 126 | db.stats[val].Error = errors.New(name + ": " + err.Error()) 127 | } else { 128 | db.stats[val].Connected = true 129 | db.inactivedb = append(db.inactivedb[:i], db.inactivedb[i+1:]...) 130 | i-- 131 | db.activedb = append(db.activedb, val) 132 | db.stats[val].LastActive = time.Now().Format(time.RFC1123) 133 | db.stats[val].Error = nil 134 | dbLengthMutex.Lock() 135 | db.length++ 136 | dbLengthMutex.Unlock() 137 | } 138 | } 139 | return err 140 | } 141 | 142 | // SelectContext using slave db. 143 | func (db *DB) SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error { 144 | return db.sqlxdb[db.slave()].SelectContext(ctx, dest, query, args...) 145 | } 146 | 147 | // SelectMasterContext using master db. 148 | func (db *DB) SelectMasterContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error { 149 | return db.sqlxdb[0].SelectContext(ctx, dest, query, args...) 150 | } 151 | 152 | // GetContext using slave. 153 | func (db *DB) GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error { 154 | return db.sqlxdb[db.slave()].GetContext(ctx, dest, query, args...) 155 | } 156 | 157 | // GetMasterContext using master. 158 | func (db *DB) GetMasterContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error { 159 | return db.sqlxdb[0].GetContext(ctx, dest, query, args...) 160 | } 161 | 162 | // PrepareContext return sql stmt 163 | func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error) { 164 | var err error 165 | stmt := new(Stmt) 166 | stmts := make([]*sql.Stmt, len(db.sqlxdb)) 167 | 168 | for i := range db.sqlxdb { 169 | stmts[i], err = db.sqlxdb[i].PrepareContext(ctx, query) 170 | 171 | if err != nil { 172 | return nil, err 173 | } 174 | } 175 | stmt.db = db 176 | stmt.stmts = stmts 177 | return stmt, nil 178 | } 179 | 180 | // PreparexContext sqlx stmt 181 | func (db *DB) PreparexContext(ctx context.Context, query string) (*Stmtx, error) { 182 | var err error 183 | stmts := make([]*sqlx.Stmt, len(db.sqlxdb)) 184 | 185 | for i := range db.sqlxdb { 186 | stmts[i], err = db.sqlxdb[i].PreparexContext(ctx, query) 187 | 188 | if err != nil { 189 | return nil, err 190 | } 191 | } 192 | 193 | return &Stmtx{db: db, stmts: stmts}, nil 194 | } 195 | 196 | // QueryContext queries the database and returns an *sql.Rows. 197 | func (db *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { 198 | r, err := db.sqlxdb[db.slave()].QueryContext(ctx, query, args...) 199 | return r, err 200 | } 201 | 202 | // QueryRowContext queries the database and returns an *sqlx.Row. 203 | func (db *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { 204 | rows := db.sqlxdb[db.slave()].QueryRowContext(ctx, query, args...) 205 | return rows 206 | } 207 | 208 | // QueryxContext queries the database and returns an *sqlx.Rows. 209 | func (db *DB) QueryxContext(ctx context.Context, query string, args ...interface{}) (*sqlx.Rows, error) { 210 | r, err := db.sqlxdb[db.slave()].QueryxContext(ctx, query, args...) 211 | return r, err 212 | } 213 | 214 | // QueryRowxContext queries the database and returns an *sqlx.Row. 215 | func (db *DB) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *sqlx.Row { 216 | rows := db.sqlxdb[db.slave()].QueryRowxContext(ctx, query, args...) 217 | return rows 218 | } 219 | 220 | // ExecContext using master db 221 | func (db *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { 222 | return db.sqlxdb[0].ExecContext(ctx, query, args...) 223 | } 224 | 225 | // MustExecContext (panic) runs MustExec using master database. 226 | func (db *DB) MustExecContext(ctx context.Context, query string, args ...interface{}) sql.Result { 227 | return db.sqlxdb[0].MustExecContext(ctx, query, args...) 228 | } 229 | 230 | // ExecContext will always go to production 231 | func (st *Stmtx) ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error) { 232 | return st.stmts[0].ExecContext(ctx, args...) 233 | } 234 | 235 | // QueryContext will always go to slave 236 | func (st *Stmtx) QueryContext(ctx context.Context, args ...interface{}) (*sql.Rows, error) { 237 | return st.stmts[st.db.slave()].QueryContext(ctx, args...) 238 | } 239 | 240 | // QueryMasterContext will use master db 241 | func (st *Stmtx) QueryMasterContext(ctx context.Context, args ...interface{}) (*sql.Rows, error) { 242 | return st.stmts[0].QueryContext(ctx, args...) 243 | } 244 | 245 | // QueryRowContext will always go to slave 246 | func (st *Stmtx) QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row { 247 | return st.stmts[st.db.slave()].QueryRowContext(ctx, args...) 248 | } 249 | 250 | // QueryRowMasterContext will use master db 251 | func (st *Stmtx) QueryRowMasterContext(ctx context.Context, args ...interface{}) *sql.Row { 252 | return st.stmts[0].QueryRowContext(ctx, args...) 253 | } 254 | 255 | // MustExecContext using master database 256 | func (st *Stmtx) MustExecContext(ctx context.Context, args ...interface{}) sql.Result { 257 | return st.stmts[0].MustExecContext(ctx, args...) 258 | } 259 | 260 | // QueryxContext will always go to slave 261 | func (st *Stmtx) QueryxContext(ctx context.Context, args ...interface{}) (*sqlx.Rows, error) { 262 | return st.stmts[st.db.slave()].QueryxContext(ctx, args...) 263 | } 264 | 265 | // QueryRowxContext will always go to slave 266 | func (st *Stmtx) QueryRowxContext(ctx context.Context, args ...interface{}) *sqlx.Row { 267 | return st.stmts[st.db.slave()].QueryRowxContext(ctx, args...) 268 | } 269 | 270 | // QueryRowxMasterContext will always go to master 271 | func (st *Stmtx) QueryRowxMasterContext(ctx context.Context, args ...interface{}) *sqlx.Row { 272 | return st.stmts[0].QueryRowxContext(ctx, args...) 273 | } 274 | 275 | // GetContext will always go to slave 276 | func (st *Stmtx) GetContext(ctx context.Context, dest interface{}, args ...interface{}) error { 277 | return st.stmts[st.db.slave()].GetContext(ctx, dest, args...) 278 | } 279 | 280 | // GetMasterContext will always go to master 281 | func (st *Stmtx) GetMasterContext(ctx context.Context, dest interface{}, args ...interface{}) error { 282 | return st.stmts[0].GetContext(ctx, dest, args...) 283 | } 284 | 285 | // SelectContext will always go to slave 286 | func (st *Stmtx) SelectContext(ctx context.Context, dest interface{}, args ...interface{}) error { 287 | return st.stmts[st.db.slave()].SelectContext(ctx, dest, args...) 288 | } 289 | 290 | // SelectMasterContext will always go to master 291 | func (st *Stmtx) SelectMasterContext(ctx context.Context, dest interface{}, args ...interface{}) error { 292 | return st.stmts[0].SelectContext(ctx, dest, args...) 293 | } 294 | 295 | // BeginTx return sql.Tx 296 | func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) { 297 | return db.Master().BeginTx(ctx, opts) 298 | } 299 | 300 | // BeginTxx return sqlx.Tx 301 | func (db *DB) BeginTxx(ctx context.Context, opts *sql.TxOptions) (*sqlx.Tx, error) { 302 | return db.Master().BeginTxx(ctx, opts) 303 | } 304 | --------------------------------------------------------------------------------