diff --git a/database/gdb/gdb_core_utility.go b/database/gdb/gdb_core_utility.go index 87add6748..5b0f1973a 100644 --- a/database/gdb/gdb_core_utility.go +++ b/database/gdb/gdb_core_utility.go @@ -31,6 +31,7 @@ func (c *Core) SlaveLink(schema ...string) (Link, error) { // QuoteWord checks given string `s` a word, if true quotes it with security chars of the database // and returns the quoted string; or else return `s` without any change. +// The meaning of a `word` can be considered as a column name. func (c *Core) QuoteWord(s string) string { charLeft, charRight := c.db.GetChars() return doQuoteWord(s, charLeft, charRight) @@ -38,6 +39,7 @@ func (c *Core) QuoteWord(s string) string { // QuoteString quotes string with quote chars. Strings like: // "user", "user u", "user,user_detail", "user u, user_detail ut", "u.id asc". +// The meaning of a `string` can be considered as part of a statement string including columns. func (c *Core) QuoteString(s string) string { charLeft, charRight := c.db.GetChars() return doQuoteString(s, charLeft, charRight) diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index a9d1fe2dd..fe761dca1 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -73,6 +73,7 @@ const ( whereHolderOperatorWhere = 1 whereHolderOperatorAnd = 2 whereHolderOperatorOr = 3 + defaultFields = "*" ) // Table is alias of Core.Model. @@ -130,7 +131,7 @@ func (c *Core) Model(tableNameQueryOrStruct ...interface{}) *Model { db: c.db, tablesInit: tableStr, tables: tableStr, - fields: "*", + fields: defaultFields, start: -1, offset: -1, filter: true, diff --git a/database/gdb/gdb_model_condition.go b/database/gdb/gdb_model_condition.go index 933c3ede9..5853fe7df 100644 --- a/database/gdb/gdb_model_condition.go +++ b/database/gdb/gdb_model_condition.go @@ -237,10 +237,13 @@ func (m *Model) WhereOrNotNull(columns ...string) *Model { } // Group sets the "GROUP BY" statement for the model. -func (m *Model) Group(groupBy string) *Model { - model := m.getModel() - model.groupBy = m.db.GetCore().QuoteString(groupBy) - return model +func (m *Model) Group(groupBy ...string) *Model { + if len(groupBy) > 0 { + model := m.getModel() + model.groupBy = m.db.GetCore().QuoteString(gstr.Join(groupBy, ",")) + return model + } + return m } // And adds "AND" condition to the where statement. diff --git a/database/gdb/gdb_model_fields.go b/database/gdb/gdb_model_fields.go index 1d3b8a626..a4115039e 100644 --- a/database/gdb/gdb_model_fields.go +++ b/database/gdb/gdb_model_fields.go @@ -14,7 +14,7 @@ import ( "github.com/gogf/gf/util/gutil" ) -// Fields sets the operation fields of the model, multiple fields joined using char ','. +// Fields appends `fieldNamesOrMapStruct` to the operation fields of the model, multiple fields joined using char ','. // The parameter `fieldNamesOrMapStruct` can be type of string/map/*map/struct/*struct. func (m *Model) Fields(fieldNamesOrMapStruct ...interface{}) *Model { length := len(fieldNamesOrMapStruct) @@ -24,26 +24,32 @@ func (m *Model) Fields(fieldNamesOrMapStruct ...interface{}) *Model { switch { // String slice. case length >= 2: - model := m.getModel() - model.fields = gstr.Join(m.mappingAndFilterToTableFields(gconv.Strings(fieldNamesOrMapStruct), true), ",") - return model - // It need type asserting. + return m.appendFieldsByStr(gstr.Join( + m.mappingAndFilterToTableFields(gconv.Strings(fieldNamesOrMapStruct), true), + ",", + )) + // It needs type asserting. case length == 1: - model := m.getModel() switch r := fieldNamesOrMapStruct[0].(type) { case string: - model.fields = gstr.Join(m.mappingAndFilterToTableFields([]string{r}, false), ",") + return m.appendFieldsByStr(gstr.Join( + m.mappingAndFilterToTableFields([]string{r}, false), ",", + )) case []string: - model.fields = gstr.Join(m.mappingAndFilterToTableFields(r, true), ",") + return m.appendFieldsByStr(gstr.Join( + m.mappingAndFilterToTableFields(r, true), ",", + )) default: - model.fields = gstr.Join(m.mappingAndFilterToTableFields(gutil.Keys(r), true), ",") + return m.appendFieldsByStr(gstr.Join( + m.mappingAndFilterToTableFields(gutil.Keys(r), true), ",", + )) } - return model } return m } -// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','. +// FieldsEx appends `fieldNamesOrMapStruct` to the excluded operation fields of the model, +// multiple fields joined using char ','. // Note that this function supports only single table operations. // The parameter `fieldNamesOrMapStruct` can be type of string/map/*map/struct/*struct. func (m *Model) FieldsEx(fieldNamesOrMapStruct ...interface{}) *Model { @@ -70,6 +76,78 @@ func (m *Model) FieldsEx(fieldNamesOrMapStruct ...interface{}) *Model { return m } +// FieldCount formats and appends commonly used field `COUNT(column)` to the select fields of model. +func (m *Model) FieldCount(column string, as ...string) *Model { + asStr := "" + if len(as) > 0 && as[0] != "" { + asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0])) + } + return m.appendFieldsByStr(fmt.Sprintf(`COUNT(%s)%s`, m.db.GetCore().QuoteWord(column), asStr)) +} + +// FieldSum formats and appends commonly used field `SUM(column)` to the select fields of model. +func (m *Model) FieldSum(column string, as ...string) *Model { + asStr := "" + if len(as) > 0 && as[0] != "" { + asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0])) + } + return m.appendFieldsByStr(fmt.Sprintf(`SUM(%s)%s`, m.db.GetCore().QuoteWord(column), asStr)) +} + +// FieldMin formats and appends commonly used field `MIN(column)` to the select fields of model. +func (m *Model) FieldMin(column string, as ...string) *Model { + asStr := "" + if len(as) > 0 && as[0] != "" { + asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0])) + } + return m.appendFieldsByStr(fmt.Sprintf(`MIN(%s)%s`, m.db.GetCore().QuoteWord(column), asStr)) +} + +// FieldMax formats and appends commonly used field `MAX(column)` to the select fields of model. +func (m *Model) FieldMax(column string, as ...string) *Model { + asStr := "" + if len(as) > 0 && as[0] != "" { + asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0])) + } + return m.appendFieldsByStr(fmt.Sprintf(`MAX(%s)%s`, m.db.GetCore().QuoteWord(column), asStr)) +} + +// FieldAvg formats and appends commonly used field `AVG(column)` to the select fields of model. +func (m *Model) FieldAvg(column string, as ...string) *Model { + asStr := "" + if len(as) > 0 && as[0] != "" { + asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0])) + } + return m.appendFieldsByStr(fmt.Sprintf(`AVG(%s)%s`, m.db.GetCore().QuoteWord(column), asStr)) +} + +func (m *Model) appendFieldsByStr(fields string) *Model { + if fields != "" { + model := m.getModel() + if model.fields == defaultFields { + model.fields = "" + } + if model.fields != "" { + model.fields += "," + } + model.fields += fields + return model + } + return m +} + +func (m *Model) appendFieldsExByStr(fieldsEx string) *Model { + if fieldsEx != "" { + model := m.getModel() + if model.fieldsEx != "" { + model.fieldsEx += "," + } + model.fieldsEx += fieldsEx + return model + } + return m +} + // Filter marks filtering the fields which does not exist in the fields of the operated table. // Note that this function supports only single table operations. // Deprecated, filter feature is automatically enabled from GoFrame v1.16.0, it is so no longer used. diff --git a/database/gdb/gdb_z_mysql_model_test.go b/database/gdb/gdb_z_mysql_model_test.go index ea4e32049..806cc80bc 100644 --- a/database/gdb/gdb_z_mysql_model_test.go +++ b/database/gdb/gdb_z_mysql_model_test.go @@ -3768,3 +3768,55 @@ func Test_Model_Handler(t *testing.T) { t.Assert(all[2]["id"], 4) }) } + +func Test_Model_FieldCount(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + all, err := db.Model(table).Fields("id").FieldCount("id", "total").Group("id").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(all), TableSize) + t.Assert(all[0]["id"], 1) + t.Assert(all[0]["total"], 1) + }) +} + +func Test_Model_FieldMax(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + all, err := db.Model(table).Fields("id").FieldMax("id", "total").Group("id").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(all), TableSize) + t.Assert(all[0]["id"], 1) + t.Assert(all[0]["total"], 1) + }) +} + +func Test_Model_FieldMin(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + all, err := db.Model(table).Fields("id").FieldMin("id", "total").Group("id").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(all), TableSize) + t.Assert(all[0]["id"], 1) + t.Assert(all[0]["total"], 1) + }) +} + +func Test_Model_FieldAvg(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + all, err := db.Model(table).Fields("id").FieldAvg("id", "total").Group("id").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(all), TableSize) + t.Assert(all[0]["id"], 1) + t.Assert(all[0]["total"], 1) + }) +} diff --git a/net/ghttp/ghttp_request_middleware.go b/net/ghttp/ghttp_request_middleware.go index 8554b4f3f..66dc66275 100644 --- a/net/ghttp/ghttp_request_middleware.go +++ b/net/ghttp/ghttp_request_middleware.go @@ -153,13 +153,17 @@ func (m *middleware) callHandlerFunc(funcInfo handlerFuncInfo) { switch len(results) { case 1: if !results[0].IsNil() { - m.request.handlerResponse.Error = results[0].Interface().(error) + if err, ok := results[0].Interface().(error); ok { + m.request.handlerResponse.Error = err + } } case 2: m.request.handlerResponse.Object = results[0].Interface() if !results[1].IsNil() { - m.request.handlerResponse.Error = results[1].Interface().(error) + if err, ok := results[1].Interface().(error); ok { + m.request.handlerResponse.Error = err + } } } } diff --git a/util/gvalid/gvalid.go b/util/gvalid/gvalid.go index 24160e741..a035b9337 100644 --- a/util/gvalid/gvalid.go +++ b/util/gvalid/gvalid.go @@ -24,9 +24,9 @@ import ( // required-if format: required-if:field,value,... brief: Required unless all given field and its value are equal. // required-unless format: required-unless:field,value,... brief: Required unless all given field and its value are not equal. // required-with format: required-with:field1,field2,... brief: Required if any of given fields are not empty. -// required-with-all format: required-with-all:field1,field2,... brief: Required if all of given fields are not empty. +// required-with-all format: required-with-all:field1,field2,... brief: Required if all given fields are not empty. // required-without format: required-without:field1,field2,... brief: Required if any of given fields are empty. -// required-without-all format: required-without-all:field1,field2,...brief: Required if all of given fields are empty. +// required-without-all format: required-without-all:field1,field2,...brief: Required if all given fields are empty. // bail format: bail brief: Stop validating when this field's validation failed. // date format: date brief: Standard date, like: 2006-01-02, 20060102, 2006.01.02 // date-format format: date-format:format brief: Custom date format.