vitess.io/vitess@v0.16.2/go/vt/vtgate/planbuilder/insert.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package planbuilder
    18  
    19  import (
    20  	"fmt"
    21  	"strconv"
    22  	"strings"
    23  
    24  	"vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext"
    25  
    26  	"vitess.io/vitess/go/vt/vtgate/evalengine"
    27  	"vitess.io/vitess/go/vt/vtgate/semantics"
    28  
    29  	"vitess.io/vitess/go/vt/sqlparser"
    30  	"vitess.io/vitess/go/vt/vterrors"
    31  	"vitess.io/vitess/go/vt/vtgate/engine"
    32  	"vitess.io/vitess/go/vt/vtgate/vindexes"
    33  )
    34  
    35  // buildInsertPlan builds the route for an INSERT statement.
    36  func buildInsertPlan(stmt sqlparser.Statement, reservedVars *sqlparser.ReservedVars, vschema plancontext.VSchema) (*planResult, error) {
    37  	pb := newStmtAwarePrimitiveBuilder(vschema, newJointab(reservedVars), stmt)
    38  	ins := stmt.(*sqlparser.Insert)
    39  	exprs := sqlparser.TableExprs{&sqlparser.AliasedTableExpr{Expr: ins.Table}}
    40  	rb, err := pb.processDMLTable(exprs, reservedVars, nil)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  	// The table might have been routed to a different one.
    45  	ins.Table = exprs[0].(*sqlparser.AliasedTableExpr).Expr.(sqlparser.TableName)
    46  	if rb.eroute.TargetDestination != nil {
    47  		return nil, vterrors.VT12001("INSERT with a target destination")
    48  	}
    49  
    50  	if len(pb.st.tables) != 1 {
    51  		// Unreachable.
    52  		return nil, vterrors.VT12001("multi-table INSERT statement in a sharded keyspace")
    53  	}
    54  	var vschemaTable *vindexes.Table
    55  	for _, tval := range pb.st.tables {
    56  		// There is only one table.
    57  		vschemaTable = tval.vschemaTable
    58  	}
    59  	if !rb.eroute.Keyspace.Sharded {
    60  		return buildInsertUnshardedPlan(ins, vschemaTable, reservedVars, vschema)
    61  	}
    62  	if ins.Action == sqlparser.ReplaceAct {
    63  		return nil, vterrors.VT12001("REPLACE INTO with sharded keyspace")
    64  	}
    65  	return buildInsertShardedPlan(ins, vschemaTable, reservedVars, vschema)
    66  }
    67  
    68  func buildInsertUnshardedPlan(ins *sqlparser.Insert, table *vindexes.Table, reservedVars *sqlparser.ReservedVars, vschema plancontext.VSchema) (*planResult, error) {
    69  	eins := engine.NewSimpleInsert(
    70  		engine.InsertUnsharded,
    71  		table,
    72  		table.Keyspace,
    73  	)
    74  	applyCommentDirectives(ins, eins)
    75  
    76  	var rows sqlparser.Values
    77  	tc := &tableCollector{}
    78  	tc.addVindexTable(table)
    79  	switch insertValues := ins.Rows.(type) {
    80  	case *sqlparser.Select, *sqlparser.Union:
    81  		if eins.Table.AutoIncrement != nil {
    82  			return nil, vterrors.VT12001("auto-increment and SELECT in INSERT")
    83  		}
    84  		plan, err := subquerySelectPlan(ins, vschema, reservedVars, false)
    85  		if err != nil {
    86  			return nil, err
    87  		}
    88  		tc.addAllTables(plan.tables)
    89  		if route, ok := plan.primitive.(*engine.Route); ok && !route.Keyspace.Sharded && table.Keyspace.Name == route.Keyspace.Name {
    90  			eins.Query = generateQuery(ins)
    91  		} else {
    92  			eins.Input = plan.primitive
    93  			generateInsertSelectQuery(ins, eins)
    94  		}
    95  		return newPlanResult(eins, tc.getTables()...), nil
    96  	case sqlparser.Values:
    97  		rows = insertValues
    98  	default:
    99  		return nil, vterrors.VT13001(fmt.Sprintf("unexpected construct in INSERT: %T", insertValues))
   100  	}
   101  	if eins.Table.AutoIncrement == nil {
   102  		eins.Query = generateQuery(ins)
   103  	} else {
   104  		// Table has auto-inc and has a VALUES clause.
   105  		// If the column list is nil then add all the columns
   106  		// If the column list is empty then add only the auto-inc column and this happens on calling modifyForAutoinc
   107  		if ins.Columns == nil {
   108  			if table.ColumnListAuthoritative {
   109  				populateInsertColumnlist(ins, table)
   110  			} else {
   111  				return nil, vterrors.VT13001("column list required for tables with auto-inc columns")
   112  			}
   113  		}
   114  		for _, row := range rows {
   115  			if len(ins.Columns) != len(row) {
   116  				return nil, vterrors.VT13001("column list does not match values")
   117  			}
   118  		}
   119  		if err := modifyForAutoinc(ins, eins); err != nil {
   120  			return nil, err
   121  		}
   122  		eins.Query = generateQuery(ins)
   123  	}
   124  
   125  	return newPlanResult(eins, tc.getTables()...), nil
   126  }
   127  
   128  func buildInsertShardedPlan(ins *sqlparser.Insert, table *vindexes.Table, reservedVars *sqlparser.ReservedVars, vschema plancontext.VSchema) (*planResult, error) {
   129  	eins := &engine.Insert{
   130  		Table:    table,
   131  		Keyspace: table.Keyspace,
   132  	}
   133  	tc := &tableCollector{}
   134  	tc.addVindexTable(table)
   135  	eins.Ignore = bool(ins.Ignore)
   136  	if ins.OnDup != nil {
   137  		if isVindexChanging(sqlparser.UpdateExprs(ins.OnDup), eins.Table.ColumnVindexes) {
   138  			return nil, vterrors.VT12001("DML cannot update vindex column")
   139  		}
   140  		eins.Ignore = true
   141  	}
   142  	if ins.Columns == nil && table.ColumnListAuthoritative {
   143  		populateInsertColumnlist(ins, table)
   144  	}
   145  
   146  	applyCommentDirectives(ins, eins)
   147  	eins.ColVindexes = getColVindexes(eins.Table.ColumnVindexes)
   148  
   149  	// Till here common plan building done for insert by providing values or select query.
   150  
   151  	rows, isRowValues := ins.Rows.(sqlparser.Values)
   152  	if !isRowValues {
   153  		return buildInsertSelectPlan(ins, table, reservedVars, vschema, eins)
   154  	}
   155  	eins.Opcode = engine.InsertSharded
   156  
   157  	for _, value := range rows {
   158  		if len(ins.Columns) != len(value) {
   159  			return nil, vterrors.VT13001("column list does not match values")
   160  		}
   161  	}
   162  
   163  	if err := modifyForAutoinc(ins, eins); err != nil {
   164  		return nil, err
   165  	}
   166  
   167  	// Fill out the 3-d Values structure. Please see documentation of Insert.Values for details.
   168  	colVindexes := eins.ColVindexes
   169  	routeValues := make([][][]evalengine.Expr, len(colVindexes))
   170  	for vIdx, colVindex := range colVindexes {
   171  		routeValues[vIdx] = make([][]evalengine.Expr, len(colVindex.Columns))
   172  		for colIdx, col := range colVindex.Columns {
   173  			routeValues[vIdx][colIdx] = make([]evalengine.Expr, len(rows))
   174  			colNum := findOrAddColumn(ins, col)
   175  			for rowNum, row := range rows {
   176  				innerpv, err := evalengine.Translate(row[colNum], semantics.EmptySemTable())
   177  				if err != nil {
   178  					return nil, err
   179  				}
   180  				routeValues[vIdx][colIdx][rowNum] = innerpv
   181  			}
   182  		}
   183  	}
   184  	for _, colVindex := range colVindexes {
   185  		for _, col := range colVindex.Columns {
   186  			colNum := findOrAddColumn(ins, col)
   187  			for rowNum, row := range rows {
   188  				name := engine.InsertVarName(col, rowNum)
   189  				row[colNum] = sqlparser.NewArgument(name)
   190  			}
   191  		}
   192  	}
   193  	eins.VindexValues = routeValues
   194  	eins.Query = generateQuery(ins)
   195  	generateInsertShardedQuery(ins, eins, rows)
   196  	return newPlanResult(eins, tc.getTables()...), nil
   197  }
   198  
   199  // buildInsertSelectPlan builds an insert using select plan.
   200  func buildInsertSelectPlan(ins *sqlparser.Insert, table *vindexes.Table, reservedVars *sqlparser.ReservedVars, vschema plancontext.VSchema, eins *engine.Insert) (*planResult, error) {
   201  	eins.Opcode = engine.InsertSelect
   202  	tc := &tableCollector{}
   203  	tc.addVindexTable(table)
   204  
   205  	// check if column list is provided if not, then vschema should be able to provide the column list.
   206  	if len(ins.Columns) == 0 {
   207  		if !table.ColumnListAuthoritative {
   208  			return nil, vterrors.VT09004()
   209  		}
   210  		populateInsertColumnlist(ins, table)
   211  	}
   212  
   213  	// select plan will be taken as input to insert rows into the table.
   214  	plan, err := subquerySelectPlan(ins, vschema, reservedVars, true)
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  	tc.addAllTables(plan.tables)
   219  	eins.Input = plan.primitive
   220  
   221  	// When the table you are steaming data from and table you are inserting from are same.
   222  	// Then due to locking of the index range on the table we might not be able to insert into the table.
   223  	// Therefore, instead of streaming, this flag will ensure the records are first read and then inserted.
   224  	if strings.Contains(plan.primitive.GetTableName(), table.Name.String()) {
   225  		eins.ForceNonStreaming = true
   226  	}
   227  
   228  	// auto-increment column is added explicitly if not provided.
   229  	if err := modifyForAutoinc(ins, eins); err != nil {
   230  		return nil, err
   231  	}
   232  
   233  	// Fill out the 3-d Values structure
   234  	eins.VindexValueOffset, err = extractColVindexOffsets(ins, eins.ColVindexes)
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  
   239  	generateInsertSelectQuery(ins, eins)
   240  	return newPlanResult(eins, tc.getTables()...), nil
   241  }
   242  
   243  func subquerySelectPlan(ins *sqlparser.Insert, vschema plancontext.VSchema, reservedVars *sqlparser.ReservedVars, sharded bool) (*planResult, error) {
   244  	selectStmt, queryPlanner, err := getStatementAndPlanner(ins, vschema)
   245  	if err != nil {
   246  		return nil, err
   247  	}
   248  
   249  	// validate the columns to match on insert and select
   250  	// for sharded insert table only
   251  	if sharded {
   252  		if err := checkColumnCounts(ins, selectStmt); err != nil {
   253  			return nil, err
   254  		}
   255  	}
   256  
   257  	// Override the locking with `for update` to lock the rows for inserting the data.
   258  	selectStmt.SetLock(sqlparser.ForUpdateLock)
   259  
   260  	return queryPlanner(selectStmt, reservedVars, vschema)
   261  }
   262  
   263  func getStatementAndPlanner(
   264  	ins *sqlparser.Insert,
   265  	vschema plancontext.VSchema,
   266  ) (selectStmt sqlparser.SelectStatement, configuredPlanner stmtPlanner, err error) {
   267  	switch stmt := ins.Rows.(type) {
   268  	case *sqlparser.Select:
   269  		configuredPlanner, err = getConfiguredPlanner(vschema, buildSelectPlan, stmt, "")
   270  		selectStmt = stmt
   271  	case *sqlparser.Union:
   272  		configuredPlanner, err = getConfiguredPlanner(vschema, buildUnionPlan, stmt, "")
   273  		selectStmt = stmt
   274  	default:
   275  		err = vterrors.VT12001(fmt.Sprintf("INSERT plan with %T", ins.Rows))
   276  	}
   277  
   278  	if err != nil {
   279  		return nil, nil, err
   280  	}
   281  
   282  	return selectStmt, configuredPlanner, nil
   283  }
   284  
   285  func checkColumnCounts(ins *sqlparser.Insert, selectStmt sqlparser.SelectStatement) error {
   286  	if len(ins.Columns) < selectStmt.GetColumnCount() {
   287  		return vterrors.VT03006()
   288  	}
   289  	if len(ins.Columns) > selectStmt.GetColumnCount() {
   290  		sel := sqlparser.GetFirstSelect(selectStmt)
   291  		var hasStarExpr bool
   292  		for _, sExpr := range sel.SelectExprs {
   293  			if _, hasStarExpr = sExpr.(*sqlparser.StarExpr); hasStarExpr {
   294  				break
   295  			}
   296  		}
   297  		if !hasStarExpr {
   298  			return vterrors.VT03006()
   299  		}
   300  	}
   301  	return nil
   302  }
   303  
   304  func applyCommentDirectives(ins *sqlparser.Insert, eins *engine.Insert) {
   305  	directives := ins.Comments.Directives()
   306  	if directives.IsSet(sqlparser.DirectiveMultiShardAutocommit) {
   307  		eins.MultiShardAutocommit = true
   308  	}
   309  	eins.QueryTimeout = queryTimeout(directives)
   310  }
   311  
   312  func getColVindexes(allColVindexes []*vindexes.ColumnVindex) (colVindexes []*vindexes.ColumnVindex) {
   313  	for _, colVindex := range allColVindexes {
   314  		if colVindex.IsPartialVindex() {
   315  			continue
   316  		}
   317  		colVindexes = append(colVindexes, colVindex)
   318  	}
   319  	return
   320  }
   321  
   322  func extractColVindexOffsets(ins *sqlparser.Insert, colVindexes []*vindexes.ColumnVindex) ([][]int, error) {
   323  	vv := make([][]int, len(colVindexes))
   324  	for idx, colVindex := range colVindexes {
   325  		for _, col := range colVindex.Columns {
   326  			colNum := findColumn(ins, col)
   327  			// sharding column values should be provided in the insert.
   328  			if colNum == -1 && idx == 0 {
   329  				return nil, vterrors.VT09003(col)
   330  			}
   331  			vv[idx] = append(vv[idx], colNum)
   332  		}
   333  	}
   334  	return vv, nil
   335  }
   336  
   337  // findColumn returns the column index where it is placed on the insert column list.
   338  // Otherwise, return -1 when not found.
   339  func findColumn(ins *sqlparser.Insert, col sqlparser.IdentifierCI) int {
   340  	for i, column := range ins.Columns {
   341  		if col.Equal(column) {
   342  			return i
   343  		}
   344  	}
   345  	return -1
   346  }
   347  
   348  func populateInsertColumnlist(ins *sqlparser.Insert, table *vindexes.Table) {
   349  	cols := make(sqlparser.Columns, 0, len(table.Columns))
   350  	for _, c := range table.Columns {
   351  		cols = append(cols, c.Name)
   352  	}
   353  	ins.Columns = cols
   354  }
   355  
   356  func generateInsertShardedQuery(node *sqlparser.Insert, eins *engine.Insert, valueTuples sqlparser.Values) {
   357  	prefixBuf := sqlparser.NewTrackedBuffer(dmlFormatter)
   358  	midBuf := sqlparser.NewTrackedBuffer(dmlFormatter)
   359  	suffixBuf := sqlparser.NewTrackedBuffer(dmlFormatter)
   360  	eins.Mid = make([]string, len(valueTuples))
   361  	prefixBuf.Myprintf("insert %v%sinto %v%v values ",
   362  		node.Comments, node.Ignore.ToString(),
   363  		node.Table, node.Columns)
   364  	eins.Prefix = prefixBuf.String()
   365  	for rowNum, val := range valueTuples {
   366  		midBuf.Myprintf("%v", val)
   367  		eins.Mid[rowNum] = midBuf.String()
   368  		midBuf.Reset()
   369  	}
   370  	suffixBuf.Myprintf("%v", node.OnDup)
   371  	eins.Suffix = suffixBuf.String()
   372  }
   373  
   374  func generateInsertSelectQuery(node *sqlparser.Insert, eins *engine.Insert) {
   375  	prefixBuf := sqlparser.NewTrackedBuffer(dmlFormatter)
   376  	suffixBuf := sqlparser.NewTrackedBuffer(dmlFormatter)
   377  	prefixBuf.Myprintf("insert %v%sinto %v%v ",
   378  		node.Comments, node.Ignore.ToString(),
   379  		node.Table, node.Columns)
   380  	eins.Prefix = prefixBuf.String()
   381  	suffixBuf.Myprintf("%v", node.OnDup)
   382  	eins.Suffix = suffixBuf.String()
   383  }
   384  
   385  // modifyForAutoinc modifies the AST and the plan to generate necessary autoinc values.
   386  // For row values cases, bind variable names are generated using baseName.
   387  func modifyForAutoinc(ins *sqlparser.Insert, eins *engine.Insert) error {
   388  	if eins.Table.AutoIncrement == nil {
   389  		return nil
   390  	}
   391  	colNum := findOrAddColumn(ins, eins.Table.AutoIncrement.Column)
   392  	eins.Generate = &engine.Generate{
   393  		Keyspace: eins.Table.AutoIncrement.Sequence.Keyspace,
   394  		Query:    fmt.Sprintf("select next :n values from %s", sqlparser.String(eins.Table.AutoIncrement.Sequence.Name)),
   395  	}
   396  	switch rows := ins.Rows.(type) {
   397  	case sqlparser.SelectStatement:
   398  		eins.Generate.Offset = colNum
   399  		return nil
   400  	case sqlparser.Values:
   401  		autoIncValues := make([]evalengine.Expr, 0, len(rows))
   402  		for rowNum, row := range rows {
   403  			// Support the DEFAULT keyword by treating it as null
   404  			if _, ok := row[colNum].(*sqlparser.Default); ok {
   405  				row[colNum] = &sqlparser.NullVal{}
   406  			}
   407  
   408  			pv, err := evalengine.Translate(row[colNum], semantics.EmptySemTable())
   409  			if err != nil {
   410  				return err
   411  			}
   412  			autoIncValues = append(autoIncValues, pv)
   413  			row[colNum] = sqlparser.NewArgument(engine.SeqVarName + strconv.Itoa(rowNum))
   414  		}
   415  		eins.Generate.Values = evalengine.NewTupleExpr(autoIncValues...)
   416  		return nil
   417  	}
   418  	return vterrors.VT13001(fmt.Sprintf("unexpected construct in INSERT: %T", ins.Rows))
   419  }
   420  
   421  // findOrAddColumn finds the position of a column in the insert. If it's
   422  // absent it appends it to the with NULL values and returns that position.
   423  func findOrAddColumn(ins *sqlparser.Insert, col sqlparser.IdentifierCI) int {
   424  	colNum := findColumn(ins, col)
   425  	if colNum >= 0 {
   426  		return colNum
   427  	}
   428  	colOffset := len(ins.Columns)
   429  	ins.Columns = append(ins.Columns, col)
   430  	if rows, ok := ins.Rows.(sqlparser.Values); ok {
   431  		for i := range rows {
   432  			rows[i] = append(rows[i], &sqlparser.NullVal{})
   433  		}
   434  	}
   435  	return colOffset
   436  }
   437  
   438  // isVindexChanging returns true if any of the update
   439  // expressions modify a vindex column.
   440  func isVindexChanging(setClauses sqlparser.UpdateExprs, colVindexes []*vindexes.ColumnVindex) bool {
   441  	for _, assignment := range setClauses {
   442  		for _, vcol := range colVindexes {
   443  			for _, col := range vcol.Columns {
   444  				if col.Equal(assignment.Name.Name) {
   445  					valueExpr, isValuesFuncExpr := assignment.Expr.(*sqlparser.ValuesFuncExpr)
   446  					if !isValuesFuncExpr {
   447  						return true
   448  					}
   449  					// update on duplicate key is changing the vindex column, not supported.
   450  					if !valueExpr.Name.Name.Equal(assignment.Name.Name) {
   451  						return true
   452  					}
   453  				}
   454  			}
   455  		}
   456  	}
   457  	return false
   458  }