201 lines
6.2 KiB
Go
201 lines
6.2 KiB
Go
// Copyright 2019 PingCAP, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package bindinfo
|
|
|
|
import (
|
|
"time"
|
|
"unsafe"
|
|
|
|
"github.com/pingcap/tidb/pkg/parser"
|
|
"github.com/pingcap/tidb/pkg/parser/ast"
|
|
"github.com/pingcap/tidb/pkg/sessionctx"
|
|
"github.com/pingcap/tidb/pkg/types"
|
|
"github.com/pingcap/tidb/pkg/util/hint"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
const (
|
|
// Enabled is the bind info's in enabled status.
|
|
// It is the same as the previous 'Using' status.
|
|
// Only use 'Enabled' status in the future, not the 'Using' status.
|
|
// The 'Using' status is preserved for compatibility.
|
|
Enabled = "enabled"
|
|
// Disabled is the bind info's in disabled status.
|
|
Disabled = "disabled"
|
|
// Using is the bind info's in use status.
|
|
// The 'Using' status is preserved for compatibility.
|
|
Using = "using"
|
|
// deleted is the bind info's deleted status.
|
|
deleted = "deleted"
|
|
// Manual indicates the binding is created by SQL like "create binding for ...".
|
|
Manual = "manual"
|
|
// Builtin indicates the binding is a builtin record for internal locking purpose. It is also the status for the builtin binding.
|
|
Builtin = "builtin"
|
|
// History indicate the binding is created from statement summary by plan digest
|
|
History = "history"
|
|
)
|
|
|
|
// Binding stores the basic bind hint info.
|
|
type Binding struct {
|
|
OriginalSQL string
|
|
Db string
|
|
BindSQL string
|
|
// Status represents the status of the binding. It can only be one of the following values:
|
|
// 1. deleted: Bindings is deleted, can not be used anymore.
|
|
// 2. enabled, using: Binding is in the normal active mode.
|
|
Status string
|
|
CreateTime types.Time
|
|
UpdateTime types.Time
|
|
Source string
|
|
Charset string
|
|
Collation string
|
|
// Hint is the parsed hints, it is used to bind hints to stmt node.
|
|
Hint *hint.HintsSet `json:"-"`
|
|
// ID is the string form of Hint. It would be non-empty only when the status is `Using` or `PendingVerify`.
|
|
ID string `json:"-"`
|
|
SQLDigest string
|
|
PlanDigest string
|
|
|
|
// TableNames records all schema and table names in this binding statement, which are used for cross-db matching.
|
|
TableNames []*ast.TableName `json:"-"`
|
|
}
|
|
|
|
func (b *Binding) isSame(rb *Binding) bool {
|
|
if b.ID != "" && rb.ID != "" {
|
|
return b.ID == rb.ID
|
|
}
|
|
// Sometimes we cannot construct `ID` because of the changed schema, so we need to compare by bind sql.
|
|
return b.BindSQL == rb.BindSQL
|
|
}
|
|
|
|
// IsBindingEnabled returns whether the binding is enabled.
|
|
func (b *Binding) IsBindingEnabled() bool {
|
|
return b.Status == Enabled || b.Status == Using
|
|
}
|
|
|
|
// IsBindingAvailable returns whether the binding is available.
|
|
// The available means the binding can be used or can be converted into a usable status.
|
|
// It includes the 'Enabled', 'Using' and 'Disabled' status.
|
|
func (b *Binding) IsBindingAvailable() bool {
|
|
return b.IsBindingEnabled() || b.Status == Disabled
|
|
}
|
|
|
|
// SinceUpdateTime returns the duration since last update time. Export for test.
|
|
func (b *Binding) SinceUpdateTime() (time.Duration, error) {
|
|
updateTime, err := b.UpdateTime.GoTime(time.Local)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return time.Since(updateTime), nil
|
|
}
|
|
|
|
// prepareHints builds ID and Hint for Bindings. If sctx is not nil, we check if
|
|
// the BindSQL is still valid.
|
|
func prepareHints(sctx sessionctx.Context, binding *Binding) (rerr error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
rerr = errors.Errorf("panic when preparing hints for binding %v, panic: %v", binding.BindSQL, r)
|
|
}
|
|
}()
|
|
|
|
p := parser.New()
|
|
if (binding.Hint != nil && binding.ID != "") || binding.Status == deleted {
|
|
return nil
|
|
}
|
|
dbName := binding.Db
|
|
bindingStmt, err := p.ParseOneStmt(binding.BindSQL, binding.Charset, binding.Collation)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tableNames := CollectTableNames(bindingStmt)
|
|
isFuzzy := isCrossDBBinding(bindingStmt)
|
|
if isFuzzy {
|
|
dbName = "*" // ues '*' for universal bindings
|
|
}
|
|
|
|
hintsSet, stmt, warns, err := hint.ParseHintsSet(p, binding.BindSQL, binding.Charset, binding.Collation, dbName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if sctx != nil && !isFuzzy {
|
|
paramChecker := ¶mMarkerChecker{}
|
|
stmt.Accept(paramChecker)
|
|
if !paramChecker.hasParamMarker {
|
|
_, err = getHintsForSQL(sctx, binding.BindSQL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
hintsStr, err := hintsSet.Restore()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// For `create global binding for select * from t using select * from t`, we allow it though hintsStr is empty.
|
|
// For `create global binding for select * from t using select /*+ non_exist_hint() */ * from t`,
|
|
// the hint is totally invalid, we escalate warning to error.
|
|
if hintsStr == "" && len(warns) > 0 {
|
|
return warns[0]
|
|
}
|
|
binding.Hint = hintsSet
|
|
binding.ID = hintsStr
|
|
binding.TableNames = tableNames
|
|
return nil
|
|
}
|
|
|
|
// `merge` merges two Bindings. It will replace old bindings with new bindings if there are new updates.
|
|
func merge(lBindings, rBindings []*Binding) []*Binding {
|
|
if lBindings == nil {
|
|
return rBindings
|
|
}
|
|
if rBindings == nil {
|
|
return lBindings
|
|
}
|
|
result := lBindings
|
|
for i := range rBindings {
|
|
rbind := rBindings[i]
|
|
found := false
|
|
for j, lbind := range lBindings {
|
|
if lbind.isSame(rbind) {
|
|
found = true
|
|
if rbind.UpdateTime.Compare(lbind.UpdateTime) >= 0 {
|
|
result[j] = rbind
|
|
}
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
result = append(result, rbind)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func removeDeletedBindings(br []*Binding) []*Binding {
|
|
result := make([]*Binding, 0, len(br))
|
|
for _, binding := range br {
|
|
if binding.Status != deleted {
|
|
result = append(result, binding)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// size calculates the memory size of a bind info.
|
|
func (b *Binding) size() float64 {
|
|
res := len(b.OriginalSQL) + len(b.Db) + len(b.BindSQL) + len(b.Status) + 2*int(unsafe.Sizeof(b.CreateTime)) + len(b.Charset) + len(b.Collation) + len(b.ID)
|
|
return float64(res)
|
|
}
|