2021-01-17 21:46:25 +08:00
|
|
|
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
2020-03-13 17:21:30 +08:00
|
|
|
//
|
|
|
|
// 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 (
|
2022-03-24 15:33:30 +08:00
|
|
|
"context"
|
2020-03-13 17:21:30 +08:00
|
|
|
"fmt"
|
2021-05-21 13:25:53 +08:00
|
|
|
"reflect"
|
|
|
|
|
2021-10-11 21:41:56 +08:00
|
|
|
"github.com/gogf/gf/v2/container/gset"
|
2021-11-13 23:23:55 +08:00
|
|
|
"github.com/gogf/gf/v2/errors/gcode"
|
|
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
2022-03-11 10:24:42 +08:00
|
|
|
"github.com/gogf/gf/v2/internal/reflection"
|
2021-10-11 21:41:56 +08:00
|
|
|
"github.com/gogf/gf/v2/text/gstr"
|
|
|
|
"github.com/gogf/gf/v2/util/gconv"
|
2020-03-13 17:21:30 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
//
|
2021-02-08 17:57:21 +08:00
|
|
|
// The optional parameter `where` is the same as the parameter of Model.Where function,
|
2020-03-13 17:21:30 +08:00
|
|
|
// see Model.Where.
|
|
|
|
func (m *Model) All(where ...interface{}) (Result, error) {
|
2022-03-24 17:51:49 +08:00
|
|
|
var ctx = m.GetCtx()
|
|
|
|
return m.doGetAll(ctx, false, where...)
|
2020-07-15 09:15:03 +08:00
|
|
|
}
|
|
|
|
|
2021-08-01 10:33:33 +08:00
|
|
|
// Chunk iterates the query result with given `size` and `handler` function.
|
|
|
|
func (m *Model) Chunk(size int, handler ChunkHandler) {
|
2020-03-13 17:21:30 +08:00
|
|
|
page := m.start
|
2020-11-26 14:26:32 +08:00
|
|
|
if page <= 0 {
|
2020-03-13 17:21:30 +08:00
|
|
|
page = 1
|
|
|
|
}
|
|
|
|
model := m
|
|
|
|
for {
|
2021-08-01 10:33:33 +08:00
|
|
|
model = model.Page(page, size)
|
2020-03-13 17:21:30 +08:00
|
|
|
data, err := model.All()
|
|
|
|
if err != nil {
|
2021-08-01 10:33:33 +08:00
|
|
|
handler(nil, err)
|
2020-03-13 17:21:30 +08:00
|
|
|
break
|
|
|
|
}
|
|
|
|
if len(data) == 0 {
|
|
|
|
break
|
|
|
|
}
|
2022-11-01 20:12:21 +08:00
|
|
|
if !handler(data, err) {
|
2020-03-13 17:21:30 +08:00
|
|
|
break
|
|
|
|
}
|
2021-08-01 10:33:33 +08:00
|
|
|
if len(data) < size {
|
2020-03-13 17:21:30 +08:00
|
|
|
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.
|
|
|
|
//
|
2021-02-08 17:57:21 +08:00
|
|
|
// The optional parameter `where` is the same as the parameter of Model.Where function,
|
2020-03-13 17:21:30 +08:00
|
|
|
// see Model.Where.
|
|
|
|
func (m *Model) One(where ...interface{}) (Record, error) {
|
2022-03-24 17:51:49 +08:00
|
|
|
var ctx = m.GetCtx()
|
2020-03-13 17:21:30 +08:00
|
|
|
if len(where) > 0 {
|
|
|
|
return m.Where(where[0], where[1:]...).One()
|
|
|
|
}
|
2022-03-24 17:51:49 +08:00
|
|
|
all, err := m.doGetAll(ctx, true)
|
2020-03-13 17:21:30 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(all) > 0 {
|
|
|
|
return all[0], nil
|
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Array queries and returns data values as slice from database.
|
2021-05-02 12:17:06 +08:00
|
|
|
// Note that if there are multiple columns in the result, it returns just one column values randomly.
|
2020-03-13 17:21:30 +08:00
|
|
|
//
|
2021-02-08 17:57:21 +08:00
|
|
|
// If the optional parameter `fieldsAndWhere` is given, the fieldsAndWhere[0] is the selected fields
|
2020-03-13 17:21:30 +08:00
|
|
|
// 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.
|
2021-02-08 17:57:21 +08:00
|
|
|
// The parameter `pointer` should be type of *struct/**struct. If type **struct is given,
|
2020-03-13 17:21:30 +08:00
|
|
|
// it can create the struct internally during converting.
|
|
|
|
//
|
2021-02-08 17:57:21 +08:00
|
|
|
// The optional parameter `where` is the same as the parameter of Model.Where function,
|
2020-03-13 17:21:30 +08:00
|
|
|
// see Model.Where.
|
|
|
|
//
|
2021-06-22 21:48:56 +08:00
|
|
|
// Note that it returns sql.ErrNoRows if the given parameter `pointer` pointed to a variable that has
|
|
|
|
// default value and there's no record retrieved with the given conditions from table.
|
2020-03-13 17:21:30 +08:00
|
|
|
//
|
2021-06-22 21:48:56 +08:00
|
|
|
// Example:
|
2020-03-13 17:21:30 +08:00
|
|
|
// user := new(User)
|
2021-06-22 21:48:56 +08:00
|
|
|
// err := db.Model("user").Where("id", 1).Scan(user)
|
2020-03-13 17:21:30 +08:00
|
|
|
//
|
|
|
|
// user := (*User)(nil)
|
2021-11-16 17:21:13 +08:00
|
|
|
// err := db.Model("user").Where("id", 1).Scan(&user).
|
2021-06-22 21:48:56 +08:00
|
|
|
func (m *Model) doStruct(pointer interface{}, where ...interface{}) error {
|
|
|
|
model := m
|
|
|
|
// Auto selecting fields by struct attributes.
|
|
|
|
if model.fieldsEx == "" && (model.fields == "" || model.fields == "*") {
|
2021-09-15 22:28:26 +08:00
|
|
|
if v, ok := pointer.(reflect.Value); ok {
|
|
|
|
model = m.Fields(v.Interface())
|
|
|
|
} else {
|
|
|
|
model = m.Fields(pointer)
|
|
|
|
}
|
2021-06-22 21:48:56 +08:00
|
|
|
}
|
|
|
|
one, err := model.One(where...)
|
2020-03-13 17:21:30 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-02-08 17:57:21 +08:00
|
|
|
if err = one.Struct(pointer); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-06-22 21:48:56 +08:00
|
|
|
return model.doWithScanStruct(pointer)
|
|
|
|
}
|
|
|
|
|
2020-03-13 17:21:30 +08:00
|
|
|
// Structs retrieves records from table and converts them into given struct slice.
|
2021-02-08 17:57:21 +08:00
|
|
|
// The parameter `pointer` should be type of *[]struct/*[]*struct. It can create and fill the struct
|
2020-03-13 17:21:30 +08:00
|
|
|
// slice internally during converting.
|
|
|
|
//
|
2021-02-08 17:57:21 +08:00
|
|
|
// The optional parameter `where` is the same as the parameter of Model.Where function,
|
2020-03-13 17:21:30 +08:00
|
|
|
// see Model.Where.
|
|
|
|
//
|
2021-06-22 21:48:56 +08:00
|
|
|
// Note that it returns sql.ErrNoRows if the given parameter `pointer` pointed to a variable that has
|
|
|
|
// default value and there's no record retrieved with the given conditions from table.
|
2020-03-13 17:21:30 +08:00
|
|
|
//
|
2021-06-22 21:48:56 +08:00
|
|
|
// Example:
|
2020-03-13 17:21:30 +08:00
|
|
|
// users := ([]User)(nil)
|
2021-06-22 21:48:56 +08:00
|
|
|
// err := db.Model("user").Scan(&users)
|
2020-03-13 17:21:30 +08:00
|
|
|
//
|
|
|
|
// users := ([]*User)(nil)
|
2021-11-16 17:21:13 +08:00
|
|
|
// err := db.Model("user").Scan(&users).
|
2021-06-22 21:48:56 +08:00
|
|
|
func (m *Model) doStructs(pointer interface{}, where ...interface{}) error {
|
|
|
|
model := m
|
|
|
|
// Auto selecting fields by struct attributes.
|
|
|
|
if model.fieldsEx == "" && (model.fields == "" || model.fields == "*") {
|
2021-09-15 22:28:26 +08:00
|
|
|
if v, ok := pointer.(reflect.Value); ok {
|
|
|
|
model = m.Fields(
|
|
|
|
reflect.New(
|
|
|
|
v.Type().Elem(),
|
|
|
|
).Interface(),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
model = m.Fields(
|
|
|
|
reflect.New(
|
|
|
|
reflect.ValueOf(pointer).Elem().Type().Elem(),
|
|
|
|
).Interface(),
|
|
|
|
)
|
|
|
|
}
|
2021-06-22 21:48:56 +08:00
|
|
|
}
|
|
|
|
all, err := model.All(where...)
|
2020-03-13 17:21:30 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-02-09 18:00:43 +08:00
|
|
|
if err = all.Structs(pointer); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-06-22 21:48:56 +08:00
|
|
|
return model.doWithScanStructs(pointer)
|
2020-03-13 17:21:30 +08:00
|
|
|
}
|
|
|
|
|
2021-02-08 17:57:21 +08:00
|
|
|
// Scan automatically calls Struct or Structs function according to the type of parameter `pointer`.
|
2021-06-22 21:48:56 +08:00
|
|
|
// It calls function doStruct if `pointer` is type of *struct/**struct.
|
|
|
|
// It calls function doStructs if `pointer` is type of *[]struct/*[]*struct.
|
2020-03-13 17:21:30 +08:00
|
|
|
//
|
2021-06-22 21:48:56 +08:00
|
|
|
// The optional parameter `where` is the same as the parameter of Model.Where function, see Model.Where.
|
2020-03-13 17:21:30 +08:00
|
|
|
//
|
2021-06-22 21:48:56 +08:00
|
|
|
// Note that it returns sql.ErrNoRows if the given parameter `pointer` pointed to a variable that has
|
|
|
|
// default value and there's no record retrieved with the given conditions from table.
|
2020-03-13 17:21:30 +08:00
|
|
|
//
|
2021-06-22 21:48:56 +08:00
|
|
|
// Example:
|
2020-03-13 17:21:30 +08:00
|
|
|
// user := new(User)
|
2020-12-29 13:30:15 +08:00
|
|
|
// err := db.Model("user").Where("id", 1).Scan(user)
|
2020-03-13 17:21:30 +08:00
|
|
|
//
|
|
|
|
// user := (*User)(nil)
|
2020-12-29 13:30:15 +08:00
|
|
|
// err := db.Model("user").Where("id", 1).Scan(&user)
|
2020-03-13 17:21:30 +08:00
|
|
|
//
|
|
|
|
// users := ([]User)(nil)
|
2020-12-29 13:30:15 +08:00
|
|
|
// err := db.Model("user").Scan(&users)
|
2020-03-13 17:21:30 +08:00
|
|
|
//
|
|
|
|
// users := ([]*User)(nil)
|
2021-11-16 17:21:13 +08:00
|
|
|
// err := db.Model("user").Scan(&users).
|
2020-03-13 17:21:30 +08:00
|
|
|
func (m *Model) Scan(pointer interface{}, where ...interface{}) error {
|
2022-03-11 10:24:42 +08:00
|
|
|
reflectInfo := reflection.OriginTypeAndKind(pointer)
|
2021-10-28 23:18:23 +08:00
|
|
|
if reflectInfo.InputKind != reflect.Ptr {
|
|
|
|
return gerror.NewCode(
|
|
|
|
gcode.CodeInvalidParameter,
|
|
|
|
`the parameter "pointer" for function Scan should type of pointer`,
|
|
|
|
)
|
2021-06-22 21:48:56 +08:00
|
|
|
}
|
2021-10-28 23:18:23 +08:00
|
|
|
switch reflectInfo.OriginKind {
|
2021-06-22 21:48:56 +08:00
|
|
|
case reflect.Slice, reflect.Array:
|
|
|
|
return m.doStructs(pointer, where...)
|
|
|
|
|
|
|
|
case reflect.Struct, reflect.Invalid:
|
|
|
|
return m.doStruct(pointer, where...)
|
|
|
|
|
|
|
|
default:
|
2021-07-20 23:02:02 +08:00
|
|
|
return gerror.NewCode(
|
2021-08-24 21:18:59 +08:00
|
|
|
gcode.CodeInvalidParameter,
|
2021-07-20 23:02:02 +08:00
|
|
|
`element of parameter "pointer" for function Scan should type of struct/*struct/[]struct/[]*struct`,
|
|
|
|
)
|
2020-03-13 17:21:30 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-08 17:57:21 +08:00
|
|
|
// ScanList converts `r` to struct slice which contains other complex struct attributes.
|
|
|
|
// Note that the parameter `listPointer` should be type of *[]struct/*[]*struct.
|
2020-07-05 11:54:37 +08:00
|
|
|
//
|
2021-11-05 01:07:06 +08:00
|
|
|
// See Result.ScanList.
|
|
|
|
func (m *Model) ScanList(structSlicePointer interface{}, bindToAttrName string, relationAttrNameAndFields ...string) (err error) {
|
2022-01-15 21:38:15 +08:00
|
|
|
var result Result
|
2021-11-23 15:28:40 +08:00
|
|
|
out, err := checkGetSliceElementInfoForScanList(structSlicePointer, bindToAttrName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-01-15 21:38:15 +08:00
|
|
|
if m.fields != defaultFields || m.fieldsEx != "" {
|
|
|
|
// There are custom fields.
|
|
|
|
result, err = m.All()
|
|
|
|
} else {
|
|
|
|
// Filter fields using temporary created struct using reflect.New.
|
|
|
|
result, err = m.Fields(reflect.New(out.BindToAttrType).Interface()).All()
|
|
|
|
}
|
2020-07-05 11:54:37 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-11-05 01:07:06 +08:00
|
|
|
var (
|
|
|
|
relationAttrName string
|
|
|
|
relationFields string
|
|
|
|
)
|
|
|
|
switch len(relationAttrNameAndFields) {
|
|
|
|
case 2:
|
|
|
|
relationAttrName = relationAttrNameAndFields[0]
|
|
|
|
relationFields = relationAttrNameAndFields[1]
|
|
|
|
case 1:
|
|
|
|
relationFields = relationAttrNameAndFields[0]
|
|
|
|
}
|
2021-11-23 15:28:40 +08:00
|
|
|
return doScanList(doScanListInput{
|
|
|
|
Model: m,
|
|
|
|
Result: result,
|
|
|
|
StructSlicePointer: structSlicePointer,
|
|
|
|
StructSliceValue: out.SliceReflectValue,
|
|
|
|
BindToAttrName: bindToAttrName,
|
|
|
|
RelationAttrName: relationAttrName,
|
|
|
|
RelationFields: relationFields,
|
|
|
|
})
|
2020-07-05 11:54:37 +08:00
|
|
|
}
|
|
|
|
|
2022-11-16 10:04:49 +08:00
|
|
|
// 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 `fieldsAndWhere` 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) {
|
|
|
|
var ctx = m.GetCtx()
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var (
|
|
|
|
sqlWithHolder, holderArgs = m.getFormattedSqlAndArgs(ctx, queryTypeValue, true)
|
|
|
|
all, err = m.doGetAllBySql(ctx, queryTypeValue, sqlWithHolder, holderArgs...)
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(all) > 0 {
|
|
|
|
if internalData := m.db.GetCore().GetInternalCtxDataFromCtx(ctx); internalData != nil {
|
|
|
|
record := all[0]
|
|
|
|
if v, ok := record[internalData.FirstResultColumn]; ok {
|
|
|
|
return v, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, gerror.NewCode(
|
|
|
|
gcode.CodeInternalError,
|
|
|
|
`query value error: the internal context data is missing. there's internal issue should be fixed`,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2020-03-13 17:21:30 +08:00
|
|
|
// Count does "SELECT COUNT(x) FROM ..." statement for the model.
|
2021-02-08 17:57:21 +08:00
|
|
|
// The optional parameter `where` is the same as the parameter of Model.Where function,
|
2020-03-13 17:21:30 +08:00
|
|
|
// see Model.Where.
|
2022-12-30 16:54:43 +08:00
|
|
|
func (m *Model) Count(where ...interface{}) (int, error) {
|
2022-03-24 17:51:49 +08:00
|
|
|
var ctx = m.GetCtx()
|
2020-03-13 17:21:30 +08:00
|
|
|
if len(where) > 0 {
|
|
|
|
return m.Where(where[0], where[1:]...).Count()
|
|
|
|
}
|
2021-05-27 22:18:16 +08:00
|
|
|
var (
|
2022-03-24 15:33:30 +08:00
|
|
|
sqlWithHolder, holderArgs = m.getFormattedSqlAndArgs(ctx, queryTypeCount, false)
|
|
|
|
all, err = m.doGetAllBySql(ctx, queryTypeCount, sqlWithHolder, holderArgs...)
|
2021-05-27 22:18:16 +08:00
|
|
|
)
|
2020-03-13 17:21:30 +08:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2022-03-24 15:33:30 +08:00
|
|
|
if len(all) > 0 {
|
2022-04-12 21:31:51 +08:00
|
|
|
if internalData := m.db.GetCore().GetInternalCtxDataFromCtx(ctx); internalData != nil {
|
2022-03-24 15:33:30 +08:00
|
|
|
record := all[0]
|
|
|
|
if v, ok := record[internalData.FirstResultColumn]; ok {
|
2022-12-30 16:54:43 +08:00
|
|
|
return v.Int(), nil
|
2022-03-24 15:33:30 +08:00
|
|
|
}
|
2020-03-13 17:21:30 +08:00
|
|
|
}
|
2022-03-31 16:15:44 +08:00
|
|
|
return 0, gerror.NewCode(
|
|
|
|
gcode.CodeInternalError,
|
2022-04-18 20:28:00 +08:00
|
|
|
`query count error: the internal context data is missing. there's internal issue should be fixed`,
|
2022-03-31 16:15:44 +08:00
|
|
|
)
|
2020-03-13 17:21:30 +08:00
|
|
|
}
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
2021-05-02 12:17:06 +08:00
|
|
|
// CountColumn does "SELECT COUNT(x) FROM ..." statement for the model.
|
2022-12-30 16:54:43 +08:00
|
|
|
func (m *Model) CountColumn(column string) (int, error) {
|
2021-05-02 12:17:06 +08:00
|
|
|
if len(column) == 0 {
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
return m.Fields(column).Count()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Min does "SELECT MIN(x) FROM ..." statement for the model.
|
|
|
|
func (m *Model) Min(column string) (float64, error) {
|
|
|
|
if len(column) == 0 {
|
|
|
|
return 0, nil
|
|
|
|
}
|
2021-11-17 21:29:46 +08:00
|
|
|
value, err := m.Fields(fmt.Sprintf(`MIN(%s)`, m.QuoteWord(column))).Value()
|
2021-05-02 12:17:06 +08:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return value.Float64(), err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Max does "SELECT MAX(x) FROM ..." statement for the model.
|
|
|
|
func (m *Model) Max(column string) (float64, error) {
|
|
|
|
if len(column) == 0 {
|
|
|
|
return 0, nil
|
|
|
|
}
|
2021-11-17 21:29:46 +08:00
|
|
|
value, err := m.Fields(fmt.Sprintf(`MAX(%s)`, m.QuoteWord(column))).Value()
|
2021-05-02 12:17:06 +08:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return value.Float64(), err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Avg does "SELECT AVG(x) FROM ..." statement for the model.
|
|
|
|
func (m *Model) Avg(column string) (float64, error) {
|
|
|
|
if len(column) == 0 {
|
|
|
|
return 0, nil
|
|
|
|
}
|
2021-11-17 21:29:46 +08:00
|
|
|
value, err := m.Fields(fmt.Sprintf(`AVG(%s)`, m.QuoteWord(column))).Value()
|
2021-05-02 12:17:06 +08:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return value.Float64(), err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sum does "SELECT SUM(x) FROM ..." statement for the model.
|
|
|
|
func (m *Model) Sum(column string) (float64, error) {
|
|
|
|
if len(column) == 0 {
|
|
|
|
return 0, nil
|
|
|
|
}
|
2021-11-17 21:29:46 +08:00
|
|
|
value, err := m.Fields(fmt.Sprintf(`SUM(%s)`, m.QuoteWord(column))).Value()
|
2021-05-02 12:17:06 +08:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return value.Float64(), err
|
|
|
|
}
|
|
|
|
|
2021-06-06 23:06:39 +08:00
|
|
|
// Union does "(SELECT xxx FROM xxx) UNION (SELECT xxx FROM xxx) ..." statement for the model.
|
|
|
|
func (m *Model) Union(unions ...*Model) *Model {
|
|
|
|
return m.db.Union(unions...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnionAll does "(SELECT xxx FROM xxx) UNION ALL (SELECT xxx FROM xxx) ..." statement for the model.
|
|
|
|
func (m *Model) UnionAll(unions ...*Model) *Model {
|
|
|
|
return m.db.UnionAll(unions...)
|
|
|
|
}
|
|
|
|
|
2021-11-17 22:01:17 +08:00
|
|
|
// Limit sets the "LIMIT" statement for the model.
|
|
|
|
// The parameter `limit` 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
|
|
|
|
}
|
|
|
|
|
|
|
|
// Distinct forces the query to only return distinct results.
|
|
|
|
func (m *Model) Distinct() *Model {
|
|
|
|
model := m.getModel()
|
|
|
|
model.distinct = "DISTINCT "
|
|
|
|
return model
|
|
|
|
}
|
|
|
|
|
|
|
|
// Page sets the paging number for the model.
|
|
|
|
// The parameter `page` is started from 1 for paging.
|
|
|
|
// Note that, it differs that the Limit function starts 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
|
|
|
|
}
|
|
|
|
|
|
|
|
// Having sets the having statement for the model.
|
|
|
|
// The parameters of this function usage are as the same as function Where.
|
|
|
|
// See Where.
|
|
|
|
func (m *Model) Having(having interface{}, args ...interface{}) *Model {
|
|
|
|
model := m.getModel()
|
|
|
|
model.having = []interface{}{
|
|
|
|
having, args,
|
|
|
|
}
|
|
|
|
return model
|
|
|
|
}
|
|
|
|
|
2022-11-16 10:04:49 +08:00
|
|
|
// doGetAll 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 parameter `limit1` specifies whether limits querying only one record if m.limit is not set.
|
|
|
|
// The optional parameter `where` is the same as the parameter of Model.Where function,
|
|
|
|
// see Model.Where.
|
|
|
|
func (m *Model) doGetAll(ctx context.Context, limit1 bool, where ...interface{}) (Result, error) {
|
|
|
|
if len(where) > 0 {
|
|
|
|
return m.Where(where[0], where[1:]...).All()
|
|
|
|
}
|
|
|
|
sqlWithHolder, holderArgs := m.getFormattedSqlAndArgs(ctx, queryTypeNormal, limit1)
|
|
|
|
return m.doGetAllBySql(ctx, queryTypeNormal, sqlWithHolder, holderArgs...)
|
|
|
|
}
|
|
|
|
|
2020-07-15 09:15:03 +08:00
|
|
|
// doGetAllBySql does the select statement on the database.
|
2022-11-16 10:04:49 +08:00
|
|
|
func (m *Model) doGetAllBySql(ctx context.Context, queryType queryType, sql string, args ...interface{}) (result Result, err error) {
|
2022-03-31 15:42:12 +08:00
|
|
|
if result, err = m.getSelectResultFromCache(ctx, sql, args...); err != nil || result != nil {
|
|
|
|
return
|
2020-04-26 21:31:55 +08:00
|
|
|
}
|
2022-03-14 23:47:55 +08:00
|
|
|
|
|
|
|
in := &HookSelectInput{
|
|
|
|
internalParamHookSelect: internalParamHookSelect{
|
|
|
|
internalParamHook: internalParamHook{
|
2022-05-09 14:22:28 +08:00
|
|
|
link: m.getLink(false),
|
2022-03-14 23:47:55 +08:00
|
|
|
},
|
2022-03-24 15:33:30 +08:00
|
|
|
handler: m.hookHandler.Select,
|
2022-03-14 23:47:55 +08:00
|
|
|
},
|
2022-05-09 14:22:28 +08:00
|
|
|
Model: m,
|
2022-03-23 16:23:33 +08:00
|
|
|
Table: m.tables,
|
|
|
|
Sql: sql,
|
|
|
|
Args: m.mergeArguments(args),
|
2022-03-14 23:47:55 +08:00
|
|
|
}
|
2022-03-31 15:42:12 +08:00
|
|
|
if result, err = in.Next(ctx); err != nil {
|
|
|
|
return
|
2020-04-26 21:31:55 +08:00
|
|
|
}
|
2022-03-31 15:42:12 +08:00
|
|
|
|
2022-11-16 10:04:49 +08:00
|
|
|
err = m.saveSelectResultToCache(ctx, queryType, result, sql, args...)
|
2022-03-31 15:42:12 +08:00
|
|
|
return
|
2020-04-26 21:31:55 +08:00
|
|
|
}
|
2021-05-27 22:18:16 +08:00
|
|
|
|
2023-02-08 19:08:10 +08:00
|
|
|
func (m *Model) getFormattedSqlAndArgs(
|
|
|
|
ctx context.Context, queryType queryType, limit1 bool,
|
|
|
|
) (sqlWithHolder string, holderArgs []interface{}) {
|
2021-05-27 22:18:16 +08:00
|
|
|
switch queryType {
|
|
|
|
case queryTypeCount:
|
2022-03-24 15:33:30 +08:00
|
|
|
queryFields := "COUNT(1)"
|
2021-05-27 22:18:16 +08:00
|
|
|
if m.fields != "" && m.fields != "*" {
|
|
|
|
// DO NOT quote the m.fields here, in case of fields like:
|
|
|
|
// DISTINCT t.user_id uid
|
2022-03-24 15:33:30 +08:00
|
|
|
queryFields = fmt.Sprintf(`COUNT(%s%s)`, m.distinct, m.fields)
|
2021-05-27 22:18:16 +08:00
|
|
|
}
|
2021-06-21 19:21:38 +08:00
|
|
|
// Raw SQL Model.
|
|
|
|
if m.rawSql != "" {
|
2022-03-24 15:33:30 +08:00
|
|
|
sqlWithHolder = fmt.Sprintf("SELECT %s FROM (%s) AS T", queryFields, m.rawSql)
|
2021-06-21 19:21:38 +08:00
|
|
|
return sqlWithHolder, nil
|
|
|
|
}
|
2022-03-24 17:51:49 +08:00
|
|
|
conditionWhere, conditionExtra, conditionArgs := m.formatCondition(ctx, false, true)
|
2022-03-24 15:33:30 +08:00
|
|
|
sqlWithHolder = fmt.Sprintf("SELECT %s FROM %s%s", queryFields, m.tables, conditionWhere+conditionExtra)
|
2021-05-27 22:18:16 +08:00
|
|
|
if len(m.groupBy) > 0 {
|
|
|
|
sqlWithHolder = fmt.Sprintf("SELECT COUNT(1) FROM (%s) count_alias", sqlWithHolder)
|
|
|
|
}
|
|
|
|
return sqlWithHolder, conditionArgs
|
|
|
|
|
|
|
|
default:
|
2022-03-24 17:51:49 +08:00
|
|
|
conditionWhere, conditionExtra, conditionArgs := m.formatCondition(ctx, limit1, false)
|
2021-06-06 23:06:39 +08:00
|
|
|
// Raw SQL Model, especially for UNION/UNION ALL featured SQL.
|
|
|
|
if m.rawSql != "" {
|
|
|
|
sqlWithHolder = fmt.Sprintf(
|
|
|
|
"%s%s",
|
|
|
|
m.rawSql,
|
|
|
|
conditionWhere+conditionExtra,
|
|
|
|
)
|
|
|
|
return sqlWithHolder, conditionArgs
|
|
|
|
}
|
2021-05-27 22:18:16 +08:00
|
|
|
// DO NOT quote the m.fields where, in case of fields like:
|
|
|
|
// DISTINCT t.user_id uid
|
|
|
|
sqlWithHolder = fmt.Sprintf(
|
|
|
|
"SELECT %s%s FROM %s%s",
|
2022-03-24 15:33:30 +08:00
|
|
|
m.distinct, m.getFieldsFiltered(), m.tables, conditionWhere+conditionExtra,
|
2021-05-27 22:18:16 +08:00
|
|
|
)
|
|
|
|
return sqlWithHolder, conditionArgs
|
|
|
|
}
|
|
|
|
}
|
2021-11-17 22:01:17 +08:00
|
|
|
|
2023-02-08 19:08:10 +08:00
|
|
|
func (m *Model) getHolderAndArgsAsSubModel(ctx context.Context) (holder string, args []interface{}) {
|
|
|
|
holder, args = m.getFormattedSqlAndArgs(
|
|
|
|
ctx, queryTypeNormal, false,
|
|
|
|
)
|
|
|
|
args = m.mergeArguments(args)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-04-30 15:53:56 +08:00
|
|
|
func (m *Model) getAutoPrefix() string {
|
2021-11-17 22:01:17 +08:00
|
|
|
autoPrefix := ""
|
|
|
|
if gstr.Contains(m.tables, " JOIN ") {
|
|
|
|
autoPrefix = m.db.GetCore().QuoteWord(
|
|
|
|
m.db.GetCore().guessPrimaryTableName(m.tablesInit),
|
|
|
|
)
|
|
|
|
}
|
2022-04-30 15:53:56 +08:00
|
|
|
return autoPrefix
|
|
|
|
}
|
2021-11-17 22:01:17 +08:00
|
|
|
|
2022-11-16 10:04:49 +08:00
|
|
|
// getFieldsFiltered checks the fields and fieldsEx attributes, filters and returns the fields that will
|
|
|
|
// really be committed to underlying database driver.
|
|
|
|
func (m *Model) getFieldsFiltered() string {
|
|
|
|
if m.fieldsEx == "" {
|
|
|
|
// No filtering, containing special chars.
|
|
|
|
if gstr.ContainsAny(m.fields, "()") {
|
|
|
|
return m.fields
|
|
|
|
}
|
|
|
|
// No filtering.
|
|
|
|
if !gstr.ContainsAny(m.fields, ". ") {
|
|
|
|
return m.db.GetCore().QuoteString(m.fields)
|
|
|
|
}
|
|
|
|
return m.fields
|
|
|
|
}
|
|
|
|
var (
|
|
|
|
fieldsArray []string
|
|
|
|
fieldsExSet = gset.NewStrSetFrom(gstr.SplitAndTrim(m.fieldsEx, ","))
|
|
|
|
)
|
|
|
|
if m.fields != "*" {
|
|
|
|
// Filter custom fields with fieldEx.
|
|
|
|
fieldsArray = make([]string, 0, 8)
|
|
|
|
for _, v := range gstr.SplitAndTrim(m.fields, ",") {
|
|
|
|
fieldsArray = append(fieldsArray, v[gstr.PosR(v, "-")+1:])
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if gstr.Contains(m.tables, " ") {
|
|
|
|
panic("function FieldsEx supports only single table operations")
|
|
|
|
}
|
|
|
|
// Filter table fields with fieldEx.
|
|
|
|
tableFields, err := m.TableFields(m.tablesInit)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
if len(tableFields) == 0 {
|
|
|
|
panic(fmt.Sprintf(`empty table fields for table "%s"`, m.tables))
|
|
|
|
}
|
|
|
|
fieldsArray = make([]string, len(tableFields))
|
|
|
|
for k, v := range tableFields {
|
|
|
|
fieldsArray[v.Index] = k
|
|
|
|
}
|
|
|
|
}
|
|
|
|
newFields := ""
|
|
|
|
for _, k := range fieldsArray {
|
|
|
|
if fieldsExSet.Contains(k) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if len(newFields) > 0 {
|
|
|
|
newFields += ","
|
|
|
|
}
|
|
|
|
newFields += m.db.GetCore().QuoteWord(k)
|
|
|
|
}
|
|
|
|
return newFields
|
|
|
|
}
|
|
|
|
|
2022-04-30 15:53:56 +08:00
|
|
|
// 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 `m`.
|
|
|
|
//
|
|
|
|
// The parameter `limit1` specifies whether limits querying only one record if m.limit is not set.
|
2023-02-15 09:45:40 +08:00
|
|
|
func (m *Model) formatCondition(
|
|
|
|
ctx context.Context, limit1 bool, isCountStatement bool,
|
|
|
|
) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) {
|
2022-04-30 15:53:56 +08:00
|
|
|
var autoPrefix = m.getAutoPrefix()
|
2021-11-17 22:01:17 +08:00
|
|
|
// GROUP BY.
|
|
|
|
if m.groupBy != "" {
|
|
|
|
conditionExtra += " GROUP BY " + m.groupBy
|
|
|
|
}
|
2022-04-30 15:53:56 +08:00
|
|
|
// WHERE
|
|
|
|
conditionWhere, conditionArgs = m.whereBuilder.Build()
|
2022-05-06 22:21:43 +08:00
|
|
|
softDeletingCondition := m.getConditionForSoftDeleting()
|
|
|
|
if m.rawSql != "" && conditionWhere != "" {
|
|
|
|
if gstr.ContainsI(m.rawSql, " WHERE ") {
|
|
|
|
conditionWhere = " AND " + conditionWhere
|
|
|
|
} else {
|
|
|
|
conditionWhere = " WHERE " + conditionWhere
|
|
|
|
}
|
|
|
|
} else if !m.unscoped && softDeletingCondition != "" {
|
|
|
|
if conditionWhere == "" {
|
|
|
|
conditionWhere = fmt.Sprintf(` WHERE %s`, softDeletingCondition)
|
|
|
|
} else {
|
|
|
|
conditionWhere = fmt.Sprintf(` WHERE (%s) AND %s`, conditionWhere, softDeletingCondition)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if conditionWhere != "" {
|
|
|
|
conditionWhere = " WHERE " + conditionWhere
|
|
|
|
}
|
|
|
|
}
|
2021-11-17 22:01:17 +08:00
|
|
|
// HAVING.
|
|
|
|
if len(m.having) > 0 {
|
2022-04-30 15:53:56 +08:00
|
|
|
havingHolder := WhereHolder{
|
2021-12-28 17:15:01 +08:00
|
|
|
Where: m.having[0],
|
|
|
|
Args: gconv.Interfaces(m.having[1]),
|
|
|
|
Prefix: autoPrefix,
|
|
|
|
}
|
2022-03-24 17:51:49 +08:00
|
|
|
havingStr, havingArgs := formatWhereHolder(ctx, m.db, formatWhereHolderInput{
|
2022-04-30 15:53:56 +08:00
|
|
|
WhereHolder: havingHolder,
|
|
|
|
OmitNil: m.option&optionOmitNilWhere > 0,
|
|
|
|
OmitEmpty: m.option&optionOmitEmptyWhere > 0,
|
|
|
|
Schema: m.schema,
|
|
|
|
Table: m.tables,
|
2021-11-17 22:01:17 +08:00
|
|
|
})
|
|
|
|
if len(havingStr) > 0 {
|
|
|
|
conditionExtra += " HAVING " + havingStr
|
|
|
|
conditionArgs = append(conditionArgs, havingArgs...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// ORDER BY.
|
|
|
|
if m.orderBy != "" {
|
|
|
|
conditionExtra += " ORDER BY " + m.orderBy
|
|
|
|
}
|
|
|
|
// LIMIT.
|
|
|
|
if !isCountStatement {
|
|
|
|
if m.limit != 0 {
|
|
|
|
if m.start >= 0 {
|
|
|
|
conditionExtra += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit)
|
|
|
|
} else {
|
|
|
|
conditionExtra += fmt.Sprintf(" LIMIT %d", m.limit)
|
|
|
|
}
|
|
|
|
} else if limit1 {
|
|
|
|
conditionExtra += " LIMIT 1"
|
|
|
|
}
|
|
|
|
|
|
|
|
if m.offset >= 0 {
|
|
|
|
conditionExtra += fmt.Sprintf(" OFFSET %d", m.offset)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if m.lockInfo != "" {
|
|
|
|
conditionExtra += " " + m.lockInfo
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|