mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-14 18:24:14 +01:00
278 lines
6.8 KiB
Go
278 lines
6.8 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 (
|
||
|
"sort"
|
||
|
|
||
|
"github.com/juju/errors"
|
||
|
"github.com/pingcap/tidb/ast"
|
||
|
"github.com/pingcap/tidb/context"
|
||
|
"github.com/pingcap/tidb/evaluator"
|
||
|
"github.com/pingcap/tidb/infoschema"
|
||
|
"github.com/pingcap/tidb/optimizer"
|
||
|
"github.com/pingcap/tidb/optimizer/plan"
|
||
|
"github.com/pingcap/tidb/parser"
|
||
|
"github.com/pingcap/tidb/sessionctx"
|
||
|
"github.com/pingcap/tidb/sessionctx/variable"
|
||
|
)
|
||
|
|
||
|
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
|
||
|
ResultFields []*ast.ResultField
|
||
|
ParamCount int
|
||
|
Err error
|
||
|
}
|
||
|
|
||
|
// Fields implements Executor Fields interface.
|
||
|
func (e *PrepareExec) Fields() []*ast.ResultField {
|
||
|
// returns nil to indicate prepare will not return Recordset.
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Next implements 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 := variable.GetSessionVars(e.Ctx)
|
||
|
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 := variable.GetCharsetInfo(e.Ctx)
|
||
|
stmts, err := parser.Parse(e.SQLText, charset, collation)
|
||
|
if err != nil {
|
||
|
e.Err = errors.Trace(err)
|
||
|
return
|
||
|
}
|
||
|
if len(stmts) != 1 {
|
||
|
e.Err = ErrPrepareMulti
|
||
|
return
|
||
|
}
|
||
|
stmt := stmts[0]
|
||
|
var extractor paramMarkerExtractor
|
||
|
stmt.Accept(&extractor)
|
||
|
|
||
|
// 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 = optimizer.Prepare(e.IS, e.Ctx, stmt)
|
||
|
if err != nil {
|
||
|
e.Err = errors.Trace(err)
|
||
|
return
|
||
|
}
|
||
|
if resultSetNode, ok := stmt.(ast.ResultSetNode); ok {
|
||
|
e.ResultFields = resultSetNode.GetResultFields()
|
||
|
}
|
||
|
|
||
|
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 executes a prepared statement.
|
||
|
type ExecuteExec struct {
|
||
|
IS infoschema.InfoSchema
|
||
|
Ctx context.Context
|
||
|
Name string
|
||
|
UsingVars []ast.ExprNode
|
||
|
ID uint32
|
||
|
StmtExec Executor
|
||
|
}
|
||
|
|
||
|
// Fields implements Executor Fields interface.
|
||
|
func (e *ExecuteExec) Fields() []*ast.ResultField {
|
||
|
// Will never be called.
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Next implements 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.
|
||
|
func (e *ExecuteExec) Build() error {
|
||
|
vars := variable.GetSessionVars(e.Ctx)
|
||
|
if e.Name != "" {
|
||
|
e.ID = vars.PreparedStmtNameToID[e.Name]
|
||
|
}
|
||
|
v := vars.PreparedStmts[e.ID]
|
||
|
if v == nil {
|
||
|
return ErrStmtNotFound
|
||
|
}
|
||
|
prepared := v.(*Prepared)
|
||
|
|
||
|
if len(prepared.Params) != len(e.UsingVars) {
|
||
|
return ErrWrongParamCount
|
||
|
}
|
||
|
|
||
|
for i, usingVar := range e.UsingVars {
|
||
|
val, err := evaluator.Eval(e.Ctx, usingVar)
|
||
|
if err != nil {
|
||
|
return errors.Trace(err)
|
||
|
}
|
||
|
prepared.Params[i].SetValue(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 := optimizer.Prepare(e.IS, e.Ctx, prepared.Stmt)
|
||
|
if err != nil {
|
||
|
return ErrSchemaChanged.Gen("Schema change casued error: %s", err.Error())
|
||
|
}
|
||
|
prepared.SchemaVersion = e.IS.SchemaMetaVersion()
|
||
|
}
|
||
|
sb := &subqueryBuilder{is: e.IS}
|
||
|
plan, err := optimizer.Optimize(e.Ctx, prepared.Stmt, sb)
|
||
|
if err != nil {
|
||
|
return errors.Trace(err)
|
||
|
}
|
||
|
b := newExecutorBuilder(e.Ctx, e.IS)
|
||
|
stmtExec := b.build(plan)
|
||
|
if b.err != nil {
|
||
|
return errors.Trace(b.err)
|
||
|
}
|
||
|
e.StmtExec = stmtExec
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// DeallocateExec represent a DEALLOCATE executor.
|
||
|
type DeallocateExec struct {
|
||
|
Name string
|
||
|
ctx context.Context
|
||
|
}
|
||
|
|
||
|
// Fields implements Executor Fields interface.
|
||
|
func (e *DeallocateExec) Fields() []*ast.ResultField {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Next implements Executor Next interface.
|
||
|
func (e *DeallocateExec) Next() (*Row, error) {
|
||
|
vars := variable.GetSessionVars(e.ctx)
|
||
|
id, ok := vars.PreparedStmtNameToID[e.Name]
|
||
|
if !ok {
|
||
|
return nil, 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{ID: ID}
|
||
|
execPlan.UsingVars = make([]ast.ExprNode, len(args))
|
||
|
for i, val := range args {
|
||
|
execPlan.UsingVars[i] = ast.NewValueExpr(val)
|
||
|
}
|
||
|
sa := &statement{
|
||
|
is: sessionctx.GetDomain(ctx).InfoSchema(),
|
||
|
plan: execPlan,
|
||
|
}
|
||
|
return sa
|
||
|
}
|