diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 728d942d6..7ff52956f 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -60,10 +60,11 @@ func (c *Core) Ctx(ctx context.Context) DB { // GetCtx returns the context for current DB. // It returns `context.Background()` is there's no context previously set. func (c *Core) GetCtx() context.Context { - if c.ctx != nil { - return c.ctx + ctx := c.ctx + if ctx == nil { + ctx = context.TODO() } - return c.injectInternalCtxData(context.TODO()) + return c.injectInternalCtxData(ctx) } // GetCtxTimeout returns the context and cancel function for specified timeout type. @@ -241,7 +242,7 @@ func (c *Core) GetValue(ctx context.Context, sql string, args ...interface{}) (V // GetCount queries and returns the count from database. func (c *Core) GetCount(ctx context.Context, sql string, args ...interface{}) (int, error) { - // If the query fields do not contains function "COUNT", + // If the query fields do not contain function "COUNT", // it replaces the sql string and adds the "COUNT" function to the fields. if !gregex.IsMatchString(`(?i)SELECT\s+COUNT\(.+\)\s+FROM`, sql) { sql, _ = gregex.ReplaceString(`(?i)(SELECT)\s+(.+)\s+(FROM)`, `$1 COUNT($2) $3`, sql) diff --git a/database/gdb/gdb_core_transaction.go b/database/gdb/gdb_core_transaction.go index 67750f92c..bf5f3751b 100644 --- a/database/gdb/gdb_core_transaction.go +++ b/database/gdb/gdb_core_transaction.go @@ -158,6 +158,9 @@ func (tx *TX) transactionKeyForNestedPoint() string { // Ctx sets the context for current transaction. func (tx *TX) Ctx(ctx context.Context) *TX { tx.ctx = ctx + if tx.ctx != nil { + tx.ctx = tx.db.GetCore().injectInternalCtxData(tx.ctx) + } return tx } diff --git a/database/gdb/gdb_core_underlying.go b/database/gdb/gdb_core_underlying.go index a8bc2ef60..af90cb169 100644 --- a/database/gdb/gdb_core_underlying.go +++ b/database/gdb/gdb_core_underlying.go @@ -163,7 +163,7 @@ func (c *Core) sqlParsingHandler(ctx context.Context, in sqlParsingHandlerInput) // DoCommit commits current sql and arguments to underlying sql driver. func (c *Core) DoCommit(ctx context.Context, in DoCommitInput) (out DoCommitOutput, err error) { - // Inject internal data into ctx, just for double check. + // Inject internal data into ctx, especially for transaction creating. ctx = c.injectInternalCtxData(ctx) var ( diff --git a/database/gdb/gdb_model_cache.go b/database/gdb/gdb_model_cache.go index 7ed513d45..e05d40740 100644 --- a/database/gdb/gdb_model_cache.go +++ b/database/gdb/gdb_model_cache.go @@ -8,9 +8,13 @@ package gdb import ( "context" + "fmt" "time" + "github.com/gogf/gf/v2/crypto/gmd5" "github.com/gogf/gf/v2/internal/intlog" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/util/gconv" ) type CacheOption struct { @@ -30,6 +34,11 @@ type CacheOption struct { Force bool } +type selectCacheItem struct { + Result Result + FirstResultColumn string +} + // Cache sets the cache feature for the model. It caches the result of the sql, which means // if there's another same sql request, it just reads and returns the result from cache, it // but not committed and executed into the database. @@ -43,12 +52,85 @@ func (m *Model) Cache(option CacheOption) *Model { return model } -// checkAndRemoveCache checks and removes the cache in insert/update/delete statement if +// checkAndRemoveSelectCache checks and removes the cache in insert/update/delete statement if // cache feature is enabled. -func (m *Model) checkAndRemoveCache(ctx context.Context) { +func (m *Model) checkAndRemoveSelectCache(ctx context.Context) { if m.cacheEnabled && m.cacheOption.Duration < 0 && len(m.cacheOption.Name) > 0 { if _, err := m.db.GetCache().Remove(ctx, m.cacheOption.Name); err != nil { intlog.Errorf(ctx, `%+v`, err) } } } + +func (m *Model) getSelectResultFromCache(ctx context.Context, sql string, args ...interface{}) (result Result, err error) { + if !m.cacheEnabled || m.tx != nil { + return + } + var ( + ok bool + cacheItem *selectCacheItem + cacheKey = m.makeSelectCacheKey(sql, args...) + cacheObj = m.db.GetCache() + ) + defer func() { + if cacheItem != nil { + if internalData := m.db.GetCore().getInternalCtxDataFromCtx(ctx); internalData != nil { + if internalData.FirstResultColumn == "" { + internalData.FirstResultColumn = cacheItem.FirstResultColumn + } + } + } + }() + if v, _ := cacheObj.Get(ctx, cacheKey); !v.IsNil() { + if cacheItem, ok = v.Val().(*selectCacheItem); ok { + // In-memory cache. + return cacheItem.Result, nil + } else if err = json.UnmarshalUseNumber(v.Bytes(), &cacheItem); err != nil { + // Other cache, it needs conversion. + return nil, err + } + } + return +} + +func (m *Model) saveSelectResultToCache(ctx context.Context, result Result, sql string, args ...interface{}) (err error) { + if !m.cacheEnabled || m.tx != nil { + return + } + var ( + cacheKey = m.makeSelectCacheKey(sql, args...) + cacheObj = m.db.GetCache() + ) + if m.cacheOption.Duration < 0 { + 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 nil +} + +func (m *Model) makeSelectCacheKey(sql string, args ...interface{}) string { + var cacheKey = m.cacheOption.Name + if len(cacheKey) == 0 { + cacheKey = fmt.Sprintf( + `GCache@Schema(%s):%s`, + m.db.GetSchema(), + gmd5.MustEncryptString(sql+", @PARAMS:"+gconv.String(args)), + ) + } + return cacheKey +} diff --git a/database/gdb/gdb_model_delete.go b/database/gdb/gdb_model_delete.go index b6406e5df..bab1d740b 100644 --- a/database/gdb/gdb_model_delete.go +++ b/database/gdb/gdb_model_delete.go @@ -26,7 +26,7 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) { } defer func() { if err == nil { - m.checkAndRemoveCache(ctx) + m.checkAndRemoveSelectCache(ctx) } }() var ( diff --git a/database/gdb/gdb_model_insert.go b/database/gdb/gdb_model_insert.go index 12300d5cf..412e615b8 100644 --- a/database/gdb/gdb_model_insert.go +++ b/database/gdb/gdb_model_insert.go @@ -228,7 +228,7 @@ func (m *Model) Save(data ...interface{}) (result sql.Result, err error) { func (m *Model) doInsertWithOption(ctx context.Context, insertOption int) (result sql.Result, err error) { defer func() { if err == nil { - m.checkAndRemoveCache(ctx) + m.checkAndRemoveSelectCache(ctx) } }() if m.data == nil { diff --git a/database/gdb/gdb_model_select.go b/database/gdb/gdb_model_select.go index b2b11b57f..ebe8432c9 100644 --- a/database/gdb/gdb_model_select.go +++ b/database/gdb/gdb_model_select.go @@ -13,11 +13,8 @@ import ( "github.com/gogf/gf/v2/container/gset" "github.com/gogf/gf/v2/container/gvar" - "github.com/gogf/gf/v2/crypto/gmd5" "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/internal/intlog" - "github.com/gogf/gf/v2/internal/json" "github.com/gogf/gf/v2/internal/reflection" "github.com/gogf/gf/v2/text/gstr" "github.com/gogf/gf/v2/util/gconv" @@ -521,33 +518,8 @@ func (m *Model) Having(having interface{}, args ...interface{}) *Model { // 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) { - var ( - ok bool - cacheKey = "" - cacheObj = m.db.GetCache() - ) - // Retrieve from cache. - if m.cacheEnabled && m.tx == nil { - cacheKey = m.cacheOption.Name - if len(cacheKey) == 0 { - cacheKey = fmt.Sprintf( - `GCache@Schema(%s):%s`, - m.db.GetSchema(), - gmd5.MustEncryptString(sql+", @PARAMS:"+gconv.String(args)), - ) - } - if v, _ := cacheObj.Get(ctx, cacheKey); !v.IsNil() { - if result, ok = v.Val().(Result); ok { - // In-memory cache. - return result, nil - } - // Other cache, it needs conversion. - if err = json.UnmarshalUseNumber(v.Bytes(), &result); err != nil { - return nil, err - } else { - return result, nil - } - } + if result, err = m.getSelectResultFromCache(ctx, sql, args...); err != nil || result != nil { + return } in := &HookSelectInput{ @@ -562,25 +534,12 @@ func (m *Model) doGetAllBySql(ctx context.Context, queryType int, sql string, ar Sql: sql, Args: m.mergeArguments(args), } - result, err = in.Next(ctx) - - // Cache the result. - if cacheKey != "" && err == nil { - if m.cacheOption.Duration < 0 { - 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{} - } - if errCache := cacheObj.Set(ctx, cacheKey, result, m.cacheOption.Duration); errCache != nil { - intlog.Errorf(ctx, `%+v`, errCache) - } - } + if result, err = in.Next(ctx); err != nil { + return } - return result, err + + err = m.saveSelectResultToCache(ctx, result, sql, args...) + return } func (m *Model) getFormattedSqlAndArgs(ctx context.Context, queryType int, limit1 bool) (sqlWithHolder string, holderArgs []interface{}) { diff --git a/database/gdb/gdb_model_update.go b/database/gdb/gdb_model_update.go index 817f6fa5f..db49ffbb7 100644 --- a/database/gdb/gdb_model_update.go +++ b/database/gdb/gdb_model_update.go @@ -37,7 +37,7 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro } defer func() { if err == nil { - m.checkAndRemoveCache(ctx) + m.checkAndRemoveSelectCache(ctx) } }() if m.data == nil { diff --git a/protocol/goai/goai_operation.go b/protocol/goai/goai_operation.go index 7c717e80f..db6e0b30d 100644 --- a/protocol/goai/goai_operation.go +++ b/protocol/goai/goai_operation.go @@ -26,7 +26,7 @@ type Operation struct { Security *SecurityRequirements `json:"security,omitempty"` Servers *Servers `json:"servers,omitempty"` ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"` - XExtensions `json:"-"` + XExtensions XExtensions `json:"-"` } func (oai *OpenApiV3) tagMapToOperation(tagMap map[string]string, operation *Operation) error { diff --git a/protocol/goai/goai_parameter.go b/protocol/goai/goai_parameter.go index 09d1a08be..d20e92cdc 100644 --- a/protocol/goai/goai_parameter.go +++ b/protocol/goai/goai_parameter.go @@ -28,7 +28,7 @@ type Parameter struct { Example interface{} `json:"example,omitempty"` Examples *Examples `json:"examples,omitempty"` Content *Content `json:"content,omitempty"` - XExtensions `json:"-"` + XExtensions XExtensions `json:"-"` } func (oai *OpenApiV3) tagMapToParameter(tagMap map[string]string, parameter *Parameter) error { diff --git a/protocol/goai/goai_path.go b/protocol/goai/goai_path.go index 314a670b8..d5d8af66f 100644 --- a/protocol/goai/goai_path.go +++ b/protocol/goai/goai_path.go @@ -19,21 +19,21 @@ import ( ) type Path struct { - Ref string `json:"$ref,omitempty"` - Summary string `json:"summary,omitempty"` - Description string `json:"description,omitempty"` - Connect *Operation `json:"connect,omitempty"` - Delete *Operation `json:"delete,omitempty"` - Get *Operation `json:"get,omitempty"` - Head *Operation `json:"head,omitempty"` - Options *Operation `json:"options,omitempty"` - Patch *Operation `json:"patch,omitempty"` - Post *Operation `json:"post,omitempty"` - Put *Operation `json:"put,omitempty"` - Trace *Operation `json:"trace,omitempty"` - Servers Servers `json:"servers,omitempty"` - Parameters Parameters `json:"parameters,omitempty"` - XExtensions `json:"-"` + Ref string `json:"$ref,omitempty"` + Summary string `json:"summary,omitempty"` + Description string `json:"description,omitempty"` + Connect *Operation `json:"connect,omitempty"` + Delete *Operation `json:"delete,omitempty"` + Get *Operation `json:"get,omitempty"` + Head *Operation `json:"head,omitempty"` + Options *Operation `json:"options,omitempty"` + Patch *Operation `json:"patch,omitempty"` + Post *Operation `json:"post,omitempty"` + Put *Operation `json:"put,omitempty"` + Trace *Operation `json:"trace,omitempty"` + Servers Servers `json:"servers,omitempty"` + Parameters Parameters `json:"parameters,omitempty"` + XExtensions XExtensions `json:"-"` } // Paths are specified by OpenAPI/Swagger standard version 3.0. diff --git a/protocol/goai/goai_response.go b/protocol/goai/goai_response.go index cdf0c10a6..8961bb395 100644 --- a/protocol/goai/goai_response.go +++ b/protocol/goai/goai_response.go @@ -14,11 +14,11 @@ import ( // Response is specified by OpenAPI/Swagger 3.0 standard. type Response struct { - Description string `json:"description"` - Headers Headers `json:"headers,omitempty"` - Content Content `json:"content,omitempty"` - Links Links `json:"links,omitempty"` - XExtensions `json:"-"` + Description string `json:"description"` + Headers Headers `json:"headers,omitempty"` + Content Content `json:"content,omitempty"` + Links Links `json:"links,omitempty"` + XExtensions XExtensions `json:"-"` } func (oai *OpenApiV3) tagMapToResponse(tagMap map[string]string, response *Response) error { diff --git a/protocol/goai/goai_shema.go b/protocol/goai/goai_shema.go index 398d2475d..c4516746e 100644 --- a/protocol/goai/goai_shema.go +++ b/protocol/goai/goai_shema.go @@ -59,7 +59,7 @@ type Schema struct { MaxProps *uint64 `json:"maxProperties,omitempty"` AdditionalProperties *SchemaRef `json:"additionalProperties,omitempty"` Discriminator *Discriminator `json:"discriminator,omitempty"` - XExtensions `json:"-"` + XExtensions XExtensions `json:"-"` } func (s Schema) MarshalJSON() ([]byte, error) { diff --git a/util/gconv/gconv_struct.go b/util/gconv/gconv_struct.go index 0a3072bb0..b24d9d711 100644 --- a/util/gconv/gconv_struct.go +++ b/util/gconv/gconv_struct.go @@ -194,8 +194,7 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string var ( tempName string elemFieldType reflect.StructField - elemTempValue reflect.Value - elemOriginKind reflect.Kind + elemFieldValue reflect.Value elemType = pointerElemReflectValue.Type() attrMap = make(map[string]string) // Attribute name to its check name which has no symbols. ) @@ -205,15 +204,17 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string if !utils.IsLetterUpper(elemFieldType.Name[0]) { continue } - elemTempValue = pointerElemReflectValue.Field(i) - elemOriginKind = elemTempValue.Kind() - for elemOriginKind == reflect.Ptr || elemOriginKind == reflect.Interface { - elemTempValue = elemTempValue.Elem() - elemOriginKind = elemTempValue.Kind() - } // Maybe it's struct/*struct embedded. - if elemFieldType.Anonymous && elemOriginKind == reflect.Struct { - if err = doStruct(paramsMap, pointerElemReflectValue.Field(i), mapping, priorityTag); err != nil { + if elemFieldType.Anonymous { + elemFieldValue = pointerElemReflectValue.Field(i) + // Ignore the interface attribute if it's nil. + if elemFieldValue.Kind() == reflect.Interface { + elemFieldValue = elemFieldValue.Elem() + if !elemFieldValue.IsValid() { + continue + } + } + if err = doStruct(paramsMap, elemFieldValue, mapping, priorityTag); err != nil { return err } } else {