// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. // package gdb import ( "database/sql" "errors" "fmt" "reflect" "regexp" "strings" "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/text/gregex" "github.com/gogf/gf/util/gconv" ) const ( gPATH_FILTER_KEY = "/database/gdb/gdb" ) var ( // lastOperatorReg is the regular expression object for a string // which has operator at its tail. lastOperatorReg = regexp.MustCompile(`[<>=]+\s*$`) ) // Master creates and returns a connection from master node if master-slave configured. // It returns the default connection if master-slave not configured. func (c *Core) Master() (*sql.DB, error) { return c.getSqlDb(true, c.schema.Val()) } // Slave creates and returns a connection from slave node if master-slave configured. // It returns the default connection if master-slave not configured. func (c *Core) Slave() (*sql.DB, error) { return c.getSqlDb(false, c.schema.Val()) } // Query commits one query SQL to underlying driver and returns the execution result. // It is most commonly used for data querying. func (c *Core) Query(query string, args ...interface{}) (rows *sql.Rows, err error) { link, err := c.DB.Slave() if err != nil { return nil, err } return c.DB.DoQuery(link, query, args...) } // doQuery commits the query string and its arguments to underlying driver // through given link object and returns the execution result. func (c *Core) DoQuery(link Link, query string, args ...interface{}) (rows *sql.Rows, err error) { query, args = formatQuery(query, args) query, args = c.DB.HandleSqlBeforeCommit(link, query, args) if c.DB.GetDebug() { mTime1 := gtime.TimestampMilli() rows, err = link.Query(query, args...) mTime2 := gtime.TimestampMilli() s := &Sql{ Sql: query, Args: args, Format: bindArgsToQuery(query, args), Error: err, Start: mTime1, End: mTime2, } c.writeSqlToLogger(s) } else { rows, err = link.Query(query, args...) } if err == nil { return rows, nil } else { err = formatError(err, query, args...) } return nil, err } // Exec commits one query SQL to underlying driver and returns the execution result. // It is most commonly used for data inserting and updating. func (c *Core) Exec(query string, args ...interface{}) (result sql.Result, err error) { link, err := c.DB.Master() if err != nil { return nil, err } return c.DB.DoExec(link, query, args...) } // doExec commits the query string and its arguments to underlying driver // through given link object and returns the execution result. func (c *Core) DoExec(link Link, query string, args ...interface{}) (result sql.Result, err error) { query, args = formatQuery(query, args) query, args = c.DB.HandleSqlBeforeCommit(link, query, args) if c.DB.GetDebug() { mTime1 := gtime.TimestampMilli() result, err = link.Exec(query, args...) mTime2 := gtime.TimestampMilli() s := &Sql{ Sql: query, Args: args, Format: bindArgsToQuery(query, args), Error: err, Start: mTime1, End: mTime2, } c.writeSqlToLogger(s) } else { result, err = link.Exec(query, args...) } return result, formatError(err, query, args...) } // Prepare creates a prepared statement for later queries or executions. // Multiple queries or executions may be run concurrently from the // returned statement. // The caller must call the statement's Close method // when the statement is no longer needed. // // The parameter specifies whether executing the sql on master node, // or else it executes the sql on slave node if master-slave configured. func (c *Core) Prepare(query string, execOnMaster ...bool) (*sql.Stmt, error) { err := (error)(nil) link := (Link)(nil) if len(execOnMaster) > 0 && execOnMaster[0] { if link, err = c.DB.Master(); err != nil { return nil, err } } else { if link, err = c.DB.Slave(); err != nil { return nil, err } } return c.DB.DoPrepare(link, query) } // doPrepare calls prepare function on given link object and returns the statement object. func (c *Core) DoPrepare(link Link, query string) (*sql.Stmt, error) { return link.Prepare(query) } // GetAll queries and returns data records from database. func (c *Core) GetAll(query string, args ...interface{}) (Result, error) { return c.DB.DoGetAll(nil, query, args...) } // doGetAll queries and returns data records from database. func (c *Core) DoGetAll(link Link, query string, args ...interface{}) (result Result, err error) { if link == nil { link, err = c.DB.Slave() if err != nil { return nil, err } } rows, err := c.DB.DoQuery(link, query, args...) if err != nil || rows == nil { return nil, err } defer rows.Close() return c.DB.rowsToResult(rows) } // GetOne queries and returns one record from database. func (c *Core) GetOne(query string, args ...interface{}) (Record, error) { list, err := c.DB.GetAll(query, args...) if err != nil { return nil, err } if len(list) > 0 { return list[0], nil } return nil, nil } // GetStruct queries one record from database and converts it to given struct. // The parameter should be a pointer to struct. func (c *Core) GetStruct(pointer interface{}, query string, args ...interface{}) error { one, err := c.DB.GetOne(query, args...) if err != nil { return err } if len(one) == 0 { return sql.ErrNoRows } return one.Struct(pointer) } // GetStructs queries records from database and converts them to given struct. // The parameter should be type of struct slice: []struct/[]*struct. func (c *Core) GetStructs(pointer interface{}, query string, args ...interface{}) error { all, err := c.DB.GetAll(query, args...) if err != nil { return err } if len(all) == 0 { return sql.ErrNoRows } return all.Structs(pointer) } // GetScan queries one or more records from database and converts them to given struct or // struct array. // // If parameter is type of struct pointer, it calls GetStruct internally for // the conversion. If parameter is type of slice, it calls GetStructs internally // for conversion. func (c *Core) GetScan(pointer interface{}, query string, args ...interface{}) error { t := reflect.TypeOf(pointer) k := t.Kind() if k != reflect.Ptr { return fmt.Errorf("params should be type of pointer, but got: %v", k) } k = t.Elem().Kind() switch k { case reflect.Array, reflect.Slice: return c.DB.GetStructs(pointer, query, args...) case reflect.Struct: return c.DB.GetStruct(pointer, query, args...) } return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k) } // GetValue queries and returns the field value from database. // The sql should queries only one field from database, or else it returns only one // field of the result. func (c *Core) GetValue(query string, args ...interface{}) (Value, error) { one, err := c.DB.GetOne(query, args...) if err != nil { return nil, err } for _, v := range one { return v, nil } return nil, nil } // GetCount queries and returns the count from database. func (c *Core) GetCount(query string, args ...interface{}) (int, error) { // If the query fields do not contains function "COUNT", // it replaces the query string and adds the "COUNT" function to the fields. if !gregex.IsMatchString(`(?i)SELECT\s+COUNT\(.+\)\s+FROM`, query) { query, _ = gregex.ReplaceString(`(?i)(SELECT)\s+(.+)\s+(FROM)`, `$1 COUNT($2) $3`, query) } value, err := c.DB.GetValue(query, args...) if err != nil { return 0, err } return value.Int(), nil } // PingMaster pings the master node to check authentication or keeps the connection alive. func (c *Core) PingMaster() error { if master, err := c.DB.Master(); err != nil { return err } else { return master.Ping() } } // PingSlave pings the slave node to check authentication or keeps the connection alive. func (c *Core) PingSlave() error { if slave, err := c.DB.Slave(); err != nil { return err } else { return slave.Ping() } } // Begin starts and returns the transaction object. // You should call Commit or Rollback functions of the transaction object // if you no longer use the transaction. Commit or Rollback functions will also // close the transaction automatically. func (c *Core) Begin() (*TX, error) { if master, err := c.DB.Master(); err != nil { return nil, err } else { if tx, err := master.Begin(); err == nil { return &TX{ db: c.DB, tx: tx, master: master, }, nil } else { return nil, err } } } // Insert does "INSERT INTO ..." statement for the table. // If there's already one unique record of the data in the table, it returns error. // // The parameter can be type of map/gmap/struct/*struct/[]map/[]struct, etc. // Eg: // Data(g.Map{"uid": 10000, "name":"john"}) // Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) // // The parameter specifies the batch operation count when given data is slice. func (c *Core) Insert(table string, data interface{}, batch ...int) (sql.Result, error) { return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_DEFAULT, batch...) } // InsertIgnore does "INSERT IGNORE INTO ..." statement for the table. // If there's already one unique record of the data in the table, it ignores the inserting. // // The parameter can be type of map/gmap/struct/*struct/[]map/[]struct, etc. // Eg: // Data(g.Map{"uid": 10000, "name":"john"}) // Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) // // The parameter specifies the batch operation count when given data is slice. func (c *Core) InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) { return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_IGNORE, batch...) } // Replace does "REPLACE INTO ..." statement for the table. // If there's already one unique record of the data in the table, it deletes the record // and inserts a new one. // // The parameter can be type of map/gmap/struct/*struct/[]map/[]struct, etc. // Eg: // Data(g.Map{"uid": 10000, "name":"john"}) // Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) // // The parameter can be type of map/gmap/struct/*struct/[]map/[]struct, etc. // If given data is type of slice, it then does batch replacing, and the optional parameter // specifies the batch operation count. func (c *Core) Replace(table string, data interface{}, batch ...int) (sql.Result, error) { return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_REPLACE, batch...) } // Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the table. // It updates the record if there's primary or unique index in the saving data, // or else it inserts a new record into the table. // // The parameter can be type of map/gmap/struct/*struct/[]map/[]struct, etc. // Eg: // Data(g.Map{"uid": 10000, "name":"john"}) // Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) // // If given data is type of slice, it then does batch saving, and the optional parameter // specifies the batch operation count. func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, error) { return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_SAVE, batch...) } // doInsert inserts or updates data for given table. // // The parameter can be type of map/gmap/struct/*struct/[]map/[]struct, etc. // Eg: // Data(g.Map{"uid": 10000, "name":"john"}) // Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) // // The parameter