func (s *Shard) Transact(txFun TxFunc) errs.Err { conn, stdErr := s.db.Begin() if stdErr != nil { return errs.Wrap(stdErr, errs.Info{"Description": "Could not open transaction"}) } defer func() { if panicErr := recover(); panicErr != nil { rbErr := conn.Rollback() panic(errs.New(errs.Info{ "Description": "Panic during sql transcation", "PanicErr": panicErr, "RollbackErr": rbErr, })) } }() err := txFun(&Shard{s.DBName, nil, conn}) if err != nil { rbErr := conn.Rollback() if rbErr != nil { return errs.Wrap(rbErr, errs.Info{"Description": "Transact rollback error", "TransactionError": err}) } } else { stdErr = conn.Commit() if stdErr != nil { return errs.Wrap(stdErr, errs.Info{"Description": "Could not commit transaction"}) } } return nil }
func do(method, url, contentType string, bodyReader io.Reader) (statusCode int, responseBody string, err errs.Err) { req, stdErr := http.NewRequest(method, url, bodyReader) if stdErr != nil { err = errs.Wrap(stdErr, errs.Info{"URL": url}) return } if contentType != "" { req.Header.Set("Content-Type", contentType) } req.Close = true req.Header.Set("Connection", "close") res, stdErr := http.DefaultClient.Do(req) if stdErr != nil { err = errs.Wrap(stdErr, errs.Info{"URL": url}) return } defer res.Body.Close() statusCode = res.StatusCode bodyBytes, stdErr := ioutil.ReadAll(res.Body) if stdErr != nil { err = errs.Wrap(stdErr, errs.Info{"URL": url}) return } responseBody = string(bodyBytes) return }
func (s *Shard) queryOne(query string, args []interface{}, out interface{}) (found bool, err errs.Err) { rows, err := s.Query(query, args...) if err != nil { return } defer rows.Close() if rows.Next() { stdErr := rows.Scan(out) if stdErr != nil { err = errs.Wrap(stdErr, errInfo("queryOne rows.Scan error", query, args)) return } if rows.Next() { err = errs.New(errInfo("queryOne query returned too many rows", query, args)) return } found = true } stdErr := rows.Err() if stdErr != nil { err = errs.Wrap(stdErr, errInfo("queryOne rows.Err", query, args)) return } return }
func (s *Shard) scanOne(output interface{}, query string, required bool, args ...interface{}) (found bool, err errs.Err) { // Check types var outputReflectionPtr = reflect.ValueOf(output) if !outputReflectionPtr.IsValid() { panic(scanOneTypeError) } if outputReflectionPtr.Kind() != reflect.Ptr { panic(scanOneTypeError) } var outputReflection = outputReflectionPtr.Elem() if outputReflection.Kind() != reflect.Ptr { panic(scanOneTypeError) } // Query DB rows, err := s.Query(query, args...) if err != nil { return } defer rows.Close() // Reflect onto struct columns, stdErr := rows.Columns() if stdErr != nil { err = errs.Wrap(stdErr, errInfo("rows.Columns() error", query, args)) return } if !rows.Next() { return } var vStruct reflect.Value if outputReflection.IsNil() { structPtrVal := reflect.New(outputReflection.Type().Elem()) outputReflection.Set(structPtrVal) vStruct = structPtrVal.Elem() } else { vStruct = outputReflection.Elem() } err = structFromRow(vStruct, columns, rows, query, args) if err != nil { return } if rows.Next() { err = errs.New(errInfo("scanOne got multiple rows", query, args)) return } stdErr = rows.Err() if stdErr != nil { err = errs.Wrap(stdErr, errInfo("scanOne rows.Err() error", query, args)) return } found = true return }
func JSONBytesIndent(v interface{}, prefix, indent string) ([]byte, errs.Err) { jsonBytes, stdErr := json.MarshalIndent(v, prefix, indent) if stdErr != nil { return nil, errs.Wrap(stdErr, errs.Info{}) } return jsonBytes, nil }
func mymysqlDriverOpener(username, password, dbName, host string, port int, connVars funGoSql.ConnVariables) (*sql.DB, errs.Err) { db, stdErr := sql.Open("sqlite3", dbName) if stdErr != nil { return nil, errs.Wrap(stdErr, errs.Info{}) } return db, nil }
func DecodeJSON(reader io.Reader, v interface{}) errs.Err { stdErr := json.NewDecoder(reader).Decode(v) if stdErr != nil { return errs.Wrap(stdErr, errs.Info{}) } return nil }
func ParseJSONBytes(jsonBytes []byte, v interface{}) errs.Err { stdErr := json.Unmarshal(jsonBytes, v) if stdErr != nil { return errs.Wrap(stdErr, errs.Info{"JSON": string(jsonBytes)}, "Could not parse JSON") } return nil }
func JSONBytes(v interface{}) ([]byte, errs.Err) { bytes, stdErr := json.Marshal(v) if stdErr != nil { return nil, errs.Wrap(stdErr, errs.Info{}, "Could not convert to JSON") } return bytes, nil }
// Execute with fixed args func (s *Shard) Exec(query string, args ...interface{}) (sql.Result, errs.Err) { fixArgs(args) res, stdErr := s.sqlConn.Exec(query, args...) if stdErr != nil { return nil, errs.Wrap(stdErr, errInfo("Exec sqlConn.Exec() error", query, args)) } return res, nil }
func Open(path string) (file *os.File, err errs.Err) { file, stdErr := os.Open(path) if stdErr != nil { err = errs.Wrap(stdErr, errs.Info{"Path": path}) return } return }
// Query with fixed args func (s *Shard) Query(query string, args ...interface{}) (*sql.Rows, errs.Err) { fixArgs(args) rows, stdErr := s.sqlConn.Query(query, args...) if stdErr != nil { return nil, errs.Wrap(stdErr, errInfo("Query sqlConn.Query() error", query, args)) } return rows, nil }
func goSqlDriverOpener(username, password, dbName, host string, port int, connVars funGoSql.ConnVariables) (*sql.DB, errs.Err) { sourceString := fmt.Sprintf( "%s:%s@tcp(%s:%d)/%s?%s", username, password, host, port, dbName, connVars.Join("&")) db, stdErr := sql.Open("mysql", sourceString) if stdErr != nil { return nil, errs.Wrap(stdErr, errs.Info{}) } return db, nil }
func mymysqlDriverOpener(username, password, dbName, host string, port int, connVars funGoSql.ConnVariables) (*sql.DB, error) { sourceString := fmt.Sprintf( "tcp:%s:%d,%s*%s/%s/%s", host, port, connVars.Join(","), dbName, username, password) db, stdErr := sql.Open("mymysql", sourceString) if stdErr != nil { return nil, errs.Wrap(stdErr, errs.Info{}) } return db, nil }
func (s *Shard) Insert(query string, args ...interface{}) (id int64, err errs.Err) { res, err := s.Exec(query, args...) if err != nil { return } id, stdErr := res.LastInsertId() if stdErr != nil { err = errs.Wrap(stdErr, errInfo("Insert LastInsertIderror", query, args)) return } return }
func (s *Shard) Update(query string, args ...interface{}) (rowsAffected int64, err errs.Err) { res, err := s.Exec(query, args...) if err != nil { return } rowsAffected, stdErr := res.RowsAffected() if stdErr != nil { err = errs.Wrap(stdErr, errInfo("Update RowsAffected error", query, args)) return } return }
func Uid(numChars int) (uid string, err errs.Err) { if numChars%4 != 0 { err = errs.New(nil, "uid length must be a multiple of 4") return } buf := make([]byte, numChars) _, stdErr := io.ReadFull(rand.Reader, buf) if stdErr != nil { err = errs.Wrap(stdErr, nil) return } uid = base64.URLEncoding.EncodeToString(buf) return }
func scanColumnValue(column string, reflectVal reflect.Value, value *sql.RawBytes, query string, args []interface{}) errs.Err { bytes := []byte(*value) if bytes == nil { return nil // Leave struct field empty } switch reflectVal.Kind() { case reflect.String: reflectVal.SetString(string(bytes)) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: uintVal, stdErr := strconv.ParseUint(string(bytes), 10, 64) if stdErr != nil { return errs.Wrap(stdErr, errInfo("strconv.ParseUint error", query, args, errs.Info{"Bytes": bytes})) } reflectVal.SetUint(reflect.ValueOf(uintVal).Uint()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: intVal, stdErr := strconv.ParseInt(string(bytes), 10, 64) if stdErr != nil { return errs.Wrap(stdErr, errInfo("strconv.ParseInt error", query, args, errs.Info{"Bytes": bytes})) } reflectVal.SetInt(reflect.ValueOf(intVal).Int()) case reflect.Bool: boolVal, stdErr := strconv.ParseBool(string(bytes)) if stdErr != nil { return errs.Wrap(stdErr, errInfo("strconv.ParseBool error", query, args, errs.Info{"Bytes": bytes})) } reflectVal.SetBool(reflect.ValueOf(boolVal).Bool()) default: if reflectVal.Kind() == reflect.Slice { // && reflectVal. == reflect.Uint8 { // byte slice reflectVal.SetBytes(bytes) } else { return errs.New(errInfo("Bad row value for column "+column+": "+reflectVal.Kind().String(), query, args)) } } return nil }
func newShard(s *ShardSet, dbName string, autoIncrementOffset int) (*Shard, errs.Err) { connVars := ConnVariables{ "autocommit": "true", "clientFoundRows": "true", "charset": "utf8mb4", "collation": "utf8_unicode_ci", "auto_increment_increment": strconv.Itoa(s.maxShards), "auto_increment_offset": strconv.Itoa(autoIncrementOffset), "sql_mode": "STRICT_ALL_TABLES", } db, err := dbOpener(s.username, s.password, dbName, s.host, s.port, connVars) if err != nil { return nil, err } db.SetMaxOpenConns(s.maxConns) // db.SetMaxIdleConns(n) stdErr := db.Ping() if stdErr != nil { return nil, errs.Wrap(stdErr, nil) } return &Shard{dbName, db, db}, nil }
func structFromRow(outputItemStructVal reflect.Value, columns []string, rows *sql.Rows, query string, args []interface{}) errs.Err { vals := make([]interface{}, len(columns)) for i, _ := range columns { vals[i] = &sql.RawBytes{} } stdErr := rows.Scan(vals...) if stdErr != nil { return errs.Wrap(stdErr, errInfo("structFromRow error", query, args)) } for i, column := range columns { structFieldValue := outputItemStructVal.FieldByName(column) if !structFieldValue.IsValid() { fmt.Println("Warning: no corresponding struct field found for column: " + column) continue } err := scanColumnValue(column, structFieldValue, vals[i].(*sql.RawBytes), query, args) if err != nil { return err } } return nil }
func (s *Shard) Select(output interface{}, query string, args ...interface{}) errs.Err { // Check types var outputPtr = reflect.ValueOf(output) if outputPtr.Kind() != reflect.Ptr { return errs.New(errInfo("Select expects a pointer to a slice of items", query, args)) } var outputReflection = reflect.Indirect(outputPtr) if outputReflection.Kind() != reflect.Slice { return errs.New(errInfo("Select expects items to be a slice", query, args)) } if outputReflection.Len() != 0 { return errs.New(errInfo("Select expects items to be empty", query, args)) } outputReflection.Set(reflect.MakeSlice(outputReflection.Type(), 0, 0)) // Query DB var rows, err = s.Query(query, args...) if err != nil { return err } defer rows.Close() columns, stdErr := rows.Columns() if stdErr != nil { return errs.Wrap(stdErr, errInfo("Select rows.Columns error", query, args)) } valType := outputReflection.Type().Elem() isStruct := (valType.Kind() == reflect.Ptr && valType.Elem().Kind() == reflect.Struct) if isStruct { // Reflect onto structs for rows.Next() { structPtrVal := reflect.New(valType.Elem()) outputItemStructVal := structPtrVal.Elem() err = structFromRow(outputItemStructVal, columns, rows, query, args) if err != nil { return err } outputReflection.Set(reflect.Append(outputReflection, structPtrVal)) } } else { if len(columns) != 1 { return errs.New(errInfo("Select expected single column in select statement for slice of non-struct values", query, args)) } for rows.Next() { rawBytes := &sql.RawBytes{} stdErr = rows.Scan(rawBytes) if stdErr != nil { return errs.Wrap(stdErr, errInfo("Select rows.Scan error", query, args)) } outputValue := reflect.New(valType).Elem() err = scanColumnValue(columns[0], outputValue, rawBytes, query, args) if err != nil { return err } outputReflection.Set(reflect.Append(outputReflection, outputValue)) } } stdErr = rows.Err() if err != nil { return errs.Wrap(stdErr, errInfo("Select rows.Err() error", query, args)) } return nil }