vitess.io/vitess@v0.16.2/go/vt/vtctl/workflow/stream_migrator_test.go (about)

     1  /*
     2  Copyright 2021 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 workflow
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"testing"
    23  
    24  	"github.com/stretchr/testify/assert"
    25  	"github.com/stretchr/testify/require"
    26  
    27  	"vitess.io/vitess/go/vt/proto/vschema"
    28  	"vitess.io/vitess/go/vt/vtgate/vindexes"
    29  	"vitess.io/vitess/go/vt/vttablet/tabletmanager/vreplication"
    30  
    31  	binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata"
    32  	vschemapb "vitess.io/vitess/go/vt/proto/vschema"
    33  )
    34  
    35  func TestTemplatize(t *testing.T) {
    36  	tests := []struct {
    37  		in  []*VReplicationStream
    38  		out string
    39  		err string
    40  	}{{
    41  		// First test contains all fields.
    42  		in: []*VReplicationStream{{
    43  			ID:       1,
    44  			Workflow: "test",
    45  			BinlogSource: &binlogdatapb.BinlogSource{
    46  				Keyspace: "ks",
    47  				Shard:    "80-",
    48  				Filter: &binlogdatapb.Filter{
    49  					Rules: []*binlogdatapb.Rule{{
    50  						Match:  "t1",
    51  						Filter: "select * from t1 where in_keyrange('-80')",
    52  					}},
    53  				},
    54  			},
    55  		}},
    56  		out: `[{"ID":1,"Workflow":"test","BinlogSource":{"keyspace":"ks","shard":"80-","filter":{"rules":[{"match":"t1","filter":"select * from t1 where in_keyrange('{{.}}')"}]}}}]`,
    57  	}, {
    58  		// Reference table.
    59  		in: []*VReplicationStream{{
    60  			BinlogSource: &binlogdatapb.BinlogSource{
    61  				Filter: &binlogdatapb.Filter{
    62  					Rules: []*binlogdatapb.Rule{{
    63  						Match:  "ref",
    64  						Filter: "",
    65  					}},
    66  				},
    67  			},
    68  		}},
    69  		out: "",
    70  	}, {
    71  		// Sharded table.
    72  		in: []*VReplicationStream{{
    73  			BinlogSource: &binlogdatapb.BinlogSource{
    74  				Filter: &binlogdatapb.Filter{
    75  					Rules: []*binlogdatapb.Rule{{
    76  						Match:  "t1",
    77  						Filter: "-80",
    78  					}},
    79  				},
    80  			},
    81  		}},
    82  		out: `[{"ID":0,"Workflow":"","BinlogSource":{"filter":{"rules":[{"match":"t1","filter":"{{.}}"}]}}}]`,
    83  	}, {
    84  		// table not found
    85  		in: []*VReplicationStream{{
    86  			BinlogSource: &binlogdatapb.BinlogSource{
    87  				Filter: &binlogdatapb.Filter{
    88  					Rules: []*binlogdatapb.Rule{{
    89  						Match: "t3",
    90  					}},
    91  				},
    92  			},
    93  		}},
    94  		err: `table t3 not found in vschema`,
    95  	}, {
    96  		// sharded table with no filter
    97  		in: []*VReplicationStream{{
    98  			BinlogSource: &binlogdatapb.BinlogSource{
    99  				Filter: &binlogdatapb.Filter{
   100  					Rules: []*binlogdatapb.Rule{{
   101  						Match: "t1",
   102  					}},
   103  				},
   104  			},
   105  		}},
   106  		err: `rule match:"t1"  does not have a select expression in vreplication`,
   107  	}, {
   108  		// Excluded table.
   109  		in: []*VReplicationStream{{
   110  			BinlogSource: &binlogdatapb.BinlogSource{
   111  				Filter: &binlogdatapb.Filter{
   112  					Rules: []*binlogdatapb.Rule{{
   113  						Match:  "t1",
   114  						Filter: vreplication.ExcludeStr,
   115  					}},
   116  				},
   117  			},
   118  		}},
   119  		err: `unexpected rule in vreplication: match:"t1" filter:"exclude" `,
   120  	}, {
   121  		// Sharded table and ref table
   122  		in: []*VReplicationStream{{
   123  			BinlogSource: &binlogdatapb.BinlogSource{
   124  				Filter: &binlogdatapb.Filter{
   125  					Rules: []*binlogdatapb.Rule{{
   126  						Match:  "t1",
   127  						Filter: "-80",
   128  					}, {
   129  						Match:  "ref",
   130  						Filter: "",
   131  					}},
   132  				},
   133  			},
   134  		}},
   135  		err: `cannot migrate streams with a mix of reference and sharded tables: filter:<rules:<match:"t1" filter:"{{.}}" > rules:<match:"ref" > > `,
   136  	}, {
   137  		// Ref table and sharded table (different code path)
   138  		in: []*VReplicationStream{{
   139  			BinlogSource: &binlogdatapb.BinlogSource{
   140  				Filter: &binlogdatapb.Filter{
   141  					Rules: []*binlogdatapb.Rule{{
   142  						Match:  "ref",
   143  						Filter: "",
   144  					}, {
   145  						Match:  "t2",
   146  						Filter: "-80",
   147  					}},
   148  				},
   149  			},
   150  		}},
   151  		err: `cannot migrate streams with a mix of reference and sharded tables: filter:<rules:<match:"ref" > rules:<match:"t2" filter:"{{.}}" > > `,
   152  	}, {
   153  		// Ref table with select expression
   154  		in: []*VReplicationStream{{
   155  			BinlogSource: &binlogdatapb.BinlogSource{
   156  				Filter: &binlogdatapb.Filter{
   157  					Rules: []*binlogdatapb.Rule{{
   158  						Match:  "ref",
   159  						Filter: "select * from t1",
   160  					}},
   161  				},
   162  			},
   163  		}},
   164  		out: "",
   165  	}, {
   166  		// Select expresstion with no keyrange value
   167  		in: []*VReplicationStream{{
   168  			BinlogSource: &binlogdatapb.BinlogSource{
   169  				Filter: &binlogdatapb.Filter{
   170  					Rules: []*binlogdatapb.Rule{{
   171  						Match:  "t1",
   172  						Filter: "select * from t1",
   173  					}},
   174  				},
   175  			},
   176  		}},
   177  		out: `[{"ID":0,"Workflow":"","BinlogSource":{"filter":{"rules":[{"match":"t1","filter":"select * from t1 where in_keyrange(c1, 'hash', '{{.}}')"}]}}}]`,
   178  	}, {
   179  		// Select expresstion with one keyrange value
   180  		in: []*VReplicationStream{{
   181  			BinlogSource: &binlogdatapb.BinlogSource{
   182  				Filter: &binlogdatapb.Filter{
   183  					Rules: []*binlogdatapb.Rule{{
   184  						Match:  "t1",
   185  						Filter: "select * from t1 where in_keyrange('-80')",
   186  					}},
   187  				},
   188  			},
   189  		}},
   190  		out: `[{"ID":0,"Workflow":"","BinlogSource":{"filter":{"rules":[{"match":"t1","filter":"select * from t1 where in_keyrange('{{.}}')"}]}}}]`,
   191  	}, {
   192  		// Select expresstion with three keyrange values
   193  		in: []*VReplicationStream{{
   194  			BinlogSource: &binlogdatapb.BinlogSource{
   195  				Filter: &binlogdatapb.Filter{
   196  					Rules: []*binlogdatapb.Rule{{
   197  						Match:  "t1",
   198  						Filter: "select * from t1 where in_keyrange(col, vdx, '-80')",
   199  					}},
   200  				},
   201  			},
   202  		}},
   203  		out: `[{"ID":0,"Workflow":"","BinlogSource":{"filter":{"rules":[{"match":"t1","filter":"select * from t1 where in_keyrange(col, vdx, '{{.}}')"}]}}}]`,
   204  	}, {
   205  		// syntax error
   206  		in: []*VReplicationStream{{
   207  			BinlogSource: &binlogdatapb.BinlogSource{
   208  				Filter: &binlogdatapb.Filter{
   209  					Rules: []*binlogdatapb.Rule{{
   210  						Match:  "t1",
   211  						Filter: "bad syntax",
   212  					}},
   213  				},
   214  			},
   215  		}},
   216  		err: "syntax error at position 4 near 'bad'",
   217  	}, {
   218  		// invalid statement
   219  		in: []*VReplicationStream{{
   220  			BinlogSource: &binlogdatapb.BinlogSource{
   221  				Filter: &binlogdatapb.Filter{
   222  					Rules: []*binlogdatapb.Rule{{
   223  						Match:  "t1",
   224  						Filter: "update t set a=1",
   225  					}},
   226  				},
   227  			},
   228  		}},
   229  		err: "unexpected query: update t set a=1",
   230  	}, {
   231  		// invalid in_keyrange
   232  		in: []*VReplicationStream{{
   233  			BinlogSource: &binlogdatapb.BinlogSource{
   234  				Filter: &binlogdatapb.Filter{
   235  					Rules: []*binlogdatapb.Rule{{
   236  						Match:  "t1",
   237  						Filter: "select * from t1 where in_keyrange(col, vdx, '-80', extra)",
   238  					}},
   239  				},
   240  			},
   241  		}},
   242  		err: "unexpected in_keyrange parameters: in_keyrange(col, vdx, '-80', extra)",
   243  	}, {
   244  		// * in_keyrange
   245  		in: []*VReplicationStream{{
   246  			BinlogSource: &binlogdatapb.BinlogSource{
   247  				Filter: &binlogdatapb.Filter{
   248  					Rules: []*binlogdatapb.Rule{{
   249  						Match:  "t1",
   250  						Filter: "select * from t1 where in_keyrange(*)",
   251  					}},
   252  				},
   253  			},
   254  		}},
   255  		err: "unexpected in_keyrange parameters: in_keyrange(*)",
   256  	}, {
   257  		// non-string in_keyrange
   258  		in: []*VReplicationStream{{
   259  			BinlogSource: &binlogdatapb.BinlogSource{
   260  				Filter: &binlogdatapb.Filter{
   261  					Rules: []*binlogdatapb.Rule{{
   262  						Match:  "t1",
   263  						Filter: "select * from t1 where in_keyrange(aa)",
   264  					}},
   265  				},
   266  			},
   267  		}},
   268  		err: "unexpected in_keyrange parameters: in_keyrange(aa)",
   269  	}, {
   270  		// '{{' in query
   271  		in: []*VReplicationStream{{
   272  			BinlogSource: &binlogdatapb.BinlogSource{
   273  				Filter: &binlogdatapb.Filter{
   274  					Rules: []*binlogdatapb.Rule{{
   275  						Match:  "t1",
   276  						Filter: "select '{{' from t1 where in_keyrange('-80')",
   277  					}},
   278  				},
   279  			},
   280  		}},
   281  		err: "cannot migrate queries that contain '{{' in their string: select '{{' from t1 where in_keyrange('-80')",
   282  	}}
   283  	vs := &vschemapb.Keyspace{
   284  		Sharded: true,
   285  		Vindexes: map[string]*vschema.Vindex{
   286  			"thash": {
   287  				Type: "hash",
   288  			},
   289  		},
   290  		Tables: map[string]*vschema.Table{
   291  			"t1": {
   292  				ColumnVindexes: []*vschema.ColumnVindex{{
   293  					Columns: []string{"c1"},
   294  					Name:    "thash",
   295  				}},
   296  			},
   297  			"t2": {
   298  				ColumnVindexes: []*vschema.ColumnVindex{{
   299  					Columns: []string{"c1"},
   300  					Name:    "thash",
   301  				}},
   302  			},
   303  			"ref": {
   304  				Type: vindexes.TypeReference,
   305  			},
   306  		},
   307  	}
   308  	ksschema, err := vindexes.BuildKeyspaceSchema(vs, "ks")
   309  	require.NoError(t, err, "could not create test keyspace %+v", vs)
   310  
   311  	ts := &testTrafficSwitcher{
   312  		sourceKeyspaceSchema: ksschema,
   313  	}
   314  	for _, tt := range tests {
   315  		sm := &StreamMigrator{ts: ts}
   316  		out, err := sm.templatize(context.Background(), tt.in)
   317  		if tt.err != "" {
   318  			assert.Error(t, err, "templatize(%v) expected to get err=%s, got %+v", stringifyVRS(tt.in), tt.err, err)
   319  		}
   320  
   321  		got := stringifyVRS(out)
   322  		assert.Equal(t, tt.out, got, "templatize(%v) mismatch", stringifyVRS(tt.in))
   323  	}
   324  }
   325  
   326  func stringifyVRS(streams []*VReplicationStream) string {
   327  	if len(streams) == 0 {
   328  		return ""
   329  	}
   330  
   331  	type testVRS struct {
   332  		ID           uint32
   333  		Workflow     string
   334  		BinlogSource *binlogdatapb.BinlogSource
   335  	}
   336  
   337  	converted := make([]*testVRS, len(streams))
   338  	for i, stream := range streams {
   339  		converted[i] = &testVRS{
   340  			ID:           stream.ID,
   341  			Workflow:     stream.Workflow,
   342  			BinlogSource: stream.BinlogSource,
   343  		}
   344  	}
   345  
   346  	b, _ := json.Marshal(converted)
   347  	return string(b)
   348  }