diff --git a/go/vt/vtgate/engine/insert.go b/go/vt/vtgate/engine/insert.go index d1d4a5763a3..a1babe74f13 100644 --- a/go/vt/vtgate/engine/insert.go +++ b/go/vt/vtgate/engine/insert.go @@ -273,6 +273,22 @@ func (ins *Insert) execInsertSharded(vcursor VCursor, bindVars map[string]*query return result, nil } +// shouldGenerate determines if a sequence value should be generated for a given value +func shouldGenerate(v sqltypes.Value) bool { + if v.IsNull() { + return true + } + + // Unless the NO_AUTO_VALUE_ON_ZERO sql mode is active in mysql, it also + // treats 0 as a value that should generate a new sequence. + n, err := evalengine.ToUint64(v) + if err == nil && n == 0 { + return true + } + + return false +} + // processGenerate generates new values using a sequence if necessary. // If no value was generated, it returns 0. Values are generated only // for cases where none are supplied. @@ -289,7 +305,7 @@ func (ins *Insert) processGenerate(vcursor VCursor, bindVars map[string]*querypb } count := int64(0) for _, val := range resolved { - if val.IsNull() { + if shouldGenerate(val) { count++ } } @@ -319,7 +335,7 @@ func (ins *Insert) processGenerate(vcursor VCursor, bindVars map[string]*querypb // Fill the holes where no value was supplied. cur := insertID for i, v := range resolved { - if v.IsNull() { + if shouldGenerate(v) { bindVars[SeqVarName+strconv.Itoa(i)] = sqltypes.Int64BindVariable(cur) cur++ } else { diff --git a/go/vt/vtgate/engine/insert_test.go b/go/vt/vtgate/engine/insert_test.go index 5923247d0b3..ec59c2a70a1 100644 --- a/go/vt/vtgate/engine/insert_test.go +++ b/go/vt/vtgate/engine/insert_test.go @@ -117,6 +117,61 @@ func TestInsertUnshardedGenerate(t *testing.T) { expectResult(t, "Execute", result, &sqltypes.Result{InsertID: 4}) } +func TestInsertUnshardedGenerate_Zeros(t *testing.T) { + ins := NewQueryInsert( + InsertUnsharded, + &vindexes.Keyspace{ + Name: "ks", + Sharded: false, + }, + "dummy_insert", + ) + ins.Generate = &Generate{ + Keyspace: &vindexes.Keyspace{ + Name: "ks2", + Sharded: false, + }, + Query: "dummy_generate", + Values: sqltypes.PlanValue{ + Values: []sqltypes.PlanValue{ + {Value: sqltypes.NewInt64(1)}, + {Value: sqltypes.NewInt64(0)}, + {Value: sqltypes.NewInt64(2)}, + {Value: sqltypes.NewInt64(0)}, + {Value: sqltypes.NewInt64(3)}, + }, + }, + } + + vc := newDMLTestVCursor("0") + vc.results = []*sqltypes.Result{ + sqltypes.MakeTestResult( + sqltypes.MakeTestFields( + "nextval", + "int64", + ), + "4", + ), + {InsertID: 1}, + } + + result, err := ins.Execute(vc, map[string]*querypb.BindVariable{}, false) + if err != nil { + t.Fatal(err) + } + vc.ExpectLog(t, []string{ + // Fetch two sequence value. + `ResolveDestinations ks2 [] Destinations:DestinationAnyShard()`, + `ExecuteStandalone dummy_generate n: type:INT64 value:"2" ks2 0`, + // Fill those values into the insert. + `ResolveDestinations ks [] Destinations:DestinationAllShards()`, + `ExecuteMultiShard ks.0: dummy_insert {__seq0: type:INT64 value:"1" __seq1: type:INT64 value:"4" __seq2: type:INT64 value:"2" __seq3: type:INT64 value:"5" __seq4: type:INT64 value:"3" } true true`, + }) + + // The insert id returned by ExecuteMultiShard should be overwritten by processGenerate. + expectResult(t, "Execute", result, &sqltypes.Result{InsertID: 4}) +} + func TestInsertShardedSimple(t *testing.T) { invschema := &vschemapb.SrvVSchema{ Keyspaces: map[string]*vschemapb.Keyspace{