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 }