vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/vstreamer/rowstreamer_test.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 vstreamer
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"regexp"
    23  	"testing"
    24  	"time"
    25  
    26  	"vitess.io/vitess/go/vt/log"
    27  
    28  	"github.com/stretchr/testify/require"
    29  
    30  	"vitess.io/vitess/go/mysql"
    31  	"vitess.io/vitess/go/sqltypes"
    32  
    33  	binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata"
    34  )
    35  
    36  func TestStreamRowsScan(t *testing.T) {
    37  	if testing.Short() {
    38  		t.Skip()
    39  	}
    40  
    41  	execStatements(t, []string{
    42  		// Single PK
    43  		"create table t1(id int, val varbinary(128), primary key(id))",
    44  		"insert into t1 values (1, 'aaa'), (2, 'bbb')",
    45  		// Composite PK
    46  		"create table t2(id1 int, id2 int, val varbinary(128), primary key(id1, id2))",
    47  		"insert into t2 values (1, 2, 'aaa'), (1, 3, 'bbb')",
    48  		// No PK
    49  		"create table t3(id int, val varbinary(128))",
    50  		"insert into t3 values (1, 'aaa'), (2, 'bbb')",
    51  		// Three-column PK
    52  		"create table t4(id1 int, id2 int, id3 int, val varbinary(128), primary key(id1, id2, id3))",
    53  		"insert into t4 values (1, 2, 3, 'aaa'), (2, 3, 4, 'bbb')",
    54  	})
    55  
    56  	defer execStatements(t, []string{
    57  		"drop table t1",
    58  		"drop table t2",
    59  		"drop table t3",
    60  		"drop table t4",
    61  	})
    62  
    63  	engine.se.Reload(context.Background())
    64  
    65  	// t1: simulates rollup
    66  	wantStream := []string{
    67  		`fields:{name:"1" type:INT64} pkfields:{name:"id" type:INT32}`,
    68  		`rows:{lengths:1 values:"1"} rows:{lengths:1 values:"1"} lastpk:{lengths:1 values:"2"}`,
    69  	}
    70  	wantQuery := "select id, val from t1 order by id"
    71  	checkStream(t, "select 1 from t1", nil, wantQuery, wantStream)
    72  
    73  	// t1: simulates rollup, with non-pk column
    74  	wantStream = []string{
    75  		`fields:{name:"1" type:INT64} fields:{name:"val" type:VARBINARY table:"t1" org_table:"t1" database:"vttest" org_name:"val" column_length:128 charset:63} pkfields:{name:"id" type:INT32}`,
    76  		`rows:{lengths:1 lengths:3 values:"1aaa"} rows:{lengths:1 lengths:3 values:"1bbb"} lastpk:{lengths:1 values:"2"}`,
    77  	}
    78  	wantQuery = "select id, val from t1 order by id"
    79  	checkStream(t, "select 1, val from t1", nil, wantQuery, wantStream)
    80  
    81  	// t1: simulates rollup, with pk and non-pk column
    82  	wantStream = []string{
    83  		`fields:{name:"1" type:INT64} fields:{name:"id" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"id" column_length:11 charset:63} fields:{name:"val" type:VARBINARY table:"t1" org_table:"t1" database:"vttest" org_name:"val" column_length:128 charset:63} pkfields:{name:"id" type:INT32}`,
    84  		`rows:{lengths:1 lengths:1 lengths:3 values:"11aaa"} rows:{lengths:1 lengths:1 lengths:3 values:"12bbb"} lastpk:{lengths:1 values:"2"}`,
    85  	}
    86  	wantQuery = "select id, val from t1 order by id"
    87  	checkStream(t, "select 1, id, val from t1", nil, wantQuery, wantStream)
    88  
    89  	// t1: no pk in select list
    90  	wantStream = []string{
    91  		`fields:{name:"val" type:VARBINARY table:"t1" org_table:"t1" database:"vttest" org_name:"val" column_length:128 charset:63} pkfields:{name:"id" type:INT32}`,
    92  		`rows:{lengths:3 values:"aaa"} rows:{lengths:3 values:"bbb"} lastpk:{lengths:1 values:"2"}`,
    93  	}
    94  	wantQuery = "select id, val from t1 order by id"
    95  	checkStream(t, "select val from t1", nil, wantQuery, wantStream)
    96  
    97  	// t1: all rows
    98  	wantStream = []string{
    99  		`fields:{name:"id" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"id" column_length:11 charset:63} fields:{name:"val" type:VARBINARY table:"t1" org_table:"t1" database:"vttest" org_name:"val" column_length:128 charset:63} pkfields:{name:"id" type:INT32}`,
   100  		`rows:{lengths:1 lengths:3 values:"1aaa"} rows:{lengths:1 lengths:3 values:"2bbb"} lastpk:{lengths:1 values:"2"}`,
   101  	}
   102  	wantQuery = "select id, val from t1 order by id"
   103  	checkStream(t, "select * from t1", nil, wantQuery, wantStream)
   104  
   105  	// t1: lastpk=1
   106  	wantStream = []string{
   107  		`fields:{name:"id" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"id" column_length:11 charset:63} fields:{name:"val" type:VARBINARY table:"t1" org_table:"t1" database:"vttest" org_name:"val" column_length:128 charset:63} pkfields:{name:"id" type:INT32}`,
   108  		`rows:{lengths:1 lengths:3 values:"2bbb"} lastpk:{lengths:1 values:"2"}`,
   109  	}
   110  	wantQuery = "select id, val from t1 where (id > 1) order by id"
   111  	checkStream(t, "select * from t1", []sqltypes.Value{sqltypes.NewInt64(1)}, wantQuery, wantStream)
   112  
   113  	// t1: different column ordering
   114  	wantStream = []string{
   115  		`fields:{name:"val" type:VARBINARY table:"t1" org_table:"t1" database:"vttest" org_name:"val" column_length:128 charset:63} fields:{name:"id" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"id" column_length:11 charset:63} pkfields:{name:"id" type:INT32}`,
   116  		`rows:{lengths:3 lengths:1 values:"aaa1"} rows:{lengths:3 lengths:1 values:"bbb2"} lastpk:{lengths:1 values:"2"}`,
   117  	}
   118  	wantQuery = "select id, val from t1 order by id"
   119  	checkStream(t, "select val, id from t1", nil, wantQuery, wantStream)
   120  
   121  	// t2: all rows
   122  	wantStream = []string{
   123  		`fields:{name:"id1" type:INT32 table:"t2" org_table:"t2" database:"vttest" org_name:"id1" column_length:11 charset:63} fields:{name:"id2" type:INT32 table:"t2" org_table:"t2" database:"vttest" org_name:"id2" column_length:11 charset:63} fields:{name:"val" type:VARBINARY table:"t2" org_table:"t2" database:"vttest" org_name:"val" column_length:128 charset:63} pkfields:{name:"id1" type:INT32} pkfields:{name:"id2" type:INT32}`,
   124  		`rows:{lengths:1 lengths:1 lengths:3 values:"12aaa"} rows:{lengths:1 lengths:1 lengths:3 values:"13bbb"} lastpk:{lengths:1 lengths:1 values:"13"}`,
   125  	}
   126  	wantQuery = "select id1, id2, val from t2 order by id1, id2"
   127  	checkStream(t, "select * from t2", nil, wantQuery, wantStream)
   128  
   129  	// t2: lastpk=1,2
   130  	wantStream = []string{
   131  		`fields:{name:"id1" type:INT32 table:"t2" org_table:"t2" database:"vttest" org_name:"id1" column_length:11 charset:63} fields:{name:"id2" type:INT32 table:"t2" org_table:"t2" database:"vttest" org_name:"id2" column_length:11 charset:63} fields:{name:"val" type:VARBINARY table:"t2" org_table:"t2" database:"vttest" org_name:"val" column_length:128 charset:63} pkfields:{name:"id1" type:INT32} pkfields:{name:"id2" type:INT32}`,
   132  		`rows:{lengths:1 lengths:1 lengths:3 values:"13bbb"} lastpk:{lengths:1 lengths:1 values:"13"}`,
   133  	}
   134  	wantQuery = "select id1, id2, val from t2 where (id1 = 1 and id2 > 2) or (id1 > 1) order by id1, id2"
   135  	checkStream(t, "select * from t2", []sqltypes.Value{sqltypes.NewInt64(1), sqltypes.NewInt64(2)}, wantQuery, wantStream)
   136  
   137  	// t3: all rows
   138  	wantStream = []string{
   139  		`fields:{name:"id" type:INT32 table:"t3" org_table:"t3" database:"vttest" org_name:"id" column_length:11 charset:63} fields:{name:"val" type:VARBINARY table:"t3" org_table:"t3" database:"vttest" org_name:"val" column_length:128 charset:63} pkfields:{name:"id" type:INT32} pkfields:{name:"val" type:VARBINARY}`,
   140  		`rows:{lengths:1 lengths:3 values:"1aaa"} rows:{lengths:1 lengths:3 values:"2bbb"} lastpk:{lengths:1 lengths:3 values:"2bbb"}`,
   141  	}
   142  	wantQuery = "select id, val from t3 order by id, val"
   143  	checkStream(t, "select * from t3", nil, wantQuery, wantStream)
   144  
   145  	// t3: lastpk: 1,'aaa'
   146  	wantStream = []string{
   147  		`fields:{name:"id" type:INT32 table:"t3" org_table:"t3" database:"vttest" org_name:"id" column_length:11 charset:63} fields:{name:"val" type:VARBINARY table:"t3" org_table:"t3" database:"vttest" org_name:"val" column_length:128 charset:63} pkfields:{name:"id" type:INT32} pkfields:{name:"val" type:VARBINARY}`,
   148  		`rows:{lengths:1 lengths:3 values:"2bbb"} lastpk:{lengths:1 lengths:3 values:"2bbb"}`,
   149  	}
   150  	wantQuery = "select id, val from t3 where (id = 1 and val > 'aaa') or (id > 1) order by id, val"
   151  	checkStream(t, "select * from t3", []sqltypes.Value{sqltypes.NewInt64(1), sqltypes.NewVarBinary("aaa")}, wantQuery, wantStream)
   152  
   153  	// t4: all rows
   154  	wantStream = []string{
   155  		`fields:{name:"id1" type:INT32 table:"t4" org_table:"t4" database:"vttest" org_name:"id1" column_length:11 charset:63} fields:{name:"id2" type:INT32 table:"t4" org_table:"t4" database:"vttest" org_name:"id2" column_length:11 charset:63} fields:{name:"id3" type:INT32 table:"t4" org_table:"t4" database:"vttest" org_name:"id3" column_length:11 charset:63} fields:{name:"val" type:VARBINARY table:"t4" org_table:"t4" database:"vttest" org_name:"val" column_length:128 charset:63} pkfields:{name:"id1" type:INT32} pkfields:{name:"id2" type:INT32} pkfields:{name:"id3" type:INT32}`,
   156  		`rows:{lengths:1 lengths:1 lengths:1 lengths:3 values:"123aaa"} rows:{lengths:1 lengths:1 lengths:1 lengths:3 values:"234bbb"} lastpk:{lengths:1 lengths:1 lengths:1 values:"234"}`,
   157  	}
   158  	wantQuery = "select id1, id2, id3, val from t4 order by id1, id2, id3"
   159  	checkStream(t, "select * from t4", nil, wantQuery, wantStream)
   160  
   161  	// t4: lastpk: 1,2,3
   162  	wantStream = []string{
   163  		`fields:{name:"id1" type:INT32 table:"t4" org_table:"t4" database:"vttest" org_name:"id1" column_length:11 charset:63} fields:{name:"id2" type:INT32 table:"t4" org_table:"t4" database:"vttest" org_name:"id2" column_length:11 charset:63} fields:{name:"id3" type:INT32 table:"t4" org_table:"t4" database:"vttest" org_name:"id3" column_length:11 charset:63} fields:{name:"val" type:VARBINARY table:"t4" org_table:"t4" database:"vttest" org_name:"val" column_length:128 charset:63} pkfields:{name:"id1" type:INT32} pkfields:{name:"id2" type:INT32} pkfields:{name:"id3" type:INT32}`,
   164  		`rows:{lengths:1 lengths:1 lengths:1 lengths:3 values:"234bbb"} lastpk:{lengths:1 lengths:1 lengths:1 values:"234"}`,
   165  	}
   166  	wantQuery = "select id1, id2, id3, val from t4 where (id1 = 1 and id2 = 2 and id3 > 3) or (id1 = 1 and id2 > 2) or (id1 > 1) order by id1, id2, id3"
   167  	checkStream(t, "select * from t4", []sqltypes.Value{sqltypes.NewInt64(1), sqltypes.NewInt64(2), sqltypes.NewInt64(3)}, wantQuery, wantStream)
   168  
   169  	// t1: test for unsupported integer literal
   170  	wantError := "only the integer literal 1 is supported"
   171  	expectStreamError(t, "select 2 from t1", wantError)
   172  
   173  	// t1: test for unsupported literal type
   174  	wantError = "only integer literals are supported"
   175  	expectStreamError(t, "select 'a' from t1", wantError)
   176  }
   177  
   178  func TestStreamRowsUnicode(t *testing.T) {
   179  	if testing.Short() {
   180  		t.Skip()
   181  	}
   182  
   183  	execStatements(t, []string{
   184  		"create table t1(id int, val varchar(128) COLLATE utf8_unicode_ci, primary key(id))",
   185  	})
   186  
   187  	defer execStatements(t, []string{
   188  		"drop table t1",
   189  	})
   190  
   191  	// Use an engine with latin1 charset.
   192  	savedEngine := engine
   193  	defer func() {
   194  		engine = savedEngine
   195  	}()
   196  	engine = customEngine(t, func(in mysql.ConnParams) mysql.ConnParams {
   197  		in.Charset = "latin1"
   198  		return in
   199  	})
   200  	defer engine.Close()
   201  	engine.se.Reload(context.Background())
   202  	// We need a latin1 connection.
   203  	conn, err := env.Mysqld.GetDbaConnection(context.Background())
   204  	if err != nil {
   205  		t.Fatal(err)
   206  	}
   207  	defer conn.Close()
   208  
   209  	if _, err := conn.ExecuteFetch("set names latin1", 10000, false); err != nil {
   210  		t.Fatal(err)
   211  	}
   212  	// This will get "Mojibaked" into the utf8 column.
   213  	if _, err := conn.ExecuteFetch("insert into t1 values(1, '👍')", 10000, false); err != nil {
   214  		t.Fatal(err)
   215  	}
   216  
   217  	err = engine.StreamRows(context.Background(), "select * from t1", nil, func(rows *binlogdatapb.VStreamRowsResponse) error {
   218  		// Skip fields.
   219  		if len(rows.Rows) == 0 {
   220  			return nil
   221  		}
   222  		got := fmt.Sprintf("%q", rows.Rows[0].Values)
   223  		// We should expect a "Mojibaked" version of the string.
   224  		want := `"1ðŸ‘\u008d"`
   225  		if got != want {
   226  			t.Errorf("rows.Rows[0].Values: %s, want %s", got, want)
   227  		}
   228  		return nil
   229  	})
   230  	require.NoError(t, err)
   231  }
   232  
   233  func TestStreamRowsKeyRange(t *testing.T) {
   234  	if testing.Short() {
   235  		t.Skip()
   236  	}
   237  	engine.se.Reload(context.Background())
   238  
   239  	if err := env.SetVSchema(shardedVSchema); err != nil {
   240  		t.Fatal(err)
   241  	}
   242  	defer env.SetVSchema("{}")
   243  
   244  	execStatements(t, []string{
   245  		"create table t1(id1 int, val varbinary(128), primary key(id1))",
   246  		"insert into t1 values (1, 'aaa'), (6, 'bbb')",
   247  	})
   248  
   249  	defer execStatements(t, []string{
   250  		"drop table t1",
   251  	})
   252  	engine.se.Reload(context.Background())
   253  
   254  	time.Sleep(1 * time.Second)
   255  
   256  	// Only the first row should be returned, but lastpk should be 6.
   257  	wantStream := []string{
   258  		`fields:{name:"id1" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"id1" column_length:11 charset:63} fields:{name:"val" type:VARBINARY table:"t1" org_table:"t1" database:"vttest" org_name:"val" column_length:128 charset:63} pkfields:{name:"id1" type:INT32}`,
   259  		`rows:{lengths:1 lengths:3 values:"1aaa"} lastpk:{lengths:1 values:"6"}`,
   260  	}
   261  	wantQuery := "select id1, val from t1 order by id1"
   262  	checkStream(t, "select * from t1 where in_keyrange('-80')", nil, wantQuery, wantStream)
   263  }
   264  
   265  func TestStreamRowsFilterInt(t *testing.T) {
   266  	if testing.Short() {
   267  		t.Skip()
   268  	}
   269  	engine.rowStreamerNumPackets.Reset()
   270  	engine.rowStreamerNumRows.Reset()
   271  
   272  	if err := env.SetVSchema(shardedVSchema); err != nil {
   273  		t.Fatal(err)
   274  	}
   275  	defer env.SetVSchema("{}")
   276  
   277  	execStatements(t, []string{
   278  		"create table t1(id1 int, id2 int, val varbinary(128), primary key(id1))",
   279  		"insert into t1 values (1, 100, 'aaa'), (2, 200, 'bbb'), (3, 200, 'ccc'), (4, 100, 'ddd'), (5, 200, 'eee')",
   280  	})
   281  
   282  	defer execStatements(t, []string{
   283  		"drop table t1",
   284  	})
   285  	engine.se.Reload(context.Background())
   286  
   287  	time.Sleep(1 * time.Second)
   288  
   289  	wantStream := []string{
   290  		`fields:{name:"id1" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"id1" column_length:11 charset:63} fields:{name:"val" type:VARBINARY table:"t1" org_table:"t1" database:"vttest" org_name:"val" column_length:128 charset:63} pkfields:{name:"id1" type:INT32}`,
   291  		`rows:{lengths:1 lengths:3 values:"1aaa"} rows:{lengths:1 lengths:3 values:"4ddd"} lastpk:{lengths:1 values:"5"}`,
   292  	}
   293  	wantQuery := "select id1, id2, val from t1 order by id1"
   294  	checkStream(t, "select id1, val from t1 where id2 = 100", nil, wantQuery, wantStream)
   295  	require.Equal(t, int64(0), engine.rowStreamerNumPackets.Get())
   296  	require.Equal(t, int64(2), engine.rowStreamerNumRows.Get())
   297  	require.Less(t, int64(0), engine.vstreamerPacketSize.Get())
   298  }
   299  
   300  func TestStreamRowsFilterVarBinary(t *testing.T) {
   301  	if testing.Short() {
   302  		t.Skip()
   303  	}
   304  
   305  	if err := env.SetVSchema(shardedVSchema); err != nil {
   306  		t.Fatal(err)
   307  	}
   308  	defer env.SetVSchema("{}")
   309  
   310  	execStatements(t, []string{
   311  		"create table t1(id1 int, val varbinary(128), primary key(id1))",
   312  		"insert into t1 values (1,'kepler'), (2, 'newton'), (3, 'newton'), (4, 'kepler'), (5, 'newton'), (6, 'kepler')",
   313  	})
   314  
   315  	defer execStatements(t, []string{
   316  		"drop table t1",
   317  	})
   318  	engine.se.Reload(context.Background())
   319  
   320  	time.Sleep(1 * time.Second)
   321  
   322  	wantStream := []string{
   323  		`fields:{name:"id1" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"id1" column_length:11 charset:63} fields:{name:"val" type:VARBINARY table:"t1" org_table:"t1" database:"vttest" org_name:"val" column_length:128 charset:63} pkfields:{name:"id1" type:INT32}`,
   324  		`rows:{lengths:1 lengths:6 values:"2newton"} rows:{lengths:1 lengths:6 values:"3newton"} rows:{lengths:1 lengths:6 values:"5newton"} lastpk:{lengths:1 values:"6"}`,
   325  	}
   326  	wantQuery := "select id1, val from t1 order by id1"
   327  	checkStream(t, "select id1, val from t1 where val = 'newton'", nil, wantQuery, wantStream)
   328  }
   329  
   330  func TestStreamRowsMultiPacket(t *testing.T) {
   331  	if testing.Short() {
   332  		t.Skip()
   333  	}
   334  
   335  	reset := AdjustPacketSize(10)
   336  	defer reset()
   337  
   338  	execStatements(t, []string{
   339  		"create table t1(id int, val varbinary(128), primary key(id))",
   340  		"insert into t1 values (1, '234'), (2, '6789'), (3, '1'), (4, '2345678901'), (5, '2')",
   341  	})
   342  
   343  	defer execStatements(t, []string{
   344  		"drop table t1",
   345  	})
   346  	engine.se.Reload(context.Background())
   347  
   348  	wantStream := []string{
   349  		`fields:{name:"id" type:INT32 table:"t1" org_table:"t1" database:"vttest" org_name:"id" column_length:11 charset:63} fields:{name:"val" type:VARBINARY table:"t1" org_table:"t1" database:"vttest" org_name:"val" column_length:128 charset:63} pkfields:{name:"id" type:INT32}`,
   350  		`rows:{lengths:1 lengths:3 values:"1234"} rows:{lengths:1 lengths:4 values:"26789"} rows:{lengths:1 lengths:1 values:"31"} lastpk:{lengths:1 values:"3"}`,
   351  		`rows:{lengths:1 lengths:10 values:"42345678901"} lastpk:{lengths:1 values:"4"}`,
   352  		`rows:{lengths:1 lengths:1 values:"52"} lastpk:{lengths:1 values:"5"}`,
   353  	}
   354  	wantQuery := "select id, val from t1 order by id"
   355  	checkStream(t, "select * from t1", nil, wantQuery, wantStream)
   356  }
   357  
   358  func TestStreamRowsCancel(t *testing.T) {
   359  	if testing.Short() {
   360  		t.Skip()
   361  	}
   362  
   363  	reset := AdjustPacketSize(10)
   364  	defer reset()
   365  
   366  	execStatements(t, []string{
   367  		"create table t1(id int, val varbinary(128), primary key(id))",
   368  		"insert into t1 values (1, '234567890'), (2, '234')",
   369  	})
   370  
   371  	defer execStatements(t, []string{
   372  		"drop table t1",
   373  	})
   374  	engine.se.Reload(context.Background())
   375  
   376  	ctx, cancel := context.WithCancel(context.Background())
   377  	defer cancel()
   378  
   379  	err := engine.StreamRows(ctx, "select * from t1", nil, func(rows *binlogdatapb.VStreamRowsResponse) error {
   380  		cancel()
   381  		return nil
   382  	})
   383  	if got, want := err.Error(), "stream ended: context canceled"; got != want {
   384  		t.Errorf("err: %v, want %s", err, want)
   385  	}
   386  }
   387  
   388  func checkStream(t *testing.T, query string, lastpk []sqltypes.Value, wantQuery string, wantStream []string) {
   389  	t.Helper()
   390  
   391  	i := 0
   392  	ch := make(chan error)
   393  	// We don't want to report errors inside callback functions because
   394  	// line numbers come out wrong.
   395  	go func() {
   396  		first := true
   397  		defer close(ch)
   398  		err := engine.StreamRows(context.Background(), query, lastpk, func(rows *binlogdatapb.VStreamRowsResponse) error {
   399  			if first {
   400  				if rows.Gtid == "" {
   401  					ch <- fmt.Errorf("stream gtid is empty")
   402  				}
   403  				if got := engine.rowStreamers[engine.streamIdx-1].sendQuery; got != wantQuery {
   404  					log.Infof("Got: %v", got)
   405  					ch <- fmt.Errorf("query sent:\n%v, want\n%v", got, wantQuery)
   406  				}
   407  			}
   408  			first = false
   409  			rows.Gtid = ""
   410  			if i >= len(wantStream) {
   411  				ch <- fmt.Errorf("unexpected stream rows: %v", rows)
   412  				return nil
   413  			}
   414  			srows := fmt.Sprintf("%v", rows)
   415  			re, _ := regexp.Compile(` flags:[\d]+`)
   416  			srows = re.ReplaceAllString(srows, "")
   417  
   418  			if srows != wantStream[i] {
   419  				ch <- fmt.Errorf("stream %d:\n%s, want\n%s", i, srows, wantStream[i])
   420  			}
   421  			i++
   422  			return nil
   423  		})
   424  		if err != nil {
   425  			ch <- err
   426  		}
   427  	}()
   428  	for err := range ch {
   429  		t.Error(err)
   430  	}
   431  }
   432  
   433  func expectStreamError(t *testing.T, query string, want string) {
   434  	t.Helper()
   435  	ch := make(chan error)
   436  	go func() {
   437  		defer close(ch)
   438  		err := engine.StreamRows(context.Background(), query, nil, func(rows *binlogdatapb.VStreamRowsResponse) error {
   439  			return nil
   440  		})
   441  		require.EqualError(t, err, want, "Got incorrect error")
   442  	}()
   443  }