зеркало из https://github.com/github/vitess-gh.git
1134 строки
28 KiB
Go
1134 строки
28 KiB
Go
// Copyright 2012, Google Inc. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package sqlparser
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
|
|
log "github.com/golang/glog"
|
|
"github.com/youtube/vitess/go/sqltypes"
|
|
"github.com/youtube/vitess/go/vt/schema"
|
|
)
|
|
|
|
type PlanType int
|
|
|
|
const (
|
|
PLAN_PASS_SELECT PlanType = iota
|
|
PLAN_PASS_DML
|
|
PLAN_PK_EQUAL
|
|
PLAN_PK_IN
|
|
PLAN_SELECT_SUBQUERY
|
|
PLAN_DML_PK
|
|
PLAN_DML_SUBQUERY
|
|
PLAN_INSERT_PK
|
|
PLAN_INSERT_SUBQUERY
|
|
PLAN_SET
|
|
PLAN_DDL
|
|
NumPlans
|
|
)
|
|
|
|
// Must exactly match order of plan constants.
|
|
var planName = []string{
|
|
"PASS_SELECT",
|
|
"PASS_DML",
|
|
"PK_EQUAL",
|
|
"PK_IN",
|
|
"SELECT_SUBQUERY",
|
|
"DML_PK",
|
|
"DML_SUBQUERY",
|
|
"INSERT_PK",
|
|
"INSERT_SUBQUERY",
|
|
"SET",
|
|
"DDL",
|
|
}
|
|
|
|
func (pt PlanType) String() string {
|
|
if pt < 0 || pt >= NumPlans {
|
|
return ""
|
|
}
|
|
return planName[pt]
|
|
}
|
|
|
|
func PlanByName(s string) (pt PlanType, ok bool) {
|
|
for i, v := range planName {
|
|
if v == s {
|
|
return PlanType(i), true
|
|
}
|
|
}
|
|
return NumPlans, false
|
|
}
|
|
|
|
func (pt PlanType) IsSelect() bool {
|
|
return pt == PLAN_PASS_SELECT || pt == PLAN_PK_EQUAL || pt == PLAN_PK_IN || pt == PLAN_SELECT_SUBQUERY
|
|
}
|
|
|
|
func (pt PlanType) MarshalJSON() ([]byte, error) {
|
|
return ([]byte)(fmt.Sprintf("\"%s\"", pt.String())), nil
|
|
}
|
|
|
|
type ReasonType int
|
|
|
|
const (
|
|
REASON_DEFAULT ReasonType = iota
|
|
REASON_SELECT
|
|
REASON_TABLE
|
|
REASON_NOCACHE
|
|
REASON_SELECT_LIST
|
|
REASON_LOCK
|
|
REASON_WHERE
|
|
REASON_ORDER
|
|
REASON_PKINDEX
|
|
REASON_NOINDEX_MATCH
|
|
REASON_TABLE_NOINDEX
|
|
REASON_PK_CHANGE
|
|
REASON_COMPOSITE_PK
|
|
REASON_HAS_HINTS
|
|
REASON_UPSERT
|
|
)
|
|
|
|
// Must exactly match order of reason constants.
|
|
var reasonName = []string{
|
|
"DEFAULT",
|
|
"SELECT",
|
|
"TABLE",
|
|
"NOCACHE",
|
|
"SELECT_LIST",
|
|
"LOCK",
|
|
"WHERE",
|
|
"ORDER",
|
|
"PKINDEX",
|
|
"NOINDEX_MATCH",
|
|
"TABLE_NOINDEX",
|
|
"PK_CHANGE",
|
|
"COMPOSITE_PK",
|
|
"HAS_HINTS",
|
|
"UPSERT",
|
|
}
|
|
|
|
func (rt ReasonType) String() string {
|
|
return reasonName[rt]
|
|
}
|
|
|
|
func (rt ReasonType) MarshalJSON() ([]byte, error) {
|
|
return ([]byte)(fmt.Sprintf("\"%s\"", rt.String())), nil
|
|
}
|
|
|
|
// ExecPlan is built for selects and DMLs.
|
|
// PK Values values within ExecPlan can be:
|
|
// sqltypes.Value: sourced form the query, or
|
|
// string: bind variable name starting with ':', or
|
|
// nil if no value was specified
|
|
type ExecPlan struct {
|
|
PlanId PlanType
|
|
Reason ReasonType
|
|
TableName string
|
|
|
|
// DisplayQuery is the displayable version of the
|
|
// original query. Depending on the mode, it may be
|
|
// the original query, or an anonymized version.
|
|
DisplayQuery string
|
|
|
|
// FieldQuery is used to fetch field info
|
|
FieldQuery *ParsedQuery
|
|
|
|
// FullQuery will be set for all plans.
|
|
FullQuery *ParsedQuery
|
|
|
|
// For PK plans, only OuterQuery is set.
|
|
// For SUBQUERY plans, Subquery is also set.
|
|
// IndexUsed is set only for PLAN_SELECT_SUBQUERY
|
|
OuterQuery *ParsedQuery
|
|
Subquery *ParsedQuery
|
|
IndexUsed string
|
|
|
|
// For selects, columns to be returned
|
|
// For PLAN_INSERT_SUBQUERY, columns to be inserted
|
|
ColumnNumbers []int
|
|
|
|
// PLAN_PK_EQUAL, PLAN_DML_PK: where clause values
|
|
// PLAN_PK_IN: IN clause values
|
|
// PLAN_INSERT_PK: values clause
|
|
PKValues []interface{}
|
|
|
|
// For update: set clause if pk is changing
|
|
SecondaryPKValues []interface{}
|
|
|
|
// For PLAN_INSERT_SUBQUERY: pk columns in the subquery result
|
|
SubqueryPKColumns []int
|
|
|
|
// PLAN_SET
|
|
SetKey string
|
|
SetValue interface{}
|
|
}
|
|
|
|
type DDLPlan struct {
|
|
Action int
|
|
TableName string
|
|
NewName string
|
|
}
|
|
|
|
type StreamExecPlan struct {
|
|
DisplayQuery string
|
|
FullQuery *ParsedQuery
|
|
}
|
|
|
|
type TableGetter func(tableName string) (*schema.Table, bool)
|
|
|
|
func ExecParse(sql string, getTable TableGetter, sensitiveMode bool) (plan *ExecPlan, err error) {
|
|
defer handleError(&err)
|
|
|
|
tree, err := Parse(sql)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
plan = tree.execAnalyzeSql(getTable)
|
|
if plan.PlanId == PLAN_PASS_DML {
|
|
log.Warningf("PASS_DML: %s", sql)
|
|
}
|
|
if sensitiveMode {
|
|
plan.DisplayQuery = tree.GenerateAnonymizedQuery()
|
|
} else {
|
|
plan.DisplayQuery = sql
|
|
}
|
|
return plan, nil
|
|
}
|
|
|
|
func StreamExecParse(sql string, sensitiveMode bool) (plan *StreamExecPlan, err error) {
|
|
defer handleError(&err)
|
|
|
|
tree, err := Parse(sql)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch tree.Type {
|
|
case SELECT:
|
|
if tree.At(SELECT_LOCK_OFFSET).Type != NO_LOCK {
|
|
return nil, NewParserError("select with lock disallowed with streaming")
|
|
}
|
|
case UNION, UNION_ALL, MINUS, EXCEPT, INTERSECT:
|
|
default:
|
|
return nil, NewParserError("%s not allowed for streaming", string(tree.Value))
|
|
}
|
|
plan = &StreamExecPlan{FullQuery: tree.GenerateFullQuery()}
|
|
|
|
if sensitiveMode {
|
|
plan.DisplayQuery = tree.GenerateAnonymizedQuery()
|
|
} else {
|
|
plan.DisplayQuery = sql
|
|
}
|
|
|
|
return plan, nil
|
|
}
|
|
|
|
func DDLParse(sql string) (plan *DDLPlan) {
|
|
rootNode, err := Parse(sql)
|
|
if err != nil {
|
|
return &DDLPlan{Action: 0}
|
|
}
|
|
switch rootNode.Type {
|
|
case CREATE, ALTER, DROP:
|
|
return &DDLPlan{
|
|
Action: rootNode.Type,
|
|
TableName: string(rootNode.At(0).Value),
|
|
NewName: string(rootNode.At(0).Value),
|
|
}
|
|
case RENAME:
|
|
return &DDLPlan{
|
|
Action: rootNode.Type,
|
|
TableName: string(rootNode.At(0).Value),
|
|
NewName: string(rootNode.At(1).Value),
|
|
}
|
|
}
|
|
return &DDLPlan{Action: 0}
|
|
}
|
|
|
|
//-----------------------------------------------
|
|
// Implementation
|
|
|
|
func (node *Node) execAnalyzeSql(getTable TableGetter) (plan *ExecPlan) {
|
|
switch node.Type {
|
|
case SELECT, UNION, UNION_ALL, MINUS, EXCEPT, INTERSECT:
|
|
return node.execAnalyzeSelect(getTable)
|
|
case INSERT:
|
|
return node.execAnalyzeInsert(getTable)
|
|
case UPDATE:
|
|
return node.execAnalyzeUpdate(getTable)
|
|
case DELETE:
|
|
return node.execAnalyzeDelete(getTable)
|
|
case SET:
|
|
return node.execAnalyzeSet()
|
|
case CREATE, ALTER, DROP, RENAME:
|
|
return &ExecPlan{PlanId: PLAN_DDL}
|
|
}
|
|
panic(NewParserError("invalid SQL"))
|
|
}
|
|
|
|
func (node *Node) execAnalyzeSelect(getTable TableGetter) (plan *ExecPlan) {
|
|
// Default plan
|
|
plan = &ExecPlan{
|
|
PlanId: PLAN_PASS_SELECT,
|
|
FieldQuery: node.GenerateFieldQuery(),
|
|
FullQuery: node.GenerateSelectLimitQuery(),
|
|
}
|
|
|
|
// There are bind variables in the SELECT list
|
|
if plan.FieldQuery == nil {
|
|
plan.Reason = REASON_SELECT_LIST
|
|
return plan
|
|
}
|
|
|
|
if !node.execAnalyzeSelectStructure() {
|
|
plan.Reason = REASON_SELECT
|
|
return plan
|
|
}
|
|
|
|
// from
|
|
tableName, hasHints := node.At(SELECT_FROM_OFFSET).execAnalyzeFrom()
|
|
if tableName == "" {
|
|
plan.Reason = REASON_TABLE
|
|
return plan
|
|
}
|
|
tableInfo := plan.setTableInfo(tableName, getTable)
|
|
|
|
// Don't improve the plan if the select is locking the row
|
|
if node.At(SELECT_LOCK_OFFSET).Type != NO_LOCK {
|
|
plan.Reason = REASON_LOCK
|
|
return plan
|
|
}
|
|
|
|
// Further improvements possible only if table is row-cached
|
|
if tableInfo.CacheType == schema.CACHE_NONE || tableInfo.CacheType == schema.CACHE_W {
|
|
plan.Reason = REASON_NOCACHE
|
|
return plan
|
|
}
|
|
|
|
// Select expressions
|
|
selects := node.At(SELECT_EXPR_OFFSET).execAnalyzeSelectExpressions(tableInfo)
|
|
if selects == nil {
|
|
plan.Reason = REASON_SELECT_LIST
|
|
return plan
|
|
}
|
|
plan.ColumnNumbers = selects
|
|
|
|
// where
|
|
conditions := node.At(SELECT_WHERE_OFFSET).execAnalyzeWhere()
|
|
if conditions == nil {
|
|
plan.Reason = REASON_WHERE
|
|
return plan
|
|
}
|
|
|
|
// order
|
|
if node.At(SELECT_ORDER_OFFSET).Len() != 0 {
|
|
plan.Reason = REASON_ORDER
|
|
return plan
|
|
}
|
|
|
|
// This check should never fail because we only cache tables with primary keys.
|
|
if len(tableInfo.Indexes) == 0 || tableInfo.Indexes[0].Name != "PRIMARY" {
|
|
panic("unexpected")
|
|
}
|
|
|
|
// Attempt PK match only if there's no limit clause
|
|
if node.At(SELECT_LIMIT_OFFSET).Len() == 0 {
|
|
planId, pkValues := getSelectPKValues(conditions, tableInfo.Indexes[0])
|
|
switch planId {
|
|
case PLAN_PK_EQUAL:
|
|
plan.PlanId = PLAN_PK_EQUAL
|
|
plan.OuterQuery = node.GenerateEqualOuterQuery(tableInfo)
|
|
plan.PKValues = pkValues
|
|
return plan
|
|
case PLAN_PK_IN:
|
|
plan.PlanId = PLAN_PK_IN
|
|
plan.OuterQuery = node.GenerateInOuterQuery(tableInfo)
|
|
plan.PKValues = pkValues
|
|
return plan
|
|
}
|
|
}
|
|
|
|
if len(tableInfo.Indexes[0].Columns) != 1 {
|
|
plan.Reason = REASON_COMPOSITE_PK
|
|
return plan
|
|
}
|
|
|
|
// TODO: Analyze hints to improve plan.
|
|
if hasHints {
|
|
plan.Reason = REASON_HAS_HINTS
|
|
return plan
|
|
}
|
|
|
|
plan.IndexUsed = getIndexMatch(conditions, tableInfo.Indexes)
|
|
if plan.IndexUsed == "" {
|
|
plan.Reason = REASON_NOINDEX_MATCH
|
|
return plan
|
|
}
|
|
if plan.IndexUsed == "PRIMARY" {
|
|
plan.Reason = REASON_PKINDEX
|
|
return plan
|
|
}
|
|
// TODO: We can further optimize. Change this to pass-through if select list matches all columns in index.
|
|
plan.PlanId = PLAN_SELECT_SUBQUERY
|
|
plan.OuterQuery = node.GenerateInOuterQuery(tableInfo)
|
|
plan.Subquery = node.GenerateSelectSubquery(tableInfo, plan.IndexUsed)
|
|
return plan
|
|
}
|
|
|
|
func (node *Node) execAnalyzeInsert(getTable TableGetter) (plan *ExecPlan) {
|
|
plan = &ExecPlan{
|
|
PlanId: PLAN_PASS_DML,
|
|
FullQuery: node.GenerateFullQuery(),
|
|
}
|
|
tableName := node.At(INSERT_TABLE_OFFSET).collectTableName()
|
|
if tableName == "" {
|
|
plan.Reason = REASON_TABLE
|
|
return plan
|
|
}
|
|
tableInfo := plan.setTableInfo(tableName, getTable)
|
|
|
|
if len(tableInfo.Indexes) == 0 || tableInfo.Indexes[0].Name != "PRIMARY" {
|
|
log.Warningf("no primary key for table %s", tableName)
|
|
plan.Reason = REASON_TABLE_NOINDEX
|
|
return plan
|
|
}
|
|
|
|
pkColumnNumbers := node.At(INSERT_COLUMN_LIST_OFFSET).getInsertPKColumns(tableInfo)
|
|
|
|
if node.At(INSERT_ON_DUP_OFFSET).Len() != 0 {
|
|
// Upserts are not safe for statement based replication:
|
|
// http://bugs.mysql.com/bug.php?id=58637
|
|
plan.Reason = REASON_UPSERT
|
|
return plan
|
|
}
|
|
|
|
rowValues := node.At(INSERT_VALUES_OFFSET) // VALUES/SELECT
|
|
if rowValues.Type == SELECT {
|
|
plan.PlanId = PLAN_INSERT_SUBQUERY
|
|
plan.OuterQuery = node.GenerateInsertOuterQuery()
|
|
plan.Subquery = rowValues.GenerateSelectLimitQuery()
|
|
// Column list syntax is a subset of select expressions
|
|
if node.At(INSERT_COLUMN_LIST_OFFSET).Len() != 0 {
|
|
plan.ColumnNumbers = node.At(INSERT_COLUMN_LIST_OFFSET).execAnalyzeSelectExpressions(tableInfo)
|
|
} else {
|
|
// SELECT_STAR node will expand into all columns
|
|
n := NewSimpleParseNode(NODE_LIST, "")
|
|
n.Push(NewSimpleParseNode(SELECT_STAR, "*"))
|
|
plan.ColumnNumbers = n.execAnalyzeSelectExpressions(tableInfo)
|
|
}
|
|
plan.SubqueryPKColumns = pkColumnNumbers
|
|
return plan
|
|
}
|
|
|
|
rowList := rowValues.At(0) // VALUES->NODE_LIST
|
|
if pkValues := getInsertPKValues(pkColumnNumbers, rowList, tableInfo); pkValues != nil {
|
|
plan.PlanId = PLAN_INSERT_PK
|
|
plan.OuterQuery = plan.FullQuery
|
|
plan.PKValues = pkValues
|
|
}
|
|
return plan
|
|
}
|
|
|
|
func (node *Node) execAnalyzeUpdate(getTable TableGetter) (plan *ExecPlan) {
|
|
// Default plan
|
|
plan = &ExecPlan{
|
|
PlanId: PLAN_PASS_DML,
|
|
FullQuery: node.GenerateFullQuery(),
|
|
}
|
|
|
|
tableName := node.At(UPDATE_TABLE_OFFSET).collectTableName()
|
|
if tableName == "" {
|
|
plan.Reason = REASON_TABLE
|
|
return plan
|
|
}
|
|
tableInfo := plan.setTableInfo(tableName, getTable)
|
|
|
|
if len(tableInfo.Indexes) == 0 || tableInfo.Indexes[0].Name != "PRIMARY" {
|
|
log.Warningf("no primary key for table %s", tableName)
|
|
plan.Reason = REASON_TABLE_NOINDEX
|
|
return plan
|
|
}
|
|
|
|
var ok bool
|
|
if plan.SecondaryPKValues, ok = node.At(UPDATE_LIST_OFFSET).execAnalyzeUpdateExpressions(tableInfo.Indexes[0]); !ok {
|
|
plan.Reason = REASON_PK_CHANGE
|
|
return plan
|
|
}
|
|
|
|
plan.PlanId = PLAN_DML_SUBQUERY
|
|
plan.OuterQuery = node.GenerateUpdateOuterQuery(tableInfo.Indexes[0])
|
|
plan.Subquery = node.GenerateUpdateSubquery(tableInfo)
|
|
|
|
conditions := node.At(UPDATE_WHERE_OFFSET).execAnalyzeWhere()
|
|
if conditions == nil {
|
|
plan.Reason = REASON_WHERE
|
|
return plan
|
|
}
|
|
|
|
if pkValues := getPKValues(conditions, tableInfo.Indexes[0]); pkValues != nil {
|
|
plan.PlanId = PLAN_DML_PK
|
|
plan.OuterQuery = plan.FullQuery
|
|
plan.PKValues = pkValues
|
|
return plan
|
|
}
|
|
|
|
return plan
|
|
}
|
|
|
|
func (node *Node) execAnalyzeDelete(getTable TableGetter) (plan *ExecPlan) {
|
|
// Default plan
|
|
plan = &ExecPlan{
|
|
PlanId: PLAN_PASS_DML,
|
|
FullQuery: node.GenerateFullQuery(),
|
|
}
|
|
|
|
tableName := node.At(DELETE_TABLE_OFFSET).collectTableName()
|
|
if tableName == "" {
|
|
plan.Reason = REASON_TABLE
|
|
return plan
|
|
}
|
|
tableInfo := plan.setTableInfo(tableName, getTable)
|
|
|
|
if len(tableInfo.Indexes) == 0 || tableInfo.Indexes[0].Name != "PRIMARY" {
|
|
log.Warningf("no primary key for table %s", tableName)
|
|
plan.Reason = REASON_TABLE_NOINDEX
|
|
return plan
|
|
}
|
|
|
|
plan.PlanId = PLAN_DML_SUBQUERY
|
|
plan.OuterQuery = node.GenerateDeleteOuterQuery(tableInfo.Indexes[0])
|
|
plan.Subquery = node.GenerateDeleteSubquery(tableInfo)
|
|
|
|
conditions := node.At(DELETE_WHERE_OFFSET).execAnalyzeWhere()
|
|
if conditions == nil {
|
|
plan.Reason = REASON_WHERE
|
|
return plan
|
|
}
|
|
|
|
if pkValues := getPKValues(conditions, tableInfo.Indexes[0]); pkValues != nil {
|
|
plan.PlanId = PLAN_DML_PK
|
|
plan.OuterQuery = plan.FullQuery
|
|
plan.PKValues = pkValues
|
|
return plan
|
|
}
|
|
|
|
return plan
|
|
}
|
|
|
|
func (node *Node) execAnalyzeSet() (plan *ExecPlan) {
|
|
plan = &ExecPlan{
|
|
PlanId: PLAN_SET,
|
|
FullQuery: node.GenerateFullQuery(),
|
|
}
|
|
update_list := node.At(1) // NODE_LIST
|
|
if update_list.Len() > 1 { // Multiple set values
|
|
return
|
|
}
|
|
update_expression := update_list.At(0) // '='
|
|
plan.SetKey = string(update_expression.At(0).Value) // ID
|
|
expression := update_expression.At(1)
|
|
valstr := string(expression.Value)
|
|
if expression.Type == NUMBER {
|
|
if ival, err := strconv.ParseInt(valstr, 0, 64); err == nil {
|
|
plan.SetValue = ival
|
|
} else if fval, err := strconv.ParseFloat(valstr, 64); err == nil {
|
|
plan.SetValue = fval
|
|
}
|
|
}
|
|
return plan
|
|
}
|
|
|
|
func (node *ExecPlan) setTableInfo(tableName string, getTable TableGetter) *schema.Table {
|
|
tableInfo, ok := getTable(tableName)
|
|
if !ok {
|
|
panic(NewParserError("table %s not found in schema", tableName))
|
|
}
|
|
node.TableName = tableInfo.Name
|
|
return tableInfo
|
|
}
|
|
|
|
//-----------------------------------------------
|
|
// Select
|
|
|
|
func (node *Node) execAnalyzeSelectStructure() bool {
|
|
switch node.Type {
|
|
case UNION, UNION_ALL, MINUS, EXCEPT, INTERSECT:
|
|
return false
|
|
}
|
|
if node.At(SELECT_DISTINCT_OFFSET).Type == DISTINCT {
|
|
return false
|
|
}
|
|
if node.At(SELECT_GROUP_OFFSET).Len() > 0 {
|
|
return false
|
|
}
|
|
if node.At(SELECT_HAVING_OFFSET).Len() > 0 {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
//-----------------------------------------------
|
|
// Select Expressions
|
|
|
|
func (node *Node) execAnalyzeSelectExpressions(table *schema.Table) (selects []int) {
|
|
selects = make([]int, 0, node.Len())
|
|
for i := 0; i < node.Len(); i++ {
|
|
if name := node.At(i).execAnalyzeSelectExpression(); name != "" {
|
|
if name == "*" {
|
|
for colIndex := range table.Columns {
|
|
selects = append(selects, colIndex)
|
|
}
|
|
} else if colIndex := table.FindColumn(name); colIndex != -1 {
|
|
selects = append(selects, colIndex)
|
|
} else {
|
|
panic(NewParserError("column %s not found in table %s", name, table.Name))
|
|
}
|
|
} else {
|
|
// Complex expression
|
|
return nil
|
|
}
|
|
}
|
|
return selects
|
|
}
|
|
|
|
func (node *Node) execAnalyzeSelectExpression() (name string) {
|
|
switch node.Type {
|
|
case ID, SELECT_STAR:
|
|
return string(node.Value)
|
|
case '.':
|
|
return node.At(1).execAnalyzeSelectExpression()
|
|
case AS:
|
|
return node.At(0).execAnalyzeSelectExpression()
|
|
}
|
|
return ""
|
|
}
|
|
|
|
//-----------------------------------------------
|
|
// From
|
|
|
|
func (node *Node) execAnalyzeFrom() (tablename string, hasHints bool) {
|
|
if node.Len() > 1 {
|
|
return "", false
|
|
}
|
|
if node.At(0).Type != TABLE_EXPR {
|
|
return "", false
|
|
}
|
|
hasHints = (node.At(0).At(2).Len() > 0)
|
|
return node.At(0).At(0).collectTableName(), hasHints
|
|
}
|
|
|
|
func (node *Node) collectTableName() string {
|
|
if node.Type == ID {
|
|
return string(node.Value)
|
|
}
|
|
// sub-select or '.' expression
|
|
return ""
|
|
}
|
|
|
|
//-----------------------------------------------
|
|
// Where
|
|
|
|
func (node *Node) execAnalyzeWhere() (conditions []*Node) {
|
|
if node.Len() == 0 {
|
|
return nil
|
|
}
|
|
return node.At(0).execAnalyzeBoolean()
|
|
}
|
|
|
|
func (node *Node) execAnalyzeBoolean() (conditions []*Node) {
|
|
switch node.Type {
|
|
case AND:
|
|
left := node.At(0).execAnalyzeBoolean()
|
|
right := node.At(1).execAnalyzeBoolean()
|
|
if left == nil || right == nil {
|
|
return nil
|
|
}
|
|
if hasINClause(left) && hasINClause(right) {
|
|
return nil
|
|
}
|
|
return append(left, right...)
|
|
case '(':
|
|
return node.At(0).execAnalyzeBoolean()
|
|
case '=', '<', '>', LE, GE, NULL_SAFE_EQUAL, LIKE:
|
|
left := node.At(0).execAnalyzeID()
|
|
right := node.At(1).execAnalyzeValue()
|
|
if left == nil || right == nil {
|
|
return nil
|
|
}
|
|
n := NewParseNode(node.Type, node.Value)
|
|
n.PushTwo(left, right)
|
|
return []*Node{n}
|
|
case IN:
|
|
left := node.At(0).execAnalyzeID()
|
|
right := node.At(1).execAnalyzeSimpleINList()
|
|
if left == nil || right == nil {
|
|
return nil
|
|
}
|
|
n := NewParseNode(node.Type, node.Value)
|
|
n.PushTwo(left, right)
|
|
return []*Node{n}
|
|
case BETWEEN:
|
|
left := node.At(0).execAnalyzeID()
|
|
right1 := node.At(1).execAnalyzeValue()
|
|
right2 := node.At(2).execAnalyzeValue()
|
|
if left == nil || right1 == nil || right2 == nil {
|
|
return nil
|
|
}
|
|
return []*Node{node}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (node *Node) execAnalyzeSimpleINList() *Node {
|
|
list := node.At(0) // '('->NODE_LIST
|
|
for i := 0; i < list.Len(); i++ {
|
|
if n := list.At(i).execAnalyzeValue(); n == nil {
|
|
return nil
|
|
}
|
|
}
|
|
return node
|
|
}
|
|
|
|
func (node *Node) execAnalyzeID() *Node {
|
|
switch node.Type {
|
|
case ID:
|
|
return node
|
|
case '.':
|
|
return node.At(1).execAnalyzeID()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (node *Node) execAnalyzeValue() *Node {
|
|
switch node.Type {
|
|
case STRING, NUMBER, VALUE_ARG:
|
|
return node
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func hasINClause(conditions []*Node) bool {
|
|
for _, node := range conditions {
|
|
if node.Type == IN {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (node *Node) parseList() (values interface{}, isList bool) {
|
|
vals := make([]interface{}, node.Len())
|
|
for i := 0; i < node.Len(); i++ {
|
|
vals[i] = asInterface(node.At(i))
|
|
}
|
|
return vals, true
|
|
}
|
|
|
|
//-----------------------------------------------
|
|
// Update expressions
|
|
|
|
func (node *Node) execAnalyzeUpdateExpressions(pkIndex *schema.Index) (pkValues []interface{}, ok bool) {
|
|
for i := 0; i < node.Len(); i++ {
|
|
columnName := string(node.At(i).At(0).execAnalyzeSelectExpression())
|
|
index := pkIndex.FindColumn(columnName)
|
|
if index == -1 {
|
|
continue
|
|
}
|
|
value := node.At(i).At(1).execAnalyzeValue()
|
|
if value == nil {
|
|
log.Warningf("expression is too complex %v", node.At(i).At(0))
|
|
return nil, false
|
|
}
|
|
if pkValues == nil {
|
|
pkValues = make([]interface{}, len(pkIndex.Columns))
|
|
}
|
|
pkValues[index] = asInterface(value)
|
|
}
|
|
return pkValues, true
|
|
}
|
|
|
|
//-----------------------------------------------
|
|
// Insert
|
|
|
|
func (node *Node) getInsertPKColumns(tableInfo *schema.Table) (pkColumnNumbers []int) {
|
|
if node.Len() == 0 {
|
|
return tableInfo.PKColumns
|
|
}
|
|
pkIndex := tableInfo.Indexes[0]
|
|
pkColumnNumbers = make([]int, len(pkIndex.Columns))
|
|
for i := range pkColumnNumbers {
|
|
pkColumnNumbers[i] = -1
|
|
}
|
|
for i, column := range node.Sub {
|
|
index := pkIndex.FindColumn(string(column.execAnalyzeSelectExpression()))
|
|
if index == -1 {
|
|
continue
|
|
}
|
|
pkColumnNumbers[index] = i
|
|
}
|
|
return pkColumnNumbers
|
|
}
|
|
|
|
func getInsertPKValues(pkColumnNumbers []int, rowList *Node, tableInfo *schema.Table) (pkValues []interface{}) {
|
|
pkValues = make([]interface{}, len(pkColumnNumbers))
|
|
for index, columnNumber := range pkColumnNumbers {
|
|
if columnNumber == -1 {
|
|
pkValues[index] = tableInfo.GetPKColumn(index).Default
|
|
continue
|
|
}
|
|
values := make([]interface{}, rowList.Len())
|
|
for j := 0; j < rowList.Len(); j++ {
|
|
if columnNumber >= rowList.At(j).At(0).Len() { // NODE_LIST->'('->NODE_LIST
|
|
panic(NewParserError("column count doesn't match value count"))
|
|
}
|
|
node := rowList.At(j).At(0).At(columnNumber) // NODE_LIST->'('->NODE_LIST->Value
|
|
value := node.execAnalyzeValue()
|
|
if value == nil {
|
|
log.Warningf("insert is too complex %v", node)
|
|
return nil
|
|
}
|
|
values[j] = asInterface(value)
|
|
}
|
|
if len(values) == 1 {
|
|
pkValues[index] = values[0]
|
|
} else {
|
|
pkValues[index] = values
|
|
}
|
|
}
|
|
return pkValues
|
|
}
|
|
|
|
//-----------------------------------------------
|
|
// Index Analysis
|
|
|
|
type IndexScore struct {
|
|
Index *schema.Index
|
|
ColumnMatch []bool
|
|
MatchFailed bool
|
|
}
|
|
|
|
type scoreValue int64
|
|
|
|
const (
|
|
NO_MATCH = scoreValue(-1)
|
|
PERFECT_SCORE = scoreValue(0)
|
|
)
|
|
|
|
func NewIndexScore(index *schema.Index) *IndexScore {
|
|
return &IndexScore{index, make([]bool, len(index.Columns)), false}
|
|
}
|
|
|
|
func (is *IndexScore) FindMatch(columnName string) int {
|
|
if is.MatchFailed {
|
|
return -1
|
|
}
|
|
if index := is.Index.FindColumn(columnName); index != -1 {
|
|
is.ColumnMatch[index] = true
|
|
return index
|
|
}
|
|
// If the column is among the data columns, we can still use
|
|
// the index without going to the main table
|
|
if index := is.Index.FindDataColumn(columnName); index == -1 {
|
|
is.MatchFailed = true
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func (is *IndexScore) GetScore() scoreValue {
|
|
if is.MatchFailed {
|
|
return NO_MATCH
|
|
}
|
|
score := NO_MATCH
|
|
for i, indexColumn := range is.ColumnMatch {
|
|
if indexColumn {
|
|
score = scoreValue(is.Index.Cardinality[i])
|
|
continue
|
|
}
|
|
return score
|
|
}
|
|
return PERFECT_SCORE
|
|
}
|
|
|
|
func NewIndexScoreList(indexes []*schema.Index) []*IndexScore {
|
|
scoreList := make([]*IndexScore, len(indexes))
|
|
for i, v := range indexes {
|
|
scoreList[i] = NewIndexScore(v)
|
|
}
|
|
return scoreList
|
|
}
|
|
|
|
func getSelectPKValues(conditions []*Node, pkIndex *schema.Index) (planId PlanType, pkValues []interface{}) {
|
|
pkValues = getPKValues(conditions, pkIndex)
|
|
if pkValues == nil {
|
|
return PLAN_PASS_SELECT, nil
|
|
}
|
|
for _, pkValue := range pkValues {
|
|
inList, ok := pkValue.([]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
if len(pkValues) == 1 {
|
|
return PLAN_PK_IN, inList
|
|
}
|
|
return PLAN_PASS_SELECT, nil
|
|
}
|
|
return PLAN_PK_EQUAL, pkValues
|
|
}
|
|
|
|
func getPKValues(conditions []*Node, pkIndex *schema.Index) (pkValues []interface{}) {
|
|
pkIndexScore := NewIndexScore(pkIndex)
|
|
pkValues = make([]interface{}, len(pkIndexScore.ColumnMatch))
|
|
for _, condition := range conditions {
|
|
if condition.Type != '=' && condition.Type != IN {
|
|
return nil
|
|
}
|
|
index := pkIndexScore.FindMatch(string(condition.At(0).Value))
|
|
if index == -1 {
|
|
return nil
|
|
}
|
|
switch condition.Type {
|
|
case '=':
|
|
pkValues[index] = asInterface(condition.At(1))
|
|
case IN:
|
|
pkValues[index], _ = condition.At(1).At(0).parseList()
|
|
}
|
|
}
|
|
if pkIndexScore.GetScore() == PERFECT_SCORE {
|
|
return pkValues
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getIndexMatch(conditions []*Node, indexes []*schema.Index) string {
|
|
indexScores := NewIndexScoreList(indexes)
|
|
for _, condition := range conditions {
|
|
for _, index := range indexScores {
|
|
index.FindMatch(string(condition.At(0).Value))
|
|
}
|
|
}
|
|
highScore := NO_MATCH
|
|
highScorer := -1
|
|
for i, index := range indexScores {
|
|
curScore := index.GetScore()
|
|
if curScore == NO_MATCH {
|
|
continue
|
|
}
|
|
if curScore == PERFECT_SCORE {
|
|
highScorer = i
|
|
break
|
|
}
|
|
// Prefer secondary index over primary key
|
|
if curScore >= highScore {
|
|
highScore = curScore
|
|
highScorer = i
|
|
}
|
|
}
|
|
if highScorer == -1 {
|
|
return ""
|
|
}
|
|
return indexes[highScorer].Name
|
|
}
|
|
|
|
//-----------------------------------------------
|
|
// Query Generation
|
|
func (node *Node) GenerateFullQuery() *ParsedQuery {
|
|
buf := NewTrackedBuffer(nil)
|
|
buf.Fprintf("%v", node)
|
|
return buf.ParsedQuery()
|
|
}
|
|
|
|
func (node *Node) GenerateAnonymizedQuery() string {
|
|
buf := NewTrackedBuffer(AnonymizedFormatNode)
|
|
buf.Fprintf("%v", node)
|
|
return buf.ParsedQuery().Query
|
|
}
|
|
|
|
func (node *Node) GenerateFieldQuery() *ParsedQuery {
|
|
buf := NewTrackedBuffer(FormatImpossible)
|
|
FormatImpossible(buf, node)
|
|
if len(buf.bindLocations) != 0 {
|
|
return nil
|
|
}
|
|
return buf.ParsedQuery()
|
|
}
|
|
|
|
// FormatImpossible is a callback function used by TrackedBuffer
|
|
// to generate a modified version of the query where all selects
|
|
// have impossible where clauses. It overrides a few node types
|
|
// and passes the rest down to the default FormatNode.
|
|
func FormatImpossible(buf *TrackedBuffer, node *Node) {
|
|
switch node.Type {
|
|
case SELECT:
|
|
buf.Fprintf("select %v from %v where 1 != 1",
|
|
node.At(SELECT_EXPR_OFFSET),
|
|
node.At(SELECT_FROM_OFFSET),
|
|
)
|
|
case JOIN, STRAIGHT_JOIN, CROSS, NATURAL:
|
|
// We skip ON clauses (if any)
|
|
buf.Fprintf("%v %s %v", node.At(0), node.Value, node.At(1))
|
|
case LEFT, RIGHT:
|
|
// ON clause is requried
|
|
buf.Fprintf("%v %s %v on 1 != 1", node.At(0), node.Value, node.At(1))
|
|
default:
|
|
FormatNode(buf, node)
|
|
}
|
|
}
|
|
|
|
func (node *Node) GenerateSelectLimitQuery() *ParsedQuery {
|
|
buf := NewTrackedBuffer(nil)
|
|
if node.Type == SELECT {
|
|
limit := node.At(SELECT_LIMIT_OFFSET)
|
|
if limit.Len() == 0 {
|
|
limit.PushLimit()
|
|
defer limit.Pop()
|
|
}
|
|
}
|
|
FormatNode(buf, node)
|
|
return buf.ParsedQuery()
|
|
}
|
|
|
|
func (node *Node) GenerateEqualOuterQuery(tableInfo *schema.Table) *ParsedQuery {
|
|
buf := NewTrackedBuffer(nil)
|
|
fmt.Fprintf(buf, "select ")
|
|
writeColumnList(buf, tableInfo.Columns)
|
|
buf.Fprintf(" from %v where ", node.At(SELECT_FROM_OFFSET))
|
|
generatePKWhere(buf, tableInfo.Indexes[0])
|
|
return buf.ParsedQuery()
|
|
}
|
|
|
|
func (node *Node) GenerateInOuterQuery(tableInfo *schema.Table) *ParsedQuery {
|
|
buf := NewTrackedBuffer(nil)
|
|
fmt.Fprintf(buf, "select ")
|
|
writeColumnList(buf, tableInfo.Columns)
|
|
// We assume there is one and only one PK column.
|
|
// A '*' argument name means all variables of the list.
|
|
buf.Fprintf(" from %v where %s in (%a)", node.At(SELECT_FROM_OFFSET), tableInfo.Indexes[0].Columns[0], "*")
|
|
return buf.ParsedQuery()
|
|
}
|
|
|
|
func (node *Node) GenerateInsertOuterQuery() *ParsedQuery {
|
|
buf := NewTrackedBuffer(nil)
|
|
buf.Fprintf("insert %vinto %v%v values %a%v",
|
|
node.At(INSERT_COMMENT_OFFSET),
|
|
node.At(INSERT_TABLE_OFFSET),
|
|
node.At(INSERT_COLUMN_LIST_OFFSET),
|
|
"_rowValues",
|
|
node.At(INSERT_ON_DUP_OFFSET),
|
|
)
|
|
return buf.ParsedQuery()
|
|
}
|
|
|
|
func (node *Node) GenerateUpdateOuterQuery(pkIndex *schema.Index) *ParsedQuery {
|
|
buf := NewTrackedBuffer(nil)
|
|
buf.Fprintf("update %v%v set %v where ",
|
|
node.At(UPDATE_COMMENT_OFFSET), node.At(UPDATE_TABLE_OFFSET), node.At(UPDATE_LIST_OFFSET))
|
|
generatePKWhere(buf, pkIndex)
|
|
return buf.ParsedQuery()
|
|
}
|
|
|
|
func (node *Node) GenerateDeleteOuterQuery(pkIndex *schema.Index) *ParsedQuery {
|
|
buf := NewTrackedBuffer(nil)
|
|
buf.Fprintf("delete %vfrom %v where ", node.At(DELETE_COMMENT_OFFSET), node.At(DELETE_TABLE_OFFSET))
|
|
generatePKWhere(buf, pkIndex)
|
|
return buf.ParsedQuery()
|
|
}
|
|
|
|
func generatePKWhere(buf *TrackedBuffer, pkIndex *schema.Index) {
|
|
for i := 0; i < len(pkIndex.Columns); i++ {
|
|
if i != 0 {
|
|
buf.WriteString(" and ")
|
|
}
|
|
buf.Fprintf("%s = %a", pkIndex.Columns[i], strconv.FormatInt(int64(i), 10))
|
|
}
|
|
}
|
|
|
|
func (node *Node) GenerateSelectSubquery(tableInfo *schema.Table, index string) *ParsedQuery {
|
|
hint := NewSimpleParseNode(USE, "use")
|
|
hint.Push(NewSimpleParseNode(COLUMN_LIST, ""))
|
|
hint.At(0).Push(NewSimpleParseNode(ID, index))
|
|
table_expr := node.At(SELECT_FROM_OFFSET).At(0)
|
|
savedHint := table_expr.Sub[2]
|
|
table_expr.Sub[2] = hint
|
|
defer func() {
|
|
table_expr.Sub[2] = savedHint
|
|
}()
|
|
return GenerateSubquery(
|
|
tableInfo.Indexes[0].Columns,
|
|
node.At(SELECT_FROM_OFFSET),
|
|
node.At(SELECT_WHERE_OFFSET),
|
|
node.At(SELECT_ORDER_OFFSET),
|
|
node.At(SELECT_LIMIT_OFFSET),
|
|
false,
|
|
)
|
|
}
|
|
|
|
func (node *Node) GenerateUpdateSubquery(tableInfo *schema.Table) *ParsedQuery {
|
|
return GenerateSubquery(
|
|
tableInfo.Indexes[0].Columns,
|
|
node.At(UPDATE_TABLE_OFFSET),
|
|
node.At(UPDATE_WHERE_OFFSET),
|
|
node.At(UPDATE_ORDER_OFFSET),
|
|
node.At(UPDATE_LIMIT_OFFSET),
|
|
true,
|
|
)
|
|
}
|
|
|
|
func (node *Node) GenerateDeleteSubquery(tableInfo *schema.Table) *ParsedQuery {
|
|
return GenerateSubquery(
|
|
tableInfo.Indexes[0].Columns,
|
|
node.At(DELETE_TABLE_OFFSET),
|
|
node.At(DELETE_WHERE_OFFSET),
|
|
node.At(DELETE_ORDER_OFFSET),
|
|
node.At(DELETE_LIMIT_OFFSET),
|
|
true,
|
|
)
|
|
}
|
|
|
|
func (node *Node) PushLimit() {
|
|
node.Push(NewSimpleParseNode(VALUE_ARG, ":_vtMaxResultSize"))
|
|
}
|
|
|
|
func GenerateSubquery(columns []string, table *Node, where *Node, order *Node, limit *Node, for_update bool) *ParsedQuery {
|
|
buf := NewTrackedBuffer(nil)
|
|
if limit.Len() == 0 {
|
|
limit.PushLimit()
|
|
defer limit.Pop()
|
|
}
|
|
fmt.Fprintf(buf, "select ")
|
|
i := 0
|
|
for i = 0; i < len(columns)-1; i++ {
|
|
fmt.Fprintf(buf, "%s, ", columns[i])
|
|
}
|
|
fmt.Fprintf(buf, "%s", columns[i])
|
|
buf.Fprintf(" from %v%v%v%v", table, where, order, limit)
|
|
if for_update {
|
|
buf.Fprintf(" for update")
|
|
}
|
|
return buf.ParsedQuery()
|
|
}
|
|
|
|
func writeColumnList(buf *TrackedBuffer, columns []schema.TableColumn) {
|
|
i := 0
|
|
for i = 0; i < len(columns)-1; i++ {
|
|
fmt.Fprintf(buf, "%s, ", columns[i].Name)
|
|
}
|
|
fmt.Fprintf(buf, "%s", columns[i].Name)
|
|
}
|
|
|
|
func asInterface(node *Node) interface{} {
|
|
switch node.Type {
|
|
case VALUE_ARG:
|
|
return string(node.Value)
|
|
case STRING:
|
|
return sqltypes.MakeString(node.Value)
|
|
case NUMBER:
|
|
n, err := sqltypes.BuildNumeric(string(node.Value))
|
|
if err != nil {
|
|
panic(NewParserError("type mismatch: %s", err))
|
|
}
|
|
return n
|
|
}
|
|
panic(NewParserError("unexpected node %v", node))
|
|
}
|