mirror of
https://gitee.com/johng/gf
synced 2026-06-09 02:57:43 +08:00
Compare commits
3 Commits
contrib/dr
...
v2.2.4
| Author | SHA1 | Date | |
|---|---|---|---|
| 14d2d747f6 | |||
| 73dc8c9c4b | |||
| 576f1a798c |
@ -731,16 +731,157 @@ func Test_Model_Count(t *testing.T) {
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, TableSize)
|
||||
})
|
||||
// gtest.C(t, func(t *gtest.T) {
|
||||
// count, err := db.Model(table).Fields("id myid").Where("id>8").Count()
|
||||
// t.AssertNil(err)
|
||||
// t.Assert(count, 2)
|
||||
// })
|
||||
// gtest.C(t, func(t *gtest.T) {
|
||||
// count, err := db.Model(table).As("u1").LeftJoin(table, "u2", "u2.id=u1.id").Fields("u2.id u2id").Where("u1.id>8").Count()
|
||||
// t.AssertNil(err)
|
||||
// t.Assert(count, 2)
|
||||
// })
|
||||
}
|
||||
|
||||
func Test_Model_Value_WithCache(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
value, err := db.Model(table).Where("id", 1).Cache(gdb.CacheOption{
|
||||
Duration: time.Second * 10,
|
||||
Force: false,
|
||||
}).Value()
|
||||
t.AssertNil(err)
|
||||
t.Assert(value.Int(), 0)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).Data(g.MapStrAny{
|
||||
"id": 1,
|
||||
"passport": fmt.Sprintf(`passport_%d`, 1),
|
||||
"password": fmt.Sprintf(`password_%d`, 1),
|
||||
"nickname": fmt.Sprintf(`nickname_%d`, 1),
|
||||
}).Insert()
|
||||
t.AssertNil(err)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
value, err := db.Model(table).Where("id", 1).Cache(gdb.CacheOption{
|
||||
Duration: time.Second * 10,
|
||||
Force: false,
|
||||
}).Value()
|
||||
t.AssertNil(err)
|
||||
t.Assert(value.Int(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Count_WithCache(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
count, err := db.Model(table).Where("id", 1).Cache(gdb.CacheOption{
|
||||
Duration: time.Second * 10,
|
||||
Force: false,
|
||||
}).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 0)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).Data(g.MapStrAny{
|
||||
"id": 1,
|
||||
"passport": fmt.Sprintf(`passport_%d`, 1),
|
||||
"password": fmt.Sprintf(`password_%d`, 1),
|
||||
"nickname": fmt.Sprintf(`nickname_%d`, 1),
|
||||
}).Insert()
|
||||
t.AssertNil(err)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
count, err := db.Model(table).Where("id", 1).Cache(gdb.CacheOption{
|
||||
Duration: time.Second * 10,
|
||||
Force: false,
|
||||
}).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Count_All_WithCache(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
count, err := db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second * 10,
|
||||
Force: false,
|
||||
}).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 0)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).Data(g.MapStrAny{
|
||||
"id": 1,
|
||||
"passport": fmt.Sprintf(`passport_%d`, 1),
|
||||
"password": fmt.Sprintf(`password_%d`, 1),
|
||||
"nickname": fmt.Sprintf(`nickname_%d`, 1),
|
||||
}).Insert()
|
||||
t.AssertNil(err)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
count, err := db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second * 10,
|
||||
Force: false,
|
||||
}).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 1)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).Data(g.MapStrAny{
|
||||
"id": 2,
|
||||
"passport": fmt.Sprintf(`passport_%d`, 2),
|
||||
"password": fmt.Sprintf(`password_%d`, 2),
|
||||
"nickname": fmt.Sprintf(`nickname_%d`, 2),
|
||||
}).Insert()
|
||||
t.AssertNil(err)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
count, err := db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second * 10,
|
||||
Force: false,
|
||||
}).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_CountColumn_WithCache(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
count, err := db.Model(table).Where("id", 1).Cache(gdb.CacheOption{
|
||||
Duration: time.Second * 10,
|
||||
Force: false,
|
||||
}).CountColumn("id")
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 0)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).Data(g.MapStrAny{
|
||||
"id": 1,
|
||||
"passport": fmt.Sprintf(`passport_%d`, 1),
|
||||
"password": fmt.Sprintf(`password_%d`, 1),
|
||||
"nickname": fmt.Sprintf(`nickname_%d`, 1),
|
||||
}).Insert()
|
||||
t.AssertNil(err)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
count, err := db.Model(table).Where("id", 1).Cache(gdb.CacheOption{
|
||||
Duration: time.Second * 10,
|
||||
Force: false,
|
||||
}).CountColumn("id")
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Select(t *testing.T) {
|
||||
|
||||
@ -289,20 +289,23 @@ type CatchSQLManager struct {
|
||||
DoCommit bool
|
||||
}
|
||||
|
||||
type queryType int
|
||||
|
||||
const (
|
||||
defaultModelSafe = false
|
||||
defaultCharset = `utf8`
|
||||
defaultProtocol = `tcp`
|
||||
queryTypeNormal = 0
|
||||
queryTypeCount = 1
|
||||
unionTypeNormal = 0
|
||||
unionTypeAll = 1
|
||||
defaultMaxIdleConnCount = 10 // Max idle connection count in pool.
|
||||
defaultMaxOpenConnCount = 0 // Max open connection count in pool. Default is no limit.
|
||||
defaultMaxConnLifeTime = 30 * time.Second // Max lifetime for per connection in pool in seconds.
|
||||
ctxTimeoutTypeExec = iota
|
||||
ctxTimeoutTypeQuery
|
||||
ctxTimeoutTypePrepare
|
||||
defaultModelSafe = false
|
||||
defaultCharset = `utf8`
|
||||
defaultProtocol = `tcp`
|
||||
queryTypeNormal queryType = 0
|
||||
queryTypeCount queryType = 1
|
||||
queryTypeValue queryType = 2
|
||||
unionTypeNormal = 0
|
||||
unionTypeAll = 1
|
||||
defaultMaxIdleConnCount = 10 // Max idle connection count in pool.
|
||||
defaultMaxOpenConnCount = 0 // Max open connection count in pool. Default is no limit.
|
||||
defaultMaxConnLifeTime = 30 * time.Second // Max lifetime for per connection in pool in seconds.
|
||||
ctxTimeoutTypeExec = 0
|
||||
ctxTimeoutTypeQuery = 1
|
||||
ctxTimeoutTypePrepare = 2
|
||||
cachePrefixTableFields = `TableFields:`
|
||||
cachePrefixSelectCache = `SelectCache:`
|
||||
commandEnvKeyForDryRun = "gf.gdb.dryrun"
|
||||
|
||||
@ -397,7 +397,7 @@ func (c *Core) RowsToResult(ctx context.Context, rows *sql.Rows) (Result, error)
|
||||
record := Record{}
|
||||
for i, value := range values {
|
||||
if value == nil {
|
||||
// Do not use `gvar.New(nil)` here as it creates an initialized object
|
||||
// DO NOT use `gvar.New(nil)` here as it creates an initialized object
|
||||
// which will cause struct converting issue.
|
||||
record[columnNames[i]] = nil
|
||||
} else {
|
||||
|
||||
@ -85,18 +85,19 @@ func (m *Model) getSelectResultFromCache(ctx context.Context, sql string, args .
|
||||
if cacheItem, ok = v.Val().(*selectCacheItem); ok {
|
||||
// In-memory cache.
|
||||
return cacheItem.Result, nil
|
||||
} else {
|
||||
// Other cache, it needs conversion.
|
||||
if err = json.UnmarshalUseNumber(v.Bytes(), &cacheItem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cacheItem.Result, nil
|
||||
}
|
||||
// Other cache, it needs conversion.
|
||||
if err = json.UnmarshalUseNumber(v.Bytes(), &cacheItem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cacheItem.Result, nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Model) saveSelectResultToCache(ctx context.Context, result Result, sql string, args ...interface{}) (err error) {
|
||||
func (m *Model) saveSelectResultToCache(
|
||||
ctx context.Context, queryType queryType, result Result, sql string, args ...interface{},
|
||||
) (err error) {
|
||||
if !m.cacheEnabled || m.tx != nil {
|
||||
return
|
||||
}
|
||||
@ -108,22 +109,38 @@ func (m *Model) saveSelectResultToCache(ctx context.Context, result Result, sql
|
||||
if _, errCache := cacheObj.Remove(ctx, cacheKey); errCache != nil {
|
||||
intlog.Errorf(ctx, `%+v`, errCache)
|
||||
}
|
||||
} else {
|
||||
// In case of Cache Penetration.
|
||||
if result.IsEmpty() && m.cacheOption.Force {
|
||||
result = Result{}
|
||||
}
|
||||
var cacheItem = &selectCacheItem{
|
||||
Result: result,
|
||||
}
|
||||
if internalData := m.db.GetCore().GetInternalCtxDataFromCtx(ctx); internalData != nil {
|
||||
cacheItem.FirstResultColumn = internalData.FirstResultColumn
|
||||
}
|
||||
if errCache := cacheObj.Set(ctx, cacheKey, cacheItem, m.cacheOption.Duration); errCache != nil {
|
||||
intlog.Errorf(ctx, `%+v`, errCache)
|
||||
return
|
||||
}
|
||||
// Special handler for Value/Count operations result.
|
||||
if len(result) > 0 {
|
||||
switch queryType {
|
||||
case queryTypeValue, queryTypeCount:
|
||||
if internalData := m.db.GetCore().GetInternalCtxDataFromCtx(ctx); internalData != nil {
|
||||
if result[0][internalData.FirstResultColumn].IsEmpty() {
|
||||
result = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
// In case of Cache Penetration.
|
||||
if result.IsEmpty() {
|
||||
if m.cacheOption.Force {
|
||||
result = Result{}
|
||||
} else {
|
||||
result = nil
|
||||
}
|
||||
}
|
||||
var cacheItem = &selectCacheItem{
|
||||
Result: result,
|
||||
}
|
||||
if internalData := m.db.GetCore().GetInternalCtxDataFromCtx(ctx); internalData != nil {
|
||||
cacheItem.FirstResultColumn = internalData.FirstResultColumn
|
||||
}
|
||||
if errCache := cacheObj.Set(ctx, cacheKey, cacheItem, m.cacheOption.Duration); errCache != nil {
|
||||
intlog.Errorf(ctx, `%+v`, errCache)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Model) makeSelectCacheKey(sql string, args ...interface{}) string {
|
||||
|
||||
@ -12,7 +12,6 @@ import (
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gset"
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/reflection"
|
||||
@ -31,75 +30,6 @@ func (m *Model) All(where ...interface{}) (Result, error) {
|
||||
return m.doGetAll(ctx, false, where...)
|
||||
}
|
||||
|
||||
// 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...)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Chunk iterates the query result with given `size` and `handler` function.
|
||||
func (m *Model) Chunk(size int, handler ChunkHandler) {
|
||||
page := m.start
|
||||
@ -147,45 +77,6 @@ func (m *Model) One(where ...interface{}) (Record, error) {
|
||||
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 `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 (
|
||||
all Result
|
||||
err error
|
||||
)
|
||||
if all, err = m.doGetAll(ctx, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(all) == 0 {
|
||||
return gvar.New(nil), nil
|
||||
}
|
||||
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`,
|
||||
)
|
||||
}
|
||||
|
||||
// Array queries and returns data values as slice from database.
|
||||
// Note that if there are multiple columns in the result, it returns just one column values randomly.
|
||||
//
|
||||
@ -375,6 +266,45 @@ func (m *Model) ScanList(structSlicePointer interface{}, bindToAttrName string,
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Count does "SELECT COUNT(x) FROM ..." statement for the model.
|
||||
// The optional parameter `where` is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
@ -526,8 +456,23 @@ func (m *Model) Having(having interface{}, args ...interface{}) *Model {
|
||||
return model
|
||||
}
|
||||
|
||||
// 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...)
|
||||
}
|
||||
|
||||
// doGetAllBySql does the select statement on the database.
|
||||
func (m *Model) doGetAllBySql(ctx context.Context, queryType int, sql string, args ...interface{}) (result Result, err error) {
|
||||
func (m *Model) doGetAllBySql(ctx context.Context, queryType queryType, sql string, args ...interface{}) (result Result, err error) {
|
||||
if result, err = m.getSelectResultFromCache(ctx, sql, args...); err != nil || result != nil {
|
||||
return
|
||||
}
|
||||
@ -548,11 +493,11 @@ func (m *Model) doGetAllBySql(ctx context.Context, queryType int, sql string, ar
|
||||
return
|
||||
}
|
||||
|
||||
err = m.saveSelectResultToCache(ctx, result, sql, args...)
|
||||
err = m.saveSelectResultToCache(ctx, queryType, result, sql, args...)
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Model) getFormattedSqlAndArgs(ctx context.Context, queryType int, limit1 bool) (sqlWithHolder string, holderArgs []interface{}) {
|
||||
func (m *Model) getFormattedSqlAndArgs(ctx context.Context, queryType queryType, limit1 bool) (sqlWithHolder string, holderArgs []interface{}) {
|
||||
switch queryType {
|
||||
case queryTypeCount:
|
||||
queryFields := "COUNT(1)"
|
||||
@ -604,6 +549,60 @@ func (m *Model) getAutoPrefix() string {
|
||||
return autoPrefix
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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`.
|
||||
//
|
||||
|
||||
@ -16,7 +16,7 @@ import (
|
||||
|
||||
// IsEmpty checks and returns whether `r` is empty.
|
||||
func (r Result) IsEmpty() bool {
|
||||
return r.Len() == 0
|
||||
return r == nil || r.Len() == 0
|
||||
}
|
||||
|
||||
// Len returns the length of result list.
|
||||
|
||||
@ -22,7 +22,7 @@ func Test_View(t *testing.T) {
|
||||
t.AssertNE(View(), nil)
|
||||
b, e := View().ParseContent(context.TODO(), `{{"我是中国人" | substr 2 -1}}`, nil)
|
||||
t.Assert(e, nil)
|
||||
t.Assert(b, "中国人")
|
||||
t.Assert(b, "中国")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
tpl := "t.tpl"
|
||||
@ -32,7 +32,7 @@ func Test_View(t *testing.T) {
|
||||
|
||||
b, e := View().Parse(context.TODO(), "t.tpl", nil)
|
||||
t.Assert(e, nil)
|
||||
t.Assert(b, "中国人")
|
||||
t.Assert(b, "中国")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
path := fmt.Sprintf(`%s/%d`, gfile.Temp(), gtime.TimestampNano())
|
||||
@ -45,7 +45,7 @@ func Test_View(t *testing.T) {
|
||||
|
||||
b, e := View().Parse(context.TODO(), "t.tpl", nil)
|
||||
t.Assert(e, nil)
|
||||
t.Assert(b, "中国人")
|
||||
t.Assert(b, "中国")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -159,6 +159,10 @@ func newCommandFromObjectMeta(object interface{}, name string) (command *Command
|
||||
if command.Description == "" {
|
||||
command.Description = metaData[gtag.DescriptionShort]
|
||||
}
|
||||
if command.Brief == "" && command.Description != "" {
|
||||
command.Brief = command.Description
|
||||
command.Description = ""
|
||||
}
|
||||
if command.Examples == "" {
|
||||
command.Examples = metaData[gtag.ExampleShort]
|
||||
}
|
||||
@ -355,6 +359,9 @@ func newArgumentsFromInput(object interface{}) (args []Argument, err error) {
|
||||
arg.Short, reflect.TypeOf(object).String(), field.Name(),
|
||||
)
|
||||
}
|
||||
if arg.Brief == "" {
|
||||
arg.Brief = field.TagDescription()
|
||||
}
|
||||
if v, ok := metaData[gtag.Arg]; ok {
|
||||
arg.IsArg = gconv.Bool(v)
|
||||
}
|
||||
|
||||
@ -8,16 +8,11 @@ package gstructs
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/utils"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
)
|
||||
|
||||
const (
|
||||
jsonTagName = `json`
|
||||
)
|
||||
|
||||
// Tag returns the value associated with key in the tag string. If there is no
|
||||
// such key in the tag, Tag returns the empty string.
|
||||
func (f *Field) Tag(key string) string {
|
||||
@ -28,14 +23,6 @@ func (f *Field) Tag(key string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
// TagJsonName returns the `json` tag name string of the field.
|
||||
func (f *Field) TagJsonName() string {
|
||||
if jsonTag := f.Tag(jsonTagName); jsonTag != "" {
|
||||
return strings.Split(jsonTag, ",")[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// TagLookup returns the value associated with key in the tag string.
|
||||
// If the key is present in the tag the value (which may be empty)
|
||||
// is returned. Otherwise, the returned value will be the empty string.
|
||||
|
||||
90
os/gstructs/gstructs_field_tag.go
Normal file
90
os/gstructs/gstructs_field_tag.go
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). 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 gstructs
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
)
|
||||
|
||||
// TagJsonName returns the `json` tag name string of the field.
|
||||
func (f *Field) TagJsonName() string {
|
||||
if jsonTag := f.Tag(gtag.Json); jsonTag != "" {
|
||||
return strings.Split(jsonTag, ",")[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// TagDefault returns the most commonly used tag `default/d` value of the field.
|
||||
func (f *Field) TagDefault() string {
|
||||
v := f.Tag(gtag.Default)
|
||||
if v == "" {
|
||||
v = f.Tag(gtag.DefaultShort)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// TagParam returns the most commonly used tag `param/p` value of the field.
|
||||
func (f *Field) TagParam() string {
|
||||
v := f.Tag(gtag.Param)
|
||||
if v == "" {
|
||||
v = f.Tag(gtag.ParamShort)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// TagValid returns the most commonly used tag `valid/v` value of the field.
|
||||
func (f *Field) TagValid() string {
|
||||
v := f.Tag(gtag.Valid)
|
||||
if v == "" {
|
||||
v = f.Tag(gtag.ValidShort)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// TagDescription returns the most commonly used tag `description/des/dc` value of the field.
|
||||
func (f *Field) TagDescription() string {
|
||||
v := f.Tag(gtag.Description)
|
||||
if v == "" {
|
||||
v = f.Tag(gtag.DescriptionShort)
|
||||
}
|
||||
if v == "" {
|
||||
v = f.Tag(gtag.DescriptionShort2)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// TagSummary returns the most commonly used tag `summary/sum/sm` value of the field.
|
||||
func (f *Field) TagSummary() string {
|
||||
v := f.Tag(gtag.Summary)
|
||||
if v == "" {
|
||||
v = f.Tag(gtag.SummaryShort)
|
||||
}
|
||||
if v == "" {
|
||||
v = f.Tag(gtag.SummaryShort2)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// TagAdditional returns the most commonly used tag `additional/ad` value of the field.
|
||||
func (f *Field) TagAdditional() string {
|
||||
v := f.Tag(gtag.Additional)
|
||||
if v == "" {
|
||||
v = f.Tag(gtag.AdditionalShort)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// TagExample returns the most commonly used tag `example/eg` value of the field.
|
||||
func (f *Field) TagExample() string {
|
||||
v := f.Tag(gtag.Example)
|
||||
if v == "" {
|
||||
v = f.Tag(gtag.ExampleShort)
|
||||
}
|
||||
return v
|
||||
}
|
||||
@ -381,3 +381,149 @@ func TestType_TagMap(t *testing.T) {
|
||||
t.Assert(r[1].TagMap()["description"], `应用Id`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestType_TagJsonName(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type A struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
r, err := gstructs.Fields(gstructs.FieldsInput{
|
||||
Pointer: new(A),
|
||||
RecursiveOption: 0,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 1)
|
||||
t.Assert(r[0].TagJsonName(), `name`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestType_TagDefault(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type A struct {
|
||||
Name string `default:"john"`
|
||||
Name2 string `d:"john"`
|
||||
}
|
||||
r, err := gstructs.Fields(gstructs.FieldsInput{
|
||||
Pointer: new(A),
|
||||
RecursiveOption: 0,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 2)
|
||||
t.Assert(r[0].TagDefault(), `john`)
|
||||
t.Assert(r[1].TagDefault(), `john`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestType_TagParam(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type A struct {
|
||||
Name string `param:"name"`
|
||||
Name2 string `p:"name"`
|
||||
}
|
||||
r, err := gstructs.Fields(gstructs.FieldsInput{
|
||||
Pointer: new(A),
|
||||
RecursiveOption: 0,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 2)
|
||||
t.Assert(r[0].TagParam(), `name`)
|
||||
t.Assert(r[1].TagParam(), `name`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestType_TagValid(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type A struct {
|
||||
Name string `valid:"required"`
|
||||
Name2 string `v:"required"`
|
||||
}
|
||||
r, err := gstructs.Fields(gstructs.FieldsInput{
|
||||
Pointer: new(A),
|
||||
RecursiveOption: 0,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 2)
|
||||
t.Assert(r[0].TagValid(), `required`)
|
||||
t.Assert(r[1].TagValid(), `required`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestType_TagDescription(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type A struct {
|
||||
Name string `description:"my name"`
|
||||
Name2 string `des:"my name"`
|
||||
Name3 string `dc:"my name"`
|
||||
}
|
||||
r, err := gstructs.Fields(gstructs.FieldsInput{
|
||||
Pointer: new(A),
|
||||
RecursiveOption: 0,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 3)
|
||||
t.Assert(r[0].TagDescription(), `my name`)
|
||||
t.Assert(r[1].TagDescription(), `my name`)
|
||||
t.Assert(r[2].TagDescription(), `my name`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestType_TagSummary(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type A struct {
|
||||
Name string `summary:"my name"`
|
||||
Name2 string `sum:"my name"`
|
||||
Name3 string `sm:"my name"`
|
||||
}
|
||||
r, err := gstructs.Fields(gstructs.FieldsInput{
|
||||
Pointer: new(A),
|
||||
RecursiveOption: 0,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 3)
|
||||
t.Assert(r[0].TagSummary(), `my name`)
|
||||
t.Assert(r[1].TagSummary(), `my name`)
|
||||
t.Assert(r[2].TagSummary(), `my name`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestType_TagAdditional(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type A struct {
|
||||
Name string `additional:"my name"`
|
||||
Name2 string `ad:"my name"`
|
||||
}
|
||||
r, err := gstructs.Fields(gstructs.FieldsInput{
|
||||
Pointer: new(A),
|
||||
RecursiveOption: 0,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 2)
|
||||
t.Assert(r[0].TagAdditional(), `my name`)
|
||||
t.Assert(r[1].TagAdditional(), `my name`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestType_TagExample(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type A struct {
|
||||
Name string `example:"john"`
|
||||
Name2 string `eg:"john"`
|
||||
}
|
||||
r, err := gstructs.Fields(gstructs.FieldsInput{
|
||||
Pointer: new(A),
|
||||
RecursiveOption: 0,
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 2)
|
||||
t.Assert(r[0].TagExample(), `john`)
|
||||
t.Assert(r[1].TagExample(), `john`)
|
||||
})
|
||||
}
|
||||
|
||||
@ -141,7 +141,7 @@ func Test_Func(t *testing.T) {
|
||||
str = `{{"我是中国人" | substr 2 -1}};{{"我是中国人" | substr 2 2}}`
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `中国人;中国`)
|
||||
t.Assert(result, `中国;中国`)
|
||||
|
||||
str = `{{"我是中国人" | strlimit 2 "..."}}`
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
|
||||
@ -55,24 +55,37 @@ func StrTillEx(haystack string, needle string) string {
|
||||
// The parameter `length` is optional, it uses the length of `str` in default.
|
||||
func SubStr(str string, start int, length ...int) (substr string) {
|
||||
strLength := len(str)
|
||||
// Simple border checks.
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
if start >= strLength {
|
||||
start = strLength
|
||||
}
|
||||
end := strLength
|
||||
if len(length) > 0 {
|
||||
end = start + length[0]
|
||||
if end < start {
|
||||
end = strLength
|
||||
if -start > strLength {
|
||||
start = 0
|
||||
} else {
|
||||
start = strLength + start
|
||||
}
|
||||
} else if start > strLength {
|
||||
return ""
|
||||
}
|
||||
if end > strLength {
|
||||
end = strLength
|
||||
realLength := 0
|
||||
if len(length) > 0 {
|
||||
realLength = length[0]
|
||||
if realLength < 0 {
|
||||
if -realLength > strLength-start {
|
||||
realLength = 0
|
||||
} else {
|
||||
realLength = strLength - start + realLength
|
||||
}
|
||||
} else if realLength > strLength-start {
|
||||
realLength = strLength - start
|
||||
}
|
||||
} else {
|
||||
realLength = strLength - start
|
||||
}
|
||||
|
||||
if realLength == strLength {
|
||||
return str
|
||||
} else {
|
||||
end := start + realLength
|
||||
return str[start:end]
|
||||
}
|
||||
return str[start:end]
|
||||
}
|
||||
|
||||
// SubStrRune returns a portion of string `str` specified by the `start` and `length` parameters.
|
||||
@ -85,20 +98,32 @@ func SubStrRune(str string, start int, length ...int) (substr string) {
|
||||
runesLength = len(runes)
|
||||
)
|
||||
|
||||
// Simple border checks.
|
||||
strLength := runesLength
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
if start >= runesLength {
|
||||
start = runesLength
|
||||
}
|
||||
end := runesLength
|
||||
if len(length) > 0 {
|
||||
end = start + length[0]
|
||||
if end < start {
|
||||
end = runesLength
|
||||
if -start > strLength {
|
||||
start = 0
|
||||
} else {
|
||||
start = strLength + start
|
||||
}
|
||||
} else if start > strLength {
|
||||
return ""
|
||||
}
|
||||
realLength := 0
|
||||
if len(length) > 0 {
|
||||
realLength = length[0]
|
||||
if realLength < 0 {
|
||||
if -realLength > strLength-start {
|
||||
realLength = 0
|
||||
} else {
|
||||
realLength = strLength - start + realLength
|
||||
}
|
||||
} else if realLength > strLength-start {
|
||||
realLength = strLength - start
|
||||
}
|
||||
} else {
|
||||
realLength = strLength - start
|
||||
}
|
||||
end := start + realLength
|
||||
if end > runesLength {
|
||||
end = runesLength
|
||||
}
|
||||
|
||||
@ -89,8 +89,12 @@ func Test_SubStr(t *testing.T) {
|
||||
t.Assert(gstr.SubStr("我爱GoFrame", 0), "我爱GoFrame")
|
||||
t.Assert(gstr.SubStr("我爱GoFrame", 6), "GoFrame")
|
||||
t.Assert(gstr.SubStr("我爱GoFrame", 6, 2), "Go")
|
||||
t.Assert(gstr.SubStr("我爱GoFrame", -1, 30), "我爱GoFrame")
|
||||
t.Assert(gstr.SubStr("我爱GoFrame", -1, 30), "e")
|
||||
t.Assert(gstr.SubStr("我爱GoFrame", 30, 30), "")
|
||||
t.Assert(gstr.SubStr("abcdef", 0, -1), "abcde")
|
||||
t.Assert(gstr.SubStr("abcdef", 2, -1), "cde")
|
||||
t.Assert(gstr.SubStr("abcdef", 4, -4), "")
|
||||
t.Assert(gstr.SubStr("abcdef", -3, -1), "de")
|
||||
})
|
||||
}
|
||||
|
||||
@ -99,8 +103,16 @@ func Test_SubStrRune(t *testing.T) {
|
||||
t.Assert(gstr.SubStrRune("我爱GoFrame", 0), "我爱GoFrame")
|
||||
t.Assert(gstr.SubStrRune("我爱GoFrame", 2), "GoFrame")
|
||||
t.Assert(gstr.SubStrRune("我爱GoFrame", 2, 2), "Go")
|
||||
t.Assert(gstr.SubStrRune("我爱GoFrame", -1, 30), "我爱GoFrame")
|
||||
t.Assert(gstr.SubStrRune("我爱GoFrame", -1, 30), "e")
|
||||
t.Assert(gstr.SubStrRune("我爱GoFrame", 30, 30), "")
|
||||
t.Assert(gstr.SubStrRune("abcdef", 0, -1), "abcde")
|
||||
t.Assert(gstr.SubStrRune("abcdef", 2, -1), "cde")
|
||||
t.Assert(gstr.SubStrRune("abcdef", 4, -4), "")
|
||||
t.Assert(gstr.SubStrRune("abcdef", -3, -1), "de")
|
||||
t.Assert(gstr.SubStrRune("我爱GoFrame呵呵", -3, 100), "e呵呵")
|
||||
t.Assert(gstr.SubStrRune("abcdef哈哈", -3, -1), "f哈")
|
||||
t.Assert(gstr.SubStrRune("ab我爱GoFramecdef哈哈", -3, -1), "f哈")
|
||||
t.Assert(gstr.SubStrRune("我爱GoFrame", 0, 3), "我爱G")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -2,5 +2,5 @@ package gf
|
||||
|
||||
const (
|
||||
// VERSION is the current GoFrame version.
|
||||
VERSION = "v2.2.3"
|
||||
VERSION = "v2.2.4"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user