@@ -31,6 +31,7 @@ import (
31
31
"vitess.io/vitess/go/sqltypes"
32
32
"vitess.io/vitess/go/test/endtoend/utils"
33
33
"vitess.io/vitess/go/vt/log"
34
+ "vitess.io/vitess/go/vt/sqlparser"
34
35
)
35
36
36
37
type QueryFormat string
@@ -52,6 +53,7 @@ type fuzzer struct {
52
53
updateShare int
53
54
concurrency int
54
55
queryFormat QueryFormat
56
+ fkState * bool
55
57
56
58
// shouldStop is an internal state variable, that tells the fuzzer
57
59
// whether it should stop or not.
@@ -71,7 +73,7 @@ type debugInfo struct {
71
73
}
72
74
73
75
// newFuzzer creates a new fuzzer struct.
74
- func newFuzzer (concurrency int , maxValForId int , maxValForCol int , insertShare int , deleteShare int , updateShare int , queryFormat QueryFormat ) * fuzzer {
76
+ func newFuzzer (concurrency int , maxValForId int , maxValForCol int , insertShare int , deleteShare int , updateShare int , queryFormat QueryFormat , fkState * bool ) * fuzzer {
75
77
fz := & fuzzer {
76
78
concurrency : concurrency ,
77
79
maxValForId : maxValForId ,
@@ -80,6 +82,7 @@ func newFuzzer(concurrency int, maxValForId int, maxValForCol int, insertShare i
80
82
deleteShare : deleteShare ,
81
83
updateShare : updateShare ,
82
84
queryFormat : queryFormat ,
85
+ fkState : fkState ,
83
86
wg : sync.WaitGroup {},
84
87
}
85
88
// Initially the fuzzer thread is stopped.
@@ -123,25 +126,26 @@ func (fz *fuzzer) generateQuery() []string {
123
126
}
124
127
125
128
func getInsertType () string {
126
- return "insert"
129
+ return [] string { "insert" , "replace" }[ rand . Intn ( 2 )]
127
130
}
128
131
129
132
// generateInsertDMLQuery generates an INSERT query from the parameters for the fuzzer.
130
133
func (fz * fuzzer ) generateInsertDMLQuery (insertType string ) string {
131
134
tableId := rand .Intn (len (fkTables ))
132
135
idValue := 1 + rand .Intn (fz .maxValForId )
133
136
tableName := fkTables [tableId ]
137
+ setVarFkChecksVal := fz .getSetVarFkChecksVal ()
134
138
if tableName == "fk_t20" {
135
139
colValue := rand .Intn (1 + fz .maxValForCol )
136
140
col2Value := rand .Intn (1 + fz .maxValForCol )
137
- return fmt .Sprintf ("%s into %v (id, col, col2) values (%v, %v, %v)" , insertType , tableName , idValue , convertIntValueToString (colValue ), convertIntValueToString (col2Value ))
141
+ return fmt .Sprintf ("%s %vinto %v (id, col, col2) values (%v, %v, %v)" , insertType , setVarFkChecksVal , tableName , idValue , convertIntValueToString (colValue ), convertIntValueToString (col2Value ))
138
142
} else if isMultiColFkTable (tableName ) {
139
143
colaValue := rand .Intn (1 + fz .maxValForCol )
140
144
colbValue := rand .Intn (1 + fz .maxValForCol )
141
- return fmt .Sprintf ("%s into %v (id, cola, colb) values (%v, %v, %v)" , insertType , tableName , idValue , convertIntValueToString (colaValue ), convertIntValueToString (colbValue ))
145
+ return fmt .Sprintf ("%s %vinto %v (id, cola, colb) values (%v, %v, %v)" , insertType , setVarFkChecksVal , tableName , idValue , convertIntValueToString (colaValue ), convertIntValueToString (colbValue ))
142
146
} else {
143
147
colValue := rand .Intn (1 + fz .maxValForCol )
144
- return fmt .Sprintf ("%s into %v (id, col) values (%v, %v)" , insertType , tableName , idValue , convertIntValueToString (colValue ))
148
+ return fmt .Sprintf ("%s %vinto %v (id, col) values (%v, %v)" , insertType , setVarFkChecksVal , tableName , idValue , convertIntValueToString (colValue ))
145
149
}
146
150
}
147
151
@@ -150,10 +154,11 @@ func (fz *fuzzer) generateUpdateDMLQuery() string {
150
154
tableId := rand .Intn (len (fkTables ))
151
155
idValue := 1 + rand .Intn (fz .maxValForId )
152
156
tableName := fkTables [tableId ]
157
+ setVarFkChecksVal := fz .getSetVarFkChecksVal ()
153
158
if tableName == "fk_t20" {
154
159
colValue := convertIntValueToString (rand .Intn (1 + fz .maxValForCol ))
155
160
col2Value := convertIntValueToString (rand .Intn (1 + fz .maxValForCol ))
156
- return fmt .Sprintf ("update %v set col = %v, col2 = %v where id = %v" , tableName , colValue , col2Value , idValue )
161
+ return fmt .Sprintf ("update %v%v set col = %v, col2 = %v where id = %v" , setVarFkChecksVal , tableName , colValue , col2Value , idValue )
157
162
} else if isMultiColFkTable (tableName ) {
158
163
if rand .Intn (2 ) == 0 {
159
164
colaValue := convertIntValueToString (rand .Intn (1 + fz .maxValForCol ))
@@ -162,23 +167,24 @@ func (fz *fuzzer) generateUpdateDMLQuery() string {
162
167
colaValue = fz .generateExpression (rand .Intn (4 )+ 1 , "cola" , "colb" , "id" )
163
168
colbValue = fz .generateExpression (rand .Intn (4 )+ 1 , "cola" , "colb" , "id" )
164
169
}
165
- return fmt .Sprintf ("update %v set cola = %v, colb = %v where id = %v" , tableName , colaValue , colbValue , idValue )
170
+ return fmt .Sprintf ("update %v%v set cola = %v, colb = %v where id = %v" , setVarFkChecksVal , tableName , colaValue , colbValue , idValue )
166
171
} else {
167
172
colValue := fz .generateExpression (rand .Intn (4 )+ 1 , "cola" , "colb" , "id" )
168
173
colToUpdate := []string {"cola" , "colb" }[rand .Intn (2 )]
169
174
return fmt .Sprintf ("update %v set %v = %v where id = %v" , tableName , colToUpdate , colValue , idValue )
170
175
}
171
176
} else {
172
177
colValue := fz .generateExpression (rand .Intn (4 )+ 1 , "col" , "id" )
173
- return fmt .Sprintf ("update %v set col = %v where id = %v" , tableName , colValue , idValue )
178
+ return fmt .Sprintf ("update %v%v set col = %v where id = %v" , setVarFkChecksVal , tableName , colValue , idValue )
174
179
}
175
180
}
176
181
177
182
// generateDeleteDMLQuery generates a DELETE query from the parameters for the fuzzer.
178
183
func (fz * fuzzer ) generateDeleteDMLQuery () string {
179
184
tableId := rand .Intn (len (fkTables ))
180
185
idValue := 1 + rand .Intn (fz .maxValForId )
181
- query := fmt .Sprintf ("delete from %v where id = %v" , fkTables [tableId ], idValue )
186
+ setVarFkChecksVal := fz .getSetVarFkChecksVal ()
187
+ query := fmt .Sprintf ("delete %vfrom %v where id = %v" , setVarFkChecksVal , fkTables [tableId ], idValue )
182
188
return query
183
189
}
184
190
@@ -204,6 +210,9 @@ func (fz *fuzzer) runFuzzerThread(t *testing.T, sharded bool, fuzzerThreadId int
204
210
// Create a MySQL Compare that connects to both Vitess and MySQL and runs the queries against both.
205
211
mcmp , err := utils .NewMySQLCompare (t , vtParams , mysqlParams )
206
212
require .NoError (t , err )
213
+ if fz .fkState != nil {
214
+ mcmp .Exec (fmt .Sprintf ("SET FOREIGN_KEY_CHECKS=%v" , sqlparser .FkChecksStateString (fz .fkState )))
215
+ }
207
216
var vitessDb , mysqlDb * sql.DB
208
217
if fz .queryFormat == PreparedStatementPacket {
209
218
// Open another connection to Vitess using the go-sql-driver so that we can send prepared statements as COM_STMT_PREPARE packets.
@@ -464,6 +473,21 @@ func (fz *fuzzer) generateParameterizedDeleteQuery() (query string, params []any
464
473
return fmt .Sprintf ("delete from %v where id = ?" , fkTables [tableId ]), []any {idValue }
465
474
}
466
475
476
+ // getSetVarFkChecksVal generates an optimizer hint to randomly set the foreign key checks to on or off or leave them unaltered.
477
+ func (fz * fuzzer ) getSetVarFkChecksVal () string {
478
+ if fz .concurrency != 1 {
479
+ return ""
480
+ }
481
+ val := rand .Intn (3 )
482
+ if val == 0 {
483
+ return ""
484
+ }
485
+ if val == 1 {
486
+ return "/*+ SET_VAR(foreign_key_checks=On) */ "
487
+ }
488
+ return "/*+ SET_VAR(foreign_key_checks=Off) */ "
489
+ }
490
+
467
491
// TestFkFuzzTest is a fuzzer test that works by querying the database concurrently.
468
492
// We have a pre-written set of query templates that we will use, but the data in the queries will
469
493
// be randomly generated. The intent is that we hammer the database as a real-world application would
@@ -615,57 +639,65 @@ func TestFkFuzzTest(t *testing.T) {
615
639
updateShare : 50 ,
616
640
}}
617
641
618
- for _ , tt := range testcases {
619
- for _ , testSharded := range []bool {false , true } {
620
- for _ , queryFormat := range []QueryFormat {OlapSQLQueries , SQLQueries , PreparedStatmentQueries , PreparedStatementPacket } {
621
- t .Run (getTestName (tt .name , testSharded )+ fmt .Sprintf (" QueryFormat - %v" , queryFormat ), func (t * testing.T ) {
622
- mcmp , closer := start (t )
623
- defer closer ()
624
- // Set the correct keyspace to use from VtGates.
625
- if testSharded {
626
- t .Skip ("Skip test since we don't have sharded foreign key support yet" )
627
- _ = utils .Exec (t , mcmp .VtConn , "use `ks`" )
628
- } else {
629
- _ = utils .Exec (t , mcmp .VtConn , "use `uks`" )
642
+ valTrue := true
643
+ valFalse := false
644
+ for _ , fkState := range []* bool {nil , & valTrue , & valFalse } {
645
+ for _ , tt := range testcases {
646
+ for _ , testSharded := range []bool {false , true } {
647
+ for _ , queryFormat := range []QueryFormat {OlapSQLQueries , SQLQueries , PreparedStatmentQueries , PreparedStatementPacket } {
648
+ if fkState != nil && (queryFormat != SQLQueries || tt .concurrency != 1 ) {
649
+ continue
630
650
}
631
- // Ensure that the Vitess database is originally empty
632
- ensureDatabaseState (t , mcmp .VtConn , true )
633
- ensureDatabaseState (t , mcmp .MySQLConn , true )
634
-
635
- // Create the fuzzer.
636
- fz := newFuzzer (tt .concurrency , tt .maxValForId , tt .maxValForCol , tt .insertShare , tt .deleteShare , tt .updateShare , queryFormat )
637
-
638
- // Start the fuzzer.
639
- fz .start (t , testSharded )
640
-
641
- // Wait for the timeForTesting so that the threads continue to run.
642
- totalTime := time .After (tt .timeForTesting )
643
- done := false
644
- for ! done {
645
- select {
646
- case <- totalTime :
647
- done = true
648
- case <- time .After (10 * time .Millisecond ):
649
- validateReplication (t )
651
+ t .Run (getTestName (tt .name , testSharded )+ fmt .Sprintf (" FkState - %v QueryFormat - %v" , sqlparser .FkChecksStateString (fkState ), queryFormat ), func (t * testing.T ) {
652
+ mcmp , closer := start (t )
653
+ defer closer ()
654
+ // Set the correct keyspace to use from VtGates.
655
+ if testSharded {
656
+ t .Skip ("Skip test since we don't have sharded foreign key support yet" )
657
+ _ = utils .Exec (t , mcmp .VtConn , "use `ks`" )
658
+ } else {
659
+ _ = utils .Exec (t , mcmp .VtConn , "use `uks`" )
660
+ }
661
+
662
+ // Ensure that the Vitess database is originally empty
663
+ ensureDatabaseState (t , mcmp .VtConn , true )
664
+ ensureDatabaseState (t , mcmp .MySQLConn , true )
665
+
666
+ // Create the fuzzer.
667
+ fz := newFuzzer (tt .concurrency , tt .maxValForId , tt .maxValForCol , tt .insertShare , tt .deleteShare , tt .updateShare , queryFormat , fkState )
668
+
669
+ // Start the fuzzer.
670
+ fz .start (t , testSharded )
671
+
672
+ // Wait for the timeForTesting so that the threads continue to run.
673
+ totalTime := time .After (tt .timeForTesting )
674
+ done := false
675
+ for ! done {
676
+ select {
677
+ case <- totalTime :
678
+ done = true
679
+ case <- time .After (10 * time .Millisecond ):
680
+ validateReplication (t )
681
+ }
650
682
}
651
- }
652
683
653
- fz .stop ()
684
+ fz .stop ()
654
685
655
- // We encountered an error while running the fuzzer. Let's print out the information!
656
- if fz .firstFailureInfo != nil {
657
- log .Errorf ("Failing query - %v" , fz .firstFailureInfo .queryToFail )
658
- for idx , table := range fkTables {
659
- log .Errorf ("MySQL data for %v -\n %v" , table , fz .firstFailureInfo .mysqlState [idx ].Rows )
660
- log .Errorf ("Vitess data for %v -\n %v" , table , fz .firstFailureInfo .vitessState [idx ].Rows )
686
+ // We encountered an error while running the fuzzer. Let's print out the information!
687
+ if fz .firstFailureInfo != nil {
688
+ log .Errorf ("Failing query - %v" , fz .firstFailureInfo .queryToFail )
689
+ for idx , table := range fkTables {
690
+ log .Errorf ("MySQL data for %v -\n %v" , table , fz .firstFailureInfo .mysqlState [idx ].Rows )
691
+ log .Errorf ("Vitess data for %v -\n %v" , table , fz .firstFailureInfo .vitessState [idx ].Rows )
692
+ }
661
693
}
662
- }
663
694
664
- // ensure Vitess database has some data. This ensures not all the commands failed.
665
- ensureDatabaseState (t , mcmp .VtConn , false )
666
- // Verify the consistency of the data.
667
- verifyDataIsCorrect (t , mcmp , tt .concurrency )
668
- })
695
+ // ensure Vitess database has some data. This ensures not all the commands failed.
696
+ ensureDatabaseState (t , mcmp .VtConn , false )
697
+ // Verify the consistency of the data.
698
+ verifyDataIsCorrect (t , mcmp , tt .concurrency )
699
+ })
700
+ }
669
701
}
670
702
}
671
703
}
0 commit comments