vitess.io/vitess@v0.16.2/go/vt/binlog/binlog_streamer_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 binlog
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/stretchr/testify/require"
    27  	"google.golang.org/protobuf/proto"
    28  
    29  	"context"
    30  
    31  	"vitess.io/vitess/go/mysql"
    32  
    33  	"vitess.io/vitess/go/vt/dbconfigs"
    34  	binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata"
    35  	querypb "vitess.io/vitess/go/vt/proto/query"
    36  )
    37  
    38  // fullBinlogTransaction is a helper type for tests.
    39  type fullBinlogTransaction struct {
    40  	eventToken *querypb.EventToken
    41  	statements []FullBinlogStatement
    42  }
    43  
    44  type binlogStatements []*binlogdatapb.BinlogTransaction
    45  
    46  func (bs *binlogStatements) sendTransaction(eventToken *querypb.EventToken, statements []FullBinlogStatement) error {
    47  	var s []*binlogdatapb.BinlogTransaction_Statement
    48  	if len(statements) > 0 {
    49  		s = make([]*binlogdatapb.BinlogTransaction_Statement, len(statements))
    50  		for i, statement := range statements {
    51  			s[i] = statement.Statement
    52  		}
    53  	}
    54  	*bs = append(*bs, &binlogdatapb.BinlogTransaction{
    55  		Statements: s,
    56  		EventToken: eventToken,
    57  	})
    58  	return nil
    59  }
    60  
    61  func (bs *binlogStatements) equal(bts []*binlogdatapb.BinlogTransaction) bool {
    62  	if len(*bs) != len(bts) {
    63  		return false
    64  	}
    65  	for i, s := range *bs {
    66  		if !proto.Equal(s, bts[i]) {
    67  			return false
    68  		}
    69  	}
    70  	return true
    71  }
    72  
    73  func sendTestEvents(channel chan<- mysql.BinlogEvent, events []mysql.BinlogEvent) {
    74  	for _, ev := range events {
    75  		channel <- ev
    76  	}
    77  	close(channel)
    78  }
    79  
    80  func TestStreamerParseEventsXID(t *testing.T) {
    81  	f := mysql.NewMySQL56BinlogFormat()
    82  	s := mysql.NewFakeBinlogStream()
    83  	s.ServerID = 62344
    84  
    85  	input := []mysql.BinlogEvent{
    86  		mysql.NewRotateEvent(f, s, 0, ""),
    87  		mysql.NewFormatDescriptionEvent(f, s),
    88  		mysql.NewMariaDBGTIDEvent(f, s, mysql.MariadbGTID{Domain: 0, Sequence: 0xd}, false /* hasBegin */),
    89  		mysql.NewQueryEvent(f, s, mysql.Query{
    90  			Database: "vt_test_keyspace",
    91  			SQL:      "BEGIN"}),
    92  		mysql.NewQueryEvent(f, s, mysql.Query{
    93  			Database: "vt_test_keyspace",
    94  			SQL:      "insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */"}),
    95  		mysql.NewXIDEvent(f, s),
    96  	}
    97  
    98  	events := make(chan mysql.BinlogEvent)
    99  	errs := make(chan error)
   100  
   101  	want := []*binlogdatapb.BinlogTransaction{
   102  		{
   103  			Statements: []*binlogdatapb.BinlogTransaction_Statement{
   104  				{Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, Sql: []byte("SET TIMESTAMP=1407805592")},
   105  				{Category: binlogdatapb.BinlogTransaction_Statement_BL_INSERT, Sql: []byte("insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */")},
   106  			},
   107  			EventToken: &querypb.EventToken{
   108  				Timestamp: 1407805592,
   109  				Position: mysql.EncodePosition(mysql.Position{
   110  					GTIDSet: mysql.MariadbGTIDSet{
   111  						0: mysql.MariadbGTID{
   112  							Domain:   0,
   113  							Server:   62344,
   114  							Sequence: 0x0d,
   115  						},
   116  					},
   117  				}),
   118  			},
   119  		},
   120  	}
   121  	var got binlogStatements
   122  
   123  	// Set mock mysql.ConnParams and dbconfig
   124  	mcp := &mysql.ConnParams{
   125  		DbName: "vt_test_keyspace",
   126  	}
   127  	dbcfgs := dbconfigs.New(mcp)
   128  
   129  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, (&got).sendTransaction)
   130  
   131  	go sendTestEvents(events, input)
   132  	_, err := bls.parseEvents(context.Background(), events, errs)
   133  	if err != ErrServerEOF {
   134  		t.Errorf("unexpected error: %v", err)
   135  	}
   136  
   137  	if !got.equal(want) {
   138  		t.Errorf("binlogConnStreamer.parseEvents(): got:\n%v\nwant:\n%v", got, want)
   139  	}
   140  }
   141  
   142  func TestStreamerParseEventsCommit(t *testing.T) {
   143  	f := mysql.NewMySQL56BinlogFormat()
   144  	s := mysql.NewFakeBinlogStream()
   145  	s.ServerID = 62344
   146  
   147  	input := []mysql.BinlogEvent{
   148  		mysql.NewRotateEvent(f, s, 0, ""),
   149  		mysql.NewFormatDescriptionEvent(f, s),
   150  		mysql.NewMariaDBGTIDEvent(f, s, mysql.MariadbGTID{Domain: 0, Sequence: 0xd}, false /* hasBegin */),
   151  		mysql.NewQueryEvent(f, s, mysql.Query{
   152  			Database: "vt_test_keyspace",
   153  			SQL:      "BEGIN"}),
   154  		mysql.NewQueryEvent(f, s, mysql.Query{
   155  			Database: "vt_test_keyspace",
   156  			SQL:      "insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */"}),
   157  		mysql.NewQueryEvent(f, s, mysql.Query{
   158  			Database: "vt_test_keyspace",
   159  			SQL:      "COMMIT"}),
   160  	}
   161  
   162  	events := make(chan mysql.BinlogEvent)
   163  	errs := make(chan error)
   164  
   165  	want := []*binlogdatapb.BinlogTransaction{
   166  		{
   167  			Statements: []*binlogdatapb.BinlogTransaction_Statement{
   168  				{Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, Sql: []byte("SET TIMESTAMP=1407805592")},
   169  				{Category: binlogdatapb.BinlogTransaction_Statement_BL_INSERT, Sql: []byte("insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */")},
   170  			},
   171  			EventToken: &querypb.EventToken{
   172  				Timestamp: 1407805592,
   173  				Position: mysql.EncodePosition(mysql.Position{
   174  					GTIDSet: mysql.MariadbGTIDSet{
   175  						0: mysql.MariadbGTID{
   176  							Domain:   0,
   177  							Server:   62344,
   178  							Sequence: 0x0d,
   179  						},
   180  					},
   181  				}),
   182  			},
   183  		},
   184  	}
   185  	// Set mock mysql.ConnParams and dbconfig
   186  	mcp := &mysql.ConnParams{
   187  		DbName: "vt_test_keyspace",
   188  	}
   189  	dbcfgs := dbconfigs.New(mcp)
   190  
   191  	var got binlogStatements
   192  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, (&got).sendTransaction)
   193  
   194  	go sendTestEvents(events, input)
   195  	_, err := bls.parseEvents(context.Background(), events, errs)
   196  	if err != ErrServerEOF {
   197  		t.Errorf("unexpected error: %v", err)
   198  	}
   199  
   200  	if !got.equal(want) {
   201  		t.Errorf("binlogConnStreamer.parseEvents(): got %v, want %v", got, want)
   202  	}
   203  }
   204  
   205  func TestStreamerStop(t *testing.T) {
   206  	events := make(chan mysql.BinlogEvent)
   207  	errs := make(chan error)
   208  
   209  	sendTransaction := func(eventToken *querypb.EventToken, statements []FullBinlogStatement) error {
   210  		return nil
   211  	}
   212  
   213  	// Set mock mysql.ConnParams and dbconfig
   214  	mcp := &mysql.ConnParams{
   215  		DbName: "vt_test_keyspace",
   216  	}
   217  	dbcfgs := dbconfigs.New(mcp)
   218  
   219  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, sendTransaction)
   220  
   221  	// Start parseEvents(), but don't send it anything, so it just waits.
   222  	ctx, cancel := context.WithCancel(context.Background())
   223  	done := make(chan error)
   224  	go func() {
   225  		_, err := bls.parseEvents(ctx, events, errs)
   226  		done <- err
   227  	}()
   228  
   229  	// close the context, expect the parser to return
   230  	cancel()
   231  
   232  	select {
   233  	case err := <-done:
   234  		if err != context.Canceled {
   235  			t.Errorf("wrong context interruption returned value: %v", err)
   236  		}
   237  	case <-time.After(1 * time.Second):
   238  		t.Errorf("timed out waiting for binlogConnStreamer.Stop()")
   239  	}
   240  }
   241  
   242  func TestStreamerParseEventsClientEOF(t *testing.T) {
   243  	f := mysql.NewMySQL56BinlogFormat()
   244  	s := mysql.NewFakeBinlogStream()
   245  
   246  	input := []mysql.BinlogEvent{
   247  		mysql.NewRotateEvent(f, s, 0, ""),
   248  		mysql.NewFormatDescriptionEvent(f, s),
   249  		mysql.NewQueryEvent(f, s, mysql.Query{
   250  			Database: "vt_test_keyspace",
   251  			SQL:      "BEGIN"}),
   252  		mysql.NewQueryEvent(f, s, mysql.Query{
   253  			Database: "vt_test_keyspace",
   254  			SQL:      "insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */"}),
   255  		mysql.NewXIDEvent(f, s),
   256  	}
   257  	want := ErrClientEOF
   258  
   259  	events := make(chan mysql.BinlogEvent)
   260  	errs := make(chan error)
   261  
   262  	sendTransaction := func(eventToken *querypb.EventToken, statements []FullBinlogStatement) error {
   263  		return io.EOF
   264  	}
   265  
   266  	// Set mock mysql.ConnParams and dbconfig
   267  	mcp := &mysql.ConnParams{
   268  		DbName: "vt_test_keyspace",
   269  	}
   270  	dbcfgs := dbconfigs.New(mcp)
   271  
   272  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, sendTransaction)
   273  
   274  	go sendTestEvents(events, input)
   275  	_, err := bls.parseEvents(context.Background(), events, errs)
   276  	if err != want {
   277  		t.Errorf("wrong error, got %#v, want %#v", err, want)
   278  	}
   279  }
   280  
   281  func TestStreamerParseEventsServerEOF(t *testing.T) {
   282  	want := ErrServerEOF
   283  
   284  	events := make(chan mysql.BinlogEvent)
   285  	errs := make(chan error)
   286  	close(events)
   287  
   288  	sendTransaction := func(eventToken *querypb.EventToken, statements []FullBinlogStatement) error {
   289  		return nil
   290  	}
   291  	// Set mock mysql.ConnParams and dbconfig
   292  	mcp := &mysql.ConnParams{
   293  		DbName: "vt_test_keyspace",
   294  	}
   295  	dbcfgs := dbconfigs.New(mcp)
   296  
   297  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, sendTransaction)
   298  	_, err := bls.parseEvents(context.Background(), events, errs)
   299  	if err != want {
   300  		t.Errorf("wrong error, got %#v, want %#v", err, want)
   301  	}
   302  }
   303  
   304  // TestStreamerParseEventsGTIDPurged tests binlog streamer error
   305  // propagation generally, as well as testing specifically for
   306  // the error seen when the client needs GTIDs that have been
   307  // purged on the source.
   308  func TestStreamerParseEventsGTIDPurged(t *testing.T) {
   309  	events := make(chan mysql.BinlogEvent)
   310  	errs := make(chan error)
   311  	expectedStreamErr := mysql.NewSQLError(mysql.ERMasterFatalReadingBinlog, mysql.SSUnknownSQLState,
   312  		"Cannot replicate because the master purged required binary logs.")
   313  
   314  	sendTransaction := func(eventToken *querypb.EventToken, statements []FullBinlogStatement) error {
   315  		return nil
   316  	}
   317  	// Set mock mysql.ConnParams and dbconfig
   318  	mcp := &mysql.ConnParams{
   319  		DbName: "vt_test_keyspace",
   320  	}
   321  	dbcfgs := dbconfigs.New(mcp)
   322  
   323  	go func() {
   324  		tmr := time.NewTimer(10 * time.Second)
   325  		defer tmr.Stop()
   326  		select {
   327  		case errs <- expectedStreamErr:
   328  		case <-tmr.C:
   329  			require.FailNow(t, "timed out sending error message")
   330  		}
   331  	}()
   332  
   333  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, sendTransaction)
   334  	_, err := bls.parseEvents(context.Background(), events, errs)
   335  	require.Error(t, err)
   336  	sqlErr, ok := err.(*mysql.SQLError)
   337  	require.True(t, ok, "expected SQLError, got %T", err)
   338  	require.True(t, sqlErr.Num == mysql.ERMasterFatalReadingBinlog, "expected ERMasterFatalReadingBinlog (%d), got %d",
   339  		mysql.ERMasterFatalReadingBinlog, sqlErr.Num)
   340  }
   341  
   342  func TestStreamerParseEventsSendErrorXID(t *testing.T) {
   343  	f := mysql.NewMySQL56BinlogFormat()
   344  	s := mysql.NewFakeBinlogStream()
   345  
   346  	input := []mysql.BinlogEvent{
   347  		mysql.NewRotateEvent(f, s, 0, ""),
   348  		mysql.NewFormatDescriptionEvent(f, s),
   349  		mysql.NewQueryEvent(f, s, mysql.Query{
   350  			Database: "vt_test_keyspace",
   351  			SQL:      "BEGIN"}),
   352  		mysql.NewQueryEvent(f, s, mysql.Query{
   353  			Database: "vt_test_keyspace",
   354  			SQL:      "insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */"}),
   355  		mysql.NewXIDEvent(f, s),
   356  	}
   357  	want := "send reply error: foobar"
   358  
   359  	events := make(chan mysql.BinlogEvent)
   360  	errs := make(chan error)
   361  
   362  	sendTransaction := func(eventToken *querypb.EventToken, statements []FullBinlogStatement) error {
   363  		return fmt.Errorf("foobar")
   364  	}
   365  
   366  	// Set mock mysql.ConnParams and dbconfig
   367  	mcp := &mysql.ConnParams{
   368  		DbName: "vt_test_keyspace",
   369  	}
   370  	dbcfgs := dbconfigs.New(mcp)
   371  
   372  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, sendTransaction)
   373  
   374  	go sendTestEvents(events, input)
   375  
   376  	_, err := bls.parseEvents(context.Background(), events, errs)
   377  	if err == nil {
   378  		t.Errorf("expected error, got none")
   379  		return
   380  	}
   381  	if got := err.Error(); got != want {
   382  		t.Errorf("wrong error, got %#v, want %#v", got, want)
   383  	}
   384  }
   385  
   386  func TestStreamerParseEventsSendErrorCommit(t *testing.T) {
   387  	f := mysql.NewMySQL56BinlogFormat()
   388  	s := mysql.NewFakeBinlogStream()
   389  
   390  	input := []mysql.BinlogEvent{
   391  		mysql.NewRotateEvent(f, s, 0, ""),
   392  		mysql.NewFormatDescriptionEvent(f, s),
   393  		mysql.NewQueryEvent(f, s, mysql.Query{
   394  			Database: "vt_test_keyspace",
   395  			SQL:      "BEGIN"}),
   396  		mysql.NewQueryEvent(f, s, mysql.Query{
   397  			Database: "vt_test_keyspace",
   398  			SQL:      "insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */"}),
   399  		mysql.NewQueryEvent(f, s, mysql.Query{
   400  			Database: "vt_test_keyspace",
   401  			SQL:      "COMMIT"}),
   402  	}
   403  	want := "send reply error: foobar"
   404  
   405  	events := make(chan mysql.BinlogEvent)
   406  	errs := make(chan error)
   407  
   408  	sendTransaction := func(eventToken *querypb.EventToken, statements []FullBinlogStatement) error {
   409  		return fmt.Errorf("foobar")
   410  	}
   411  
   412  	// Set mock mysql.ConnParams and dbconfig
   413  	mcp := &mysql.ConnParams{
   414  		DbName: "vt_test_keyspace",
   415  	}
   416  	dbcfgs := dbconfigs.New(mcp)
   417  
   418  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, sendTransaction)
   419  
   420  	go sendTestEvents(events, input)
   421  	_, err := bls.parseEvents(context.Background(), events, errs)
   422  	if err == nil {
   423  		t.Errorf("expected error, got none")
   424  		return
   425  	}
   426  	if got := err.Error(); got != want {
   427  		t.Errorf("wrong error, got %#v, want %#v", got, want)
   428  	}
   429  }
   430  
   431  func TestStreamerParseEventsInvalid(t *testing.T) {
   432  	f := mysql.NewMySQL56BinlogFormat()
   433  	s := mysql.NewFakeBinlogStream()
   434  
   435  	input := []mysql.BinlogEvent{
   436  		mysql.NewRotateEvent(f, s, 0, ""),
   437  		mysql.NewFormatDescriptionEvent(f, s),
   438  		mysql.NewQueryEvent(f, s, mysql.Query{
   439  			Database: "vt_test_keyspace",
   440  			SQL:      "BEGIN"}),
   441  		mysql.NewInvalidEvent(),
   442  		mysql.NewXIDEvent(f, s),
   443  	}
   444  	want := "can't parse binlog event, invalid data:"
   445  
   446  	events := make(chan mysql.BinlogEvent)
   447  	errs := make(chan error)
   448  
   449  	sendTransaction := func(eventToken *querypb.EventToken, statements []FullBinlogStatement) error {
   450  		return nil
   451  	}
   452  
   453  	// Set mock mysql.ConnParams and dbconfig
   454  	mcp := &mysql.ConnParams{
   455  		DbName: "vt_test_keyspace",
   456  	}
   457  	dbcfgs := dbconfigs.New(mcp)
   458  
   459  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, sendTransaction)
   460  
   461  	go sendTestEvents(events, input)
   462  	_, err := bls.parseEvents(context.Background(), events, errs)
   463  	if err == nil {
   464  		t.Errorf("expected error, got none")
   465  		return
   466  	}
   467  	if got := err.Error(); !strings.HasPrefix(got, want) {
   468  		t.Errorf("wrong error, got %#v, want %#v", got, want)
   469  	}
   470  }
   471  
   472  func TestStreamerParseEventsInvalidFormat(t *testing.T) {
   473  	f := mysql.NewMySQL56BinlogFormat()
   474  	s := mysql.NewFakeBinlogStream()
   475  
   476  	input := []mysql.BinlogEvent{
   477  		mysql.NewRotateEvent(f, s, 0, ""),
   478  		mysql.NewInvalidFormatDescriptionEvent(f, s),
   479  		mysql.NewQueryEvent(f, s, mysql.Query{
   480  			Database: "vt_test_keyspace",
   481  			SQL:      "BEGIN"}),
   482  		mysql.NewQueryEvent(f, s, mysql.Query{
   483  			Database: "vt_test_keyspace",
   484  			SQL:      "insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */"}),
   485  		mysql.NewXIDEvent(f, s),
   486  	}
   487  	want := "can't parse FORMAT_DESCRIPTION_EVENT:"
   488  
   489  	events := make(chan mysql.BinlogEvent)
   490  	errs := make(chan error)
   491  
   492  	sendTransaction := func(eventToken *querypb.EventToken, statements []FullBinlogStatement) error {
   493  		return nil
   494  	}
   495  
   496  	// Set mock mysql.ConnParams and dbconfig
   497  	mcp := &mysql.ConnParams{
   498  		DbName: "vt_test_keyspace",
   499  	}
   500  	dbcfgs := dbconfigs.New(mcp)
   501  
   502  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, sendTransaction)
   503  
   504  	go sendTestEvents(events, input)
   505  	_, err := bls.parseEvents(context.Background(), events, errs)
   506  	if err == nil {
   507  		t.Errorf("expected error, got none")
   508  		return
   509  	}
   510  	if got := err.Error(); !strings.HasPrefix(got, want) {
   511  		t.Errorf("wrong error, got %#v, want %#v", got, want)
   512  	}
   513  }
   514  
   515  func TestStreamerParseEventsNoFormat(t *testing.T) {
   516  	f := mysql.NewMySQL56BinlogFormat()
   517  	s := mysql.NewFakeBinlogStream()
   518  
   519  	input := []mysql.BinlogEvent{
   520  		mysql.NewRotateEvent(f, s, 0, ""),
   521  		//mysql.NewFormatDescriptionEvent(f, s),
   522  		mysql.NewQueryEvent(f, s, mysql.Query{
   523  			Database: "vt_test_keyspace",
   524  			SQL:      "BEGIN"}),
   525  		mysql.NewQueryEvent(f, s, mysql.Query{
   526  			Database: "vt_test_keyspace",
   527  			SQL:      "insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */"}),
   528  		mysql.NewXIDEvent(f, s),
   529  	}
   530  	want := "got a real event before FORMAT_DESCRIPTION_EVENT:"
   531  
   532  	events := make(chan mysql.BinlogEvent)
   533  	errs := make(chan error)
   534  
   535  	sendTransaction := func(eventToken *querypb.EventToken, statements []FullBinlogStatement) error {
   536  		return nil
   537  	}
   538  
   539  	// Set mock mysql.ConnParams and dbconfig
   540  	mcp := &mysql.ConnParams{
   541  		DbName: "vt_test_keyspace",
   542  	}
   543  	dbcfgs := dbconfigs.New(mcp)
   544  
   545  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, sendTransaction)
   546  
   547  	go sendTestEvents(events, input)
   548  	_, err := bls.parseEvents(context.Background(), events, errs)
   549  	if err == nil {
   550  		t.Errorf("expected error, got none")
   551  		return
   552  	}
   553  	if got := err.Error(); !strings.HasPrefix(got, want) {
   554  		t.Errorf("wrong error, got %#v, want %#v", got, want)
   555  	}
   556  }
   557  
   558  func TestStreamerParseEventsInvalidQuery(t *testing.T) {
   559  	f := mysql.NewMySQL56BinlogFormat()
   560  	s := mysql.NewFakeBinlogStream()
   561  
   562  	input := []mysql.BinlogEvent{
   563  		mysql.NewRotateEvent(f, s, 0, ""),
   564  		mysql.NewFormatDescriptionEvent(f, s),
   565  		mysql.NewQueryEvent(f, s, mysql.Query{
   566  			Database: "vt_test_keyspace",
   567  			SQL:      "BEGIN"}),
   568  		mysql.NewInvalidQueryEvent(f, s),
   569  		mysql.NewXIDEvent(f, s),
   570  	}
   571  	want := "can't get query from binlog event:"
   572  
   573  	events := make(chan mysql.BinlogEvent)
   574  	errs := make(chan error)
   575  
   576  	sendTransaction := func(eventToken *querypb.EventToken, statements []FullBinlogStatement) error {
   577  		return nil
   578  	}
   579  
   580  	// Set mock mysql.ConnParams and dbconfig
   581  	mcp := &mysql.ConnParams{
   582  		DbName: "vt_test_keyspace",
   583  	}
   584  	dbcfgs := dbconfigs.New(mcp)
   585  
   586  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, sendTransaction)
   587  
   588  	go sendTestEvents(events, input)
   589  	_, err := bls.parseEvents(context.Background(), events, errs)
   590  	if err == nil {
   591  		t.Errorf("expected error, got none")
   592  		return
   593  	}
   594  	if got := err.Error(); !strings.HasPrefix(got, want) {
   595  		t.Errorf("wrong error, got %#v, want %#v", got, want)
   596  	}
   597  }
   598  
   599  func TestStreamerParseEventsRollback(t *testing.T) {
   600  	f := mysql.NewMySQL56BinlogFormat()
   601  	s := mysql.NewFakeBinlogStream()
   602  	s.ServerID = 62344
   603  
   604  	input := []mysql.BinlogEvent{
   605  		mysql.NewRotateEvent(f, s, 0, ""),
   606  		mysql.NewFormatDescriptionEvent(f, s),
   607  		mysql.NewMariaDBGTIDEvent(f, s, mysql.MariadbGTID{Domain: 0, Sequence: 0xd}, false /* hasBegin */),
   608  		mysql.NewQueryEvent(f, s, mysql.Query{
   609  			Database: "vt_test_keyspace",
   610  			SQL:      "BEGIN"}),
   611  		mysql.NewQueryEvent(f, s, mysql.Query{
   612  			Database: "vt_test_keyspace",
   613  			SQL:      "insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */"}),
   614  		mysql.NewQueryEvent(f, s, mysql.Query{
   615  			Database: "vt_test_keyspace",
   616  			SQL:      "insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */"}),
   617  		mysql.NewQueryEvent(f, s, mysql.Query{
   618  			Database: "vt_test_keyspace",
   619  			SQL:      "ROLLBACK"}),
   620  		mysql.NewQueryEvent(f, s, mysql.Query{
   621  			Database: "vt_test_keyspace",
   622  			SQL:      "BEGIN"}),
   623  		mysql.NewQueryEvent(f, s, mysql.Query{
   624  			Database: "vt_test_keyspace",
   625  			SQL:      "insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */"}),
   626  		mysql.NewXIDEvent(f, s),
   627  	}
   628  
   629  	events := make(chan mysql.BinlogEvent)
   630  	errs := make(chan error)
   631  
   632  	want := []*binlogdatapb.BinlogTransaction{
   633  		{
   634  			Statements: nil,
   635  			EventToken: &querypb.EventToken{
   636  				Timestamp: 1407805592,
   637  				Position: mysql.EncodePosition(mysql.Position{
   638  					GTIDSet: mysql.MariadbGTIDSet{
   639  						0: mysql.MariadbGTID{
   640  							Domain:   0,
   641  							Server:   62344,
   642  							Sequence: 0x0d,
   643  						},
   644  					},
   645  				}),
   646  			},
   647  		},
   648  		{
   649  			Statements: []*binlogdatapb.BinlogTransaction_Statement{
   650  				{Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, Sql: []byte("SET TIMESTAMP=1407805592")},
   651  				{Category: binlogdatapb.BinlogTransaction_Statement_BL_INSERT, Sql: []byte("insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */")},
   652  			},
   653  			EventToken: &querypb.EventToken{
   654  				Timestamp: 1407805592,
   655  				Position: mysql.EncodePosition(mysql.Position{
   656  					GTIDSet: mysql.MariadbGTIDSet{
   657  						0: mysql.MariadbGTID{
   658  							Domain:   0,
   659  							Server:   62344,
   660  							Sequence: 0x0d,
   661  						},
   662  					},
   663  				}),
   664  			},
   665  		},
   666  	}
   667  	var got binlogStatements
   668  	// Set mock mysql.ConnParams and dbconfig
   669  	mcp := &mysql.ConnParams{
   670  		DbName: "vt_test_keyspace",
   671  	}
   672  	dbcfgs := dbconfigs.New(mcp)
   673  
   674  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, (&got).sendTransaction)
   675  
   676  	go sendTestEvents(events, input)
   677  	if _, err := bls.parseEvents(context.Background(), events, errs); err != ErrServerEOF {
   678  		t.Errorf("unexpected error: %v", err)
   679  	}
   680  
   681  	if !got.equal(want) {
   682  		t.Errorf("binlogConnStreamer.parseEvents(): got:\n%v\nwant:\n%v", got, want)
   683  	}
   684  }
   685  
   686  func TestStreamerParseEventsDMLWithoutBegin(t *testing.T) {
   687  	f := mysql.NewMySQL56BinlogFormat()
   688  	s := mysql.NewFakeBinlogStream()
   689  	s.ServerID = 62344
   690  
   691  	input := []mysql.BinlogEvent{
   692  		mysql.NewRotateEvent(f, s, 0, ""),
   693  		mysql.NewFormatDescriptionEvent(f, s),
   694  		mysql.NewMariaDBGTIDEvent(f, s, mysql.MariadbGTID{Domain: 0, Sequence: 0xd}, false /* hasBegin */),
   695  		mysql.NewQueryEvent(f, s, mysql.Query{
   696  			Database: "vt_test_keyspace",
   697  			SQL:      "insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */"}),
   698  		mysql.NewXIDEvent(f, s),
   699  	}
   700  
   701  	events := make(chan mysql.BinlogEvent)
   702  	errs := make(chan error)
   703  
   704  	want := []*binlogdatapb.BinlogTransaction{
   705  		{
   706  			Statements: []*binlogdatapb.BinlogTransaction_Statement{
   707  				{Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, Sql: []byte("SET TIMESTAMP=1407805592")},
   708  				{Category: binlogdatapb.BinlogTransaction_Statement_BL_INSERT, Sql: []byte("insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */")},
   709  			},
   710  			EventToken: &querypb.EventToken{
   711  				Timestamp: 1407805592,
   712  				Position: mysql.EncodePosition(mysql.Position{
   713  					GTIDSet: mysql.MariadbGTIDSet{
   714  						0: mysql.MariadbGTID{
   715  							Domain:   0,
   716  							Server:   62344,
   717  							Sequence: 0x0d,
   718  						},
   719  					},
   720  				}),
   721  			},
   722  		},
   723  		{
   724  			Statements: nil,
   725  			EventToken: &querypb.EventToken{
   726  				Timestamp: 1407805592,
   727  				Position: mysql.EncodePosition(mysql.Position{
   728  					GTIDSet: mysql.MariadbGTIDSet{
   729  						0: mysql.MariadbGTID{
   730  							Domain:   0,
   731  							Server:   62344,
   732  							Sequence: 0x0d,
   733  						},
   734  					},
   735  				}),
   736  			},
   737  		},
   738  	}
   739  	var got binlogStatements
   740  
   741  	// Set mock mysql.ConnParams and dbconfig
   742  	mcp := &mysql.ConnParams{
   743  		DbName: "vt_test_keyspace",
   744  	}
   745  	dbcfgs := dbconfigs.New(mcp)
   746  
   747  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, (&got).sendTransaction)
   748  
   749  	go sendTestEvents(events, input)
   750  	if _, err := bls.parseEvents(context.Background(), events, errs); err != ErrServerEOF {
   751  		t.Errorf("unexpected error: %v", err)
   752  	}
   753  
   754  	if !got.equal(want) {
   755  		t.Errorf("binlogConnStreamer.parseEvents(): got:\n%v\nwant:\n%v", got, want)
   756  	}
   757  }
   758  
   759  func TestStreamerParseEventsBeginWithoutCommit(t *testing.T) {
   760  	f := mysql.NewMySQL56BinlogFormat()
   761  	s := mysql.NewFakeBinlogStream()
   762  	s.ServerID = 62344
   763  
   764  	input := []mysql.BinlogEvent{
   765  		mysql.NewRotateEvent(f, s, 0, ""),
   766  		mysql.NewFormatDescriptionEvent(f, s),
   767  		mysql.NewMariaDBGTIDEvent(f, s, mysql.MariadbGTID{Domain: 0, Sequence: 0xd}, false /* hasBegin */),
   768  		mysql.NewQueryEvent(f, s, mysql.Query{
   769  			Database: "vt_test_keyspace",
   770  			SQL:      "insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */"}),
   771  		mysql.NewQueryEvent(f, s, mysql.Query{
   772  			Database: "vt_test_keyspace",
   773  			SQL:      "BEGIN"}),
   774  		mysql.NewXIDEvent(f, s),
   775  	}
   776  
   777  	events := make(chan mysql.BinlogEvent)
   778  	errs := make(chan error)
   779  
   780  	want := []*binlogdatapb.BinlogTransaction{
   781  		{
   782  			Statements: []*binlogdatapb.BinlogTransaction_Statement{
   783  				{Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, Sql: []byte("SET TIMESTAMP=1407805592")},
   784  				{Category: binlogdatapb.BinlogTransaction_Statement_BL_INSERT, Sql: []byte("insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */")},
   785  			},
   786  			EventToken: &querypb.EventToken{
   787  				Timestamp: 1407805592,
   788  				Position: mysql.EncodePosition(mysql.Position{
   789  					GTIDSet: mysql.MariadbGTIDSet{
   790  						0: mysql.MariadbGTID{
   791  							Domain:   0,
   792  							Server:   62344,
   793  							Sequence: 0x0d,
   794  						},
   795  					},
   796  				}),
   797  			},
   798  		},
   799  		{
   800  			Statements: nil,
   801  			EventToken: &querypb.EventToken{
   802  				Timestamp: 1407805592,
   803  				Position: mysql.EncodePosition(mysql.Position{
   804  					GTIDSet: mysql.MariadbGTIDSet{
   805  						0: mysql.MariadbGTID{
   806  							Domain:   0,
   807  							Server:   62344,
   808  							Sequence: 0x0d,
   809  						},
   810  					},
   811  				}),
   812  			},
   813  		},
   814  	}
   815  	var got binlogStatements
   816  
   817  	// Set mock mysql.ConnParams and dbconfig
   818  	mcp := &mysql.ConnParams{
   819  		DbName: "vt_test_keyspace",
   820  	}
   821  	dbcfgs := dbconfigs.New(mcp)
   822  
   823  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, (&got).sendTransaction)
   824  
   825  	go sendTestEvents(events, input)
   826  	if _, err := bls.parseEvents(context.Background(), events, errs); err != ErrServerEOF {
   827  		t.Errorf("unexpected error: %v", err)
   828  	}
   829  
   830  	if !got.equal(want) {
   831  		t.Errorf("binlogConnStreamer.parseEvents(): got:\n%v\nwant:\n%v", got, want)
   832  	}
   833  }
   834  
   835  func TestStreamerParseEventsSetInsertID(t *testing.T) {
   836  	f := mysql.NewMySQL56BinlogFormat()
   837  	s := mysql.NewFakeBinlogStream()
   838  	s.ServerID = 62344
   839  
   840  	input := []mysql.BinlogEvent{
   841  		mysql.NewRotateEvent(f, s, 0, ""),
   842  		mysql.NewFormatDescriptionEvent(f, s),
   843  		mysql.NewMariaDBGTIDEvent(f, s, mysql.MariadbGTID{Domain: 0, Sequence: 0xd}, false /* hasBegin */),
   844  		mysql.NewQueryEvent(f, s, mysql.Query{
   845  			Database: "vt_test_keyspace",
   846  			SQL:      "BEGIN"}),
   847  		mysql.NewIntVarEvent(f, s, mysql.IntVarInsertID, 101),
   848  		mysql.NewQueryEvent(f, s, mysql.Query{
   849  			Database: "vt_test_keyspace",
   850  			SQL:      "insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */"}),
   851  		mysql.NewXIDEvent(f, s),
   852  	}
   853  
   854  	events := make(chan mysql.BinlogEvent)
   855  	errs := make(chan error)
   856  
   857  	want := []*binlogdatapb.BinlogTransaction{
   858  		{
   859  			Statements: []*binlogdatapb.BinlogTransaction_Statement{
   860  				{Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, Sql: []byte("SET INSERT_ID=101")},
   861  				{Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, Sql: []byte("SET TIMESTAMP=1407805592")},
   862  				{Category: binlogdatapb.BinlogTransaction_Statement_BL_INSERT, Sql: []byte("insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */")},
   863  			},
   864  			EventToken: &querypb.EventToken{
   865  				Timestamp: 1407805592,
   866  				Position: mysql.EncodePosition(mysql.Position{
   867  					GTIDSet: mysql.MariadbGTIDSet{
   868  						0: mysql.MariadbGTID{
   869  							Domain:   0,
   870  							Server:   62344,
   871  							Sequence: 0x0d,
   872  						},
   873  					},
   874  				}),
   875  			},
   876  		},
   877  	}
   878  	var got binlogStatements
   879  	// Set mock mysql.ConnParams and dbconfig
   880  	mcp := &mysql.ConnParams{
   881  		DbName: "vt_test_keyspace",
   882  	}
   883  	dbcfgs := dbconfigs.New(mcp)
   884  
   885  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, (&got).sendTransaction)
   886  
   887  	go sendTestEvents(events, input)
   888  	if _, err := bls.parseEvents(context.Background(), events, errs); err != ErrServerEOF {
   889  		t.Errorf("unexpected error: %v", err)
   890  	}
   891  
   892  	if !got.equal(want) {
   893  		t.Errorf("binlogConnStreamer.parseEvents(): got %v, want %v", got, want)
   894  	}
   895  }
   896  
   897  func TestStreamerParseEventsInvalidIntVar(t *testing.T) {
   898  	f := mysql.NewMySQL56BinlogFormat()
   899  	s := mysql.NewFakeBinlogStream()
   900  
   901  	input := []mysql.BinlogEvent{
   902  		mysql.NewRotateEvent(f, s, 0, ""),
   903  		mysql.NewFormatDescriptionEvent(f, s),
   904  		mysql.NewQueryEvent(f, s, mysql.Query{
   905  			Database: "vt_test_keyspace",
   906  			SQL:      "BEGIN"}),
   907  		mysql.NewIntVarEvent(f, s, mysql.IntVarInvalidInt, 0), // Invalid intvar.
   908  		mysql.NewQueryEvent(f, s, mysql.Query{
   909  			Database: "vt_test_keyspace",
   910  			SQL:      "insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */"}),
   911  		mysql.NewXIDEvent(f, s),
   912  	}
   913  	want := "can't parse INTVAR_EVENT:"
   914  
   915  	events := make(chan mysql.BinlogEvent)
   916  	errs := make(chan error)
   917  
   918  	sendTransaction := func(eventToken *querypb.EventToken, statements []FullBinlogStatement) error {
   919  		return nil
   920  	}
   921  	// Set mock mysql.ConnParams and dbconfig
   922  	mcp := &mysql.ConnParams{
   923  		DbName: "vt_test_keyspace",
   924  	}
   925  	dbcfgs := dbconfigs.New(mcp)
   926  
   927  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, sendTransaction)
   928  
   929  	go sendTestEvents(events, input)
   930  	_, err := bls.parseEvents(context.Background(), events, errs)
   931  	if err == nil {
   932  		t.Errorf("expected error, got none")
   933  		return
   934  	}
   935  	if got := err.Error(); !strings.HasPrefix(got, want) {
   936  		t.Errorf("wrong error, got %#v, want %#v", got, want)
   937  	}
   938  }
   939  
   940  func TestStreamerParseEventsOtherDB(t *testing.T) {
   941  	f := mysql.NewMySQL56BinlogFormat()
   942  	s := mysql.NewFakeBinlogStream()
   943  	s.ServerID = 62344
   944  
   945  	input := []mysql.BinlogEvent{
   946  		mysql.NewRotateEvent(f, s, 0, ""),
   947  		mysql.NewFormatDescriptionEvent(f, s),
   948  		mysql.NewMariaDBGTIDEvent(f, s, mysql.MariadbGTID{Domain: 0, Sequence: 0xd}, false /* hasBegin */),
   949  		mysql.NewQueryEvent(f, s, mysql.Query{
   950  			Database: "vt_test_keyspace",
   951  			SQL:      "BEGIN"}),
   952  		mysql.NewQueryEvent(f, s, mysql.Query{
   953  			Database: "other",
   954  			SQL:      "INSERT INTO test values (3, 4)"}),
   955  		mysql.NewQueryEvent(f, s, mysql.Query{
   956  			Database: "vt_test_keyspace",
   957  			SQL:      "insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */"}),
   958  		mysql.NewXIDEvent(f, s),
   959  	}
   960  
   961  	events := make(chan mysql.BinlogEvent)
   962  	errs := make(chan error)
   963  
   964  	want := []*binlogdatapb.BinlogTransaction{
   965  		{
   966  			Statements: []*binlogdatapb.BinlogTransaction_Statement{
   967  				{Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, Sql: []byte("SET TIMESTAMP=1407805592")},
   968  				{Category: binlogdatapb.BinlogTransaction_Statement_BL_INSERT, Sql: []byte("insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */")},
   969  			},
   970  			EventToken: &querypb.EventToken{
   971  				Timestamp: 1407805592,
   972  				Position: mysql.EncodePosition(mysql.Position{
   973  					GTIDSet: mysql.MariadbGTIDSet{
   974  						0: mysql.MariadbGTID{
   975  							Domain:   0,
   976  							Server:   62344,
   977  							Sequence: 0x0d,
   978  						},
   979  					},
   980  				}),
   981  			},
   982  		},
   983  	}
   984  	var got binlogStatements
   985  	// Set mock mysql.ConnParams and dbconfig
   986  	mcp := &mysql.ConnParams{
   987  		DbName: "vt_test_keyspace",
   988  	}
   989  	dbcfgs := dbconfigs.New(mcp)
   990  
   991  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, (&got).sendTransaction)
   992  
   993  	go sendTestEvents(events, input)
   994  	if _, err := bls.parseEvents(context.Background(), events, errs); err != ErrServerEOF {
   995  		t.Errorf("unexpected error: %v", err)
   996  	}
   997  
   998  	if !got.equal(want) {
   999  		t.Errorf("binlogConnStreamer.parseEvents(): got %v, want %v", got, want)
  1000  	}
  1001  }
  1002  
  1003  func TestStreamerParseEventsOtherDBBegin(t *testing.T) {
  1004  	f := mysql.NewMySQL56BinlogFormat()
  1005  	s := mysql.NewFakeBinlogStream()
  1006  	s.ServerID = 62344
  1007  
  1008  	input := []mysql.BinlogEvent{
  1009  		mysql.NewRotateEvent(f, s, 0, ""),
  1010  		mysql.NewFormatDescriptionEvent(f, s),
  1011  		mysql.NewMariaDBGTIDEvent(f, s, mysql.MariadbGTID{Domain: 0, Sequence: 0xd}, false /* hasBegin */),
  1012  		mysql.NewQueryEvent(f, s, mysql.Query{
  1013  			Database: "other",
  1014  			SQL:      "BEGIN"}), // Check that this doesn't get filtered out.
  1015  		mysql.NewQueryEvent(f, s, mysql.Query{
  1016  			Database: "other",
  1017  			SQL:      "INSERT INTO test values (3, 4)"}),
  1018  		mysql.NewQueryEvent(f, s, mysql.Query{
  1019  			Database: "vt_test_keyspace",
  1020  			SQL:      "insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */"}),
  1021  		mysql.NewXIDEvent(f, s),
  1022  	}
  1023  
  1024  	events := make(chan mysql.BinlogEvent)
  1025  	errs := make(chan error)
  1026  
  1027  	want := []*binlogdatapb.BinlogTransaction{
  1028  		{
  1029  			Statements: []*binlogdatapb.BinlogTransaction_Statement{
  1030  				{Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, Sql: []byte("SET TIMESTAMP=1407805592")},
  1031  				{Category: binlogdatapb.BinlogTransaction_Statement_BL_INSERT, Sql: []byte("insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */")},
  1032  			},
  1033  			EventToken: &querypb.EventToken{
  1034  				Timestamp: 1407805592,
  1035  				Position: mysql.EncodePosition(mysql.Position{
  1036  					GTIDSet: mysql.MariadbGTIDSet{
  1037  						0: mysql.MariadbGTID{
  1038  							Domain:   0,
  1039  							Server:   62344,
  1040  							Sequence: 0x0d,
  1041  						},
  1042  					},
  1043  				}),
  1044  			},
  1045  		},
  1046  	}
  1047  	var got binlogStatements
  1048  	// Set mock mysql.ConnParams and dbconfig
  1049  	mcp := &mysql.ConnParams{
  1050  		DbName: "vt_test_keyspace",
  1051  	}
  1052  	dbcfgs := dbconfigs.New(mcp)
  1053  
  1054  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, (&got).sendTransaction)
  1055  
  1056  	go sendTestEvents(events, input)
  1057  	if _, err := bls.parseEvents(context.Background(), events, errs); err != ErrServerEOF {
  1058  		t.Errorf("unexpected error: %v", err)
  1059  	}
  1060  
  1061  	if !got.equal(want) {
  1062  		t.Errorf("binlogConnStreamer.parseEvents(): got %v, want %v", got, want)
  1063  	}
  1064  }
  1065  
  1066  func TestStreamerParseEventsBeginAgain(t *testing.T) {
  1067  	f := mysql.NewMySQL56BinlogFormat()
  1068  	s := mysql.NewFakeBinlogStream()
  1069  
  1070  	input := []mysql.BinlogEvent{
  1071  		mysql.NewRotateEvent(f, s, 0, ""),
  1072  		mysql.NewFormatDescriptionEvent(f, s),
  1073  		mysql.NewQueryEvent(f, s, mysql.Query{
  1074  			Database: "vt_test_keyspace",
  1075  			SQL:      "BEGIN"}),
  1076  		mysql.NewQueryEvent(f, s, mysql.Query{
  1077  			Database: "vt_test_keyspace",
  1078  			SQL:      "insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */"}),
  1079  		mysql.NewQueryEvent(f, s, mysql.Query{
  1080  			Database: "vt_test_keyspace",
  1081  			SQL:      "BEGIN"}),
  1082  	}
  1083  
  1084  	events := make(chan mysql.BinlogEvent)
  1085  	errs := make(chan error)
  1086  
  1087  	sendTransaction := func(eventToken *querypb.EventToken, statements []FullBinlogStatement) error {
  1088  		return nil
  1089  	}
  1090  	// Set mock mysql.ConnParams and dbconfig
  1091  	mcp := &mysql.ConnParams{
  1092  		DbName: "vt_test_keyspace",
  1093  	}
  1094  	dbcfgs := dbconfigs.New(mcp)
  1095  
  1096  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, sendTransaction)
  1097  	before := binlogStreamerErrors.Counts()["ParseEvents"]
  1098  
  1099  	go sendTestEvents(events, input)
  1100  	if _, err := bls.parseEvents(context.Background(), events, errs); err != ErrServerEOF {
  1101  		t.Errorf("unexpected error: %v", err)
  1102  	}
  1103  	after := binlogStreamerErrors.Counts()["ParseEvents"]
  1104  	if got := after - before; got != 1 {
  1105  		t.Errorf("error count change = %v, want 1", got)
  1106  	}
  1107  }
  1108  
  1109  // TestStreamerParseEventsMariadbStandaloneGTID tests a MariaDB server
  1110  // with no checksum, using a GTID with a Begin.
  1111  func TestStreamerParseEventsMariadbBeginGTID(t *testing.T) {
  1112  	f := mysql.NewMariaDBBinlogFormat()
  1113  	s := mysql.NewFakeBinlogStream()
  1114  	s.ServerID = 62344
  1115  	s.Timestamp = 1409892744
  1116  
  1117  	input := []mysql.BinlogEvent{
  1118  		mysql.NewRotateEvent(f, s, 4, "filename.0001"),
  1119  		mysql.NewFormatDescriptionEvent(f, s),
  1120  		mysql.NewMariaDBGTIDEvent(f, s, mysql.MariadbGTID{Domain: 0, Sequence: 10}, true /* hasBegin */),
  1121  		mysql.NewQueryEvent(f, s, mysql.Query{
  1122  			Charset: &binlogdatapb.Charset{Client: 33, Conn: 33, Server: 33},
  1123  			SQL:     "insert into vt_insert_test(msg) values ('test 0') /* _stream vt_insert_test (id ) (null ); */",
  1124  		}),
  1125  		mysql.NewXIDEvent(f, s),
  1126  	}
  1127  
  1128  	events := make(chan mysql.BinlogEvent)
  1129  	errs := make(chan error)
  1130  
  1131  	want := []*binlogdatapb.BinlogTransaction{
  1132  		{
  1133  			Statements: []*binlogdatapb.BinlogTransaction_Statement{
  1134  				{
  1135  					Category: binlogdatapb.BinlogTransaction_Statement_BL_SET,
  1136  					Charset:  &binlogdatapb.Charset{Client: 33, Conn: 33, Server: 33},
  1137  					Sql:      []byte("SET TIMESTAMP=1409892744"),
  1138  				},
  1139  				{
  1140  					Category: binlogdatapb.BinlogTransaction_Statement_BL_INSERT,
  1141  					Charset:  &binlogdatapb.Charset{Client: 33, Conn: 33, Server: 33},
  1142  					Sql:      []byte("insert into vt_insert_test(msg) values ('test 0') /* _stream vt_insert_test (id ) (null ); */"),
  1143  				},
  1144  			},
  1145  			EventToken: &querypb.EventToken{
  1146  				Timestamp: 1409892744,
  1147  				Position: mysql.EncodePosition(mysql.Position{
  1148  					GTIDSet: mysql.MariadbGTIDSet{
  1149  						0: mysql.MariadbGTID{
  1150  							Domain:   0,
  1151  							Server:   62344,
  1152  							Sequence: 10,
  1153  						},
  1154  					},
  1155  				}),
  1156  			},
  1157  		},
  1158  	}
  1159  	var got binlogStatements
  1160  	// Set mock mysql.ConnParams and dbconfig
  1161  	mcp := &mysql.ConnParams{
  1162  		DbName: "vt_test_keyspace",
  1163  	}
  1164  	dbcfgs := dbconfigs.New(mcp)
  1165  
  1166  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, (&got).sendTransaction)
  1167  
  1168  	go sendTestEvents(events, input)
  1169  	if _, err := bls.parseEvents(context.Background(), events, errs); err != ErrServerEOF {
  1170  		t.Errorf("unexpected error: %v", err)
  1171  	}
  1172  
  1173  	if !got.equal(want) {
  1174  		t.Errorf("binlogConnStreamer.parseEvents(): got:\n%v\nwant:\n%v", got, want)
  1175  	}
  1176  }
  1177  
  1178  // TestStreamerParseEventsMariadbStandaloneGTID tests a MariaDB server
  1179  // with no checksum, using a standalone GTID.
  1180  func TestStreamerParseEventsMariadbStandaloneGTID(t *testing.T) {
  1181  	f := mysql.NewMariaDBBinlogFormat()
  1182  	s := mysql.NewFakeBinlogStream()
  1183  	s.ServerID = 62344
  1184  	s.Timestamp = 1409892744
  1185  
  1186  	input := []mysql.BinlogEvent{
  1187  		mysql.NewRotateEvent(f, s, 4, "filename.0001"),
  1188  		mysql.NewFormatDescriptionEvent(f, s),
  1189  		mysql.NewMariaDBGTIDEvent(f, s, mysql.MariadbGTID{Domain: 0, Sequence: 9}, false /* hasBegin */),
  1190  		mysql.NewQueryEvent(f, s, mysql.Query{
  1191  			Charset: &binlogdatapb.Charset{Client: 8, Conn: 8, Server: 33},
  1192  			SQL:     "create table if not exists vt_insert_test (\nid bigint auto_increment,\nmsg varchar(64),\nprimary key (id)\n) Engine=InnoDB",
  1193  		}),
  1194  	}
  1195  
  1196  	events := make(chan mysql.BinlogEvent)
  1197  	errs := make(chan error)
  1198  
  1199  	want := []*binlogdatapb.BinlogTransaction{
  1200  		{
  1201  			Statements: []*binlogdatapb.BinlogTransaction_Statement{
  1202  				{Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, Charset: &binlogdatapb.Charset{Client: 8, Conn: 8, Server: 33}, Sql: []byte("SET TIMESTAMP=1409892744")},
  1203  				{Category: binlogdatapb.BinlogTransaction_Statement_BL_DDL, Charset: &binlogdatapb.Charset{Client: 8, Conn: 8, Server: 33}, Sql: []byte("create table if not exists vt_insert_test (\nid bigint auto_increment,\nmsg varchar(64),\nprimary key (id)\n) Engine=InnoDB")},
  1204  			},
  1205  			EventToken: &querypb.EventToken{
  1206  				Timestamp: 1409892744,
  1207  				Position: mysql.EncodePosition(mysql.Position{
  1208  					GTIDSet: mysql.MariadbGTIDSet{
  1209  						0: mysql.MariadbGTID{
  1210  							Domain:   0,
  1211  							Server:   62344,
  1212  							Sequence: 9,
  1213  						},
  1214  					},
  1215  				}),
  1216  			},
  1217  		},
  1218  	}
  1219  	var got binlogStatements
  1220  	// Set mock mysql.ConnParams and dbconfig
  1221  	mcp := &mysql.ConnParams{
  1222  		DbName: "vt_test_keyspace",
  1223  	}
  1224  	dbcfgs := dbconfigs.New(mcp)
  1225  
  1226  	bls := NewStreamer(dbcfgs, nil, nil, mysql.Position{}, 0, (&got).sendTransaction)
  1227  
  1228  	go sendTestEvents(events, input)
  1229  	if _, err := bls.parseEvents(context.Background(), events, errs); err != ErrServerEOF {
  1230  		t.Errorf("unexpected error: %v", err)
  1231  	}
  1232  
  1233  	if !got.equal(want) {
  1234  		t.Errorf("binlogConnStreamer.parseEvents(): got:\n%v\nwant:\n%v", got, want)
  1235  	}
  1236  }
  1237  
  1238  func TestGetStatementCategory(t *testing.T) {
  1239  	table := map[string]binlogdatapb.BinlogTransaction_Statement_Category{
  1240  		"":  binlogdatapb.BinlogTransaction_Statement_BL_UNRECOGNIZED,
  1241  		" ": binlogdatapb.BinlogTransaction_Statement_BL_UNRECOGNIZED,
  1242  		" UPDATE we don't try to fix leading spaces": binlogdatapb.BinlogTransaction_Statement_BL_UNRECOGNIZED,
  1243  		"FOOBAR unknown query prefix":                binlogdatapb.BinlogTransaction_Statement_BL_UNRECOGNIZED,
  1244  
  1245  		"BEGIN":    binlogdatapb.BinlogTransaction_Statement_BL_BEGIN,
  1246  		"COMMIT":   binlogdatapb.BinlogTransaction_Statement_BL_COMMIT,
  1247  		"ROLLBACK": binlogdatapb.BinlogTransaction_Statement_BL_ROLLBACK,
  1248  		"INSERT something (something, something)": binlogdatapb.BinlogTransaction_Statement_BL_INSERT,
  1249  		"UPDATE something SET something=nothing":  binlogdatapb.BinlogTransaction_Statement_BL_UPDATE,
  1250  		"DELETE something":                        binlogdatapb.BinlogTransaction_Statement_BL_DELETE,
  1251  		"CREATE something":                        binlogdatapb.BinlogTransaction_Statement_BL_DDL,
  1252  		"ALTER something":                         binlogdatapb.BinlogTransaction_Statement_BL_DDL,
  1253  		"DROP something":                          binlogdatapb.BinlogTransaction_Statement_BL_DDL,
  1254  		"TRUNCATE something":                      binlogdatapb.BinlogTransaction_Statement_BL_DDL,
  1255  		"RENAME something":                        binlogdatapb.BinlogTransaction_Statement_BL_DDL,
  1256  		"SET something=nothing":                   binlogdatapb.BinlogTransaction_Statement_BL_SET,
  1257  	}
  1258  
  1259  	for input, want := range table {
  1260  		if got := getStatementCategory(input); got != want {
  1261  			t.Errorf("getStatementCategory(%v) = %v, want %v", input, got, want)
  1262  		}
  1263  	}
  1264  }