mirror of
https://gitee.com/johng/gf
synced 2026-07-02 19:31:07 +08:00
fix issue in missing first result column when in select cache scenario
This commit is contained in:
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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{}) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user