313 lines
7.7 KiB
Go
313 lines
7.7 KiB
Go
// Copyright 2015 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,
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package executor
|
|
|
|
import (
|
|
"math"
|
|
"sort"
|
|
|
|
"github.com/juju/errors"
|
|
"github.com/pingcap/tidb/ast"
|
|
"github.com/pingcap/tidb/context"
|
|
"github.com/pingcap/tidb/expression"
|
|
"github.com/pingcap/tidb/infoschema"
|
|
"github.com/pingcap/tidb/parser"
|
|
"github.com/pingcap/tidb/plan"
|
|
"github.com/pingcap/tidb/util/sqlexec"
|
|
)
|
|
|
|
var (
|
|
_ Executor = &DeallocateExec{}
|
|
_ Executor = &ExecuteExec{}
|
|
_ Executor = &PrepareExec{}
|
|
)
|
|
|
|
type paramMarkerSorter struct {
|
|
markers []*ast.ParamMarkerExpr
|
|
}
|
|
|
|
func (p *paramMarkerSorter) Len() int {
|
|
return len(p.markers)
|
|
}
|
|
|
|
func (p *paramMarkerSorter) Less(i, j int) bool {
|
|
return p.markers[i].Offset < p.markers[j].Offset
|
|
}
|
|
|
|
func (p *paramMarkerSorter) Swap(i, j int) {
|
|
p.markers[i], p.markers[j] = p.markers[j], p.markers[i]
|
|
}
|
|
|
|
type paramMarkerExtractor struct {
|
|
markers []*ast.ParamMarkerExpr
|
|
}
|
|
|
|
func (e *paramMarkerExtractor) Enter(in ast.Node) (ast.Node, bool) {
|
|
return in, false
|
|
}
|
|
|
|
func (e *paramMarkerExtractor) Leave(in ast.Node) (ast.Node, bool) {
|
|
if x, ok := in.(*ast.ParamMarkerExpr); ok {
|
|
e.markers = append(e.markers, x)
|
|
}
|
|
return in, true
|
|
}
|
|
|
|
// Prepared represents a prepared statement.
|
|
type Prepared struct {
|
|
Stmt ast.StmtNode
|
|
Params []*ast.ParamMarkerExpr
|
|
SchemaVersion int64
|
|
}
|
|
|
|
// PrepareExec represents a PREPARE executor.
|
|
type PrepareExec struct {
|
|
IS infoschema.InfoSchema
|
|
Ctx context.Context
|
|
Name string
|
|
SQLText string
|
|
|
|
ID uint32
|
|
ParamCount int
|
|
Err error
|
|
Fields []*ast.ResultField
|
|
}
|
|
|
|
// Schema implements the Executor Schema interface.
|
|
func (e *PrepareExec) Schema() *expression.Schema {
|
|
// Will never be called.
|
|
return expression.NewSchema()
|
|
}
|
|
|
|
// Next implements the Executor Next interface.
|
|
func (e *PrepareExec) Next() (*Row, error) {
|
|
e.DoPrepare()
|
|
return nil, e.Err
|
|
}
|
|
|
|
// Close implements plan.Plan Close interface.
|
|
func (e *PrepareExec) Close() error {
|
|
return nil
|
|
}
|
|
|
|
// DoPrepare prepares the statement, it can be called multiple times without
|
|
// side effect.
|
|
func (e *PrepareExec) DoPrepare() {
|
|
vars := e.Ctx.GetSessionVars()
|
|
if e.ID != 0 {
|
|
// Must be the case when we retry a prepare.
|
|
// Make sure it is idempotent.
|
|
_, ok := vars.PreparedStmts[e.ID]
|
|
if ok {
|
|
return
|
|
}
|
|
}
|
|
charset, collation := vars.GetCharsetInfo()
|
|
var (
|
|
stmts []ast.StmtNode
|
|
err error
|
|
)
|
|
if sqlParser, ok := e.Ctx.(sqlexec.SQLParser); ok {
|
|
stmts, err = sqlParser.ParseSQL(e.SQLText, charset, collation)
|
|
} else {
|
|
stmts, err = parser.New().Parse(e.SQLText, charset, collation)
|
|
}
|
|
if err != nil {
|
|
e.Err = errors.Trace(err)
|
|
return
|
|
}
|
|
if len(stmts) != 1 {
|
|
e.Err = errors.Trace(ErrPrepareMulti)
|
|
return
|
|
}
|
|
stmt := stmts[0]
|
|
if _, ok := stmt.(ast.DDLNode); ok {
|
|
e.Err = errors.Trace(ErrPrepareDDL)
|
|
return
|
|
}
|
|
var extractor paramMarkerExtractor
|
|
stmt.Accept(&extractor)
|
|
err = plan.Preprocess(stmt, e.IS, e.Ctx)
|
|
if err != nil {
|
|
e.Err = errors.Trace(err)
|
|
return
|
|
}
|
|
if result, ok := stmt.(ast.ResultSetNode); ok {
|
|
e.Fields = result.GetResultFields()
|
|
}
|
|
|
|
// The parameter markers are appended in visiting order, which may not
|
|
// be the same as the position order in the query string. We need to
|
|
// sort it by position.
|
|
sorter := ¶mMarkerSorter{markers: extractor.markers}
|
|
sort.Sort(sorter)
|
|
e.ParamCount = len(sorter.markers)
|
|
prepared := &Prepared{
|
|
Stmt: stmt,
|
|
Params: sorter.markers,
|
|
SchemaVersion: e.IS.SchemaMetaVersion(),
|
|
}
|
|
|
|
err = plan.PrepareStmt(e.IS, e.Ctx, stmt)
|
|
if err != nil {
|
|
e.Err = errors.Trace(err)
|
|
return
|
|
}
|
|
|
|
if e.ID == 0 {
|
|
e.ID = vars.GetNextPreparedStmtID()
|
|
}
|
|
if e.Name != "" {
|
|
vars.PreparedStmtNameToID[e.Name] = e.ID
|
|
}
|
|
vars.PreparedStmts[e.ID] = prepared
|
|
}
|
|
|
|
// ExecuteExec represents an EXECUTE executor.
|
|
// It cannot be executed by itself, all it needs to do is to build
|
|
// another Executor from a prepared statement.
|
|
type ExecuteExec struct {
|
|
IS infoschema.InfoSchema
|
|
Ctx context.Context
|
|
Name string
|
|
UsingVars []expression.Expression
|
|
ID uint32
|
|
StmtExec Executor
|
|
Stmt ast.StmtNode
|
|
Plan plan.Plan
|
|
}
|
|
|
|
// Schema implements the Executor Schema interface.
|
|
func (e *ExecuteExec) Schema() *expression.Schema {
|
|
// Will never be called.
|
|
return expression.NewSchema()
|
|
}
|
|
|
|
// Next implements the Executor Next interface.
|
|
func (e *ExecuteExec) Next() (*Row, error) {
|
|
// Will never be called.
|
|
return nil, nil
|
|
}
|
|
|
|
// Close implements plan.Plan Close interface.
|
|
func (e *ExecuteExec) Close() error {
|
|
// Will never be called.
|
|
return nil
|
|
}
|
|
|
|
// Build builds a prepared statement into an executor.
|
|
// After Build, e.StmtExec will be used to do the real execution.
|
|
func (e *ExecuteExec) Build() error {
|
|
vars := e.Ctx.GetSessionVars()
|
|
if e.Name != "" {
|
|
e.ID = vars.PreparedStmtNameToID[e.Name]
|
|
}
|
|
v := vars.PreparedStmts[e.ID]
|
|
if v == nil {
|
|
return errors.Trace(ErrStmtNotFound)
|
|
}
|
|
prepared := v.(*Prepared)
|
|
|
|
if len(prepared.Params) != len(e.UsingVars) {
|
|
return errors.Trace(ErrWrongParamCount)
|
|
}
|
|
|
|
for i, usingVar := range e.UsingVars {
|
|
val, err := usingVar.Eval(nil)
|
|
if err != nil {
|
|
return errors.Trace(err)
|
|
}
|
|
prepared.Params[i].SetDatum(val)
|
|
}
|
|
|
|
if prepared.SchemaVersion != e.IS.SchemaMetaVersion() {
|
|
// If the schema version has changed we need to prepare it again,
|
|
// if this time it failed, the real reason for the error is schema changed.
|
|
err := plan.PrepareStmt(e.IS, e.Ctx, prepared.Stmt)
|
|
if err != nil {
|
|
return ErrSchemaChanged.Gen("Schema change caused error: %s", err.Error())
|
|
}
|
|
prepared.SchemaVersion = e.IS.SchemaMetaVersion()
|
|
}
|
|
p, err := plan.Optimize(e.Ctx, prepared.Stmt, e.IS)
|
|
if err != nil {
|
|
return errors.Trace(err)
|
|
}
|
|
if IsPointGetWithPKOrUniqueKeyByAutoCommit(e.Ctx, p) {
|
|
err = e.Ctx.InitTxnWithStartTS(math.MaxUint64)
|
|
} else {
|
|
err = e.Ctx.ActivePendingTxn()
|
|
}
|
|
if err != nil {
|
|
return errors.Trace(err)
|
|
}
|
|
b := newExecutorBuilder(e.Ctx, e.IS)
|
|
stmtExec := b.build(p)
|
|
if b.err != nil {
|
|
return errors.Trace(b.err)
|
|
}
|
|
e.StmtExec = stmtExec
|
|
e.Stmt = prepared.Stmt
|
|
e.Plan = p
|
|
stmtCount(e.Stmt, e.Plan)
|
|
return nil
|
|
}
|
|
|
|
// DeallocateExec represent a DEALLOCATE executor.
|
|
type DeallocateExec struct {
|
|
Name string
|
|
ctx context.Context
|
|
}
|
|
|
|
// Schema implements the Executor Schema interface.
|
|
func (e *DeallocateExec) Schema() *expression.Schema {
|
|
// Will never be called.
|
|
return expression.NewSchema()
|
|
}
|
|
|
|
// Next implements the Executor Next interface.
|
|
func (e *DeallocateExec) Next() (*Row, error) {
|
|
vars := e.ctx.GetSessionVars()
|
|
id, ok := vars.PreparedStmtNameToID[e.Name]
|
|
if !ok {
|
|
return nil, errors.Trace(ErrStmtNotFound)
|
|
}
|
|
delete(vars.PreparedStmtNameToID, e.Name)
|
|
delete(vars.PreparedStmts, id)
|
|
return nil, nil
|
|
}
|
|
|
|
// Close implements plan.Plan Close interface.
|
|
func (e *DeallocateExec) Close() error {
|
|
return nil
|
|
}
|
|
|
|
// CompileExecutePreparedStmt compiles a session Execute command to a stmt.Statement.
|
|
func CompileExecutePreparedStmt(ctx context.Context, ID uint32, args ...interface{}) ast.Statement {
|
|
execPlan := &plan.Execute{ExecID: ID}
|
|
execPlan.UsingVars = make([]expression.Expression, len(args))
|
|
for i, val := range args {
|
|
value := ast.NewValueExpr(val)
|
|
execPlan.UsingVars[i] = &expression.Constant{Value: value.Datum, RetType: &value.Type}
|
|
}
|
|
sa := &statement{
|
|
is: GetInfoSchema(ctx),
|
|
plan: execPlan,
|
|
}
|
|
if prepared, ok := ctx.GetSessionVars().PreparedStmts[ID].(*Prepared); ok {
|
|
sa.text = prepared.Stmt.Text()
|
|
}
|
|
return sa
|
|
}
|