diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index a37fccdf4..40280f893 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -364,7 +364,7 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error n.Name = nodeSchema node = &n } - // Cache the underlying connection object by node. + // Cache the underlying connection pool object by node. v := c.cache.GetOrSetFuncLock(node.String(), func() interface{} { sqlDb, err = c.DB.Open(node) if err != nil { diff --git a/database/gdb/gdb_batch_result.go b/database/gdb/gdb_batch_result.go deleted file mode 100644 index 256716edc..000000000 --- a/database/gdb/gdb_batch_result.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2019 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" - -// batchSqlResult is execution result for batch operations. -type batchSqlResult struct { - rowsAffected int64 - lastResult sql.Result -} - -// see sql.Result.RowsAffected -func (r *batchSqlResult) RowsAffected() (int64, error) { - return r.rowsAffected, nil -} - -// see sql.Result.LastInsertId -func (r *batchSqlResult) LastInsertId() (int64, error) { - return r.lastResult.LastInsertId() -} diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 4e8cb6c39..92e2ead29 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -504,7 +504,7 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i holders = append(holders, "?") } // Prepare the batch result pointer. - batchResult := new(batchSqlResult) + batchResult := new(SqlResult) charL, charR := c.DB.GetChars() keysStr := charL + strings.Join(keys, charR+","+charL) + charR valueHolderStr := "(" + strings.Join(holders, ",") + ")" @@ -555,8 +555,8 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i if n, err := r.RowsAffected(); err != nil { return r, err } else { - batchResult.lastResult = r - batchResult.rowsAffected += n + batchResult.result = r + batchResult.affected += n } params = params[:0] values = values[:0] diff --git a/database/gdb/gdb_driver_oracle.go b/database/gdb/gdb_driver_oracle.go index fbd48c339..374abffc3 100644 --- a/database/gdb/gdb_driver_oracle.go +++ b/database/gdb/gdb_driver_oracle.go @@ -377,7 +377,7 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{}, keys = append(keys, k) holders = append(holders, "?") } - batchResult := new(batchSqlResult) + batchResult := new(SqlResult) charL, charR := d.DB.GetChars() keyStr := charL + strings.Join(keys, charL+","+charR) + charR valueHolderStr := strings.Join(holders, ",") @@ -393,8 +393,8 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{}, if n, err := r.RowsAffected(); err != nil { return r, err } else { - batchResult.lastResult = r - batchResult.rowsAffected += n + batchResult.result = r + batchResult.affected += n } } return batchResult, nil @@ -421,8 +421,8 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{}, if n, err := r.RowsAffected(); err != nil { return r, err } else { - batchResult.lastResult = r - batchResult.rowsAffected += n + batchResult.result = r + batchResult.affected += n } params = params[:0] intoStr = intoStr[:0] @@ -437,8 +437,8 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{}, if n, err := r.RowsAffected(); err != nil { return r, err } else { - batchResult.lastResult = r - batchResult.rowsAffected += n + batchResult.result = r + batchResult.affected += n } } return batchResult, nil diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index b58f932e0..cead92cc0 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -288,12 +288,19 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) ( // Eg: Where/And/Or("uid>=", 1) newWhere += "?" } else if gregex.IsMatchString(`^[\w\.\-]+$`, newWhere) { - if len(newArgs) == 1 && utils.IsArray(newArgs[0]) { - // Eg: Where("id", []int{1,2,3}) - newWhere += " IN (?)" - } else { - // Eg: Where/And/Or("uid", 1) - newWhere += "=?" + newWhere = db.QuoteString(newWhere) + if len(newArgs) > 0 { + if utils.IsArray(newArgs[0]) { + // Eg: Where("id", []int{1,2,3}) + newWhere += " IN (?)" + } else if empty.IsNil(newArgs[0]) { + // Eg: Where("id", nil) + newWhere += " IS NULL" + newArgs = nil + } else { + // Eg: Where/And/Or("uid", 1) + newWhere += "=?" + } } } } @@ -323,7 +330,7 @@ func formatWhereInterfaces(db DB, where []interface{}, buffer *bytes.Buffer, new // formatWhereKeyValue handles each key-value pair of the parameter map. func formatWhereKeyValue(db DB, buffer *bytes.Buffer, newArgs []interface{}, key string, value interface{}) []interface{} { - key = db.QuoteWord(key) + quotedKey := db.QuoteWord(key) if buffer.Len() > 0 { buffer.WriteString(" AND ") } @@ -331,36 +338,43 @@ func formatWhereKeyValue(db DB, buffer *bytes.Buffer, newArgs []interface{}, key // the key string, it automatically adds '?' holder chars according to its arguments count // and converts it to "IN" statement. rv := reflect.ValueOf(value) - switch rv.Kind() { + kind := rv.Kind() + switch kind { case reflect.Slice, reflect.Array: - count := gstr.Count(key, "?") + count := gstr.Count(quotedKey, "?") if count == 0 { - buffer.WriteString(key + " IN(?)") + buffer.WriteString(quotedKey + " IN(?)") newArgs = append(newArgs, value) } else if count != rv.Len() { - buffer.WriteString(key) + buffer.WriteString(quotedKey) newArgs = append(newArgs, value) } else { - buffer.WriteString(key) + buffer.WriteString(quotedKey) newArgs = append(newArgs, gconv.Interfaces(value)...) } default: - if value == nil { - buffer.WriteString(key) + if value == nil || empty.IsNil(rv) { + if gregex.IsMatchString(`^[\w\.\-]+$`, key) { + // The key is a single field name. + buffer.WriteString(quotedKey + " IS NULL") + } else { + // The key may have operation chars. + buffer.WriteString(quotedKey) + } } else { // It also supports "LIKE" statement, which we considers it an operator. - key = gstr.Trim(key) - if gstr.Pos(key, "?") == -1 { + quotedKey = gstr.Trim(quotedKey) + if gstr.Pos(quotedKey, "?") == -1 { like := " like" - if len(key) > len(like) && gstr.Equal(key[len(key)-len(like):], like) { - buffer.WriteString(key + " ?") - } else if lastOperatorReg.MatchString(key) { - buffer.WriteString(key + " ?") + if len(quotedKey) > len(like) && gstr.Equal(quotedKey[len(quotedKey)-len(like):], like) { + buffer.WriteString(quotedKey + " ?") + } else if lastOperatorReg.MatchString(quotedKey) { + buffer.WriteString(quotedKey + " ?") } else { - buffer.WriteString(key + "=?") + buffer.WriteString(quotedKey + "=?") } } else { - buffer.WriteString(key) + buffer.WriteString(quotedKey) } newArgs = append(newArgs, value) } diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index 730483060..0e0f76084 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -7,18 +7,9 @@ package gdb import ( - "database/sql" - "errors" - "fmt" - "github.com/gogf/gf/container/garray" - "github.com/gogf/gf/container/gmap" - "reflect" "time" - "github.com/gogf/gf/container/gset" "github.com/gogf/gf/text/gstr" - - "github.com/gogf/gf/util/gconv" ) // Model is the DAO for ORM. @@ -31,7 +22,7 @@ type Model struct { tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud". fields string // Operation fields, multiple fields joined using char ','. fieldsEx string // Excluded operation fields, multiple fields joined using char ','. - whereArgs []interface{} // Arguments for where operation. + extraArgs []interface{} // Extra custom arguments for sql. whereHolder []*whereHolder // Condition strings for where operation. groupBy string // Used for "group by" statement. orderBy string // Used for "order by" statement. @@ -42,6 +33,7 @@ type Model struct { data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc. batch int // Batch number for batch Insert/Replace/Save operations. filter bool // Filter data and where key-value pairs according to the fields of the table. + lockInfo string // Lock for update or in shared lock. cacheEnabled bool // Enable sql result cache feature. cacheDuration time.Duration // Cache TTL duration. cacheName string // Cache name for custom operation. @@ -167,9 +159,9 @@ func (m *Model) Clone() *Model { } *newModel = *m // Deep copy slice attributes. - if n := len(m.whereArgs); n > 0 { - newModel.whereArgs = make([]interface{}, n) - copy(newModel.whereArgs, m.whereArgs) + if n := len(m.extraArgs); n > 0 { + newModel.extraArgs = make([]interface{}, n) + copy(newModel.extraArgs, m.extraArgs) } if n := len(m.whereHolder); n > 0 { newModel.whereHolder = make([]*whereHolder, n) @@ -203,1026 +195,3 @@ func (m *Model) Safe(safe ...bool) *Model { } return m } - -// getModel creates and returns a cloned model of current model if is true, or else it returns -// the current model. -func (m *Model) getModel() *Model { - if !m.safe { - return m - } else { - return m.Clone() - } -} - -// LeftJoin does "LEFT JOIN ... ON ..." statement on the model. -func (m *Model) LeftJoin(table string, on string) *Model { - model := m.getModel() - model.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on) - return model -} - -// RightJoin does "RIGHT JOIN ... ON ..." statement on the model. -func (m *Model) RightJoin(table string, on string) *Model { - model := m.getModel() - model.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on) - return model -} - -// InnerJoin does "INNER JOIN ... ON ..." statement on the model. -func (m *Model) InnerJoin(table string, on string) *Model { - model := m.getModel() - model.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on) - return model -} - -// Fields sets the operation fields of the model, multiple fields joined using char ','. -func (m *Model) Fields(fields string) *Model { - model := m.getModel() - model.fields = fields - return model -} - -// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','. -func (m *Model) FieldsEx(fields string) *Model { - if gstr.Contains(m.tables, " ") { - panic("function FieldsEx supports only single table operations") - } - model := m.getModel() - model.fieldsEx = fields - fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ",")) - if m, err := m.db.TableFields(m.tables); err == nil { - model.fields = "" - for k, _ := range m { - if fieldsExSet.Contains(k) { - continue - } - if len(model.fields) > 0 { - model.fields += "," - } - model.fields += k - } - } - return model -} - -// FieldsStr retrieves and returns all fields from the table, joined with char ','. -// The optional parameter specifies the prefix for each field, eg: FieldsStr("u."). -func (m *Model) FieldsStr(prefix ...string) string { - prefixStr := "" - if len(prefix) > 0 { - prefixStr = prefix[0] - } - if m, err := m.db.TableFields(m.tables); err == nil { - fieldsArray := garray.NewStrArraySize(len(m), len(m)) - for _, field := range m { - fieldsArray.Set(field.Index, prefixStr+field.Name) - } - return fieldsArray.Join(",") - } - return "" -} - -// FieldsExStr retrieves and returns fields which are not in parameter from the table, -// joined with char ','. -// The parameter specifies the fields that are excluded. -// The optional parameter specifies the prefix for each field, eg: FieldsExStr("id", "u."). -func (m *Model) FieldsExStr(fields string, prefix ...string) string { - prefixStr := "" - if len(prefix) > 0 { - prefixStr = prefix[0] - } - if m, err := m.db.TableFields(m.tables); err == nil { - fieldsArray := garray.NewStrArraySize(len(m), len(m)) - fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ",")) - for _, field := range m { - if fieldsExSet.Contains(field.Name) { - continue - } - fieldsArray.Set(field.Index, prefixStr+field.Name) - } - fieldsArray.FilterEmpty() - return fieldsArray.Join(",") - } - return "" -} - -// Option adds extra operation option for the model. -func (m *Model) Option(option int) *Model { - model := m.getModel() - model.option = model.option | option - return model -} - -// OptionOmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers -// the data and where attributes for empty values. -// Deprecated, use OmitEmpty instead. -func (m *Model) OptionOmitEmpty() *Model { - return m.Option(OPTION_OMITEMPTY) -} - -// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers -// the data and where attributes for empty values. -func (m *Model) OmitEmpty() *Model { - return m.Option(OPTION_OMITEMPTY) -} - -// Filter marks filtering the fields which does not exist in the fields of the operated table. -func (m *Model) Filter() *Model { - if gstr.Contains(m.tables, " ") { - panic("function Filter supports only single table operations") - } - model := m.getModel() - model.filter = true - return model -} - -// Where sets the condition statement for the model. The parameter can be type of -// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times, -// multiple conditions will be joined into where statement using "AND". -// Eg: -// Where("uid=10000") -// Where("uid", 10000) -// Where("money>? AND name like ?", 99999, "vip_%") -// Where("uid", 1).Where("name", "john") -// Where("status IN (?)", g.Slice{1,2,3}) -// Where("age IN(?,?)", 18, 50) -// Where(User{ Id : 1, UserName : "john"}) -func (m *Model) Where(where interface{}, args ...interface{}) *Model { - model := m.getModel() - if model.whereHolder == nil { - model.whereHolder = make([]*whereHolder, 0) - } - model.whereHolder = append(model.whereHolder, &whereHolder{ - operator: gWHERE_HOLDER_WHERE, - where: where, - args: args, - }) - return model -} - -// WherePri does the same logic as Model.Where except that if the parameter -// is a single condition like int/string/float/slice, it treats the condition as the primary -// key value. That is, if primary key is "id" and given parameter as "123", the -// WherePri function treats it as "id=123", but Model.Where treats it as string "123". -func (m *Model) WherePri(where interface{}, args ...interface{}) *Model { - if len(args) > 0 { - return m.Where(where, args...) - } - newWhere := GetPrimaryKeyCondition(m.getPrimaryKey(), where) - return m.Where(newWhere[0], newWhere[1:]...) -} - -// And adds "AND" condition to the where statement. -func (m *Model) And(where interface{}, args ...interface{}) *Model { - model := m.getModel() - if model.whereHolder == nil { - model.whereHolder = make([]*whereHolder, 0) - } - model.whereHolder = append(model.whereHolder, &whereHolder{ - operator: gWHERE_HOLDER_AND, - where: where, - args: args, - }) - return model -} - -// Or adds "OR" condition to the where statement. -func (m *Model) Or(where interface{}, args ...interface{}) *Model { - model := m.getModel() - if model.whereHolder == nil { - model.whereHolder = make([]*whereHolder, 0) - } - model.whereHolder = append(model.whereHolder, &whereHolder{ - operator: gWHERE_HOLDER_OR, - where: where, - args: args, - }) - return model -} - -// Group sets the "GROUP BY" statement for the model. -func (m *Model) Group(groupBy string) *Model { - model := m.getModel() - model.groupBy = m.db.QuoteString(groupBy) - return model -} - -// GroupBy is alias of Model.Group. -// See Model.Group. -// Deprecated. -func (m *Model) GroupBy(groupBy string) *Model { - return m.Group(groupBy) -} - -// Order sets the "ORDER BY" statement for the model. -func (m *Model) Order(orderBy string) *Model { - model := m.getModel() - model.orderBy = m.db.QuoteString(orderBy) - return model -} - -// OrderBy is alias of Model.Order. -// See Model.Order. -// Deprecated. -func (m *Model) OrderBy(orderBy string) *Model { - return m.Order(orderBy) -} - -// Limit sets the "LIMIT" statement for the model. -// The parameter can be either one or two number, if passed two number is passed, -// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]" -// statement. -func (m *Model) Limit(limit ...int) *Model { - model := m.getModel() - switch len(limit) { - case 1: - model.limit = limit[0] - case 2: - model.start = limit[0] - model.limit = limit[1] - } - return model -} - -// Offset sets the "OFFSET" statement for the model. -// It only makes sense for some databases like SQLServer, PostgreSQL, etc. -func (m *Model) Offset(offset int) *Model { - model := m.getModel() - model.offset = offset - return model -} - -// Page sets the paging number for the model. -// The parameter is started from 1 for paging. -// Note that, it differs that the Limit function start from 0 for "LIMIT" statement. -func (m *Model) Page(page, limit int) *Model { - model := m.getModel() - if page <= 0 { - page = 1 - } - model.start = (page - 1) * limit - model.limit = limit - return model -} - -// ForPage is alias of Model.Page. -// See Model.Page. -// Deprecated. -func (m *Model) ForPage(page, limit int) *Model { - return m.Page(page, limit) -} - -// Batch sets the batch operation number for the model. -func (m *Model) Batch(batch int) *Model { - model := m.getModel() - model.batch = batch - return model -} - -// Cache sets the cache feature for the model. It caches the result of the sql, which means -// if there's another same sql request, it just reads and returns the result from cache, it -// but not committed and executed into the database. -// -// If the parameter < 0, which means it clear the cache with given . -// If the parameter = 0, which means it never expires. -// If the parameter > 0, which means it expires after . -// -// The optional parameter is used to bind a name to the cache, which means you can later -// control the cache like changing the or clearing the cache with specified . -// -// Note that, the cache feature is disabled if the model is operating on a transaction. -func (m *Model) Cache(duration time.Duration, name ...string) *Model { - model := m.getModel() - model.cacheDuration = duration - if len(name) > 0 { - model.cacheName = name[0] - } - // It does not support cache on transaction. - if model.tx == nil { - model.cacheEnabled = true - } - return model -} - -// Data sets the operation data for the model. -// The parameter can be type of string/map/gmap/slice/struct/*struct, etc. -// Eg: -// Data("uid=10000") -// Data("uid", 10000) -// Data(g.Map{"uid": 10000, "name":"john"}) -// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) -func (m *Model) Data(data ...interface{}) *Model { - model := m.getModel() - if len(data) > 1 { - m := make(map[string]interface{}) - for i := 0; i < len(data); i += 2 { - m[gconv.String(data[i])] = data[i+1] - } - model.data = m - } else { - switch params := data[0].(type) { - case Result: - model.data = params.List() - case Record: - model.data = params.Map() - case List: - model.data = params - case Map: - model.data = params - default: - rv := reflect.ValueOf(params) - kind := rv.Kind() - if kind == reflect.Ptr { - rv = rv.Elem() - kind = rv.Kind() - } - switch kind { - case reflect.Slice, reflect.Array: - list := make(List, rv.Len()) - for i := 0; i < rv.Len(); i++ { - list[i] = DataToMapDeep(rv.Index(i).Interface()) - } - model.data = list - case reflect.Map, reflect.Struct: - model.data = DataToMapDeep(data[0]) - default: - model.data = data[0] - } - } - } - return model -} - -// Insert does "INSERT INTO ..." statement for the model. -// The optional parameter is the same as the parameter of Model.Data function, -// see Model.Data. -func (m *Model) Insert(data ...interface{}) (result sql.Result, err error) { - return m.doInsertWithOption(gINSERT_OPTION_DEFAULT, data...) -} - -// InsertIgnore does "INSERT IGNORE INTO ..." statement for the model. -// The optional parameter is the same as the parameter of Model.Data function, -// see Model.Data. -func (m *Model) InsertIgnore(data ...interface{}) (result sql.Result, err error) { - return m.doInsertWithOption(gINSERT_OPTION_IGNORE, data...) -} - -// doInsertWithOption inserts data with option parameter. -func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.Result, err error) { - if len(data) > 0 { - return m.Data(data...).Insert() - } - defer func() { - if err == nil { - m.checkAndRemoveCache() - } - }() - if m.data == nil { - return nil, errors.New("inserting into table with empty data") - } - if list, ok := m.data.(List); ok { - // Batch insert. - batch := 10 - if m.batch > 0 { - batch = m.batch - } - return m.db.DoBatchInsert( - m.getLink(true), - m.tables, - m.filterDataForInsertOrUpdate(list), - option, - batch, - ) - } else if data, ok := m.data.(Map); ok { - // Single insert. - return m.db.DoInsert( - m.getLink(true), - m.tables, - m.filterDataForInsertOrUpdate(data), - option, - ) - } - return nil, errors.New("inserting into table with invalid data type") -} - -// Replace does "REPLACE INTO ..." statement for the model. -// The optional parameter is the same as the parameter of Model.Data function, -// see Model.Data. -func (m *Model) Replace(data ...interface{}) (result sql.Result, err error) { - if len(data) > 0 { - return m.Data(data...).Replace() - } - defer func() { - if err == nil { - m.checkAndRemoveCache() - } - }() - if m.data == nil { - return nil, errors.New("replacing into table with empty data") - } - if list, ok := m.data.(List); ok { - // Batch replace. - batch := 10 - if m.batch > 0 { - batch = m.batch - } - return m.db.DoBatchInsert( - m.getLink(true), - m.tables, - m.filterDataForInsertOrUpdate(list), - gINSERT_OPTION_REPLACE, - batch, - ) - } else if data, ok := m.data.(Map); ok { - // Single insert. - return m.db.DoInsert( - m.getLink(true), - m.tables, - m.filterDataForInsertOrUpdate(data), - gINSERT_OPTION_REPLACE, - ) - } - return nil, errors.New("replacing into table with invalid data type") -} - -// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the model. -// The optional parameter is the same as the parameter of Model.Data function, -// see Model.Data. -// -// 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. -func (m *Model) Save(data ...interface{}) (result sql.Result, err error) { - if len(data) > 0 { - return m.Data(data...).Save() - } - defer func() { - if err == nil { - m.checkAndRemoveCache() - } - }() - if m.data == nil { - return nil, errors.New("saving into table with empty data") - } - if list, ok := m.data.(List); ok { - // Batch save. - batch := gDEFAULT_BATCH_NUM - if m.batch > 0 { - batch = m.batch - } - return m.db.DoBatchInsert( - m.getLink(true), - m.tables, - m.filterDataForInsertOrUpdate(list), - gINSERT_OPTION_SAVE, - batch, - ) - } else if data, ok := m.data.(Map); ok { - // Single save. - return m.db.DoInsert( - m.getLink(true), - m.tables, - m.filterDataForInsertOrUpdate(data), - gINSERT_OPTION_SAVE, - ) - } - return nil, errors.New("saving into table with invalid data type") -} - -// Update does "UPDATE ... " statement for the model. -// -// If the optional parameter is given, the dataAndWhere[0] is the updated data field, -// and dataAndWhere[1:] is treated as where condition fields. -// Also see Model.Data and Model.Where functions. -func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err error) { - if len(dataAndWhere) > 0 { - if len(dataAndWhere) > 2 { - return m.Data(dataAndWhere[0]).Where(dataAndWhere[1], dataAndWhere[2:]...).Update() - } else if len(dataAndWhere) == 2 { - return m.Data(dataAndWhere[0]).Where(dataAndWhere[1]).Update() - } else { - return m.Data(dataAndWhere[0]).Update() - } - } - defer func() { - if err == nil { - m.checkAndRemoveCache() - } - }() - if m.data == nil { - return nil, errors.New("updating table with empty data") - } - condition, conditionArgs := m.formatCondition(false) - return m.db.DoUpdate( - m.getLink(true), - m.tables, - m.filterDataForInsertOrUpdate(m.data), - condition, - conditionArgs..., - ) -} - -// Delete does "DELETE FROM ... " statement for the model. -// The optional parameter is the same as the parameter of Model.Where function, -// see Model.Where. -func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) { - if len(where) > 0 { - return m.Where(where[0], where[1:]...).Delete() - } - defer func() { - if err == nil { - m.checkAndRemoveCache() - } - }() - condition, conditionArgs := m.formatCondition(false) - return m.db.DoDelete(m.getLink(true), m.tables, condition, conditionArgs...) -} - -// Select is alias of Model.All. -// See Model.All. -// Deprecated. -func (m *Model) Select(where ...interface{}) (Result, error) { - return m.All(where...) -} - -// All does "SELECT FROM ..." statement for the model. -// It retrieves the records from table and returns the result as slice type. -// It returns nil if there's no record retrieved with the given conditions from table. -// -// The optional parameter is the same as the parameter of Model.Where function, -// see Model.Where. -func (m *Model) All(where ...interface{}) (Result, error) { - if len(where) > 0 { - return m.Where(where[0], where[1:]...).All() - } - condition, conditionArgs := m.formatCondition(false) - return m.getAll(fmt.Sprintf("SELECT %s FROM %s%s", m.fields, m.tables, condition), conditionArgs...) -} - -// One retrieves one record from table and returns the result as map type. -// It returns nil if there's no record retrieved with the given conditions from table. -// -// The optional parameter is the same as the parameter of Model.Where function, -// see Model.Where. -func (m *Model) One(where ...interface{}) (Record, error) { - if len(where) > 0 { - return m.Where(where[0], where[1:]...).One() - } - condition, conditionArgs := m.formatCondition(true) - all, err := m.getAll(fmt.Sprintf("SELECT %s FROM %s%s", m.fields, m.tables, condition), conditionArgs...) - if err != nil { - return nil, err - } - if len(all) > 0 { - return all[0], nil - } - return nil, nil -} - -// Value retrieves a specified record value from table and returns the result as interface type. -// It returns nil if there's no record found with the given conditions from table. -// -// If the optional parameter is given, the fieldsAndWhere[0] is the selected fields -// and fieldsAndWhere[1:] is treated as where condition fields. -// Also see Model.Fields and Model.Where functions. -func (m *Model) Value(fieldsAndWhere ...interface{}) (Value, error) { - if len(fieldsAndWhere) > 0 { - if len(fieldsAndWhere) > 2 { - return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1], fieldsAndWhere[2:]...).Value() - } else if len(fieldsAndWhere) == 2 { - return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1]).Value() - } else { - return m.Fields(gconv.String(fieldsAndWhere[0])).Value() - } - } - one, err := m.One() - if err != nil { - return nil, err - } - for _, v := range one { - return v, nil - } - return nil, nil -} - -// Array queries and returns data values as slice from database. -// Note that if there're multiple columns in the result, it returns just one column values randomly. -// -// If the optional parameter is given, the fieldsAndWhere[0] is the selected fields -// and fieldsAndWhere[1:] is treated as where condition fields. -// Also see Model.Fields and Model.Where functions. -func (m *Model) Array(fieldsAndWhere ...interface{}) ([]Value, error) { - if len(fieldsAndWhere) > 0 { - if len(fieldsAndWhere) > 2 { - return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1], fieldsAndWhere[2:]...).Array() - } else if len(fieldsAndWhere) == 2 { - return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1]).Array() - } else { - return m.Fields(gconv.String(fieldsAndWhere[0])).Array() - } - } - all, err := m.All() - if err != nil { - return nil, err - } - return all.Array(), nil -} - -// Struct retrieves one record from table and converts it into given struct. -// The parameter should be type of *struct/**struct. If type **struct is given, -// it can create the struct internally during converting. -// -// The optional parameter is the same as the parameter of Model.Where function, -// see Model.Where. -// -// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions -// from table. -// -// Eg: -// user := new(User) -// err := db.Table("user").Where("id", 1).Struct(user) -// -// user := (*User)(nil) -// err := db.Table("user").Where("id", 1).Struct(&user) -func (m *Model) Struct(pointer interface{}, where ...interface{}) error { - one, err := m.One(where...) - if err != nil { - return err - } - if len(one) == 0 { - return sql.ErrNoRows - } - return one.Struct(pointer) -} - -// Structs retrieves records from table and converts them into given struct slice. -// The parameter should be type of *[]struct/*[]*struct. It can create and fill the struct -// slice internally during converting. -// -// The optional parameter is the same as the parameter of Model.Where function, -// see Model.Where. -// -// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions -// from table. -// -// Eg: -// users := ([]User)(nil) -// err := db.Table("user").Structs(&users) -// -// users := ([]*User)(nil) -// err := db.Table("user").Structs(&users) -func (m *Model) Structs(pointer interface{}, where ...interface{}) error { - all, err := m.All(where...) - if err != nil { - return err - } - if len(all) == 0 { - return sql.ErrNoRows - } - return all.Structs(pointer) -} - -// Scan automatically calls Struct or Structs function according to the type of parameter . -// It calls function Struct if is type of *struct/**struct. -// It calls function Structs if is type of *[]struct/*[]*struct. -// -// The optional parameter is the same as the parameter of Model.Where function, -// see Model.Where. -// -// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions -// from table. -// -// Eg: -// user := new(User) -// err := db.Table("user").Where("id", 1).Struct(user) -// -// user := (*User)(nil) -// err := db.Table("user").Where("id", 1).Struct(&user) -// -// users := ([]User)(nil) -// err := db.Table("user").Structs(&users) -// -// users := ([]*User)(nil) -// err := db.Table("user").Structs(&users) -func (m *Model) Scan(pointer interface{}, where ...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) - } - switch t.Elem().Kind() { - case reflect.Array: - case reflect.Slice: - return m.Structs(pointer, where...) - default: - return m.Struct(pointer, where...) - } - return nil -} - -// Count does "SELECT COUNT(x) FROM ..." statement for the model. -// The optional parameter is the same as the parameter of Model.Where function, -// see Model.Where. -func (m *Model) Count(where ...interface{}) (int, error) { - if len(where) > 0 { - return m.Where(where[0], where[1:]...).Count() - } - countFields := "COUNT(1)" - if m.fields != "" && m.fields != "*" { - countFields = fmt.Sprintf(`COUNT(%s)`, m.fields) - } - condition, conditionArgs := m.formatCondition(false) - s := fmt.Sprintf("SELECT %s FROM %s %s", countFields, m.tables, condition) - if len(m.groupBy) > 0 { - s = fmt.Sprintf("SELECT COUNT(1) FROM (%s) count_alias", s) - } - list, err := m.getAll(s, conditionArgs...) - if err != nil { - return 0, err - } - if len(list) > 0 { - for _, v := range list[0] { - return v.Int(), nil - } - } - return 0, nil -} - -// FindOne retrieves and returns a single Record by Model.WherePri and Model.One. -// Also see Model.WherePri and Model.One. -func (m *Model) FindOne(where ...interface{}) (Record, error) { - if len(where) > 0 { - return m.WherePri(where[0], where[1:]...).One() - } - return m.One() -} - -// FindAll retrieves and returns Result by by Model.WherePri and Model.All. -// Also see Model.WherePri and Model.All. -func (m *Model) FindAll(where ...interface{}) (Result, error) { - if len(where) > 0 { - return m.WherePri(where[0], where[1:]...).All() - } - return m.All() -} - -// FindValue retrieves and returns single field value by Model.WherePri and Model.Value. -// Also see Model.WherePri and Model.Value. -func (m *Model) FindValue(fieldsAndWhere ...interface{}) (Value, error) { - if len(fieldsAndWhere) >= 2 { - return m.WherePri(fieldsAndWhere[1], fieldsAndWhere[2:]...).Fields(gconv.String(fieldsAndWhere[0])).Value() - } - if len(fieldsAndWhere) == 1 { - return m.Fields(gconv.String(fieldsAndWhere[0])).Value() - } - return m.Value() -} - -// FindArray queries and returns data values as slice from database. -// Note that if there're multiple columns in the result, it returns just one column values randomly. -// Also see Model.WherePri and Model.Value. -func (m *Model) FindArray(fieldsAndWhere ...interface{}) ([]Value, error) { - if len(fieldsAndWhere) >= 2 { - return m.WherePri(fieldsAndWhere[1], fieldsAndWhere[2:]...).Fields(gconv.String(fieldsAndWhere[0])).Array() - } - if len(fieldsAndWhere) == 1 { - return m.Fields(gconv.String(fieldsAndWhere[0])).Array() - } - return m.Array() -} - -// FindCount retrieves and returns the record number by Model.WherePri and Model.Count. -// Also see Model.WherePri and Model.Count. -func (m *Model) FindCount(where ...interface{}) (int, error) { - if len(where) > 0 { - return m.WherePri(where[0], where[1:]...).Count() - } - return m.Count() -} - -// FindScan retrieves and returns the record/records by Model.WherePri and Model.Scan. -// Also see Model.WherePri and Model.Scan. -func (m *Model) FindScan(pointer interface{}, where ...interface{}) error { - if len(where) > 0 { - return m.WherePri(where[0], where[1:]...).Scan(pointer) - } - return m.Scan(pointer) -} - -// Chunk iterates the table with given size and callback function. -func (m *Model) Chunk(limit int, callback func(result Result, err error) bool) { - page := m.start - if page == 0 { - page = 1 - } - model := m - for { - model = model.Page(page, limit) - data, err := model.All() - if err != nil { - callback(nil, err) - break - } - if len(data) == 0 { - break - } - if callback(data, err) == false { - break - } - if len(data) < limit { - break - } - page++ - } -} - -// filterDataForInsertOrUpdate does filter feature with data for inserting/updating operations. -// Note that, it does not filter list item, which is also type of map, for "omit empty" feature. -func (m *Model) filterDataForInsertOrUpdate(data interface{}) interface{} { - if list, ok := m.data.(List); ok { - for k, item := range list { - list[k] = m.doFilterDataMapForInsertOrUpdate(item, false) - } - return list - } else if item, ok := m.data.(Map); ok { - return m.doFilterDataMapForInsertOrUpdate(item, true) - } - return data -} - -// doFilterDataMapForInsertOrUpdate does the filter features for map. -// Note that, it does not filter list item, which is also type of map, for "omit empty" feature. -func (m *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool) Map { - if m.filter { - data = m.db.filterFields(m.schema, m.tables, data) - } - // Remove key-value pairs of which the value is empty. - if allowOmitEmpty && m.option&OPTION_OMITEMPTY > 0 { - m := gmap.NewStrAnyMapFrom(data) - m.FilterEmpty() - data = m.Map() - } - - if len(m.fields) > 0 && m.fields != "*" { - // Keep specified fields. - set := gset.NewStrSetFrom(gstr.SplitAndTrim(m.fields, ",")) - for k := range data { - if !set.Contains(k) { - delete(data, k) - } - } - } else if len(m.fieldsEx) > 0 { - // Filter specified fields. - for _, v := range gstr.SplitAndTrim(m.fieldsEx, ",") { - delete(data, v) - } - } - return data -} - -// getLink returns the underlying database link object with configured attribute. -// The parameter specifies whether using the master node if master-slave configured. -func (m *Model) getLink(master bool) Link { - if m.tx != nil { - return m.tx.tx - } - linkType := m.linkType - if linkType == 0 { - if master { - linkType = gLINK_TYPE_MASTER - } else { - linkType = gLINK_TYPE_SLAVE - } - } - switch linkType { - case gLINK_TYPE_MASTER: - link, err := m.db.GetMaster(m.schema) - if err != nil { - panic(err) - } - return link - case gLINK_TYPE_SLAVE: - link, err := m.db.GetSlave(m.schema) - if err != nil { - panic(err) - } - return link - } - return nil -} - -// getAll does the query from database. -func (m *Model) getAll(query string, args ...interface{}) (result Result, err error) { - cacheKey := "" - // Retrieve from cache. - if m.cacheEnabled { - cacheKey = m.cacheName - if len(cacheKey) == 0 { - cacheKey = query + "/" + gconv.String(args) - } - if v := m.db.GetCache().Get(cacheKey); v != nil { - return v.(Result), nil - } - } - result, err = m.db.DoGetAll(m.getLink(false), query, args...) - // Cache the result. - if len(cacheKey) > 0 && err == nil { - if m.cacheDuration < 0 { - m.db.GetCache().Remove(cacheKey) - } else { - m.db.GetCache().Set(cacheKey, result, m.cacheDuration) - } - } - return result, err -} - -// getPrimaryKey retrieves and returns the primary key name of the model table. -// It parses m.tables to retrieve the primary table name, supporting m.tables like: -// "user", "user u", "user as u, user_detail as ud". -func (m *Model) getPrimaryKey() string { - table := gstr.SplitAndTrim(m.tables, " ")[0] - tableFields, err := m.db.TableFields(table) - if err != nil { - return "" - } - for name, field := range tableFields { - if gstr.ContainsI(field.Key, "pri") { - return name - } - } - return "" -} - -// checkAndRemoveCache checks and remove the cache if necessary. -func (m *Model) checkAndRemoveCache() { - if m.cacheEnabled && m.cacheDuration < 0 && len(m.cacheName) > 0 { - m.db.GetCache().Remove(m.cacheName) - } -} - -// formatCondition formats where arguments of the model and returns a new condition sql and its arguments. -// Note that this function does not change any attribute value of the . -// -// The parameter specifies whether limits querying only one record if m.limit is not set. -func (m *Model) formatCondition(limit bool) (condition string, conditionArgs []interface{}) { - var where string - if len(m.whereHolder) > 0 { - for _, v := range m.whereHolder { - switch v.operator { - case gWHERE_HOLDER_WHERE: - if where == "" { - newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0) - if len(newWhere) > 0 { - where = newWhere - conditionArgs = newArgs - } - continue - } - fallthrough - - case gWHERE_HOLDER_AND: - newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0) - if len(newWhere) > 0 { - if where[0] == '(' { - where = fmt.Sprintf(`%s AND (%s)`, where, newWhere) - } else { - where = fmt.Sprintf(`(%s) AND (%s)`, where, newWhere) - } - conditionArgs = append(conditionArgs, newArgs...) - } - - case gWHERE_HOLDER_OR: - newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0) - if len(newWhere) > 0 { - if where[0] == '(' { - where = fmt.Sprintf(`%s OR (%s)`, where, newWhere) - } else { - where = fmt.Sprintf(`(%s) OR (%s)`, where, newWhere) - } - conditionArgs = append(conditionArgs, newArgs...) - } - } - } - } - if where != "" { - condition += " WHERE " + where - } - if m.groupBy != "" { - condition += " GROUP BY " + m.groupBy - } - if m.orderBy != "" { - condition += " ORDER BY " + m.orderBy - } - if m.limit != 0 { - if m.start >= 0 { - condition += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit) - } else { - condition += fmt.Sprintf(" LIMIT %d", m.limit) - } - } else if limit { - condition += " LIMIT 1" - } - if m.offset >= 0 { - condition += fmt.Sprintf(" OFFSET %d", m.offset) - } - return -} diff --git a/database/gdb/gdb_model_cache.go b/database/gdb/gdb_model_cache.go new file mode 100644 index 000000000..016c0b945 --- /dev/null +++ b/database/gdb/gdb_model_cache.go @@ -0,0 +1,36 @@ +// 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 ( + "time" +) + +// Cache sets the cache feature for the model. It caches the result of the sql, which means +// if there's another same sql request, it just reads and returns the result from cache, it +// but not committed and executed into the database. +// +// If the parameter < 0, which means it clear the cache with given . +// If the parameter = 0, which means it never expires. +// If the parameter > 0, which means it expires after . +// +// The optional parameter is used to bind a name to the cache, which means you can later +// control the cache like changing the or clearing the cache with specified . +// +// Note that, the cache feature is disabled if the model is operating on a transaction. +func (m *Model) Cache(duration time.Duration, name ...string) *Model { + model := m.getModel() + model.cacheDuration = duration + if len(name) > 0 { + model.cacheName = name[0] + } + // It does not support cache on transaction. + if model.tx == nil { + model.cacheEnabled = true + } + return model +} diff --git a/database/gdb/gdb_model_condition.go b/database/gdb/gdb_model_condition.go new file mode 100644 index 000000000..66705a25b --- /dev/null +++ b/database/gdb/gdb_model_condition.go @@ -0,0 +1,170 @@ +// 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 "github.com/gogf/gf/util/gconv" + +// Where sets the condition statement for the model. The parameter can be type of +// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times, +// multiple conditions will be joined into where statement using "AND". +// Eg: +// Where("uid=10000") +// Where("uid", 10000) +// Where("money>? AND name like ?", 99999, "vip_%") +// Where("uid", 1).Where("name", "john") +// Where("status IN (?)", g.Slice{1,2,3}) +// Where("age IN(?,?)", 18, 50) +// Where(User{ Id : 1, UserName : "john"}) +func (m *Model) Where(where interface{}, args ...interface{}) *Model { + model := m.getModel() + if model.whereHolder == nil { + model.whereHolder = make([]*whereHolder, 0) + } + model.whereHolder = append(model.whereHolder, &whereHolder{ + operator: gWHERE_HOLDER_WHERE, + where: where, + args: args, + }) + return model +} + +// WherePri does the same logic as Model.Where except that if the parameter +// is a single condition like int/string/float/slice, it treats the condition as the primary +// key value. That is, if primary key is "id" and given parameter as "123", the +// WherePri function treats it as "id=123", but Model.Where treats it as string "123". +func (m *Model) WherePri(where interface{}, args ...interface{}) *Model { + if len(args) > 0 { + return m.Where(where, args...) + } + newWhere := GetPrimaryKeyCondition(m.getPrimaryKey(), where) + return m.Where(newWhere[0], newWhere[1:]...) +} + +// And adds "AND" condition to the where statement. +func (m *Model) And(where interface{}, args ...interface{}) *Model { + model := m.getModel() + if model.whereHolder == nil { + model.whereHolder = make([]*whereHolder, 0) + } + model.whereHolder = append(model.whereHolder, &whereHolder{ + operator: gWHERE_HOLDER_AND, + where: where, + args: args, + }) + return model +} + +// Or adds "OR" condition to the where statement. +func (m *Model) Or(where interface{}, args ...interface{}) *Model { + model := m.getModel() + if model.whereHolder == nil { + model.whereHolder = make([]*whereHolder, 0) + } + model.whereHolder = append(model.whereHolder, &whereHolder{ + operator: gWHERE_HOLDER_OR, + where: where, + args: args, + }) + return model +} + +// Group sets the "GROUP BY" statement for the model. +func (m *Model) Group(groupBy string) *Model { + model := m.getModel() + model.groupBy = m.db.QuoteString(groupBy) + return model +} + +// GroupBy is alias of Model.Group. +// See Model.Group. +// Deprecated. +func (m *Model) GroupBy(groupBy string) *Model { + return m.Group(groupBy) +} + +// Order sets the "ORDER BY" statement for the model. +func (m *Model) Order(orderBy string) *Model { + model := m.getModel() + model.orderBy = m.db.QuoteString(orderBy) + return model +} + +// OrderBy is alias of Model.Order. +// See Model.Order. +// Deprecated. +func (m *Model) OrderBy(orderBy string) *Model { + return m.Order(orderBy) +} + +// Limit sets the "LIMIT" statement for the model. +// The parameter can be either one or two number, if passed two number is passed, +// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]" +// statement. +func (m *Model) Limit(limit ...int) *Model { + model := m.getModel() + switch len(limit) { + case 1: + model.limit = limit[0] + case 2: + model.start = limit[0] + model.limit = limit[1] + } + return model +} + +// Offset sets the "OFFSET" statement for the model. +// It only makes sense for some databases like SQLServer, PostgreSQL, etc. +func (m *Model) Offset(offset int) *Model { + model := m.getModel() + model.offset = offset + return model +} + +// Page sets the paging number for the model. +// The parameter is started from 1 for paging. +// Note that, it differs that the Limit function start from 0 for "LIMIT" statement. +func (m *Model) Page(page, limit int) *Model { + model := m.getModel() + if page <= 0 { + page = 1 + } + model.start = (page - 1) * limit + model.limit = limit + return model +} + +// ForPage is alias of Model.Page. +// See Model.Page. +// Deprecated. +func (m *Model) ForPage(page, limit int) *Model { + return m.Page(page, limit) +} + +// getAll does the query from database. +func (m *Model) getAll(query string, args ...interface{}) (result Result, err error) { + cacheKey := "" + // Retrieve from cache. + if m.cacheEnabled { + cacheKey = m.cacheName + if len(cacheKey) == 0 { + cacheKey = query + "/" + gconv.String(args) + } + if v := m.db.GetCache().Get(cacheKey); v != nil { + return v.(Result), nil + } + } + result, err = m.db.DoGetAll(m.getLink(false), query, m.mergeArguments(args)...) + // Cache the result. + if len(cacheKey) > 0 && err == nil { + if m.cacheDuration < 0 { + m.db.GetCache().Remove(cacheKey) + } else { + m.db.GetCache().Set(cacheKey, result, m.cacheDuration) + } + } + return result, err +} diff --git a/database/gdb/gdb_model_delete.go b/database/gdb/gdb_model_delete.go new file mode 100644 index 000000000..e0638a8f7 --- /dev/null +++ b/database/gdb/gdb_model_delete.go @@ -0,0 +1,27 @@ +// 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" +) + +// Delete does "DELETE FROM ... " statement for the model. +// The optional parameter is the same as the parameter of Model.Where function, +// see Model.Where. +func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) { + if len(where) > 0 { + return m.Where(where[0], where[1:]...).Delete() + } + defer func() { + if err == nil { + m.checkAndRemoveCache() + } + }() + condition, conditionArgs := m.formatCondition(false) + return m.db.DoDelete(m.getLink(true), m.tables, condition, conditionArgs...) +} diff --git a/database/gdb/gdb_model_fields.go b/database/gdb/gdb_model_fields.go new file mode 100644 index 000000000..163385377 --- /dev/null +++ b/database/gdb/gdb_model_fields.go @@ -0,0 +1,94 @@ +// 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 ( + "github.com/gogf/gf/container/garray" + "github.com/gogf/gf/container/gset" + "github.com/gogf/gf/text/gstr" +) + +// Filter marks filtering the fields which does not exist in the fields of the operated table. +func (m *Model) Filter() *Model { + if gstr.Contains(m.tables, " ") { + panic("function Filter supports only single table operations") + } + model := m.getModel() + model.filter = true + return model +} + +// Fields sets the operation fields of the model, multiple fields joined using char ','. +func (m *Model) Fields(fields string) *Model { + model := m.getModel() + model.fields = fields + return model +} + +// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','. +func (m *Model) FieldsEx(fields string) *Model { + if gstr.Contains(m.tables, " ") { + panic("function FieldsEx supports only single table operations") + } + model := m.getModel() + model.fieldsEx = fields + fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ",")) + if m, err := m.db.TableFields(m.tables); err == nil { + model.fields = "" + for k, _ := range m { + if fieldsExSet.Contains(k) { + continue + } + if len(model.fields) > 0 { + model.fields += "," + } + model.fields += k + } + } + return model +} + +// FieldsStr retrieves and returns all fields from the table, joined with char ','. +// The optional parameter specifies the prefix for each field, eg: FieldsStr("u."). +func (m *Model) FieldsStr(prefix ...string) string { + prefixStr := "" + if len(prefix) > 0 { + prefixStr = prefix[0] + } + if m, err := m.db.TableFields(m.tables); err == nil { + fieldsArray := garray.NewStrArraySize(len(m), len(m)) + for _, field := range m { + fieldsArray.Set(field.Index, prefixStr+field.Name) + } + return fieldsArray.Join(",") + } + return "" +} + +// FieldsExStr retrieves and returns fields which are not in parameter from the table, +// joined with char ','. +// The parameter specifies the fields that are excluded. +// The optional parameter specifies the prefix for each field, eg: FieldsExStr("id", "u."). +func (m *Model) FieldsExStr(fields string, prefix ...string) string { + prefixStr := "" + if len(prefix) > 0 { + prefixStr = prefix[0] + } + if m, err := m.db.TableFields(m.tables); err == nil { + fieldsArray := garray.NewStrArraySize(len(m), len(m)) + fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ",")) + for _, field := range m { + if fieldsExSet.Contains(field.Name) { + continue + } + fieldsArray.Set(field.Index, prefixStr+field.Name) + } + fieldsArray.FilterEmpty() + return fieldsArray.Join(",") + } + return "" +} diff --git a/database/gdb/gdb_model_insert.go b/database/gdb/gdb_model_insert.go new file mode 100644 index 000000000..2f0b67b02 --- /dev/null +++ b/database/gdb/gdb_model_insert.go @@ -0,0 +1,213 @@ +// 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" + "github.com/gogf/gf/text/gstr" + "github.com/gogf/gf/util/gconv" + "reflect" +) + +// Batch sets the batch operation number for the model. +func (m *Model) Batch(batch int) *Model { + model := m.getModel() + model.batch = batch + return model +} + +// Data sets the operation data for the model. +// The parameter can be type of string/map/gmap/slice/struct/*struct, etc. +// Eg: +// Data("uid=10000") +// Data("uid", 10000) +// Data("uid=? AND name=?", 10000, "john") +// Data(g.Map{"uid": 10000, "name":"john"}) +// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) +func (m *Model) Data(data ...interface{}) *Model { + model := m.getModel() + if len(data) > 1 { + s := gconv.String(data[0]) + if gstr.Contains(s, "?") { + model.data = s + model.extraArgs = data[1:] + } else { + m := make(map[string]interface{}) + for i := 0; i < len(data); i += 2 { + m[gconv.String(data[i])] = data[i+1] + } + model.data = m + } + } else { + switch params := data[0].(type) { + case Result: + model.data = params.List() + case Record: + model.data = params.Map() + case List: + model.data = params + case Map: + model.data = params + default: + rv := reflect.ValueOf(params) + kind := rv.Kind() + if kind == reflect.Ptr { + rv = rv.Elem() + kind = rv.Kind() + } + switch kind { + case reflect.Slice, reflect.Array: + list := make(List, rv.Len()) + for i := 0; i < rv.Len(); i++ { + list[i] = DataToMapDeep(rv.Index(i).Interface()) + } + model.data = list + case reflect.Map, reflect.Struct: + model.data = DataToMapDeep(data[0]) + default: + model.data = data[0] + } + } + } + return model +} + +// Insert does "INSERT INTO ..." statement for the model. +// The optional parameter is the same as the parameter of Model.Data function, +// see Model.Data. +func (m *Model) Insert(data ...interface{}) (result sql.Result, err error) { + return m.doInsertWithOption(gINSERT_OPTION_DEFAULT, data...) +} + +// InsertIgnore does "INSERT IGNORE INTO ..." statement for the model. +// The optional parameter is the same as the parameter of Model.Data function, +// see Model.Data. +func (m *Model) InsertIgnore(data ...interface{}) (result sql.Result, err error) { + return m.doInsertWithOption(gINSERT_OPTION_IGNORE, data...) +} + +// doInsertWithOption inserts data with option parameter. +func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.Result, err error) { + if len(data) > 0 { + return m.Data(data...).Insert() + } + defer func() { + if err == nil { + m.checkAndRemoveCache() + } + }() + if m.data == nil { + return nil, errors.New("inserting into table with empty data") + } + if list, ok := m.data.(List); ok { + // Batch insert. + batch := 10 + if m.batch > 0 { + batch = m.batch + } + return m.db.DoBatchInsert( + m.getLink(true), + m.tables, + m.filterDataForInsertOrUpdate(list), + option, + batch, + ) + } else if data, ok := m.data.(Map); ok { + // Single insert. + return m.db.DoInsert( + m.getLink(true), + m.tables, + m.filterDataForInsertOrUpdate(data), + option, + ) + } + return nil, errors.New("inserting into table with invalid data type") +} + +// Replace does "REPLACE INTO ..." statement for the model. +// The optional parameter is the same as the parameter of Model.Data function, +// see Model.Data. +func (m *Model) Replace(data ...interface{}) (result sql.Result, err error) { + if len(data) > 0 { + return m.Data(data...).Replace() + } + defer func() { + if err == nil { + m.checkAndRemoveCache() + } + }() + if m.data == nil { + return nil, errors.New("replacing into table with empty data") + } + if list, ok := m.data.(List); ok { + // Batch replace. + batch := 10 + if m.batch > 0 { + batch = m.batch + } + return m.db.DoBatchInsert( + m.getLink(true), + m.tables, + m.filterDataForInsertOrUpdate(list), + gINSERT_OPTION_REPLACE, + batch, + ) + } else if data, ok := m.data.(Map); ok { + // Single insert. + return m.db.DoInsert( + m.getLink(true), + m.tables, + m.filterDataForInsertOrUpdate(data), + gINSERT_OPTION_REPLACE, + ) + } + return nil, errors.New("replacing into table with invalid data type") +} + +// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the model. +// The optional parameter is the same as the parameter of Model.Data function, +// see Model.Data. +// +// 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. +func (m *Model) Save(data ...interface{}) (result sql.Result, err error) { + if len(data) > 0 { + return m.Data(data...).Save() + } + defer func() { + if err == nil { + m.checkAndRemoveCache() + } + }() + if m.data == nil { + return nil, errors.New("saving into table with empty data") + } + if list, ok := m.data.(List); ok { + // Batch save. + batch := gDEFAULT_BATCH_NUM + if m.batch > 0 { + batch = m.batch + } + return m.db.DoBatchInsert( + m.getLink(true), + m.tables, + m.filterDataForInsertOrUpdate(list), + gINSERT_OPTION_SAVE, + batch, + ) + } else if data, ok := m.data.(Map); ok { + // Single save. + return m.db.DoInsert( + m.getLink(true), + m.tables, + m.filterDataForInsertOrUpdate(data), + gINSERT_OPTION_SAVE, + ) + } + return nil, errors.New("saving into table with invalid data type") +} diff --git a/database/gdb/gdb_model_join.go b/database/gdb/gdb_model_join.go new file mode 100644 index 000000000..d3d156c93 --- /dev/null +++ b/database/gdb/gdb_model_join.go @@ -0,0 +1,30 @@ +// 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 "fmt" + +// LeftJoin does "LEFT JOIN ... ON ..." statement on the model. +func (m *Model) LeftJoin(table string, on string) *Model { + model := m.getModel() + model.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on) + return model +} + +// RightJoin does "RIGHT JOIN ... ON ..." statement on the model. +func (m *Model) RightJoin(table string, on string) *Model { + model := m.getModel() + model.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on) + return model +} + +// InnerJoin does "INNER JOIN ... ON ..." statement on the model. +func (m *Model) InnerJoin(table string, on string) *Model { + model := m.getModel() + model.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on) + return model +} diff --git a/database/gdb/gdb_model_lock.go b/database/gdb/gdb_model_lock.go new file mode 100644 index 000000000..36dce9ee0 --- /dev/null +++ b/database/gdb/gdb_model_lock.go @@ -0,0 +1,21 @@ +// Copyright 2019 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 + +// LockUpdate sets the lock for update for current operation. +func (m *Model) LockUpdate() *Model { + model := m.getModel() + model.lockInfo = "FOR UPDATE" + return model +} + +// LockShared sets the lock in share mode for current operation. +func (m *Model) LockShared() *Model { + model := m.getModel() + model.lockInfo = "LOCK IN SHARE MODE" + return model +} diff --git a/database/gdb/gdb_model_option.go b/database/gdb/gdb_model_option.go new file mode 100644 index 000000000..8d8d2f6f9 --- /dev/null +++ b/database/gdb/gdb_model_option.go @@ -0,0 +1,27 @@ +// 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 + +// Option adds extra operation option for the model. +func (m *Model) Option(option int) *Model { + model := m.getModel() + model.option = model.option | option + return model +} + +// OptionOmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers +// the data and where attributes for empty values. +// Deprecated, use OmitEmpty instead. +func (m *Model) OptionOmitEmpty() *Model { + return m.Option(OPTION_OMITEMPTY) +} + +// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers +// the data and where attributes for empty values. +func (m *Model) OmitEmpty() *Model { + return m.Option(OPTION_OMITEMPTY) +} diff --git a/database/gdb/gdb_model_select.go b/database/gdb/gdb_model_select.go new file mode 100644 index 000000000..c3eefcb01 --- /dev/null +++ b/database/gdb/gdb_model_select.go @@ -0,0 +1,315 @@ +// 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" + "fmt" + "github.com/gogf/gf/util/gconv" + "reflect" +) + +// Select is alias of Model.All. +// See Model.All. +// Deprecated. +func (m *Model) Select(where ...interface{}) (Result, error) { + return m.All(where...) +} + +// All does "SELECT FROM ..." statement for the model. +// It retrieves the records from table and returns the result as slice type. +// It returns nil if there's no record retrieved with the given conditions from table. +// +// The optional parameter is the same as the parameter of Model.Where function, +// see Model.Where. +func (m *Model) All(where ...interface{}) (Result, error) { + if len(where) > 0 { + return m.Where(where[0], where[1:]...).All() + } + condition, conditionArgs := m.formatCondition(false) + return m.getAll( + fmt.Sprintf("SELECT %s FROM %s%s", m.fields, m.tables, condition), + conditionArgs..., + ) +} + +// Chunk iterates the query result with given size and callback function. +func (m *Model) Chunk(limit int, callback func(result Result, err error) bool) { + page := m.start + if page == 0 { + page = 1 + } + model := m + for { + model = model.Page(page, limit) + data, err := model.All() + if err != nil { + callback(nil, err) + break + } + if len(data) == 0 { + break + } + if callback(data, err) == false { + break + } + if len(data) < limit { + break + } + page++ + } +} + +// One retrieves one record from table and returns the result as map type. +// It returns nil if there's no record retrieved with the given conditions from table. +// +// The optional parameter is the same as the parameter of Model.Where function, +// see Model.Where. +func (m *Model) One(where ...interface{}) (Record, error) { + if len(where) > 0 { + return m.Where(where[0], where[1:]...).One() + } + condition, conditionArgs := m.formatCondition(true) + all, err := m.getAll(fmt.Sprintf("SELECT %s FROM %s%s", m.fields, m.tables, condition), conditionArgs...) + if err != nil { + return nil, err + } + if len(all) > 0 { + return all[0], nil + } + return nil, nil +} + +// Value retrieves a specified record value from table and returns the result as interface type. +// It returns nil if there's no record found with the given conditions from table. +// +// If the optional parameter is given, the fieldsAndWhere[0] is the selected fields +// and fieldsAndWhere[1:] is treated as where condition fields. +// Also see Model.Fields and Model.Where functions. +func (m *Model) Value(fieldsAndWhere ...interface{}) (Value, error) { + if len(fieldsAndWhere) > 0 { + if len(fieldsAndWhere) > 2 { + return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1], fieldsAndWhere[2:]...).Value() + } else if len(fieldsAndWhere) == 2 { + return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1]).Value() + } else { + return m.Fields(gconv.String(fieldsAndWhere[0])).Value() + } + } + one, err := m.One() + if err != nil { + return nil, err + } + for _, v := range one { + return v, nil + } + return nil, nil +} + +// Array queries and returns data values as slice from database. +// Note that if there're multiple columns in the result, it returns just one column values randomly. +// +// If the optional parameter is given, the fieldsAndWhere[0] is the selected fields +// and fieldsAndWhere[1:] is treated as where condition fields. +// Also see Model.Fields and Model.Where functions. +func (m *Model) Array(fieldsAndWhere ...interface{}) ([]Value, error) { + if len(fieldsAndWhere) > 0 { + if len(fieldsAndWhere) > 2 { + return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1], fieldsAndWhere[2:]...).Array() + } else if len(fieldsAndWhere) == 2 { + return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1]).Array() + } else { + return m.Fields(gconv.String(fieldsAndWhere[0])).Array() + } + } + all, err := m.All() + if err != nil { + return nil, err + } + return all.Array(), nil +} + +// Struct retrieves one record from table and converts it into given struct. +// The parameter should be type of *struct/**struct. If type **struct is given, +// it can create the struct internally during converting. +// +// The optional parameter is the same as the parameter of Model.Where function, +// see Model.Where. +// +// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions +// from table. +// +// Eg: +// user := new(User) +// err := db.Table("user").Where("id", 1).Struct(user) +// +// user := (*User)(nil) +// err := db.Table("user").Where("id", 1).Struct(&user) +func (m *Model) Struct(pointer interface{}, where ...interface{}) error { + one, err := m.One(where...) + if err != nil { + return err + } + if len(one) == 0 { + return sql.ErrNoRows + } + return one.Struct(pointer) +} + +// Structs retrieves records from table and converts them into given struct slice. +// The parameter should be type of *[]struct/*[]*struct. It can create and fill the struct +// slice internally during converting. +// +// The optional parameter is the same as the parameter of Model.Where function, +// see Model.Where. +// +// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions +// from table. +// +// Eg: +// users := ([]User)(nil) +// err := db.Table("user").Structs(&users) +// +// users := ([]*User)(nil) +// err := db.Table("user").Structs(&users) +func (m *Model) Structs(pointer interface{}, where ...interface{}) error { + all, err := m.All(where...) + if err != nil { + return err + } + if len(all) == 0 { + return sql.ErrNoRows + } + return all.Structs(pointer) +} + +// Scan automatically calls Struct or Structs function according to the type of parameter . +// It calls function Struct if is type of *struct/**struct. +// It calls function Structs if is type of *[]struct/*[]*struct. +// +// The optional parameter is the same as the parameter of Model.Where function, +// see Model.Where. +// +// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions +// from table. +// +// Eg: +// user := new(User) +// err := db.Table("user").Where("id", 1).Struct(user) +// +// user := (*User)(nil) +// err := db.Table("user").Where("id", 1).Struct(&user) +// +// users := ([]User)(nil) +// err := db.Table("user").Structs(&users) +// +// users := ([]*User)(nil) +// err := db.Table("user").Structs(&users) +func (m *Model) Scan(pointer interface{}, where ...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) + } + switch t.Elem().Kind() { + case reflect.Array: + case reflect.Slice: + return m.Structs(pointer, where...) + default: + return m.Struct(pointer, where...) + } + return nil +} + +// Count does "SELECT COUNT(x) FROM ..." statement for the model. +// The optional parameter is the same as the parameter of Model.Where function, +// see Model.Where. +func (m *Model) Count(where ...interface{}) (int, error) { + if len(where) > 0 { + return m.Where(where[0], where[1:]...).Count() + } + countFields := "COUNT(1)" + if m.fields != "" && m.fields != "*" { + countFields = fmt.Sprintf(`COUNT(%s)`, m.fields) + } + condition, conditionArgs := m.formatCondition(false) + s := fmt.Sprintf("SELECT %s FROM %s %s", countFields, m.tables, condition) + if len(m.groupBy) > 0 { + s = fmt.Sprintf("SELECT COUNT(1) FROM (%s) count_alias", s) + } + list, err := m.getAll(s, conditionArgs...) + if err != nil { + return 0, err + } + if len(list) > 0 { + for _, v := range list[0] { + return v.Int(), nil + } + } + return 0, nil +} + +// FindOne retrieves and returns a single Record by Model.WherePri and Model.One. +// Also see Model.WherePri and Model.One. +func (m *Model) FindOne(where ...interface{}) (Record, error) { + if len(where) > 0 { + return m.WherePri(where[0], where[1:]...).One() + } + return m.One() +} + +// FindAll retrieves and returns Result by by Model.WherePri and Model.All. +// Also see Model.WherePri and Model.All. +func (m *Model) FindAll(where ...interface{}) (Result, error) { + if len(where) > 0 { + return m.WherePri(where[0], where[1:]...).All() + } + return m.All() +} + +// FindValue retrieves and returns single field value by Model.WherePri and Model.Value. +// Also see Model.WherePri and Model.Value. +func (m *Model) FindValue(fieldsAndWhere ...interface{}) (Value, error) { + if len(fieldsAndWhere) >= 2 { + return m.WherePri(fieldsAndWhere[1], fieldsAndWhere[2:]...).Fields(gconv.String(fieldsAndWhere[0])).Value() + } + if len(fieldsAndWhere) == 1 { + return m.Fields(gconv.String(fieldsAndWhere[0])).Value() + } + return m.Value() +} + +// FindArray queries and returns data values as slice from database. +// Note that if there're multiple columns in the result, it returns just one column values randomly. +// Also see Model.WherePri and Model.Value. +func (m *Model) FindArray(fieldsAndWhere ...interface{}) ([]Value, error) { + if len(fieldsAndWhere) >= 2 { + return m.WherePri(fieldsAndWhere[1], fieldsAndWhere[2:]...).Fields(gconv.String(fieldsAndWhere[0])).Array() + } + if len(fieldsAndWhere) == 1 { + return m.Fields(gconv.String(fieldsAndWhere[0])).Array() + } + return m.Array() +} + +// FindCount retrieves and returns the record number by Model.WherePri and Model.Count. +// Also see Model.WherePri and Model.Count. +func (m *Model) FindCount(where ...interface{}) (int, error) { + if len(where) > 0 { + return m.WherePri(where[0], where[1:]...).Count() + } + return m.Count() +} + +// FindScan retrieves and returns the record/records by Model.WherePri and Model.Scan. +// Also see Model.WherePri and Model.Scan. +func (m *Model) FindScan(pointer interface{}, where ...interface{}) error { + if len(where) > 0 { + return m.WherePri(where[0], where[1:]...).Scan(pointer) + } + return m.Scan(pointer) +} diff --git a/database/gdb/gdb_model_update.go b/database/gdb/gdb_model_update.go new file mode 100644 index 000000000..a8cdf7019 --- /dev/null +++ b/database/gdb/gdb_model_update.go @@ -0,0 +1,45 @@ +// 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" +) + +// Update does "UPDATE ... " statement for the model. +// +// If the optional parameter is given, the dataAndWhere[0] is the updated data field, +// and dataAndWhere[1:] is treated as where condition fields. +// Also see Model.Data and Model.Where functions. +func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err error) { + if len(dataAndWhere) > 0 { + if len(dataAndWhere) > 2 { + return m.Data(dataAndWhere[0]).Where(dataAndWhere[1], dataAndWhere[2:]...).Update() + } else if len(dataAndWhere) == 2 { + return m.Data(dataAndWhere[0]).Where(dataAndWhere[1]).Update() + } else { + return m.Data(dataAndWhere[0]).Update() + } + } + defer func() { + if err == nil { + m.checkAndRemoveCache() + } + }() + if m.data == nil { + return nil, errors.New("updating table with empty data") + } + condition, conditionArgs := m.formatCondition(false) + return m.db.DoUpdate( + m.getLink(true), + m.tables, + m.filterDataForInsertOrUpdate(m.data), + condition, + m.mergeArguments(conditionArgs)..., + ) +} diff --git a/database/gdb/gdb_model_utility.go b/database/gdb/gdb_model_utility.go new file mode 100644 index 000000000..6e0052574 --- /dev/null +++ b/database/gdb/gdb_model_utility.go @@ -0,0 +1,205 @@ +// 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 ( + "fmt" + "github.com/gogf/gf/container/gmap" + "github.com/gogf/gf/container/gset" + "github.com/gogf/gf/text/gstr" +) + +// getModel creates and returns a cloned model of current model if is true, or else it returns +// the current model. +func (m *Model) getModel() *Model { + if !m.safe { + return m + } else { + return m.Clone() + } +} + +// filterDataForInsertOrUpdate does filter feature with data for inserting/updating operations. +// Note that, it does not filter list item, which is also type of map, for "omit empty" feature. +func (m *Model) filterDataForInsertOrUpdate(data interface{}) interface{} { + if list, ok := m.data.(List); ok { + for k, item := range list { + list[k] = m.doFilterDataMapForInsertOrUpdate(item, false) + } + return list + } else if item, ok := m.data.(Map); ok { + return m.doFilterDataMapForInsertOrUpdate(item, true) + } + return data +} + +// doFilterDataMapForInsertOrUpdate does the filter features for map. +// Note that, it does not filter list item, which is also type of map, for "omit empty" feature. +func (m *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool) Map { + if m.filter { + data = m.db.filterFields(m.schema, m.tables, data) + } + // Remove key-value pairs of which the value is empty. + if allowOmitEmpty && m.option&OPTION_OMITEMPTY > 0 { + m := gmap.NewStrAnyMapFrom(data) + m.FilterEmpty() + data = m.Map() + } + + if len(m.fields) > 0 && m.fields != "*" { + // Keep specified fields. + set := gset.NewStrSetFrom(gstr.SplitAndTrim(m.fields, ",")) + for k := range data { + if !set.Contains(k) { + delete(data, k) + } + } + } else if len(m.fieldsEx) > 0 { + // Filter specified fields. + for _, v := range gstr.SplitAndTrim(m.fieldsEx, ",") { + delete(data, v) + } + } + return data +} + +// getLink returns the underlying database link object with configured attribute. +// The parameter specifies whether using the master node if master-slave configured. +func (m *Model) getLink(master bool) Link { + if m.tx != nil { + return m.tx.tx + } + linkType := m.linkType + if linkType == 0 { + if master { + linkType = gLINK_TYPE_MASTER + } else { + linkType = gLINK_TYPE_SLAVE + } + } + switch linkType { + case gLINK_TYPE_MASTER: + link, err := m.db.GetMaster(m.schema) + if err != nil { + panic(err) + } + return link + case gLINK_TYPE_SLAVE: + link, err := m.db.GetSlave(m.schema) + if err != nil { + panic(err) + } + return link + } + return nil +} + +// getPrimaryKey retrieves and returns the primary key name of the model table. +// It parses m.tables to retrieve the primary table name, supporting m.tables like: +// "user", "user u", "user as u, user_detail as ud". +func (m *Model) getPrimaryKey() string { + table := gstr.SplitAndTrim(m.tables, " ")[0] + tableFields, err := m.db.TableFields(table) + if err != nil { + return "" + } + for name, field := range tableFields { + if gstr.ContainsI(field.Key, "pri") { + return name + } + } + return "" +} + +// checkAndRemoveCache checks and remove the cache if necessary. +func (m *Model) checkAndRemoveCache() { + if m.cacheEnabled && m.cacheDuration < 0 && len(m.cacheName) > 0 { + m.db.GetCache().Remove(m.cacheName) + } +} + +// formatCondition formats where arguments of the model and returns a new condition sql and its arguments. +// Note that this function does not change any attribute value of the . +// +// The parameter specifies whether limits querying only one record if m.limit is not set. +func (m *Model) formatCondition(limit bool) (condition string, conditionArgs []interface{}) { + var where string + if len(m.whereHolder) > 0 { + for _, v := range m.whereHolder { + switch v.operator { + case gWHERE_HOLDER_WHERE: + if where == "" { + newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0) + if len(newWhere) > 0 { + where = newWhere + conditionArgs = newArgs + } + continue + } + fallthrough + + case gWHERE_HOLDER_AND: + newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0) + if len(newWhere) > 0 { + if where[0] == '(' { + where = fmt.Sprintf(`%s AND (%s)`, where, newWhere) + } else { + where = fmt.Sprintf(`(%s) AND (%s)`, where, newWhere) + } + conditionArgs = append(conditionArgs, newArgs...) + } + + case gWHERE_HOLDER_OR: + newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0) + if len(newWhere) > 0 { + if where[0] == '(' { + where = fmt.Sprintf(`%s OR (%s)`, where, newWhere) + } else { + where = fmt.Sprintf(`(%s) OR (%s)`, where, newWhere) + } + conditionArgs = append(conditionArgs, newArgs...) + } + } + } + } + if where != "" { + condition += " WHERE " + where + } + if m.groupBy != "" { + condition += " GROUP BY " + m.groupBy + } + if m.orderBy != "" { + condition += " ORDER BY " + m.orderBy + } + if m.limit != 0 { + if m.start >= 0 { + condition += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit) + } else { + condition += fmt.Sprintf(" LIMIT %d", m.limit) + } + } else if limit { + condition += " LIMIT 1" + } + if m.offset >= 0 { + condition += fmt.Sprintf(" OFFSET %d", m.offset) + } + if m.lockInfo != "" { + condition += " " + m.lockInfo + } + return +} + +// mergeArguments creates and returns new arguments by merging and given . +func (m *Model) mergeArguments(args []interface{}) []interface{} { + if len(m.extraArgs) > 0 { + newArgs := make([]interface{}, len(m.extraArgs)+len(args)) + copy(newArgs, m.extraArgs) + copy(newArgs[len(m.extraArgs):], args) + return newArgs + } + return args +} diff --git a/database/gdb/gdb_result.go b/database/gdb/gdb_result.go new file mode 100644 index 000000000..2ebfc55fe --- /dev/null +++ b/database/gdb/gdb_result.go @@ -0,0 +1,47 @@ +// Copyright 2019 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" + +// SqlResult is execution result for sql operations. +// It also supports batch operation result for rowsAffected. +type SqlResult struct { + result sql.Result + affected int64 +} + +// MustGetAffected returns the affected rows count, if any error occurs, it panics. +func (r *SqlResult) MustGetAffected() int64 { + rows, err := r.RowsAffected() + if err != nil { + panic(err) + } + return rows +} + +// MustGetInsertId returns the last insert id, if any error occurs, it panics. +func (r *SqlResult) MustGetInsertId() int64 { + id, err := r.LastInsertId() + if err != nil { + panic(err) + } + return id +} + +// see sql.Result.RowsAffected +func (r *SqlResult) RowsAffected() (int64, error) { + if r.affected > 0 { + return r.affected, nil + } + return r.result.RowsAffected() +} + +// see sql.Result.LastInsertId +func (r *SqlResult) LastInsertId() (int64, error) { + return r.result.LastInsertId() +} diff --git a/database/gdb/gdb_unit_z_mysql_model_test.go b/database/gdb/gdb_unit_z_mysql_model_test.go index 90a673732..e484e58e3 100644 --- a/database/gdb/gdb_unit_z_mysql_model_test.go +++ b/database/gdb/gdb_unit_z_mysql_model_test.go @@ -936,6 +936,18 @@ func Test_Model_GroupBy(t *testing.T) { }) } +func Test_Model_Data(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.Case(t, func() { + result, err := db.Table(table).Data("nickname=?", "test").Where("id=?", 3).Update() + gtest.Assert(err, nil) + n, _ := result.RowsAffected() + gtest.Assert(n, 1) + }) +} + func Test_Model_Where(t *testing.T) { table := createInitTable() defer dropTable(table) @@ -1179,6 +1191,44 @@ func Test_Model_Where(t *testing.T) { }) } +func Test_Model_Where_ISNULL_1(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.Case(t, func() { + //db.SetDebug(true) + result, err := db.Table(table).Data("nickname", nil).Where("id", 2).Update() + gtest.Assert(err, nil) + n, _ := result.RowsAffected() + gtest.Assert(n, 1) + + one, err := db.Table(table).Where("nickname", nil).One() + gtest.Assert(err, nil) + gtest.Assert(one.IsEmpty(), false) + gtest.Assert(one["id"], 2) + }) +} + +func Test_Model_Where_ISNULL_2(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + // complicated one. + gtest.Case(t, func() { + //db.SetDebug(true) + conditions := g.Map{ + "nickname like ?": "%name%", + "id between ? and ?": g.Slice{1, 3}, + "id > 0": nil, + "create_time > 0": nil, + "id": g.Slice{1, 2, 3}, + } + result, err := db.Table(table).WherePri(conditions).Order("id asc").All() + gtest.Assert(err, nil) + gtest.Assert(len(result), 3) + gtest.Assert(result[0]["id"].Int(), 1) + }) +} func Test_Model_WherePri(t *testing.T) { table := createInitTable() defer dropTable(table) diff --git a/frame/gmvc/model.go b/frame/gmvc/model.go index 870ec79da..81a0ea2a5 100644 --- a/frame/gmvc/model.go +++ b/frame/gmvc/model.go @@ -6,5 +6,11 @@ package gmvc -// Model is the base struct for model. -type Model struct{} +import "github.com/gogf/gf/database/gdb" + +// M is alias for Model, +// just for short write purpose. +type M = Model + +// Model is alias for *gdb.Model. +type Model = *gdb.Model diff --git a/internal/empty/empty.go b/internal/empty/empty.go index aa859c266..675317138 100644 --- a/internal/empty/empty.go +++ b/internal/empty/empty.go @@ -51,7 +51,12 @@ func IsEmpty(value interface{}) bool { return len(value) == 0 default: // Finally using reflect. - rv := reflect.ValueOf(value) + var rv reflect.Value + if v, ok := value.(reflect.Value); ok { + rv = v + } else { + rv = reflect.ValueOf(value) + } switch rv.Kind() { case reflect.Chan, reflect.Map, @@ -77,7 +82,12 @@ func IsNil(value interface{}) bool { if value == nil { return true } - rv := reflect.ValueOf(value) + var rv reflect.Value + if v, ok := value.(reflect.Value); ok { + rv = v + } else { + rv = reflect.ValueOf(value) + } switch rv.Kind() { case reflect.Chan, reflect.Map,