diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index f2241d756..a37fccdf4 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -47,6 +47,7 @@ type DB interface { GetAll(query string, args ...interface{}) (Result, error) GetOne(query string, args ...interface{}) (Record, error) GetValue(query string, args ...interface{}) (Value, error) + GetArray(query string, args ...interface{}) ([]Value, error) GetCount(query string, args ...interface{}) (int, error) GetStruct(objPointer interface{}, query string, args ...interface{}) error GetStructs(objPointerSlice interface{}, query string, args ...interface{}) error diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 077684a54..4e8cb6c39 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -177,6 +177,16 @@ func (c *Core) GetOne(query string, args ...interface{}) (Record, error) { return nil, nil } +// GetArray 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. +func (c *Core) GetArray(query string, args ...interface{}) ([]Value, error) { + all, err := c.DB.DoGetAll(nil, query, args...) + if err != nil { + return nil, err + } + return all.Array(), nil +} + // GetStruct queries one record from database and converts it to given struct. // The parameter should be a pointer to struct. func (c *Core) GetStruct(pointer interface{}, query string, args ...interface{}) error { diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index f60681580..b58f932e0 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -12,6 +12,7 @@ import ( "errors" "fmt" "github.com/gogf/gf/internal/empty" + "github.com/gogf/gf/internal/utils" "github.com/gogf/gf/os/gtime" "reflect" "regexp" @@ -282,12 +283,18 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) ( newArgs = append(newArgs, args...) newWhere = buffer.String() if len(newArgs) > 0 { - // It supports formats like: Where/And/Or("uid", 1) , Where/And/Or("uid>=", 1) if gstr.Pos(newWhere, "?") == -1 { if lastOperatorReg.MatchString(newWhere) { + // Eg: Where/And/Or("uid>=", 1) newWhere += "?" } else if gregex.IsMatchString(`^[\w\.\-]+$`, newWhere) { - 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 += "=?" + } } } } diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index 8aac0948d..730483060 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -804,6 +804,29 @@ func (m *Model) Value(fieldsAndWhere ...interface{}) (Value, error) { 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. @@ -954,6 +977,19 @@ func (m *Model) FindValue(fieldsAndWhere ...interface{}) (Value, error) { 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) { diff --git a/database/gdb/gdb_type_result.go b/database/gdb/gdb_type_result.go index b10d00b83..b73711a87 100644 --- a/database/gdb/gdb_type_result.go +++ b/database/gdb/gdb_type_result.go @@ -28,11 +28,33 @@ func (r Result) Xml(rootTag ...string) string { // List converts to a List. func (r Result) List() List { - l := make(List, len(r)) + list := make(List, len(r)) for k, v := range r { - l[k] = v.Map() + list[k] = v.Map() } - return l + return list +} + +// Array retrieves and returns specified column values as slice. +// The parameter is optional is the column field is only one. +func (r Result) Array(field ...string) []Value { + array := make([]Value, len(r)) + if len(r) == 0 { + return array + } + key := "" + if len(field) > 0 && field[0] != "" { + key = field[0] + } else { + for k, _ := range r[0] { + key = k + break + } + } + for k, v := range r { + array[k] = v[key] + } + return array } // MapKeyStr converts to a map[string]Map of which key is specified by . diff --git a/database/gdb/gdb_unit_z_mysql_model_test.go b/database/gdb/gdb_unit_z_mysql_model_test.go index f25fd1d39..7b0b16360 100644 --- a/database/gdb/gdb_unit_z_mysql_model_test.go +++ b/database/gdb/gdb_unit_z_mysql_model_test.go @@ -534,6 +534,32 @@ func Test_Model_Value(t *testing.T) { }) } +func Test_Model_Array(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.Case(t, func() { + array, err := db.Table(table).Fields("nickname").Where("id", g.Slice{1, 2, 3}).Array() + gtest.Assert(err, nil) + gtest.Assert(array, g.Slice{"name_1", "name_2", "name_3"}) + }) + gtest.Case(t, func() { + array, err := db.Table(table).Array("nickname", "id", g.Slice{1, 2, 3}) + gtest.Assert(err, nil) + gtest.Assert(array, g.Slice{"name_1", "name_2", "name_3"}) + }) + gtest.Case(t, func() { + array, err := db.Table(table).FindArray("nickname", "id", g.Slice{1, 2, 3}) + gtest.Assert(err, nil) + gtest.Assert(array, g.Slice{"name_1", "name_2", "name_3"}) + }) + gtest.Case(t, func() { + array, err := db.Table(table).FindArray("nickname", g.Slice{1, 2, 3}) + gtest.Assert(err, nil) + gtest.Assert(array, g.Slice{"name_1", "name_2", "name_3"}) + }) +} + func Test_Model_FindValue(t *testing.T) { table := createInitTable() defer dropTable(table) diff --git a/internal/utils/utils.go b/internal/utils/utils.go new file mode 100644 index 000000000..05d3fa6af --- /dev/null +++ b/internal/utils/utils.go @@ -0,0 +1,8 @@ +// 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 utils provides some utility functions for internal usage. +package utils diff --git a/internal/utils/utils_array.go b/internal/utils/utils_array.go new file mode 100644 index 000000000..eae9b96b6 --- /dev/null +++ b/internal/utils/utils_array.go @@ -0,0 +1,26 @@ +// 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 utils + +import "reflect" + +// IsArray checks whether given value is array/slice. +// Note that it uses reflect internally implementing this feature. +func IsArray(value interface{}) bool { + rv := reflect.ValueOf(value) + kind := rv.Kind() + if kind == reflect.Ptr { + rv = rv.Elem() + kind = rv.Kind() + } + switch kind { + case reflect.Array, reflect.Slice: + return true + default: + return false + } +} diff --git a/internal/utilstr/utilstr.go b/internal/utils/utils_str.go similarity index 94% rename from internal/utilstr/utilstr.go rename to internal/utils/utils_str.go index 8c80c08b6..ff56aa0e0 100644 --- a/internal/utilstr/utilstr.go +++ b/internal/utils/utils_str.go @@ -4,8 +4,7 @@ // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// Package utilstr provides some string functions for internal usage. -package utilstr +package utils import "strings" diff --git a/text/gstr/gstr.go b/text/gstr/gstr.go index 27b61f6d5..e225f423a 100644 --- a/text/gstr/gstr.go +++ b/text/gstr/gstr.go @@ -16,7 +16,7 @@ import ( "unicode" "unicode/utf8" - "github.com/gogf/gf/internal/utilstr" + "github.com/gogf/gf/internal/utils" "github.com/gogf/gf/util/gconv" @@ -98,7 +98,7 @@ func ReplaceIByArray(origin string, array []string) string { // ReplaceByMap returns a copy of , // which is replaced by a map in unordered way, case-sensitively. func ReplaceByMap(origin string, replaces map[string]string) string { - return utilstr.ReplaceByMap(origin, replaces) + return utils.ReplaceByMap(origin, replaces) } // ReplaceIByMap returns a copy of , @@ -122,7 +122,7 @@ func ToUpper(s string) string { // UcFirst returns a copy of the string s with the first letter mapped to its upper case. func UcFirst(s string) string { - return utilstr.UcFirst(s) + return utils.UcFirst(s) } // LcFirst returns a copy of the string s with the first letter mapped to its lower case. @@ -143,17 +143,17 @@ func UcWords(str string) string { // IsLetterLower tests whether the given byte b is in lower case. func IsLetterLower(b byte) bool { - return utilstr.IsLetterLower(b) + return utils.IsLetterLower(b) } // IsLetterUpper tests whether the given byte b is in upper case. func IsLetterUpper(b byte) bool { - return utilstr.IsLetterUpper(b) + return utils.IsLetterUpper(b) } // IsNumeric tests whether the given string s is numeric. func IsNumeric(s string) bool { - return utilstr.IsNumeric(s) + return utils.IsNumeric(s) } // SubStr returns a portion of string specified by the and parameters. diff --git a/util/gconv/gconv_map.go b/util/gconv/gconv_map.go index af23daef6..663b0d65f 100644 --- a/util/gconv/gconv_map.go +++ b/util/gconv/gconv_map.go @@ -13,7 +13,7 @@ import ( "strings" "github.com/gogf/gf/internal/empty" - "github.com/gogf/gf/internal/utilstr" + "github.com/gogf/gf/internal/utils" ) // apiMapStrAny is the interface support for converting struct parameter to map. @@ -176,7 +176,7 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string] rvField = rv.Field(i) // Only convert the public attributes. fieldName := rtField.Name - if !utilstr.IsLetterUpper(fieldName[0]) { + if !utils.IsLetterUpper(fieldName[0]) { continue } name = "" diff --git a/util/gconv/gconv_slice_any.go b/util/gconv/gconv_slice_any.go index 1bce42c75..dd52d09fb 100644 --- a/util/gconv/gconv_slice_any.go +++ b/util/gconv/gconv_slice_any.go @@ -7,7 +7,7 @@ package gconv import ( - "github.com/gogf/gf/internal/utilstr" + "github.com/gogf/gf/internal/utils" "reflect" ) @@ -121,7 +121,7 @@ func Interfaces(i interface{}) []interface{} { array = make([]interface{}, 0) for i := 0; i < rv.NumField(); i++ { // Only public attributes. - if !utilstr.IsLetterUpper(rt.Field(i).Name[0]) { + if !utils.IsLetterUpper(rt.Field(i).Name[0]) { continue } array = append(array, rv.Field(i).Interface()) diff --git a/util/gconv/gconv_struct.go b/util/gconv/gconv_struct.go index 46fdeea40..7f57f0735 100644 --- a/util/gconv/gconv_struct.go +++ b/util/gconv/gconv_struct.go @@ -15,7 +15,7 @@ import ( "strings" "github.com/gogf/gf/internal/structs" - "github.com/gogf/gf/internal/utilstr" + "github.com/gogf/gf/internal/utils" ) // apiUnmarshalValue is the interface for custom defined types customizing value assignment. @@ -122,7 +122,7 @@ func Struct(params interface{}, pointer interface{}, mapping ...map[string]strin tempName := "" for i := 0; i < elem.NumField(); i++ { // Only do converting to public attributes. - if !utilstr.IsLetterUpper(elemType.Field(i).Name[0]) { + if !utils.IsLetterUpper(elemType.Field(i).Name[0]) { continue } tempName = elemType.Field(i).Name @@ -193,7 +193,7 @@ func StructDeep(params interface{}, pointer interface{}, mapping ...map[string]s rt := rv.Type() for i := 0; i < rv.NumField(); i++ { // Only do converting to public attributes. - if !utilstr.IsLetterUpper(rt.Field(i).Name[0]) { + if !utils.IsLetterUpper(rt.Field(i).Name[0]) { continue } trv := rv.Field(i) diff --git a/util/gconv/gconv_time.go b/util/gconv/gconv_time.go index ecba490d1..faa25492b 100644 --- a/util/gconv/gconv_time.go +++ b/util/gconv/gconv_time.go @@ -9,7 +9,7 @@ package gconv import ( "time" - "github.com/gogf/gf/internal/utilstr" + "github.com/gogf/gf/internal/utils" "github.com/gogf/gf/os/gtime" ) @@ -26,7 +26,7 @@ func Time(i interface{}, format ...string) time.Time { // If is numeric, then it converts as nanoseconds. func Duration(i interface{}) time.Duration { s := String(i) - if !utilstr.IsNumeric(s) { + if !utils.IsNumeric(s) { d, _ := time.ParseDuration(s) return d } @@ -50,7 +50,7 @@ func GTime(i interface{}, format ...string) *gtime.Time { t, _ := gtime.StrToTimeFormat(s, format[0]) return t } - if utilstr.IsNumeric(s) { + if utils.IsNumeric(s) { return gtime.NewFromTimeStamp(Int64(s)) } else { t, _ := gtime.StrToTime(s)