diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000..32f0a000966 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,4 @@ +* @sougou + +/docker/ @derekperkins @dkhenry +/helm/ @derekperkins @dkhenry diff --git a/config/mycnf/default-fast.cnf b/config/mycnf/default-fast.cnf index 16aebb3c77f..ec7f92595f8 100644 --- a/config/mycnf/default-fast.cnf +++ b/config/mycnf/default-fast.cnf @@ -28,7 +28,7 @@ key_buffer_size = 2M log-error = {{.ErrorLogPath}} long_query_time = 2 max_allowed_packet = 16M -max_connections = 100 +max_connections = 200 net_write_timeout = 60 pid-file = {{.PidFile}} port = {{.MysqlPort}} diff --git a/data/test/tabletserver/ddl_cases.txt b/data/test/tabletserver/ddl_cases.txt index f6accdba701..c23e45e2ae0 100644 --- a/data/test/tabletserver/ddl_cases.txt +++ b/data/test/tabletserver/ddl_cases.txt @@ -1,86 +1,86 @@ "create table a(abcd)" { - "Action": "create", "NewName": "a" + "Action": "create" } "drop table b" { - "Action": "drop", "TableName": "b" + "Action": "drop" } "drop table b.c" { - "Action": "drop", "TableName": "b.c" + "Action": "drop" } "alter table c alter foo" { - "Action": "alter", "TableName": "c", "NewTable": "c" + "Action": "alter" } "alter table c comment 'aa'" { - "Action": "alter", "TableName": "c", "NewTable": "c" + "Action": "alter" } "alter table b.c comment 'aa'" { - "Action": "alter", "TableName": "b.c", "NewTable": "b.c" + "Action": "alter" } "drop index a on b" { - "Action": "alter", "TableName": "b", "NewName": "b" + "Action": "alter" } "drop index a on b.c" { - "Action": "alter", "TableName": "b.c", "NewName": "b.c" + "Action": "alter" } "drop index a on b lock=none" { - "Action": "alter", "TableName": "b", "NewName": "b" + "Action": "alter" } "rename table a to b" { - "Action": "rename", "TableName": "a", "NewTable": "b" + "Action": "rename" } "rename table c.a to c.b" { - "Action": "rename", "TableName": "c.a", "NewTable": "c.b" + "Action": "rename" } "alter table a rename b" { - "Action": "rename", "TableName": "a", "NewTable": "b" + "Action": "rename" } "alter table a rename to b" { - "Action": "rename", "TableName": "a", "NewTable": "b" + "Action": "rename" } "alter table c.a rename to c.b" { - "Action": "rename", "TableName": "c.a", "NewTable": "c.b" + "Action": "rename" } "create view a asdasd" { - "Action": "create", "NewName": "a" + "Action": "create" } "alter view c as foo" { - "Action": "alter", "TableName": "c", "NewTable": "c" + "Action": "alter" } "drop view b" { - "Action": "drop", "TableName": "b" + "Action": "drop" } "select * from a" @@ -100,7 +100,7 @@ "alter table a partition by range (id) (partition p0 values less than (10), partition p1 values less than (maxvalue))" { - "Action": "alter", "TableName": "a", "NewTable": "a" + "Action": "alter" } # truncate diff --git a/data/test/tabletserver/exec_cases.txt b/data/test/tabletserver/exec_cases.txt index 31ea6630149..d4d1a296b51 100644 --- a/data/test/tabletserver/exec_cases.txt +++ b/data/test/tabletserver/exec_cases.txt @@ -2076,12 +2076,8 @@ options:PassthroughDMLs "alter table a add column(a int)" { "PlanID": "DDL", - "TableName": "a", + "TableName": "", "Permissions": [ - { - "TableName": "a", - "Role": 2 - }, { "TableName": "a", "Role": 2 @@ -2093,7 +2089,7 @@ options:PassthroughDMLs "alter table a rename b" { "PlanID": "DDL", - "TableName": "a", + "TableName": "", "Permissions": [ { "TableName": "a", @@ -2110,7 +2106,24 @@ options:PassthroughDMLs "rename table a to b" { "PlanID": "DDL", - "TableName": "a", + "TableName": "", + "Permissions": [ + { + "TableName": "a", + "Role": 2 + }, + { + "TableName": "b", + "Role": 2 + } + ] +} + +# multi-rename +"rename table a to b, b to a" +{ + "PlanID": "DDL", + "TableName": "", "Permissions": [ { "TableName": "a", @@ -2119,6 +2132,14 @@ options:PassthroughDMLs { "TableName": "b", "Role": 2 + }, + { + "TableName": "b", + "Role": 2 + }, + { + "TableName": "a", + "Role": 2 } ] } @@ -2127,7 +2148,7 @@ options:PassthroughDMLs "drop table a" { "PlanID": "DDL", - "TableName": "a", + "TableName": "", "Permissions": [ { "TableName": "a", @@ -2136,16 +2157,29 @@ options:PassthroughDMLs ] } -# analyze -"analyze table a" +# multi-drop +"drop table a, b" { "PlanID": "DDL", - "TableName": "a", + "TableName": "", "Permissions": [ { "TableName": "a", "Role": 2 }, + { + "TableName": "b", + "Role": 2 + } + ] +} + +# analyze +"analyze table a" +{ + "PlanID": "DDL", + "TableName": "", + "Permissions": [ { "TableName": "a", "Role": 2 @@ -2157,7 +2191,7 @@ options:PassthroughDMLs "alter table a reorganize partition b into (partition c values less than (:bv), partition d values less than (maxvalue))" { "PlanID": "DDL", - "TableName": "a", + "TableName": "", "Permissions": [ { "TableName": "a", @@ -2171,12 +2205,8 @@ options:PassthroughDMLs "alter table a partition by range (id) (partition p0 values less than (10), partition p1 values less than (maxvalue))" { "PlanID": "DDL", - "TableName": "a", + "TableName": "", "Permissions": [ - { - "TableName": "a", - "Role": 2 - }, { "TableName": "a", "Role": 2 diff --git a/data/test/vtexplain/multi-output/selectsharded-output.txt b/data/test/vtexplain/multi-output/selectsharded-output.txt index cbbeff52f70..6f458bdd8b5 100644 --- a/data/test/vtexplain/multi-output/selectsharded-output.txt +++ b/data/test/vtexplain/multi-output/selectsharded-output.txt @@ -88,3 +88,41 @@ select * from (select id from user) s /* scatter paren select */ 1 ks_sharded/c0-: select * from (select id from user) as s limit 10001 /* scatter paren select */ ---------------------------------------------------------------------- +select name from user where id = (select id from t1) /* non-correlated subquery as value */ + +1 ks_unsharded/-: select id from t1 limit 10001 /* non-correlated subquery as value */ +2 ks_sharded/-40: select name from user where id = 1 limit 10001 /* non-correlated subquery as value */ + +---------------------------------------------------------------------- +select name from user where id in (select id from t1) /* non-correlated subquery in IN clause */ + +1 ks_unsharded/-: select id from t1 limit 10001 /* non-correlated subquery in IN clause */ +2 ks_sharded/-40: select name from user where 1 = 1 and (id in (1)) limit 10001 /* non-correlated subquery in IN clause */ + +---------------------------------------------------------------------- +select name from user where id not in (select id from t1) /* non-correlated subquery in NOT IN clause */ + +1 ks_unsharded/-: select id from t1 limit 10001 /* non-correlated subquery in NOT IN clause */ +2 ks_sharded/-40: select name from user where (1 = 0 or (id not in (1))) limit 10001 /* non-correlated subquery in NOT IN clause */ +2 ks_sharded/40-80: select name from user where (1 = 0 or (id not in (1))) limit 10001 /* non-correlated subquery in NOT IN clause */ +2 ks_sharded/80-c0: select name from user where (1 = 0 or (id not in (1))) limit 10001 /* non-correlated subquery in NOT IN clause */ +2 ks_sharded/c0-: select name from user where (1 = 0 or (id not in (1))) limit 10001 /* non-correlated subquery in NOT IN clause */ + +---------------------------------------------------------------------- +select name from user where exists (select id from t1) /* non-correlated subquery as EXISTS */ + +1 ks_unsharded/-: select id from t1 limit 10001 /* non-correlated subquery as EXISTS */ +2 ks_sharded/-40: select name from user where 1 limit 10001 /* non-correlated subquery as EXISTS */ +2 ks_sharded/40-80: select name from user where 1 limit 10001 /* non-correlated subquery as EXISTS */ +2 ks_sharded/80-c0: select name from user where 1 limit 10001 /* non-correlated subquery as EXISTS */ +2 ks_sharded/c0-: select name from user where 1 limit 10001 /* non-correlated subquery as EXISTS */ + +---------------------------------------------------------------------- +select * from name_info order by info /* select * and order by varchar column */ + +1 ks_sharded/-40: select name, info, weight_string(info) from name_info order by info asc limit 10001 /* select * and order by varchar column */ +1 ks_sharded/40-80: select name, info, weight_string(info) from name_info order by info asc limit 10001 /* select * and order by varchar column */ +1 ks_sharded/80-c0: select name, info, weight_string(info) from name_info order by info asc limit 10001 /* select * and order by varchar column */ +1 ks_sharded/c0-: select name, info, weight_string(info) from name_info order by info asc limit 10001 /* select * and order by varchar column */ + +---------------------------------------------------------------------- diff --git a/data/test/vtexplain/selectsharded-queries.sql b/data/test/vtexplain/selectsharded-queries.sql index debcb442294..a51570ca833 100644 --- a/data/test/vtexplain/selectsharded-queries.sql +++ b/data/test/vtexplain/selectsharded-queries.sql @@ -13,3 +13,10 @@ select name, count(*) from user group by name /* scatter aggregate */; select 1, "hello", 3.14 from user limit 10 /* select constant sql values */; select * from (select id from user) s /* scatter paren select */; + +select name from user where id = (select id from t1) /* non-correlated subquery as value */; +select name from user where id in (select id from t1) /* non-correlated subquery in IN clause */; +select name from user where id not in (select id from t1) /* non-correlated subquery in NOT IN clause */; +select name from user where exists (select id from t1) /* non-correlated subquery as EXISTS */; + +select * from name_info order by info /* select * and order by varchar column */ diff --git a/data/test/vtexplain/test-vschema.json b/data/test/vtexplain/test-vschema.json index 6c92078f5ce..0ad8a36fc41 100644 --- a/data/test/vtexplain/test-vschema.json +++ b/data/test/vtexplain/test-vschema.json @@ -1,13 +1,13 @@ { "ks_unsharded": { - "Sharded": false, - "Tables": { + "sharded": false, + "tables": { "t1": {}, "table_not_in_schema": {} } }, "ks_sharded": { - "Sharded": true, + "sharded": true, "vindexes": { "music_user_map": { "type": "lookup_hash_unique", @@ -81,7 +81,18 @@ "column": "name", "name": "md5" } - ] + ], + "columns": [ + { + "name": "name", + "type": "VARCHAR" + }, + { + "name": "info", + "type": "VARCHAR" + } + ], + "column_list_authoritative": true } } } diff --git a/data/test/vtgate/dml_cases.txt b/data/test/vtgate/dml_cases.txt index fc06a5f3558..926b934edf2 100644 --- a/data/test/vtgate/dml_cases.txt +++ b/data/test/vtgate/dml_cases.txt @@ -1015,6 +1015,10 @@ } } +# insert into a vindex not allowed +"insert into user_index(id) values(1)" +"inserting into a vindex not allowed: user_index" + # simple replace unsharded "replace into unsharded values(1, 2)" { diff --git a/data/test/vtgate/filter_cases.txt b/data/test/vtgate/filter_cases.txt index fc56c2d397b..48ae26d6f7b 100644 --- a/data/test/vtgate/filter_cases.txt +++ b/data/test/vtgate/filter_cases.txt @@ -730,6 +730,178 @@ } } +# cross-shard subquery in IN clause. +# Note the improved Underlying plan as SelectIN. +"select id from user where id in (select col from user)" +{ + "Original": "select id from user where id in (select col from user)", + "Instructions": { + "Opcode": "PulloutIn", + "SubqueryResult": "__sq1", + "HasValues": "__sq_has_values1", + "Subquery": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col from user", + "FieldQuery": "select col from user where 1 != 1" + }, + "Underlying": { + "Opcode": "SelectIN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select id from user where :__sq_has_values1 = 1 and (id in ::__vals)", + "FieldQuery": "select id from user where 1 != 1", + "Vindex": "user_index", + "Values": [ + "::__sq1" + ] + } + } +} + +# cross-shard subquery in NOT IN clause. +"select id from user where id not in (select col from user)" +{ + "Original": "select id from user where id not in (select col from user)", + "Instructions": { + "Opcode": "PulloutNotIn", + "SubqueryResult": "__sq1", + "HasValues": "__sq_has_values1", + "Subquery": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col from user", + "FieldQuery": "select col from user where 1 != 1" + }, + "Underlying": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select id from user where (:__sq_has_values1 = 0 or (id not in ::__sq1))", + "FieldQuery": "select id from user where 1 != 1" + } + } +} + +# cross-shard subquery in EXISTS clause. +"select id from user where exists (select col from user)" +{ + "Original": "select id from user where exists (select col from user)", + "Instructions": { + "Opcode": "PulloutExists", + "SubqueryResult": "__sq1", + "HasValues": "__sq_has_values1", + "Subquery": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col from user", + "FieldQuery": "select col from user where 1 != 1" + }, + "Underlying": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select id from user where :__sq_has_values1", + "FieldQuery": "select id from user where 1 != 1" + } + } +} + +# cross-shard subquery as expression +"select id from user where id = (select col from user)" +{ + "Original": "select id from user where id = (select col from user)", + "Instructions": { + "Opcode": "PulloutValue", + "SubqueryResult": "__sq1", + "HasValues": "__sq_has_values1", + "Subquery": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col from user", + "FieldQuery": "select col from user where 1 != 1" + }, + "Underlying": { + "Opcode": "SelectEqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select id from user where id = :__sq1", + "FieldQuery": "select id from user where 1 != 1", + "Vindex": "user_index", + "Values": [ + ":__sq1" + ] + } + } +} + +# multi-level pullout +"select id1 from user where id = (select id2 from user where id2 in (select id3 from user))" +{ + "Original": "select id1 from user where id = (select id2 from user where id2 in (select id3 from user))", + "Instructions": { + "Opcode": "PulloutValue", + "SubqueryResult": "__sq2", + "HasValues": "__sq_has_values2", + "Subquery": { + "Opcode": "PulloutIn", + "SubqueryResult": "__sq1", + "HasValues": "__sq_has_values1", + "Subquery": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select id3 from user", + "FieldQuery": "select id3 from user where 1 != 1" + }, + "Underlying": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select id2 from user where :__sq_has_values1 = 1 and (id2 in ::__sq1)", + "FieldQuery": "select id2 from user where 1 != 1" + } + }, + "Underlying": { + "Opcode": "SelectEqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select id1 from user where id = :__sq2", + "FieldQuery": "select id1 from user where 1 != 1", + "Vindex": "user_index", + "Values": [ + ":__sq2" + ] + } + } +} + # Case preservation test "select user_extra.Id from user join user_extra on user.iD = user_extra.User_Id where user.Id = 5" { @@ -766,4 +938,4 @@ # but they refer to different things. The first reference is to the outermost query, # and the second reference is to the the innermost 'from' subquery. "select id2 from user uu where id in (select id from user where id = uu.id and user.col in (select col from (select id from user_extra where user_id = 5) uu where uu.user_id = uu.id))" -"unsupported: UNION or subquery on different shards: vindex values are different" +"unsupported: cross-shard correlated subquery" diff --git a/data/test/vtgate/from_cases.txt b/data/test/vtgate/from_cases.txt index b6ebbe947e6..bc71a4c047f 100644 --- a/data/test/vtgate/from_cases.txt +++ b/data/test/vtgate/from_cases.txt @@ -999,6 +999,241 @@ } } +# subquery in ON clause, single route +"select unsharded_a.col from unsharded_a join unsharded_b on (select col from user)" +{ + "Original": "select unsharded_a.col from unsharded_a join unsharded_b on (select col from user)", + "Instructions": { + "Opcode": "PulloutValue", + "SubqueryResult": "__sq1", + "HasValues": "__sq_has_values1", + "Subquery": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col from user", + "FieldQuery": "select col from user where 1 != 1" + }, + "Underlying": { + "Opcode": "SelectUnsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "Query": "select unsharded_a.col from unsharded_a join unsharded_b on :__sq1", + "FieldQuery": "select unsharded_a.col from unsharded_a join unsharded_b on :__sq1 where 1 != 1" + } + } +} + +# subquery in ON clause as sub-expression +"select unsharded_a.col from unsharded_a join unsharded_b on unsharded_a.col+(select col from user)" +{ + "Original": "select unsharded_a.col from unsharded_a join unsharded_b on unsharded_a.col+(select col from user)", + "Instructions": { + "Opcode": "PulloutValue", + "SubqueryResult": "__sq1", + "HasValues": "__sq_has_values1", + "Subquery": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col from user", + "FieldQuery": "select col from user where 1 != 1" + }, + "Underlying": { + "Opcode": "SelectUnsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "Query": "select unsharded_a.col from unsharded_a join unsharded_b on unsharded_a.col + :__sq1", + "FieldQuery": "select unsharded_a.col from unsharded_a join unsharded_b on unsharded_a.col + :__sq1 where 1 != 1" + } + } +} + +# IN subquery in ON clause, single route +"select unsharded_a.col from unsharded_a join unsharded_b on unsharded_a.col in (select col from user)" +{ + "Original": "select unsharded_a.col from unsharded_a join unsharded_b on unsharded_a.col in (select col from user)", + "Instructions": { + "Opcode": "PulloutIn", + "SubqueryResult": "__sq1", + "HasValues": "__sq_has_values1", + "Subquery": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col from user", + "FieldQuery": "select col from user where 1 != 1" + }, + "Underlying": { + "Opcode": "SelectUnsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "Query": "select unsharded_a.col from unsharded_a join unsharded_b on (:__sq_has_values1 = 1 and (unsharded_a.col in ::__sq1))", + "FieldQuery": "select unsharded_a.col from unsharded_a join unsharded_b on (:__sq_has_values1 = 1 and (unsharded_a.col in ::__sq1)) where 1 != 1" + } + } +} + +# subquery in ON clause, with join primitives +"select unsharded.col from unsharded join user on user.col in (select col from user)" +{ + "Original": "select unsharded.col from unsharded join user on user.col in (select col from user)", + "Instructions": { + "Opcode": "PulloutIn", + "SubqueryResult": "__sq1", + "HasValues": "__sq_has_values1", + "Subquery": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col from user", + "FieldQuery": "select col from user where 1 != 1" + }, + "Underlying": { + "Opcode": "Join", + "Left": { + "Opcode": "SelectUnsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "Query": "select unsharded.col from unsharded", + "FieldQuery": "select unsharded.col from unsharded where 1 != 1" + }, + "Right": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select 1 from user where :__sq_has_values1 = 1 and (user.col in ::__sq1)", + "FieldQuery": "select 1 from user where 1 != 1" + }, + "Cols": [ + -1 + ] + } + } +} + +# subquery in ON clause, with left join primitives +# The subquery is not pulled all the way out. +"select unsharded.col from unsharded left join user on user.col in (select col from user)" +{ + "Original": "select unsharded.col from unsharded left join user on user.col in (select col from user)", + "Instructions": { + "Opcode": "LeftJoin", + "Left": { + "Opcode": "SelectUnsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "Query": "select unsharded.col from unsharded", + "FieldQuery": "select unsharded.col from unsharded where 1 != 1" + }, + "Right": { + "Opcode": "PulloutIn", + "SubqueryResult": "__sq1", + "HasValues": "__sq_has_values1", + "Subquery": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col from user", + "FieldQuery": "select col from user where 1 != 1" + }, + "Underlying": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select 1 from user where :__sq_has_values1 = 1 and (user.col in ::__sq1)", + "FieldQuery": "select 1 from user where 1 != 1" + } + }, + "Cols": [ + -1 + ] + } +} + +# subquery in ON clause, with join primitives, and join on top +# The subquery is not pulled all the way out. +"select unsharded.col from unsharded join user on user.col in (select col from user) join unsharded_a" +{ + "Original": "select unsharded.col from unsharded join user on user.col in (select col from user) join unsharded_a", + "Instructions": { + "Opcode": "Join", + "Left": { + "Opcode": "PulloutIn", + "SubqueryResult": "__sq1", + "HasValues": "__sq_has_values1", + "Subquery": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col from user", + "FieldQuery": "select col from user where 1 != 1" + }, + "Underlying": { + "Opcode": "Join", + "Left": { + "Opcode": "SelectUnsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "Query": "select unsharded.col from unsharded", + "FieldQuery": "select unsharded.col from unsharded where 1 != 1" + }, + "Right": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select 1 from user where :__sq_has_values1 = 1 and (user.col in ::__sq1)", + "FieldQuery": "select 1 from user where 1 != 1" + }, + "Cols": [ + -1 + ] + } + }, + "Right": { + "Opcode": "SelectUnsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "Query": "select 1 from unsharded_a", + "FieldQuery": "select 1 from unsharded_a where 1 != 1" + }, + "Cols": [ + -1 + ] + } +} # keyspace-qualified queries "select user.user.col1, main.unsharded.col1 from user.user join main.unsharded where main.unsharded.col2 = user.user.col2" diff --git a/data/test/vtgate/postprocess_cases.txt b/data/test/vtgate/postprocess_cases.txt index 04c9f81fc0f..4f21d74d498 100644 --- a/data/test/vtgate/postprocess_cases.txt +++ b/data/test/vtgate/postprocess_cases.txt @@ -80,6 +80,39 @@ } } +# HAVING uses subquery +"select id from user having id in (select col from user)" +{ + "Original": "select id from user having id in (select col from user)", + "Instructions": { + "Opcode": "PulloutIn", + "SubqueryResult": "__sq1", + "HasValues": "__sq_has_values1", + "Subquery": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col from user", + "FieldQuery": "select col from user where 1 != 1" + }, + "Underlying": { + "Opcode": "SelectIN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select id from user having :__sq_has_values1 = 1 and (id in ::__vals)", + "FieldQuery": "select id from user where 1 != 1", + "Vindex": "user_index", + "Values": [ + "::__sq1" + ] + } + } +} + # ORDER BY, reference col from local table. "select col from user where id = 5 order by aa" { @@ -135,6 +168,49 @@ } } +# ORDER BY works for select * from authoritative table +"select * from authoritative order by user_id" +{ + "Original": "select * from authoritative order by user_id", + "Instructions": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select user_id, col1, col2 from authoritative order by user_id asc", + "FieldQuery": "select user_id, col1, col2 from authoritative where 1 != 1", + "OrderBy": [ + { + "Col": 0, + "Desc": false + } + ] + } +} + +# ORDER BY works for select * from authoritative table +"select * from authoritative order by col1" +{ + "Original": "select * from authoritative order by col1", + "Instructions": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select user_id, col1, col2, weight_string(col1) from authoritative order by col1 asc", + "FieldQuery": "select user_id, col1, col2, weight_string(col1) from authoritative where 1 != 1", + "OrderBy": [ + { + "Col": 3, + "Desc": false + } + ], + "TruncateColumnCount": 3 + } +} + # ORDER BY on scatter with text column "select a, textcol1, b from user order by a, textcol1, b" { @@ -248,6 +324,41 @@ } } +# ORDER BY after pull-out subquery +"select col from user where col in (select col2 from user) order by col" +{ + "Original": "select col from user where col in (select col2 from user) order by col", + "Instructions": { + "Opcode": "PulloutIn", + "SubqueryResult": "__sq1", + "HasValues": "__sq_has_values1", + "Subquery": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col2 from user", + "FieldQuery": "select col2 from user where 1 != 1" + }, + "Underlying": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col from user where :__sq_has_values1 = 1 and (col in ::__sq1) order by col asc", + "FieldQuery": "select col from user where 1 != 1", + "OrderBy": [ + { + "Col": 0, + "Desc": false + } + ] + } + } +} + # ORDER BY NULL for join "select user.col1 as a, user.col2, music.col3 from user join music on user.id = music.id where user.id = 1 order by null" { @@ -287,6 +398,35 @@ } } +# ORDER BY NULL after pull-out subquery +"select col from user where col in (select col2 from user) order by null" +{ + "Original": "select col from user where col in (select col2 from user) order by null", + "Instructions": { + "Opcode": "PulloutIn", + "SubqueryResult": "__sq1", + "HasValues": "__sq_has_values1", + "Subquery": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col2 from user", + "FieldQuery": "select col2 from user where 1 != 1" + }, + "Underlying": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col from user where :__sq_has_values1 = 1 and (col in ::__sq1) order by null", + "FieldQuery": "select col from user where 1 != 1" + } + } +} + # ORDER BY RAND() "select col from user order by RAND()" { @@ -341,6 +481,35 @@ } } +# ORDER BY RAND() after pull-out subquery +"select col from user where col in (select col2 from user) order by rand()" +{ + "Original": "select col from user where col in (select col2 from user) order by rand()", + "Instructions": { + "Opcode": "PulloutIn", + "SubqueryResult": "__sq1", + "HasValues": "__sq_has_values1", + "Subquery": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col2 from user", + "FieldQuery": "select col2 from user where 1 != 1" + }, + "Underlying": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col from user where :__sq_has_values1 = 1 and (col in ::__sq1) order by rand()", + "FieldQuery": "select col from user where 1 != 1" + } + } +} + # Order by, '*' expression "select * from user where id = 5 order by col" { @@ -640,6 +809,40 @@ } } +# scatter limit after pullout subquery +"select col from user where col in (select col1 from user) limit 1" +{ + "Original": "select col from user where col in (select col1 from user) limit 1", + "Instructions": { + "Opcode": "Limit", + "Count": 1, + "Offset": null, + "Input": { + "Opcode": "PulloutIn", + "SubqueryResult": "__sq1", + "HasValues": "__sq_has_values1", + "Subquery": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col1 from user", + "FieldQuery": "select col1 from user where 1 != 1" + }, + "Underlying": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col from user where :__sq_has_values1 = 1 and (col in ::__sq1) limit :__upper_limit", + "FieldQuery": "select col from user where 1 != 1" + } + } + } +} + # invalid limit expression "select id from user limit 1+1" "unexpected expression in LIMIT: limit 1 + 1" diff --git a/data/test/vtgate/schema_test.json b/data/test/vtgate/schema_test.json index 0369446dcb5..01b19f5c2c2 100644 --- a/data/test/vtgate/schema_test.json +++ b/data/test/vtgate/schema_test.json @@ -128,6 +128,27 @@ } ] }, + "authoritative": { + "column_vindexes": [ + { + "column": "user_id", + "name": "user_index" + } + ], + "columns": [ + { + "name": "user_id" + }, + { + "name": "col1", + "type": "VARCHAR" + }, + { + "name": "col2" + } + ], + "column_list_authoritative": true + }, "multicolvin": { "column_vindexes": [ { diff --git a/data/test/vtgate/select_cases.txt b/data/test/vtgate/select_cases.txt index 92da4638a33..3380f73e25a 100644 --- a/data/test/vtgate/select_cases.txt +++ b/data/test/vtgate/select_cases.txt @@ -183,9 +183,9 @@ } # fully qualified '*' expression for simple route -"select user.user.* from user" +"select user.user.* from user.user" { - "Original": "select user.user.* from user", + "Original": "select user.user.* from user.user", "Instructions": { "Opcode": "SelectScatter", "Keyspace": { @@ -197,9 +197,84 @@ } } -# invalid keyspace for '*' expression for simple route -"select a.user.* from user" -"cannot resolve a.user.* to keyspace user" +# select * from authoritative table +"select * from authoritative" +{ + "Original": "select * from authoritative", + "Instructions": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select user_id, col1, col2 from authoritative", + "FieldQuery": "select user_id, col1, col2 from authoritative where 1 != 1" + } +} + +# select * from join of authoritative tables +"select * from authoritative a join authoritative b on a.user_id=b.user_id" +{ + "Original": "select * from authoritative a join authoritative b on a.user_id=b.user_id", + "Instructions": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select a.user_id as user_id, a.col1 as col1, a.col2 as col2, b.user_id as user_id, b.col1 as col1, b.col2 as col2 from authoritative as a join authoritative as b on a.user_id = b.user_id", + "FieldQuery": "select a.user_id as user_id, a.col1 as col1, a.col2 as col2, b.user_id as user_id, b.col1 as col1, b.col2 as col2 from authoritative as a join authoritative as b on a.user_id = b.user_id where 1 != 1" + } +} + +# test table lookup failure for authoritative code path +"select a.* from authoritative" +"table a not found" + +# select * from qualified authoritative table +"select a.* from authoritative a" +{ + "Original": "select a.* from authoritative a", + "Instructions": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select a.user_id, a.col1, a.col2 from authoritative as a", + "FieldQuery": "select a.user_id, a.col1, a.col2 from authoritative as a where 1 != 1" + } +} + +# select * from intermixing of authoritative table with non-authoritative results in no expansion +"select * from authoritative join user on authoritative.user_id=user.id" +{ + "Original": "select * from authoritative join user on authoritative.user_id=user.id", + "Instructions": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select * from authoritative join user on authoritative.user_id = user.id", + "FieldQuery": "select * from authoritative join user on authoritative.user_id = user.id where 1 != 1" + } +} + +# select authoritative.* with intermixing still expands +"select user.id, a.*, user.col1 from authoritative a join user on a.user_id=user.id" +{ + "Original": "select user.id, a.*, user.col1 from authoritative a join user on a.user_id=user.id", + "Instructions": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select user.id, a.user_id, a.col1, a.col2, user.col1 from authoritative as a join user on a.user_id = user.id", + "FieldQuery": "select user.id, a.user_id, a.col1, a.col2, user.col1 from authoritative as a join user on a.user_id = user.id where 1 != 1" + } +} # auto-resolve anonymous columns for simple route "select col from user join user_extra on user.id = user_extra.user_id" @@ -620,6 +695,7 @@ "FieldQuery": "select * from user where 1 != 1" } } + # sharded limit offset "select user_id from music order by user_id limit 10, 20" { @@ -784,9 +860,108 @@ } } +# top level subquery in select +"select a, (select col from user) from unsharded" +{ + "Original": "select a, (select col from user) from unsharded", + "Instructions": { + "Opcode": "PulloutValue", + "SubqueryResult": "__sq1", + "HasValues": "__sq_has_values1", + "Subquery": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col from user", + "FieldQuery": "select col from user where 1 != 1" + }, + "Underlying": { + "Opcode": "SelectUnsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "Query": "select a, :__sq1 from unsharded", + "FieldQuery": "select a, :__sq1 from unsharded where 1 != 1" + } + } +} + +# sub-expression subquery in select +"select a, 1+(select col from user) from unsharded" +{ + "Original": "select a, 1+(select col from user) from unsharded", + "Instructions": { + "Opcode": "PulloutValue", + "SubqueryResult": "__sq1", + "HasValues": "__sq_has_values1", + "Subquery": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col from user", + "FieldQuery": "select col from user where 1 != 1" + }, + "Underlying": { + "Opcode": "SelectUnsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "Query": "select a, 1 + :__sq1 from unsharded", + "FieldQuery": "select a, 1 + :__sq1 from unsharded where 1 != 1" + } + } +} + +# select * from subquery expands specific columns +"select * from (select user.id id1, user_extra.id id2 from user join user_extra) as t" +{ + "Original": "select * from (select user.id id1, user_extra.id id2 from user join user_extra) as t", + "Instructions": { + "Cols": [ + 0, + 1 + ], + "Subquery": { + "Opcode": "Join", + "Left": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select user.id as id1 from user", + "FieldQuery": "select user.id as id1 from user where 1 != 1" + }, + "Right": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select user_extra.id as id2 from user_extra", + "FieldQuery": "select user_extra.id as id2 from user_extra where 1 != 1" + }, + "Cols": [ + -1, + 1 + ] + } + } +} + +# duplicate columns not allowed in subquery +"select * from (select user.id, user_extra.id from user join user_extra) as t" +"duplicate column names in subquery: id" + # non-existent symbol in cross-shard subquery "select t.col from (select user.id from user join user_extra) as t" -"symbol t.col is referencing a non-existent column of the subquery" +"symbol t.col not found in table or subquery" # union of information_schema "select * from information_schema.a union select * from information_schema.b" diff --git a/data/test/vtgate/unsupported_cases.txt b/data/test/vtgate/unsupported_cases.txt index d6a610a2aae..a2e4049a5aa 100644 --- a/data/test/vtgate/unsupported_cases.txt +++ b/data/test/vtgate/unsupported_cases.txt @@ -35,10 +35,6 @@ "select * from user where id in (select * from user union select * from user_extra)" "unsupported: UNION or subquery containing multi-shard queries" -# subquery with join primitive (expressions) -"select * from user where id in (select user.id from user join user_extra)" -"unsupported: cross-shard query in subqueries" - # TODO: Implement support for select with a target destination "select * from `user[-]`.user_metadata" "unsupported: SELECT with a target destination" @@ -55,38 +51,6 @@ "DELETE FROM `user[-]@replica`.user_metadata limit 1" "unsupported: DELETE statement with a replica target" -# subquery keyspace different from outer query -"select * from user where id in (select m from unsharded)" -"unsupported: subquery keyspace different from outer query" - -# scatter subquery -"select * from user where id in (select id from user)" -"unsupported: scatter subquery" - -# complex on clause on join -"select c from user join user_extra on user.id in (select id from user)" -"unsupported: scatter subquery" - -# complex on clause on left join -"select c from user left join user_extra on user.id in (select id from user)" -"unsupported: scatter subquery" - -# merging routes, but complex on clause -"select user.id from user join user_extra on user_extra.user_id = user.id and user.id in (select id from user)" -"unsupported: scatter subquery" - -# subquery does not depend on unique vindex of outer query -"select id from user where id in (select user_id from user_extra where user_extra.user_id = user.col)" -"unsupported: UNION or subquery containing multi-shard queries" - -# subquery does not depend on scatter outer query -"select id from user where id in (select user_id from user_extra where user_extra.user_id = 4)" -"unsupported: UNION or subquery containing multi-shard queries" - -# subquery depends on a cross-shard subquery -"select id from (select user.id, user.col from user join user_extra) as t where id in (select t.col from user)" -"unsupported: subquery cannot be merged with cross-shard subquery" - # order by on a cross-shard subquery "select id from (select user.id, user.col from user join user_extra) as t order by id" "unsupported: cannot order by on a cross-shard subquery" @@ -103,10 +67,6 @@ "select id+1 from (select user.id, user.col from user join user_extra) as t" "unsupported: expression on results of a cross-shard subquery" -# subquery and outer query route to different shards -"select id from user where id = 5 and id in (select user_id from user_extra where user_extra.user_id = 4)" -"unsupported: UNION or subquery on different shards: vindex values are different" - # last_insert_id for sharded keyspace "select last_insert_id() from user" "unsupported: LAST_INSERT_ID is only allowed for unsharded keyspaces" @@ -119,10 +79,6 @@ "select foo from unsharded join information_schema.a" "unsupported: intermixing of information_schema and regular tables" -# scatter subquery in select -"select id, (select id from user) from user" -"unsupported: scatter subquery" - # natural join "select * from user natural join user_extra" "unsupported: natural join" @@ -135,6 +91,10 @@ "select * from user natural right join user_extra" "unsupported: natural right join" +# join with USING construct +"select * from user join user_extra using(id)" +"unsupported: join with USING(column_list) clause" + # left join with expressions "select user.id, user_extra.col+1 from user left join user_extra on user.col = user_extra.col" "unsupported: cross-shard left join and column expressions" @@ -223,14 +183,6 @@ "select id from user group by id, (select id from user_extra)" "unsupported: in group by: subqueries disallowed" -# subquery of information_schema with normal table -"select (select * from unsharded) from information_schema.a" -"unsupported: intermixing of information_schema and regular tables" - -# subquery of normal table with information_schema -"select (select * from information_schema.a) from unsharded" -"unsupported: intermixing of information_schema and regular tables" - # Order by uses cross-shard expression "select id from user order by id+1" "unsupported: in scatter query: complex order by expression: id + 1" @@ -255,10 +207,6 @@ "select id from unsharded order by (select id from unsharded)" "unsupported: order by has subquery" -# sequence in subquery -"select col from unsharded where id in (select next value from seq)" -"unsupported: use of sequence in subquery" - # subqueries in update "update user set col = (select id from unsharded)" "unsupported: subqueries in sharded DML" @@ -305,7 +253,7 @@ # multi delete multi table "delete user from user join user_extra on user.id = user_extra.id where user.name = 'foo'" -"unsupported: multi-table delete statement in sharded keyspace" +"unsupported: multi-table/vindex delete statement in sharded keyspace" # scatter delete with owned lookup vindex "delete from user" @@ -341,11 +289,11 @@ # join in update tables "update user join user_extra on user.id = user_extra.id set user.name = 'foo'" -"unsupported: multi-table update statement in sharded keyspace" +"unsupported: multi-table/vindex update statement in sharded keyspace" # multiple tables in update "update user as u, user_extra as ue set u.name = 'foo' where u.id = ue.id" -"unsupported: multi-table update statement in sharded keyspace" +"unsupported: multi-table/vindex update statement in sharded keyspace" # unsharded insert with cross-shard join" "insert into unsharded select u.col from user u join user u1" @@ -477,10 +425,10 @@ "select keyspace_id from user_index where 1 = id" "unsupported: where clause for vindex function must be of the form id = (lhs is not a column)" -"select keyspace_id from user_index where none = 1" +"select keyspace_id from user_index where keyspace_id = 1" "unsupported: where clause for vindex function must be of the form id = (lhs is not id)" -"select keyspace_id from user_index where id = a" +"select keyspace_id from user_index where id = id+1" "unsupported: where clause for vindex function must be of the form id = (rhs is not a value)" "select keyspace_id from user_index" diff --git a/data/test/vtgate/vindex_func_cases.txt b/data/test/vtgate/vindex_func_cases.txt index d8058321a48..7ba9beae062 100644 --- a/data/test/vtgate/vindex_func_cases.txt +++ b/data/test/vtgate/vindex_func_cases.txt @@ -33,6 +33,41 @@ } } +# vindex func select * +"select * from user_index where id = :id" +{ + "Original": "select * from user_index where id = :id", + "Instructions": { + "Opcode": "VindexMap", + "Fields": [ + { + "name": "id", + "type": 10262 + }, + { + "name": "keyspace_id", + "type": 10262 + }, + { + "name": "range_start", + "type": 10262 + }, + { + "name": "range_end", + "type": 10262 + } + ], + "Cols": [ + 0, + 1, + 2, + 3 + ], + "Vindex": "user_index", + "Value": ":id" + } +} + # vindex func read with id repeated "select id, keyspace_id, id from user_index where id = :id" { @@ -320,4 +355,4 @@ } "select none from user_index where id = :id" -"unrecognized column none for vindex: user_index" +"symbol none not found in table or subquery" diff --git a/data/test/vtgate/wireup_cases.txt b/data/test/vtgate/wireup_cases.txt index aa30c0d33ed..5d82b447ffb 100644 --- a/data/test/vtgate/wireup_cases.txt +++ b/data/test/vtgate/wireup_cases.txt @@ -446,6 +446,117 @@ } } +# Wire-up in subquery +"select 1 from user where id in (select u.id, e.id from user u join user_extra e where e.id = u.col limit 10)" +{ + "Original": "select 1 from user where id in (select u.id, e.id from user u join user_extra e where e.id = u.col limit 10)", + "Instructions": { + "Opcode": "PulloutIn", + "SubqueryResult": "__sq1", + "HasValues": "__sq_has_values1", + "Subquery": { + "Opcode": "Limit", + "Count": 10, + "Offset": null, + "Input": { + "Opcode": "Join", + "Left": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select u.id, u.col from user as u", + "FieldQuery": "select u.id, u.col from user as u where 1 != 1" + }, + "Right": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select e.id from user_extra as e where e.id = :u_col", + "FieldQuery": "select e.id from user_extra as e where 1 != 1" + }, + "Cols": [ + -1, + 1 + ], + "Vars": { + "u_col": 1 + } + } + }, + "Underlying": { + "Opcode": "SelectIN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select 1 from user where :__sq_has_values1 = 1 and (id in ::__vals)", + "FieldQuery": "select 1 from user where 1 != 1", + "Vindex": "user_index", + "Values": [ + "::__sq1" + ] + } + } +} + +# Wire-up in in underlying primitive after pullout +"select u.id, e.id, (select col from user) from user u join user_extra e where e.id = u.col limit 10" +{ + "Original": "select u.id, e.id, (select col from user) from user u join user_extra e where e.id = u.col limit 10", + "Instructions": { + "Opcode": "Limit", + "Count": 10, + "Offset": null, + "Input": { + "Opcode": "PulloutValue", + "SubqueryResult": "__sq1", + "HasValues": "__sq_has_values1", + "Subquery": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select col from user", + "FieldQuery": "select col from user where 1 != 1" + }, + "Underlying": { + "Opcode": "Join", + "Left": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select u.id, :__sq1, u.col from user as u", + "FieldQuery": "select u.id, :__sq1, u.col from user as u where 1 != 1" + }, + "Right": { + "Opcode": "SelectScatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "select e.id from user_extra as e where e.id = :u_col", + "FieldQuery": "select e.id from user_extra as e where 1 != 1" + }, + "Cols": [ + -1, + 1, + -2 + ], + "Vars": { + "u_col": 2 + } + } + } + } +} + # Invalid value in IN clause "select id from user where id in (18446744073709551616, 1)" "strconv.ParseUint: parsing "18446744073709551616": value out of range" diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 84e3f7670af..ab9a9d4823e 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -8,6 +8,9 @@ # https://github.com/docker/hub-feedback/issues/292 is fixed. FROM vitess/bootstrap:mysql57 +# Allows some docker builds to disable CGO +ARG CGO_ENABLED=0 + # Re-copy sources from working tree USER root COPY . /vt/src/vitess.io/vitess diff --git a/docker/k8s/Dockerfile b/docker/k8s/Dockerfile index 826c6cadd74..0eee530a1bc 100644 --- a/docker/k8s/Dockerfile +++ b/docker/k8s/Dockerfile @@ -2,6 +2,14 @@ FROM vitess/base AS base FROM debian:stretch-slim +# TODO: remove when https://github.com/vitessio/vitess/issues/3553 is fixed +RUN apt-get update && \ + apt-get upgrade -qq && \ + apt-get install mysql-client -qq --no-install-recommends && \ + apt-get autoremove && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + # Set up Vitess environment (just enough to run pre-built Go binaries) ENV VTROOT /vt ENV VTDATAROOT /vtdataroot @@ -39,6 +47,7 @@ COPY --from=base $VTTOP/config/mycnf/master_mariadb103.cnf /vt/config/mycnf/ # settings for different types of instances COPY --from=base $VTTOP/config/mycnf/default.cnf /vt/config/mycnf/ +COPY --from=base $VTTOP/config/mycnf/default-fast.cnf /vt/config/mycnf/ COPY --from=base $VTTOP/config/mycnf/master.cnf /vt/config/mycnf/ COPY --from=base $VTTOP/config/mycnf/replica.cnf /vt/config/mycnf/ COPY --from=base $VTTOP/config/mycnf/rdonly.cnf /vt/config/mycnf/ @@ -48,13 +57,5 @@ COPY --from=base $VTTOP/config/mycnf/backup.cnf /vt/config/mycnf/ COPY --from=base $VTTOP/config/mycnf/rbr.cnf /vt/config/mycnf/ # add vitess user and add permissions -RUN groupadd -r --gid 999 vitess && useradd -r -g vitess --uid 999 vitess && \ +RUN groupadd -r --gid 2000 vitess && useradd -r -g vitess --uid 1000 vitess && \ chown -R vitess:vitess /vt; - -# TODO: remove when https://github.com/vitessio/vitess/issues/3553 is fixed -RUN apt-get update && \ - apt-get upgrade -qq && \ - apt-get install mysql-client -qq --no-install-recommends && \ - apt-get autoremove && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* diff --git a/docker/k8s/logrotate/Dockerfile b/docker/k8s/logrotate/Dockerfile new file mode 100644 index 00000000000..c955e82625d --- /dev/null +++ b/docker/k8s/logrotate/Dockerfile @@ -0,0 +1,19 @@ +FROM debian:stretch-slim + +ADD logrotate.conf /vt/logrotate.conf + +ADD rotate.sh /vt/rotate.sh + +RUN mkdir -p /vt && \ + apt-get update && \ + apt-get upgrade -qq && \ + apt-get install logrotate -qq --no-install-recommends && \ + apt-get autoremove -qq && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* && \ + groupadd -r --gid 2000 vitess && \ + useradd -r -g vitess --uid 1000 vitess && \ + chown -R vitess:vitess /vt && \ + chmod +x /vt/rotate.sh + +ENTRYPOINT [ "/vt/rotate.sh" ] \ No newline at end of file diff --git a/docker/k8s/logrotate/logrotate.conf b/docker/k8s/logrotate/logrotate.conf new file mode 100644 index 00000000000..7f39d6d337a --- /dev/null +++ b/docker/k8s/logrotate/logrotate.conf @@ -0,0 +1,13 @@ +/vtdataroot/tabletdata/*.log { + create 660 vitess vitess + daily + size 100M + rotate 1 + missingok + nocompress + notifempty + sharedscripts + postrotate + /usr/bin/mysql --socket=/vtdataroot/tabletdata/mysql.sock -uroot -e 'select @@global.long_query_time into @lqt_save; set global long_query_time=2000; select sleep(2); FLUSH LOGS; select sleep(2); set global long_query_time=@lqt_save;' + endscript +} diff --git a/docker/k8s/logrotate/rotate.sh b/docker/k8s/logrotate/rotate.sh new file mode 100644 index 00000000000..917ae0ec009 --- /dev/null +++ b/docker/k8s/logrotate/rotate.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -ex + +# SIGTERM-handler +term_handler() { + exit; +} + +# setup handlers +# on callback, kill the last background process, which is `tail -f /dev/null` and execute the specified handler +trap 'kill ${!}; term_handler' SIGINT SIGTERM SIGHUP + +# wait forever +while true +do + /usr/sbin/logrotate -s /vt/logrotate.status /vt/logrotate.conf + # run once an hour + sleep 3600 & wait ${!} +done \ No newline at end of file diff --git a/docker/k8s/logtail/Dockerfile b/docker/k8s/logtail/Dockerfile new file mode 100644 index 00000000000..7de51b55ecd --- /dev/null +++ b/docker/k8s/logtail/Dockerfile @@ -0,0 +1,19 @@ +FROM debian:stretch-slim + +ENV TAIL_FILEPATH /dev/null + +ADD tail.sh /vt/tail.sh + +RUN mkdir -p /vt && \ + apt-get update && \ + apt-get upgrade -qq && \ + apt-get install mysql-client -qq --no-install-recommends && \ + apt-get autoremove -qq && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* && \ + groupadd -r --gid 2000 vitess && \ + useradd -r -g vitess --uid 1000 vitess && \ + chown -R vitess:vitess /vt && \ + chmod +x /vt/tail.sh + +ENTRYPOINT [ "/vt/tail.sh" ] \ No newline at end of file diff --git a/docker/k8s/logtail/tail.sh b/docker/k8s/logtail/tail.sh new file mode 100644 index 00000000000..03cb7ffcafd --- /dev/null +++ b/docker/k8s/logtail/tail.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -ex + +# SIGTERM-handler +term_handler() { + # block shutting down log tailers until mysql shuts down first + until [ "$MYSQL_GONE" ]; do + sleep 5 + + # poll every 5 seconds to see if mysql is still running + if ! mysqladmin ping -uroot --socket=/vtdataroot/tabletdata/mysql.sock; then + MYSQL_GONE=true + fi + done + + exit; +} + +# setup handlers +# on callback, kill the last background process and execute the specified handler +trap 'kill ${!}; term_handler' SIGINT SIGTERM SIGHUP + +# wait forever +while true +do + tail -n+1 -F "$TAIL_FILEPATH" & wait ${!} +done \ No newline at end of file diff --git a/docker/k8s/mysqlctld/Dockerfile b/docker/k8s/mysqlctld/Dockerfile index 0fc362657d2..59c495de14e 100644 --- a/docker/k8s/mysqlctld/Dockerfile +++ b/docker/k8s/mysqlctld/Dockerfile @@ -2,13 +2,20 @@ FROM vitess/k8s AS k8s FROM debian:stretch-slim +RUN apt-get update && \ + apt-get upgrade -qq && \ + apt-get install busybox -qq --no-install-recommends && \ + apt-get autoremove && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + # Set up Vitess environment (just enough to run pre-built Go binaries) ENV VTROOT /vt ENV VTDATAROOT /vtdataroot # Prepare directory structure. RUN mkdir -p /vt/bin && \ - mkdir -p /vt/config + mkdir -p /vt/config && mkdir -p /vtdataroot # Copy binaries COPY --from=k8s /vt/bin/mysqlctld /vt/bin/ @@ -18,3 +25,9 @@ COPY --from=k8s /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificate # copy vitess config COPY --from=k8s /vt/config /vt/config + +# add vitess user/group and add permissions +RUN groupadd -r --gid 2000 vitess && \ + useradd -r -g vitess --uid 1000 vitess && \ + chown -R vitess:vitess /vt && \ + chown -R vitess:vitess /vtdataroot \ No newline at end of file diff --git a/docker/k8s/orchestrator/Dockerfile b/docker/k8s/orchestrator/Dockerfile index f4bb465ddac..02c4e1e3851 100644 --- a/docker/k8s/orchestrator/Dockerfile +++ b/docker/k8s/orchestrator/Dockerfile @@ -2,22 +2,22 @@ FROM vitess/k8s AS k8s FROM debian:stretch-slim +RUN apt-get update && \ + apt-get upgrade -qq && \ + apt-get install wget -qq --no-install-recommends && \ + wget https://github.com/github/orchestrator/releases/download/v3.0.13/orchestrator_3.0.13_amd64.deb && \ + dpkg -i orchestrator_3.0.13_amd64.deb && \ + rm orchestrator_3.0.13_amd64.deb && \ + apt-get purge wget -qq && \ + apt-get autoremove -qq && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + # Copy certs to allow https calls COPY --from=k8s /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt # Copy vtctlclient to be used to notify COPY --from=k8s /vt/bin/vtctlclient /usr/bin/ -RUN apt-get update && \ - apt-get upgrade -qq && \ - apt-get install wget -qq --no-install-recommends && \ - wget https://github.com/github/orchestrator/releases/download/v3.0.13/orchestrator_3.0.13_amd64.deb && \ - dpkg -i orchestrator_3.0.13_amd64.deb && \ - rm orchestrator_3.0.13_amd64.deb && \ - apt-get purge wget -qq && \ - apt-get autoremove -qq && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - WORKDIR /usr/local/orchestrator CMD ["./orchestrator", "--config=/conf/orchestrator.conf.json", "http"] diff --git a/docker/k8s/pmm-client/Dockerfile b/docker/k8s/pmm-client/Dockerfile index e5e4418b7e4..5e5fa4f800e 100644 --- a/docker/k8s/pmm-client/Dockerfile +++ b/docker/k8s/pmm-client/Dockerfile @@ -2,16 +2,17 @@ FROM vitess/k8s AS k8s FROM debian:stretch-slim +RUN apt-get update && \ + apt-get upgrade -qq && \ + apt-get install -y procps && \ + apt-get install wget -qq --no-install-recommends && \ + wget https://www.percona.com/redir/downloads/pmm-client/1.15.0/binary/debian/stretch/x86_64/pmm-client_1.15.0-1.stretch_amd64.deb && \ + dpkg -i pmm-client_1.15.0-1.stretch_amd64.deb && \ + rm pmm-client_1.15.0-1.stretch_amd64.deb && \ + apt-get purge wget -qq && \ + apt-get autoremove -qq && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + # Copy CA certs for https calls COPY --from=k8s /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt - -RUN apt-get update && \ - apt-get upgrade -qq && \ - apt-get install wget -qq --no-install-recommends && \ - wget https://www.percona.com/redir/downloads/pmm-client/1.15.0/binary/debian/stretch/x86_64/pmm-client_1.15.0-1.stretch_amd64.deb && \ - dpkg -i pmm-client_1.15.0-1.stretch_amd64.deb && \ - rm pmm-client_1.15.0-1.stretch_amd64.deb && \ - apt-get purge wget -qq && \ - apt-get autoremove -qq && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* diff --git a/docker/k8s/vtctl/Dockerfile b/docker/k8s/vtctl/Dockerfile index d8a4a900090..ea52256e810 100644 --- a/docker/k8s/vtctl/Dockerfile +++ b/docker/k8s/vtctl/Dockerfile @@ -6,10 +6,16 @@ FROM debian:stretch-slim ENV VTROOT /vt # Prepare directory structure. -RUN mkdir -p /vt/bin +RUN mkdir -p /vt/bin && mkdir -p /vtdataroot # Copy certs to allow https calls COPY --from=k8s /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt # Copy binaries COPY --from=k8s /vt/bin/vtctl /vt/bin/ + +# add vitess user/group and add permissions +RUN groupadd -r --gid 2000 vitess && \ + useradd -r -g vitess --uid 1000 vitess && \ + chown -R vitess:vitess /vt && \ + chown -R vitess:vitess /vtdataroot \ No newline at end of file diff --git a/docker/k8s/vtctlclient/Dockerfile b/docker/k8s/vtctlclient/Dockerfile index 1af7e5ae076..bfe12e24f26 100644 --- a/docker/k8s/vtctlclient/Dockerfile +++ b/docker/k8s/vtctlclient/Dockerfile @@ -1,10 +1,20 @@ -FROM vitess/base AS base +FROM vitess/k8s AS k8s + FROM debian:stretch-slim -COPY --from=base /vt/bin/vtctlclient /usr/bin/ + RUN apt-get update && \ - apt-get upgrade -qq && \ - apt-get install jq -qq --no-install-recommends && \ - apt-get autoremove && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* + apt-get upgrade -qq && \ + apt-get install jq -qq --no-install-recommends && \ + apt-get autoremove && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +COPY --from=k8s /vt/bin/vtctlclient /usr/bin/ + +# add vitess user/group and add permissions +RUN groupadd -r --gid 2000 vitess && \ + useradd -r -g vitess --uid 1000 vitess && \ + chown -R vitess:vitess /vt && \ + chown -R vitess:vitess /vtdataroot + CMD ["/usr/bin/vtctlclient"] diff --git a/docker/k8s/vtctld/Dockerfile b/docker/k8s/vtctld/Dockerfile index 34828a62bb9..2e14365596c 100644 --- a/docker/k8s/vtctld/Dockerfile +++ b/docker/k8s/vtctld/Dockerfile @@ -7,7 +7,7 @@ ENV VTROOT /vt # Prepare directory structure. RUN mkdir -p /vt/bin && \ - mkdir -p /vt/web + mkdir -p /vt/web && mkdir -p /vtdataroot # Copy binaries COPY --from=k8s /vt/bin/vtctld /vt/bin/ @@ -16,4 +16,10 @@ COPY --from=k8s /vt/bin/vtctld /vt/bin/ COPY --from=k8s /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt # copy web admin files -COPY --from=k8s /vt/web /vt/web \ No newline at end of file +COPY --from=k8s /vt/web /vt/web + +# add vitess user/group and add permissions +RUN groupadd -r --gid 2000 vitess && \ + useradd -r -g vitess --uid 1000 vitess && \ + chown -R vitess:vitess /vt && \ + chown -R vitess:vitess /vtdataroot \ No newline at end of file diff --git a/docker/k8s/vtgate/Dockerfile b/docker/k8s/vtgate/Dockerfile index ed674428ffb..d876d0521a2 100644 --- a/docker/k8s/vtgate/Dockerfile +++ b/docker/k8s/vtgate/Dockerfile @@ -6,10 +6,16 @@ FROM debian:stretch-slim ENV VTROOT /vt # Prepare directory structure. -RUN mkdir -p /vt/bin +RUN mkdir -p /vt/bin && mkdir -p /vtdataroot # Copy certs to allow https calls COPY --from=k8s /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt # Copy binaries COPY --from=k8s /vt/bin/vtgate /vt/bin/ + +# add vitess user/group and add permissions +RUN groupadd -r --gid 2000 vitess && \ + useradd -r -g vitess --uid 1000 vitess && \ + chown -R vitess:vitess /vt && \ + chown -R vitess:vitess /vtdataroot \ No newline at end of file diff --git a/docker/k8s/vttablet/Dockerfile b/docker/k8s/vttablet/Dockerfile index c55a41f5c94..887a9f45894 100644 --- a/docker/k8s/vttablet/Dockerfile +++ b/docker/k8s/vttablet/Dockerfile @@ -2,23 +2,30 @@ FROM vitess/k8s AS k8s FROM debian:stretch-slim +# TODO: remove when https://github.com/vitessio/vitess/issues/3553 is fixed +RUN apt-get update && \ + apt-get upgrade -qq && \ + apt-get install mysql-client jq -qq --no-install-recommends && \ + apt-get autoremove && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + # Set up Vitess environment (just enough to run pre-built Go binaries) ENV VTROOT /vt ENV VTDATAROOT /vtdataroot # Prepare directory structure. -RUN mkdir -p /vt/bin +RUN mkdir -p /vt/bin && mkdir -p /vtdataroot # Copy binaries COPY --from=k8s /vt/bin/vttablet /vt/bin/ +COPY --from=k8s /vt/bin/vtctlclient /vt/bin/ # Copy certs to allow https calls COPY --from=k8s /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt -# TODO: remove when https://github.com/vitessio/vitess/issues/3553 is fixed -RUN apt-get update && \ - apt-get upgrade -qq && \ - apt-get install mysql-client -qq --no-install-recommends && \ - apt-get autoremove && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* \ No newline at end of file +# add vitess user/group and add permissions +RUN groupadd -r --gid 2000 vitess && \ + useradd -r -g vitess --uid 1000 vitess && \ + chown -R vitess:vitess /vt && \ + chown -R vitess:vitess /vtdataroot diff --git a/docker/k8s/vtworker/Dockerfile b/docker/k8s/vtworker/Dockerfile index 4f5ceb427d4..f1fab9bceb4 100644 --- a/docker/k8s/vtworker/Dockerfile +++ b/docker/k8s/vtworker/Dockerfile @@ -6,10 +6,16 @@ FROM debian:stretch-slim ENV VTROOT /vt # Prepare directory structure. -RUN mkdir -p /vt/bin +RUN mkdir -p /vt/bin && mkdir -p /vtdataroot # Copy certs to allow https calls COPY --from=k8s /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt # Copy binaries -COPY --from=k8s /vt/bin/vtworker /vt/bin/ \ No newline at end of file +COPY --from=k8s /vt/bin/vtworker /vt/bin/ + +# add vitess user/group and add permissions +RUN groupadd -r --gid 2000 vitess && \ + useradd -r -g vitess --uid 1000 vitess && \ + chown -R vitess:vitess /vt && \ + chown -R vitess:vitess /vtdataroot \ No newline at end of file diff --git a/examples/compose/vttablet-up.sh b/examples/compose/vttablet-up.sh index 91a3c1870e5..efdb0f4d498 100755 --- a/examples/compose/vttablet-up.sh +++ b/examples/compose/vttablet-up.sh @@ -36,6 +36,13 @@ if [ -d $VTDATAROOT/$tablet_dir ]; then action='start' fi +export KEYSPACE=$keyspace +export SHARD=$shard +export TABLET_ID=$alias +export TABLET_DIR=$tablet_dir +export MYSQL_PORT=3306 +export TABLET_TYPE=$tablet_role + $VTROOT/bin/mysqlctl \ -log_dir $VTDATAROOT/tmp \ -tablet_uid $uid \ diff --git a/examples/helm/101_initial_cluster.yaml b/examples/helm/101_initial_cluster.yaml new file mode 100644 index 00000000000..79d9bb972ea --- /dev/null +++ b/examples/helm/101_initial_cluster.yaml @@ -0,0 +1,78 @@ +topology: + cells: + - name: "zone1" + etcd: + replicas: 1 + vtctld: + replicas: 1 + vtgate: + replicas: 1 + mysqlProtocol: + enabled: true + authType: "none" + keyspaces: + - name: "commerce" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + schema: + initial: |- + create table product( + sku varbinary(128), + description varbinary(128), + price bigint, + primary key(sku) + ); + create table customer( + customer_id bigint not null auto_increment, + email varbinary(128), + primary key(customer_id) + ); + create table corder( + order_id bigint not null auto_increment, + customer_id bigint, + sku varbinary(128), + price bigint, + primary key(order_id) + ); + vschema: + initial: |- + { + "tables": { + "product": {}, + "customer": {}, + "corder": {} + } + } + +etcd: + replicas: 1 + resources: + +vtctld: + serviceType: "NodePort" + resources: + +vtgate: + serviceType: "NodePort" + resources: + +vttablet: + mysqlSize: "test" + resources: + mysqlResources: + +vtworker: + resources: + +pmm: + enabled: false + +orchestrator: + enabled: false diff --git a/examples/helm/201_customer_keyspace.yaml b/examples/helm/201_customer_keyspace.yaml new file mode 100644 index 00000000000..3490ac2e106 --- /dev/null +++ b/examples/helm/201_customer_keyspace.yaml @@ -0,0 +1,54 @@ +topology: + cells: + - name: "zone1" + etcd: + replicas: 1 + vtctld: + replicas: 1 + vtgate: + replicas: 1 + mysqlProtocol: + enabled: true + authType: "none" + keyspaces: + - name: "commerce" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + +jobs: + - name: "create-customer-ks" + kind: "vtctlclient" + command: "CreateKeyspace -served_from='master:commerce,replica:commerce,rdonly:commerce' customer" + +etcd: + replicas: 1 + resources: + +vtctld: + serviceType: "NodePort" + resources: + +vtgate: + serviceType: "NodePort" + resources: + +vttablet: + mysqlSize: "test" + resources: + mysqlResources: + +vtworker: + resources: + +pmm: + enabled: false + +orchestrator: + enabled: false diff --git a/examples/helm/202_customer_tablets.yaml b/examples/helm/202_customer_tablets.yaml new file mode 100644 index 00000000000..f24c929e4e1 --- /dev/null +++ b/examples/helm/202_customer_tablets.yaml @@ -0,0 +1,79 @@ +topology: + cells: + - name: "zone1" + etcd: + replicas: 1 + vtctld: + replicas: 1 + vtgate: + replicas: 1 + mysqlProtocol: + enabled: true + authType: "none" + keyspaces: + - name: "commerce" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + vschema: + vsplit: |- + { + "tables": { + "product": {} + } + } + - name: "customer" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + copySchema: + source: "commerce/0" + tables: + - "customer" + - "corder" + vschema: + vsplit: |- + { + "tables": { + "customer": {}, + "corder": {} + } + } + +etcd: + replicas: 1 + resources: + +vtctld: + serviceType: "NodePort" + resources: + +vtgate: + serviceType: "NodePort" + resources: + +vttablet: + mysqlSize: "test" + resources: + mysqlResources: + +vtworker: + resources: + +pmm: + enabled: false + +orchestrator: + enabled: false diff --git a/examples/helm/203_vertical_split.yaml b/examples/helm/203_vertical_split.yaml new file mode 100644 index 00000000000..677f2bbd9d5 --- /dev/null +++ b/examples/helm/203_vertical_split.yaml @@ -0,0 +1,65 @@ +topology: + cells: + - name: "zone1" + etcd: + replicas: 1 + vtctld: + replicas: 1 + vtgate: + replicas: 1 + mysqlProtocol: + enabled: true + authType: "none" + keyspaces: + - name: "commerce" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + - name: "customer" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + +jobs: + - name: "vertical-split" + kind: "vtworker" + cell: "zone1" + command: "VerticalSplitClone -min_healthy_rdonly_tablets=1 -tables=customer,corder customer/0" + +etcd: + replicas: 1 + resources: + +vtctld: + serviceType: "NodePort" + resources: + +vtgate: + serviceType: "NodePort" + resources: + +vttablet: + mysqlSize: "test" + resources: + mysqlResources: + +vtworker: + resources: + +pmm: + enabled: false + +orchestrator: + enabled: false diff --git a/examples/helm/204_vertical_migrate_replicas.yaml b/examples/helm/204_vertical_migrate_replicas.yaml new file mode 100644 index 00000000000..360c30020e9 --- /dev/null +++ b/examples/helm/204_vertical_migrate_replicas.yaml @@ -0,0 +1,67 @@ +topology: + cells: + - name: "zone1" + etcd: + replicas: 1 + vtctld: + replicas: 1 + vtgate: + replicas: 1 + mysqlProtocol: + enabled: true + authType: "none" + keyspaces: + - name: "commerce" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + - name: "customer" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + +jobs: + - name: "msf1" + kind: "vtctlclient" + command: "MigrateServedFrom customer/0 rdonly" + - name: "msf2" + kind: "vtctlclient" + command: "MigrateServedFrom customer/0 replica" + +etcd: + replicas: 1 + resources: + +vtctld: + serviceType: "NodePort" + resources: + +vtgate: + serviceType: "NodePort" + resources: + +vttablet: + mysqlSize: "test" + resources: + mysqlResources: + +vtworker: + resources: + +pmm: + enabled: false + +orchestrator: + enabled: false diff --git a/examples/helm/205_vertical_migrate_master.yaml b/examples/helm/205_vertical_migrate_master.yaml new file mode 100644 index 00000000000..3476c6709be --- /dev/null +++ b/examples/helm/205_vertical_migrate_master.yaml @@ -0,0 +1,64 @@ +topology: + cells: + - name: "zone1" + etcd: + replicas: 1 + vtctld: + replicas: 1 + vtgate: + replicas: 1 + mysqlProtocol: + enabled: true + authType: "none" + keyspaces: + - name: "commerce" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + - name: "customer" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + +jobs: + - name: "msf3" + kind: "vtctlclient" + command: "MigrateServedFrom customer/0 master" + +etcd: + replicas: 1 + resources: + +vtctld: + serviceType: "NodePort" + resources: + +vtgate: + serviceType: "NodePort" + resources: + +vttablet: + mysqlSize: "test" + resources: + mysqlResources: + +vtworker: + resources: + +pmm: + enabled: false + +orchestrator: + enabled: false diff --git a/examples/helm/206_clean_commerce.yaml b/examples/helm/206_clean_commerce.yaml new file mode 100644 index 00000000000..57304dea4f8 --- /dev/null +++ b/examples/helm/206_clean_commerce.yaml @@ -0,0 +1,74 @@ +topology: + cells: + - name: "zone1" + etcd: + replicas: 1 + vtctld: + replicas: 1 + vtgate: + replicas: 1 + mysqlProtocol: + enabled: true + authType: "none" + keyspaces: + - name: "commerce" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + schema: + postsplit: |- + drop table customer; + drop table corder; + - name: "customer" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + +jobs: + - name: "vclean1" + kind: "vtctlclient" + command: "SetShardTabletControl -blacklisted_tables=customer,corder -remove commerce/0 rdonly" + - name: "vclean2" + kind: "vtctlclient" + command: "SetShardTabletControl -blacklisted_tables=customer,corder -remove commerce/0 replica" + - name: "vclean3" + kind: "vtctlclient" + command: "SetShardTabletControl -blacklisted_tables=customer,corder -remove commerce/0 master" + +etcd: + replicas: 1 + resources: + +vtctld: + serviceType: "NodePort" + resources: + +vtgate: + serviceType: "NodePort" + resources: + +vttablet: + mysqlSize: "test" + resources: + mysqlResources: + +vtworker: + resources: + +pmm: + enabled: false + +orchestrator: + enabled: false diff --git a/examples/helm/301_customer_sharded.yaml b/examples/helm/301_customer_sharded.yaml new file mode 100644 index 00000000000..34ef10ca90f --- /dev/null +++ b/examples/helm/301_customer_sharded.yaml @@ -0,0 +1,118 @@ +topology: + cells: + - name: "zone1" + etcd: + replicas: 1 + vtctld: + replicas: 1 + vtgate: + replicas: 1 + mysqlProtocol: + enabled: true + authType: "none" + keyspaces: + - name: "commerce" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + schema: + seq: |- + create table customer_seq(id int, next_id bigint, cache bigint, primary key(id)) comment 'vitess_sequence'; + insert into customer_seq(id, next_id, cache) values(0, 1000, 100); + create table order_seq(id int, next_id bigint, cache bigint, primary key(id)) comment 'vitess_sequence'; + insert into order_seq(id, next_id, cache) values(0, 1000, 100); + vschema: + seq: |- + { + "tables": { + "customer_seq": { + "type": "sequence" + }, + "order_seq": { + "type": "sequence" + }, + "product": {} + } + } + - name: "customer" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + schema: + sharded: |- + alter table customer change customer_id customer_id bigint not null; + alter table corder change order_id order_id bigint not null; + vschema: + sharded: |- + { + "sharded": true, + "vindexes": { + "hash": { + "type": "hash" + } + }, + "tables": { + "customer": { + "column_vindexes": [ + { + "column": "customer_id", + "name": "hash" + } + ], + "auto_increment": { + "column": "customer_id", + "sequence": "customer_seq" + } + }, + "corder": { + "column_vindexes": [ + { + "column": "customer_id", + "name": "hash" + } + ], + "auto_increment": { + "column": "order_id", + "sequence": "order_seq" + } + } + } + } + +etcd: + replicas: 1 + resources: + +vtctld: + serviceType: "NodePort" + resources: + +vtgate: + serviceType: "NodePort" + resources: + +vttablet: + mysqlSize: "test" + resources: + mysqlResources: + +vtworker: + resources: + +pmm: + enabled: false + +orchestrator: + enabled: false diff --git a/examples/helm/302_new_shards.yaml b/examples/helm/302_new_shards.yaml new file mode 100644 index 00000000000..9ed6098cd72 --- /dev/null +++ b/examples/helm/302_new_shards.yaml @@ -0,0 +1,79 @@ +topology: + cells: + - name: "zone1" + etcd: + replicas: 1 + vtctld: + replicas: 1 + vtgate: + replicas: 1 + mysqlProtocol: + enabled: true + authType: "none" + keyspaces: + - name: "commerce" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + - name: "customer" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + - name: "-80" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + copySchema: + source: "customer/0" + - name: "80-" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + copySchema: + source: "customer/0" + +etcd: + replicas: 1 + resources: + +vtctld: + serviceType: "NodePort" + resources: + +vtgate: + serviceType: "NodePort" + resources: + +vttablet: + mysqlSize: "test" + resources: + mysqlResources: + +vtworker: + resources: + +pmm: + enabled: false + +orchestrator: + enabled: false diff --git a/examples/helm/303_horizontal_split.yaml b/examples/helm/303_horizontal_split.yaml new file mode 100644 index 00000000000..de6b2c527b4 --- /dev/null +++ b/examples/helm/303_horizontal_split.yaml @@ -0,0 +1,81 @@ +topology: + cells: + - name: "zone1" + etcd: + replicas: 1 + vtctld: + replicas: 1 + vtgate: + replicas: 1 + mysqlProtocol: + enabled: true + authType: "none" + keyspaces: + - name: "commerce" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + - name: "customer" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + - name: "-80" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + - name: "80-" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + +jobs: + - name: "horizontal-split" + kind: "vtworker" + cell: "zone1" + command: "SplitClone -min_healthy_rdonly_tablets=1 customer/0" + +etcd: + replicas: 1 + resources: + +vtctld: + serviceType: "NodePort" + resources: + +vtgate: + serviceType: "NodePort" + resources: + +vttablet: + mysqlSize: "test" + resources: + mysqlResources: + +vtworker: + resources: + +pmm: + enabled: false + +orchestrator: + enabled: false diff --git a/examples/helm/304_migrate_replicas.yaml b/examples/helm/304_migrate_replicas.yaml new file mode 100644 index 00000000000..bb5b35efa7a --- /dev/null +++ b/examples/helm/304_migrate_replicas.yaml @@ -0,0 +1,83 @@ +topology: + cells: + - name: "zone1" + etcd: + replicas: 1 + vtctld: + replicas: 1 + vtgate: + replicas: 1 + mysqlProtocol: + enabled: true + authType: "none" + keyspaces: + - name: "commerce" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + - name: "customer" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + - name: "-80" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + - name: "80-" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + +jobs: + - name: "mst1" + kind: "vtctlclient" + command: "MigrateServedTypes customer/0 rdonly" + - name: "mst2" + kind: "vtctlclient" + command: "MigrateServedTypes customer/0 replica" + +etcd: + replicas: 1 + resources: + +vtctld: + serviceType: "NodePort" + resources: + +vtgate: + serviceType: "NodePort" + resources: + +vttablet: + mysqlSize: "test" + resources: + mysqlResources: + +vtworker: + resources: + +pmm: + enabled: false + +orchestrator: + enabled: false diff --git a/examples/helm/305_migrate_master.yaml b/examples/helm/305_migrate_master.yaml new file mode 100644 index 00000000000..8b325b2bc05 --- /dev/null +++ b/examples/helm/305_migrate_master.yaml @@ -0,0 +1,80 @@ +topology: + cells: + - name: "zone1" + etcd: + replicas: 1 + vtctld: + replicas: 1 + vtgate: + replicas: 1 + mysqlProtocol: + enabled: true + authType: "none" + keyspaces: + - name: "commerce" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + - name: "customer" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + - name: "-80" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + - name: "80-" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + +jobs: + - name: "mst3" + kind: "vtctlclient" + command: "MigrateServedTypes customer/0 master" + +etcd: + replicas: 1 + resources: + +vtctld: + serviceType: "NodePort" + resources: + +vtgate: + serviceType: "NodePort" + resources: + +vttablet: + mysqlSize: "test" + resources: + mysqlResources: + +vtworker: + resources: + +pmm: + enabled: false + +orchestrator: + enabled: false diff --git a/examples/helm/306_down_shard_0.yaml b/examples/helm/306_down_shard_0.yaml new file mode 100644 index 00000000000..6b3fb1b3a01 --- /dev/null +++ b/examples/helm/306_down_shard_0.yaml @@ -0,0 +1,67 @@ +topology: + cells: + - name: "zone1" + etcd: + replicas: 1 + vtctld: + replicas: 1 + vtgate: + replicas: 1 + mysqlProtocol: + enabled: true + authType: "none" + keyspaces: + - name: "commerce" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + - name: "customer" + shards: + - name: "-80" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + - name: "80-" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + +etcd: + replicas: 1 + resources: + +vtctld: + serviceType: "NodePort" + resources: + +vtgate: + serviceType: "NodePort" + resources: + +vttablet: + mysqlSize: "test" + resources: + mysqlResources: + +vtworker: + resources: + +pmm: + enabled: false + +orchestrator: + enabled: false diff --git a/examples/helm/307_delete_shard_0.yaml b/examples/helm/307_delete_shard_0.yaml new file mode 100644 index 00000000000..f24e3410619 --- /dev/null +++ b/examples/helm/307_delete_shard_0.yaml @@ -0,0 +1,72 @@ +topology: + cells: + - name: "zone1" + etcd: + replicas: 1 + vtctld: + replicas: 1 + vtgate: + replicas: 1 + mysqlProtocol: + enabled: true + authType: "none" + keyspaces: + - name: "commerce" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + - name: "customer" + shards: + - name: "-80" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + - name: "80-" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + +jobs: + - name: "delete-shard0" + kind: "vtctlclient" + command: "DeleteShard -recursive customer/0" + +etcd: + replicas: 1 + resources: + +vtctld: + serviceType: "NodePort" + resources: + +vtgate: + serviceType: "NodePort" + resources: + +vttablet: + mysqlSize: "test" + resources: + mysqlResources: + +vtworker: + resources: + +pmm: + enabled: false + +orchestrator: + enabled: false diff --git a/examples/helm/308_final.yaml b/examples/helm/308_final.yaml new file mode 100644 index 00000000000..f24e3410619 --- /dev/null +++ b/examples/helm/308_final.yaml @@ -0,0 +1,72 @@ +topology: + cells: + - name: "zone1" + etcd: + replicas: 1 + vtctld: + replicas: 1 + vtgate: + replicas: 1 + mysqlProtocol: + enabled: true + authType: "none" + keyspaces: + - name: "commerce" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + - name: "customer" + shards: + - name: "-80" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + - name: "80-" + tablets: + - type: "replica" + vttablet: + replicas: 2 + - type: "rdonly" + vttablet: + replicas: 1 + +jobs: + - name: "delete-shard0" + kind: "vtctlclient" + command: "DeleteShard -recursive customer/0" + +etcd: + replicas: 1 + resources: + +vtctld: + serviceType: "NodePort" + resources: + +vtgate: + serviceType: "NodePort" + resources: + +vttablet: + mysqlSize: "test" + resources: + mysqlResources: + +vtworker: + resources: + +pmm: + enabled: false + +orchestrator: + enabled: false diff --git a/examples/helm/kmysql.sh b/examples/helm/kmysql.sh new file mode 100755 index 00000000000..5c997dfee26 --- /dev/null +++ b/examples/helm/kmysql.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Copyright 2018 The Vitess Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is a convenience script to run vtctlclient against the local example. + +host=$(minikube service vtgate-zone1 --format "{{.IP}}" | tail -n 1) +port=$(minikube service vtgate-zone1 --format "{{.Port}}" | tail -n 1) + +mysql -h "$host" -P "$port" $* diff --git a/examples/helm/kvtctld.sh b/examples/helm/kvtctld.sh new file mode 100755 index 00000000000..a30e77842a6 --- /dev/null +++ b/examples/helm/kvtctld.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Copyright 2018 The Vitess Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is a convenience script to run vtctlclient against the local example. + +xdg-open "$(minikube service vtctld --url|head -n 1)" diff --git a/examples/local/vttablet-up.sh b/examples/local/vttablet-up.sh index 893621345ef..fc7d40a9764 100755 --- a/examples/local/vttablet-up.sh +++ b/examples/local/vttablet-up.sh @@ -64,6 +64,19 @@ for uid_index in $uids; do printf -v alias '%s-%010d' $cell $uid printf -v tablet_dir 'vt_%010d' $uid + export KEYSPACE=$keyspace + export SHARD=$shard + export TABLET_ID=$alias + export TABLET_DIR=$tablet_dir + export MYSQL_PORT=$mysql_port + + tablet_type=replica + if [[ $uid_index -gt 2 ]]; then + tablet_type=rdonly + fi + + export TABLET_TYPE=$tablet_type + echo "Starting MySQL for tablet $alias..." action="init -init_db_sql_file $init_db_sql_file" if [ -d $VTDATAROOT/$tablet_dir ]; then diff --git a/go/mysql/auth_server.go b/go/mysql/auth_server.go index 91cb6a6489b..83d0c5e04f8 100644 --- a/go/mysql/auth_server.go +++ b/go/mysql/auth_server.go @@ -160,7 +160,7 @@ func isPassScrambleMysqlNativePassword(reply, salt []byte, mysqlNativePassword s return false } - if strings.Index(mysqlNativePassword, "*") != -1 { + if strings.Contains(mysqlNativePassword, "*") { mysqlNativePassword = mysqlNativePassword[1:] } diff --git a/go/mysql/auth_server_static.go b/go/mysql/auth_server_static.go index 4b4b823b524..34fcd7c884b 100644 --- a/go/mysql/auth_server_static.go +++ b/go/mysql/auth_server_static.go @@ -25,6 +25,7 @@ import ( "net" "os" "os/signal" + "sync" "syscall" "vitess.io/vitess/go/vt/log" @@ -48,7 +49,8 @@ type AuthServerStatic struct { // - MysqlDialog // It defaults to MysqlNativePassword. Method string - + // This mutex helps us prevent data races between the multiple updates of Entries. + mu sync.Mutex // Entries contains the users, passwords and user data. Entries map[string][]*AuthServerStaticEntry } @@ -105,6 +107,10 @@ func RegisterAuthServerStaticFromParams(file, str string) { authServerStatic := NewAuthServerStatic() authServerStatic.loadConfigFromParams(file, str) + + if len(authServerStatic.Entries) <= 0 { + log.Exitf("Failed to populate entries from file: %v", file) + } authServerStatic.installSignalHandlers() // And register the server. @@ -116,15 +122,21 @@ func (a *AuthServerStatic) loadConfigFromParams(file, str string) { if file != "" { data, err := ioutil.ReadFile(file) if err != nil { - log.Exitf("Failed to read mysql_auth_server_static_file file: %v", err) + log.Errorf("Failed to read mysql_auth_server_static_file file: %v", err) + return } jsonConfig = data } - a.Entries = make(map[string][]*AuthServerStaticEntry) // clear old entries - if err := parseConfig(jsonConfig, &a.Entries); err != nil { - log.Exitf("Error parsing auth server config: %v", err) + entries := make(map[string][]*AuthServerStaticEntry) + if err := parseConfig(jsonConfig, &entries); err != nil { + log.Errorf("Error parsing auth server config: %v", err) + return } + + a.mu.Lock() + a.Entries = entries + a.mu.Unlock() } func (a *AuthServerStatic) installSignalHandlers() { @@ -187,8 +199,10 @@ func (a *AuthServerStatic) Salt() ([]byte, error) { // ValidateHash is part of the AuthServer interface. func (a *AuthServerStatic) ValidateHash(salt []byte, user string, authResponse []byte, remoteAddr net.Addr) (Getter, error) { - // Find the entry. + a.mu.Lock() entries, ok := a.Entries[user] + a.mu.Unlock() + if !ok { return &StaticUserData{""}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user) } @@ -199,11 +213,12 @@ func (a *AuthServerStatic) ValidateHash(salt []byte, user string, authResponse [ if matchSourceHost(remoteAddr, entry.SourceHost) && isPass { return &StaticUserData{entry.UserData}, nil } - } - computedAuthResponse := ScramblePassword(salt, []byte(entry.Password)) - // Validate the password. - if matchSourceHost(remoteAddr, entry.SourceHost) && bytes.Compare(authResponse, computedAuthResponse) == 0 { - return &StaticUserData{entry.UserData}, nil + } else { + computedAuthResponse := ScramblePassword(salt, []byte(entry.Password)) + // Validate the password. + if matchSourceHost(remoteAddr, entry.SourceHost) && bytes.Compare(authResponse, computedAuthResponse) == 0 { + return &StaticUserData{entry.UserData}, nil + } } } return &StaticUserData{""}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user) @@ -219,8 +234,10 @@ func (a *AuthServerStatic) Negotiate(c *Conn, user string, remoteAddr net.Addr) return nil, err } - // Find the entry. + a.mu.Lock() entries, ok := a.Entries[user] + a.mu.Unlock() + if !ok { return &StaticUserData{""}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user) } diff --git a/go/mysql/auth_server_static_test.go b/go/mysql/auth_server_static_test.go index d44b2e7eb82..a1fd2809d11 100644 --- a/go/mysql/auth_server_static_test.go +++ b/go/mysql/auth_server_static_test.go @@ -123,3 +123,72 @@ func hupTest(t *testing.T, tmpFile *os.File, oldStr, newStr string) { t.Fatalf("%s's Password should be '%s'", newStr, newStr) } } + +func TestStaticPasswords(t *testing.T) { + jsonConfig := ` +{ + "user01": [{ "Password": "user01" }], + "user02": [{ + "MysqlNativePassword": "*B3AD996B12F211BEA47A7C666CC136FB26DC96AF" + }], + "user03": [{ + "MysqlNativePassword": "*211E0153B172BAED4352D5E4628BD76731AF83E7", + "Password": "invalid" + }], + "user04": [ + { "MysqlNativePassword": "*668425423DB5193AF921380129F465A6425216D0" }, + { "Password": "password2" } + ] +}` + + tests := []struct { + user string + password string + success bool + }{ + {"user01", "user01", true}, + {"user01", "password", false}, + {"user01", "", false}, + {"user02", "user02", true}, + {"user02", "password", false}, + {"user02", "", false}, + {"user03", "user03", true}, + {"user03", "password", false}, + {"user03", "invalid", false}, + {"user03", "", false}, + {"user04", "password1", true}, + {"user04", "password2", true}, + {"user04", "", false}, + {"userXX", "", false}, + {"userXX", "", false}, + {"", "", false}, + {"", "password", false}, + } + + auth := NewAuthServerStatic() + auth.loadConfigFromParams("", jsonConfig) + ip := net.ParseIP("127.0.0.1") + addr := &net.IPAddr{IP: ip, Zone: ""} + + for _, c := range tests { + t.Run(fmt.Sprintf("%s-%s", c.user, c.password), func(t *testing.T) { + salt, err := NewSalt() + if err != nil { + t.Fatalf("error generating salt: %v", err) + } + + scrambled := ScramblePassword(salt, []byte(c.password)) + _, err = auth.ValidateHash(salt, c.user, scrambled, addr) + + if c.success { + if err != nil { + t.Fatalf("authentication should have succeeded: %v", err) + } + } else { + if err == nil { + t.Fatalf("authentication should have failed") + } + } + }) + } +} diff --git a/go/mysql/conn.go b/go/mysql/conn.go index 15751f61116..8f24ae53555 100644 --- a/go/mysql/conn.go +++ b/go/mysql/conn.go @@ -279,13 +279,13 @@ func (c *Conn) readEphemeralPacket() ([]byte, error) { return nil, err } + c.currentEphemeralPolicy = ephemeralRead if length == 0 { // This can be caused by the packet after a packet of // exactly size MaxPacketSize. return nil, nil } - c.currentEphemeralPolicy = ephemeralRead // Use the bufPool. if length < MaxPacketSize { c.currentEphemeralBuffer = bufPool.Get(length) @@ -339,13 +339,13 @@ func (c *Conn) readEphemeralPacketDirect() ([]byte, error) { return nil, err } + c.currentEphemeralPolicy = ephemeralRead if length == 0 { // This can be caused by the packet after a packet of // exactly size MaxPacketSize. return nil, nil } - c.currentEphemeralPolicy = ephemeralRead if length < MaxPacketSize { c.currentEphemeralBuffer = bufPool.Get(length) if _, err := io.ReadFull(r, *c.currentEphemeralBuffer); err != nil { diff --git a/go/mysql/conn_test.go b/go/mysql/conn_test.go index 3ff4e0889a7..796798e167f 100644 --- a/go/mysql/conn_test.go +++ b/go/mysql/conn_test.go @@ -170,6 +170,10 @@ func TestPackets(t *testing.T) { data := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} verifyPacketComms(t, cConn, sConn, data) + // 0 length packet + data = []byte{} + verifyPacketComms(t, cConn, sConn, data) + // Under the limit, still one packet. data = make([]byte, MaxPacketSize-1) data[0] = 0xab diff --git a/go/mysql/constants.go b/go/mysql/constants.go index dc961154734..4d1a530a861 100644 --- a/go/mysql/constants.go +++ b/go/mysql/constants.go @@ -114,9 +114,9 @@ const ( // Client supports plugin authentication. CapabilityClientPluginAuth = 1 << 19 - // CLIENT_CONNECT_ATTRS 1 << 20 + // CapabilityClientConnAttr is CLIENT_CONNECT_ATTRS // Permits connection attributes in Protocol::HandshakeResponse41. - // Not yet supported. + CapabilityClientConnAttr = 1 << 20 // CapabilityClientPluginAuthLenencClientData is CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA CapabilityClientPluginAuthLenencClientData = 1 << 21 diff --git a/go/mysql/flavor.go b/go/mysql/flavor.go index aa589e838de..69cf7434b9f 100644 --- a/go/mysql/flavor.go +++ b/go/mysql/flavor.go @@ -109,7 +109,7 @@ func (c *Conn) fillFlavor() { return } - if strings.Index(c.ServerVersion, mariaDBVersionString) != -1 { + if strings.Contains(c.ServerVersion, mariaDBVersionString) { c.flavor = mariadbFlavor{} return } diff --git a/go/mysql/mariadb_gtid.go b/go/mysql/mariadb_gtid.go index b091400d8eb..e40584ab00d 100644 --- a/go/mysql/mariadb_gtid.go +++ b/go/mysql/mariadb_gtid.go @@ -59,11 +59,16 @@ func parseMariadbGTID(s string) (GTID, error) { // parseMariadbGTIDSet is registered as a GTIDSet parser. func parseMariadbGTIDSet(s string) (GTIDSet, error) { - gtid, err := parseMariadbGTID(s) - if err != nil { - return nil, err + gtidStrings := strings.Split(s, ",") + gtidSet := make(MariadbGTIDSet, len(gtidStrings)) + for i, gtidString := range gtidStrings { + gtid, err := parseMariadbGTID(gtidString) + if err != nil { + return nil, err + } + gtidSet[i] = gtid.(MariadbGTID) } - return gtid.(MariadbGTID), err + return gtidSet, nil } // MariadbGTID implements GTID. @@ -76,6 +81,9 @@ type MariadbGTID struct { Sequence uint64 } +// MariadbGTIDSet implements GTIDSet +type MariadbGTIDSet []MariadbGTID + // String implements GTID.String(). func (gtid MariadbGTID) String() string { return fmt.Sprintf("%d-%d-%d", gtid.Domain, gtid.Server, gtid.Sequence) @@ -103,49 +111,90 @@ func (gtid MariadbGTID) SequenceNumber() interface{} { // GTIDSet implements GTID.GTIDSet(). func (gtid MariadbGTID) GTIDSet() GTIDSet { - return gtid + return MariadbGTIDSet{gtid} +} + +// String implements GTIDSet.String() +func (gtidSet MariadbGTIDSet) String() string { + s := make([]string, len(gtidSet)) + for i, gtid := range gtidSet { + s[i] = gtid.String() + } + return strings.Join(s, ",") +} + +// Flavor implements GTIDSet.Flavor() +func (gtidSet MariadbGTIDSet) Flavor() string { + return mariadbFlavorID } // ContainsGTID implements GTIDSet.ContainsGTID(). -func (gtid MariadbGTID) ContainsGTID(other GTID) bool { +func (gtidSet MariadbGTIDSet) ContainsGTID(other GTID) bool { if other == nil { return true } mdbOther, ok := other.(MariadbGTID) - if !ok || gtid.Domain != mdbOther.Domain { + if !ok { return false } - return gtid.Sequence >= mdbOther.Sequence + for _, gtid := range gtidSet { + if gtid.Domain != mdbOther.Domain { + continue + } + return gtid.Sequence >= mdbOther.Sequence + } + return false } // Contains implements GTIDSet.Contains(). -func (gtid MariadbGTID) Contains(other GTIDSet) bool { +func (gtidSet MariadbGTIDSet) Contains(other GTIDSet) bool { if other == nil { return true } - mdbOther, ok := other.(MariadbGTID) - if !ok || gtid.Domain != mdbOther.Domain { + mdbOther, ok := other.(MariadbGTIDSet) + if !ok { return false } - return gtid.Sequence >= mdbOther.Sequence + for _, gtid := range mdbOther { + if !gtidSet.ContainsGTID(gtid) { + return false + } + } + return true } // Equal implements GTIDSet.Equal(). -func (gtid MariadbGTID) Equal(other GTIDSet) bool { - mdbOther, ok := other.(MariadbGTID) +func (gtidSet MariadbGTIDSet) Equal(other GTIDSet) bool { + mdbOther, ok := other.(MariadbGTIDSet) if !ok { return false } - return gtid == mdbOther + if len(gtidSet) != len(mdbOther) { + return false + } + for i, gtid := range gtidSet { + if gtid != mdbOther[i] { + return false + } + } + return true } // AddGTID implements GTIDSet.AddGTID(). -func (gtid MariadbGTID) AddGTID(other GTID) GTIDSet { +func (gtidSet MariadbGTIDSet) AddGTID(other GTID) GTIDSet { mdbOther, ok := other.(MariadbGTID) - if !ok || gtid.Domain != mdbOther.Domain || gtid.Sequence >= mdbOther.Sequence { - return gtid + if !ok || other == nil { + return gtidSet + } + for i, gtid := range gtidSet { + if mdbOther.Domain == gtid.Domain { + if mdbOther.Sequence > gtid.Sequence { + gtidSet[i] = mdbOther + } + return gtidSet + } } - return mdbOther + return append(gtidSet, mdbOther) } func init() { diff --git a/go/mysql/mariadb_gtid_test.go b/go/mysql/mariadb_gtid_test.go index 10be0a6d3e7..6bd738c9723 100644 --- a/go/mysql/mariadb_gtid_test.go +++ b/go/mysql/mariadb_gtid_test.go @@ -87,20 +87,20 @@ func TestParseMariaGTIDInvalidSequence(t *testing.T) { } func TestParseMariaGTIDSet(t *testing.T) { - input := "12-34-5678" - want := MariadbGTID{Domain: 12, Server: 34, Sequence: 5678} + input := "12-34-5678,11-22-3333" + want := MariadbGTIDSet{MariadbGTID{Domain: 12, Server: 34, Sequence: 5678}, MariadbGTID{Domain: 11, Server: 22, Sequence: 3333}} got, err := parseMariadbGTIDSet(input) if err != nil { t.Errorf("%v", err) } - if got.(MariadbGTID) != want { + if !got.Equal(want) { t.Errorf("parseMariadbGTIDSet(%#v) = %#v, want %#v", input, got, want) } } func TestParseInvalidMariaGTIDSet(t *testing.T) { - input := "12-34-56d78" + input := "12-34-5678,11-22-33e33" want := "invalid MariaDB GTID Sequence number" _, err := parseMariadbGTIDSet(input) @@ -165,17 +165,17 @@ func TestMariaGTIDSequenceNumber(t *testing.T) { func TestMariaGTIDGTIDSet(t *testing.T) { input := MariadbGTID{Domain: 12, Server: 345, Sequence: 6789} - want := GTIDSet(input) + want := MariadbGTIDSet{input} got := input.GTIDSet() - if got != want { + if !got.Equal(want) { t.Errorf("%#v.GTIDSet() = %#v, want %#v", input, got, want) } } -func TestMariaGTIDContainsLess(t *testing.T) { - input1 := MariadbGTID{Domain: 5, Server: 4727, Sequence: 300} - input2 := MariadbGTID{Domain: 5, Server: 4727, Sequence: 700} +func TestMariaGTIDSetContainsLess(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 5, Server: 4727, Sequence: 300}} + input2 := MariadbGTIDSet{MariadbGTID{Domain: 5, Server: 4727, Sequence: 700}} want := false if got := input1.Contains(input2); got != want { @@ -183,9 +183,9 @@ func TestMariaGTIDContainsLess(t *testing.T) { } } -func TestMariaGTIDContainsGreater(t *testing.T) { - input1 := MariadbGTID{Domain: 5, Server: 4727, Sequence: 9000} - input2 := MariadbGTID{Domain: 5, Server: 4727, Sequence: 100} +func TestMariaGTIDSetContainsGreater(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 5, Server: 4727, Sequence: 9000}} + input2 := MariadbGTIDSet{MariadbGTID{Domain: 5, Server: 4727, Sequence: 100}} want := true if got := input1.Contains(input2); got != want { @@ -193,9 +193,9 @@ func TestMariaGTIDContainsGreater(t *testing.T) { } } -func TestMariaGTIDContainsEqual(t *testing.T) { - input1 := MariadbGTID{Domain: 5, Server: 4727, Sequence: 1234} - input2 := MariadbGTID{Domain: 5, Server: 4727, Sequence: 1234} +func TestMariaGTIDSetContainsEqual(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 5, Server: 4727, Sequence: 1234}} + input2 := MariadbGTIDSet{MariadbGTID{Domain: 5, Server: 4727, Sequence: 1234}} want := true if got := input1.Contains(input2); got != want { @@ -203,8 +203,58 @@ func TestMariaGTIDContainsEqual(t *testing.T) { } } -func TestMariaGTIDContainsNil(t *testing.T) { - input1 := MariadbGTID{Domain: 1, Server: 2, Sequence: 123} +func TestMariaGTIDSetMultipleContainsLess(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 4, Server: 1, Sequence: 123}, MariadbGTID{Domain: 5, Server: 5, Sequence: 24601}} + input2 := MariadbGTIDSet{MariadbGTID{Domain: 4, Server: 1, Sequence: 124}} + want := false + + if got := input1.Contains(input2); got != want { + t.Errorf("%#v.Contains(%#v) = %v, want %v", input1, input2, got, want) + } +} + +func TestMariaGTIDSetMultipleContainsGreater(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 4, Server: 1, Sequence: 123}, MariadbGTID{Domain: 5, Server: 5, Sequence: 24601}} + input2 := MariadbGTIDSet{MariadbGTID{Domain: 4, Server: 1, Sequence: 122}} + want := true + + if got := input1.Contains(input2); got != want { + t.Errorf("%#v.Contains(%#v) = %v, want %v", input1, input2, got, want) + } +} + +func TestMariaGTIDSetMultipleContainsMultipleGreater(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 4, Server: 1, Sequence: 123}, MariadbGTID{Domain: 5, Server: 5, Sequence: 24601}} + input2 := MariadbGTIDSet{MariadbGTID{Domain: 4, Server: 1, Sequence: 122}, MariadbGTID{Domain: 5, Server: 4, Sequence: 1999}} + want := true + + if got := input1.Contains(input2); got != want { + t.Errorf("%#v.Contains(%#v) = %v, want %v", input1, input2, got, want) + } +} + +func TestMariaGTIDSetMultipleContainsOneLess(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 4, Server: 1, Sequence: 123}, MariadbGTID{Domain: 5, Server: 5, Sequence: 24601}} + input2 := MariadbGTIDSet{MariadbGTID{Domain: 4, Server: 1, Sequence: 122}, MariadbGTID{Domain: 5, Server: 4, Sequence: 24602}} + want := false + + if got := input1.Contains(input2); got != want { + t.Errorf("%#v.Contains(%#v) = %v, want %v", input1, input2, got, want) + } +} + +func TestMariaGTIDSetSingleContainsMultiple(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 4, Server: 1, Sequence: 123}} + input2 := MariadbGTIDSet{MariadbGTID{Domain: 4, Server: 1, Sequence: 122}, MariadbGTID{Domain: 5, Server: 4, Sequence: 24602}} + want := false + + if got := input1.Contains(input2); got != want { + t.Errorf("%#v.Contains(%#v) = %v, want %v", input1, input2, got, want) + } +} + +func TestMariaGTIDSetContainsNil(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 1, Server: 2, Sequence: 123}} input2 := GTIDSet(nil) want := true @@ -213,8 +263,8 @@ func TestMariaGTIDContainsNil(t *testing.T) { } } -func TestMariaGTIDContainsWrongType(t *testing.T) { - input1 := MariadbGTID{Domain: 5, Server: 4727, Sequence: 1234} +func TestMariaGTIDSetContainsWrongType(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 5, Server: 4727, Sequence: 1234}} input2 := fakeGTID{} want := false @@ -223,9 +273,9 @@ func TestMariaGTIDContainsWrongType(t *testing.T) { } } -func TestMariaGTIDContainsDifferentDomain(t *testing.T) { - input1 := MariadbGTID{Domain: 3, Server: 4727, Sequence: 1235} - input2 := MariadbGTID{Domain: 5, Server: 4727, Sequence: 1234} +func TestMariaGTIDSetContainsDifferentDomain(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 4727, Sequence: 1235}} + input2 := MariadbGTIDSet{MariadbGTID{Domain: 5, Server: 4727, Sequence: 1234}} want := false if got := input1.Contains(input2); got != want { @@ -233,9 +283,9 @@ func TestMariaGTIDContainsDifferentDomain(t *testing.T) { } } -func TestMariaGTIDContainsDifferentServer(t *testing.T) { - input1 := MariadbGTID{Domain: 3, Server: 4727, Sequence: 1235} - input2 := MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234} +func TestMariaGTIDSetContainsDifferentServer(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 4727, Sequence: 1235}} + input2 := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} want := true if got := input1.Contains(input2); got != want { @@ -243,8 +293,8 @@ func TestMariaGTIDContainsDifferentServer(t *testing.T) { } } -func TestMariaGTIDContainsGTIDLess(t *testing.T) { - input1 := MariadbGTID{Domain: 5, Server: 4727, Sequence: 300} +func TestMariaGTIDSetContainsGTIDLess(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 5, Server: 4727, Sequence: 300}} input2 := MariadbGTID{Domain: 5, Server: 4727, Sequence: 700} want := false @@ -253,8 +303,8 @@ func TestMariaGTIDContainsGTIDLess(t *testing.T) { } } -func TestMariaGTIDContainsGTIDGreater(t *testing.T) { - input1 := MariadbGTID{Domain: 5, Server: 4727, Sequence: 9000} +func TestMariaGTIDSetContainsGTIDGreater(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 5, Server: 4727, Sequence: 9000}} input2 := MariadbGTID{Domain: 5, Server: 4727, Sequence: 100} want := true @@ -263,8 +313,8 @@ func TestMariaGTIDContainsGTIDGreater(t *testing.T) { } } -func TestMariaGTIDContainsGTIDEqual(t *testing.T) { - input1 := MariadbGTID{Domain: 5, Server: 4727, Sequence: 1234} +func TestMariaGTIDSetContainsGTIDEqual(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 5, Server: 4727, Sequence: 1234}} input2 := MariadbGTID{Domain: 5, Server: 4727, Sequence: 1234} want := true @@ -273,8 +323,8 @@ func TestMariaGTIDContainsGTIDEqual(t *testing.T) { } } -func TestMariaGTIDContainsGTIDNil(t *testing.T) { - input1 := MariadbGTID{Domain: 1, Server: 2, Sequence: 123} +func TestMariaGTIDSetContainsGTIDNil(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 1, Server: 2, Sequence: 123}} input2 := GTID(nil) want := true @@ -283,8 +333,8 @@ func TestMariaGTIDContainsGTIDNil(t *testing.T) { } } -func TestMariaGTIDContainsGTIDWrongType(t *testing.T) { - input1 := MariadbGTID{Domain: 5, Server: 4727, Sequence: 1234} +func TestMariaGTIDSetContainsGTIDWrongType(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 5, Server: 4727, Sequence: 1234}} input2 := fakeGTID{} want := false @@ -293,8 +343,8 @@ func TestMariaGTIDContainsGTIDWrongType(t *testing.T) { } } -func TestMariaGTIDContainsGTIDDifferentDomain(t *testing.T) { - input1 := MariadbGTID{Domain: 3, Server: 4727, Sequence: 1235} +func TestMariaGTIDSetContainsGTIDDifferentDomain(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 4727, Sequence: 1235}} input2 := MariadbGTID{Domain: 5, Server: 4727, Sequence: 1234} want := false @@ -303,8 +353,8 @@ func TestMariaGTIDContainsGTIDDifferentDomain(t *testing.T) { } } -func TestMariaGTIDContainsGTIDDifferentServer(t *testing.T) { - input1 := MariadbGTID{Domain: 3, Server: 4727, Sequence: 1235} +func TestMariaGTIDSetContainsGTIDDifferentServer(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 4727, Sequence: 1235}} input2 := MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234} want := true @@ -313,9 +363,9 @@ func TestMariaGTIDContainsGTIDDifferentServer(t *testing.T) { } } -func TestMariaGTIDEqual(t *testing.T) { - input1 := MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234} - input2 := MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234} +func TestMariaGTIDSetEqual(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} + input2 := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} want := true if got := input1.Equal(input2); got != want { @@ -323,9 +373,9 @@ func TestMariaGTIDEqual(t *testing.T) { } } -func TestMariaGTIDNotEqual(t *testing.T) { - input1 := MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234} - input2 := MariadbGTID{Domain: 3, Server: 4555, Sequence: 1234} +func TestMariaGTIDSetNotEqual(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} + input2 := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 4555, Sequence: 1234}} want := false if got := input1.Equal(input2); got != want { @@ -333,8 +383,8 @@ func TestMariaGTIDNotEqual(t *testing.T) { } } -func TestMariaGTIDEqualWrongType(t *testing.T) { - input1 := MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234} +func TestMariaGTIDSetEqualWrongType(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} input2 := fakeGTID{} want := false @@ -343,8 +393,8 @@ func TestMariaGTIDEqualWrongType(t *testing.T) { } } -func TestMariaGTIDEqualNil(t *testing.T) { - input1 := MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234} +func TestMariaGTIDSetEqualNil(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} input2 := GTIDSet(nil) want := false @@ -353,72 +403,72 @@ func TestMariaGTIDEqualNil(t *testing.T) { } } -func TestMariaGTIDAddGTIDEqual(t *testing.T) { - input1 := MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234} +func TestMariaGTIDSetAddGTIDEqual(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} input2 := MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234} - want := MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234} + want := input1 - if got := input1.AddGTID(input2); got != want { + if got := input1.AddGTID(input2); !got.Equal(want) { t.Errorf("%#v.AddGTID(%#v) = %v, want %v", input1, input2, got, want) } } -func TestMariaGTIDAddGTIDGreater(t *testing.T) { - input1 := MariadbGTID{Domain: 3, Server: 5555, Sequence: 5234} +func TestMariaGTIDSetAddGTIDGreater(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 5234}} input2 := MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234} - want := MariadbGTID{Domain: 3, Server: 5555, Sequence: 5234} + want := input1 - if got := input1.AddGTID(input2); got != want { + if got := input1.AddGTID(input2); !got.Equal(want) { t.Errorf("%#v.AddGTID(%#v) = %v, want %v", input1, input2, got, want) } } -func TestMariaGTIDAddGTIDLess(t *testing.T) { - input1 := MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234} +func TestMariaGTIDSetAddGTIDLess(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} input2 := MariadbGTID{Domain: 3, Server: 5555, Sequence: 5234} - want := MariadbGTID{Domain: 3, Server: 5555, Sequence: 5234} + want := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 5234}} - if got := input1.AddGTID(input2); got != want { + if got := input1.AddGTID(input2); !got.Equal(want) { t.Errorf("%#v.AddGTID(%#v) = %v, want %v", input1, input2, got, want) } } -func TestMariaGTIDAddGTIDWrongType(t *testing.T) { - input1 := MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234} +func TestMariaGTIDSetAddGTIDWrongType(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} input2 := fakeGTID{} want := input1 - if got := input1.AddGTID(input2); got != want { + if got := input1.AddGTID(input2); !got.Equal(want) { t.Errorf("%#v.AddGTID(%#v) = %v, want %v", input1, input2, got, want) } } -func TestMariaGTIDAddGTIDNil(t *testing.T) { - input1 := MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234} +func TestMariaGTIDSetAddGTIDNil(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} input2 := GTID(nil) want := input1 - if got := input1.AddGTID(input2); got != want { + if got := input1.AddGTID(input2); !got.Equal(want) { t.Errorf("%#v.AddGTID(%#v) = %v, want %v", input1, input2, got, want) } } -func TestMariaGTIDAddGTIDDifferentServer(t *testing.T) { - input1 := MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234} +func TestMariaGTIDSetAddGTIDDifferentServer(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} input2 := MariadbGTID{Domain: 3, Server: 4444, Sequence: 5234} - want := MariadbGTID{Domain: 3, Server: 4444, Sequence: 5234} + want := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 4444, Sequence: 5234}} - if got := input1.AddGTID(input2); got != want { + if got := input1.AddGTID(input2); !got.Equal(want) { t.Errorf("%#v.AddGTID(%#v) = %v, want %v", input1, input2, got, want) } } -func TestMariaGTIDAddGTIDDifferentDomain(t *testing.T) { - input1 := MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234} +func TestMariaGTIDSetAddGTIDDifferentDomain(t *testing.T) { + input1 := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} input2 := MariadbGTID{Domain: 5, Server: 5555, Sequence: 5234} - want := MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234} + want := MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}, MariadbGTID{Domain: 5, Server: 5555, Sequence: 5234}} - if got := input1.AddGTID(input2); got != want { + if got := input1.AddGTID(input2); !got.Equal(want) { t.Errorf("%#v.AddGTID(%#v) = %v, want %v", input1, input2, got, want) } } diff --git a/go/mysql/replication_position_test.go b/go/mysql/replication_position_test.go index 051aded50a6..76dce397d00 100644 --- a/go/mysql/replication_position_test.go +++ b/go/mysql/replication_position_test.go @@ -23,8 +23,8 @@ import ( ) func TestPositionEqual(t *testing.T) { - input1 := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} - input2 := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} + input1 := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}}} + input2 := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}}} want := true if got := input1.Equal(input2); got != want { @@ -33,8 +33,8 @@ func TestPositionEqual(t *testing.T) { } func TestPositionNotEqual(t *testing.T) { - input1 := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} - input2 := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 12345}} + input1 := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}}} + input2 := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 12345}}} want := false if got := input1.Equal(input2); got != want { @@ -43,7 +43,7 @@ func TestPositionNotEqual(t *testing.T) { } func TestPositionEqualZero(t *testing.T) { - input1 := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} + input1 := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}}} input2 := Position{} want := false @@ -63,8 +63,8 @@ func TestPositionZeroEqualZero(t *testing.T) { } func TestPositionAtLeastLess(t *testing.T) { - input1 := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1233}} - input2 := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} + input1 := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1233}}} + input2 := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}}} want := false if got := input1.AtLeast(input2); got != want { @@ -73,8 +73,8 @@ func TestPositionAtLeastLess(t *testing.T) { } func TestPositionAtLeastEqual(t *testing.T) { - input1 := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} - input2 := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} + input1 := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}}} + input2 := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}}} want := true if got := input1.AtLeast(input2); got != want { @@ -83,8 +83,8 @@ func TestPositionAtLeastEqual(t *testing.T) { } func TestPositionAtLeastGreater(t *testing.T) { - input1 := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1235}} - input2 := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} + input1 := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1235}}} + input2 := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}}} want := true if got := input1.AtLeast(input2); got != want { @@ -93,8 +93,8 @@ func TestPositionAtLeastGreater(t *testing.T) { } func TestPositionAtLeastDifferentServer(t *testing.T) { - input1 := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1235}} - input2 := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 4444, Sequence: 1234}} + input1 := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1235}}} + input2 := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 4444, Sequence: 1234}}} want := true if got := input1.AtLeast(input2); got != want { @@ -103,8 +103,8 @@ func TestPositionAtLeastDifferentServer(t *testing.T) { } func TestPositionAtLeastDifferentDomain(t *testing.T) { - input1 := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1235}} - input2 := Position{GTIDSet: MariadbGTID{Domain: 4, Server: 5555, Sequence: 1234}} + input1 := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1235}}} + input2 := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 4, Server: 5555, Sequence: 1234}}} want := false if got := input1.AtLeast(input2); got != want { @@ -114,7 +114,7 @@ func TestPositionAtLeastDifferentDomain(t *testing.T) { func TestPositionZeroAtLeast(t *testing.T) { input1 := Position{} - input2 := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} + input2 := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}}} want := false if got := input1.AtLeast(input2); got != want { @@ -123,7 +123,7 @@ func TestPositionZeroAtLeast(t *testing.T) { } func TestPositionAtLeastZero(t *testing.T) { - input1 := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} + input1 := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}}} input2 := Position{} want := true @@ -143,7 +143,7 @@ func TestPositionZeroAtLeastZero(t *testing.T) { } func TestPositionString(t *testing.T) { - input := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} + input := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}}} want := "3-5555-1234" if got := input.String(); got != want { @@ -170,7 +170,7 @@ func TestPositionIsZero(t *testing.T) { } func TestPositionIsNotZero(t *testing.T) { - input := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} + input := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}}} want := false if got := input.IsZero(); got != want { @@ -179,9 +179,9 @@ func TestPositionIsNotZero(t *testing.T) { } func TestPositionAppend(t *testing.T) { - input1 := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} + input1 := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}}} input2 := MariadbGTID{Domain: 3, Server: 5555, Sequence: 1235} - want := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1235}} + want := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1235}}} if got := AppendGTID(input1, input2); !got.Equal(want) { t.Errorf("AppendGTID(%#v, %#v) = %#v, want %#v", input1, input2, got, want) @@ -189,9 +189,9 @@ func TestPositionAppend(t *testing.T) { } func TestPositionAppendNil(t *testing.T) { - input1 := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} + input1 := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}}} input2 := GTID(nil) - want := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} + want := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}}} if got := AppendGTID(input1, input2); !got.Equal(want) { t.Errorf("AppendGTID(%#v, %#v) = %#v, want %#v", input1, input2, got, want) @@ -201,7 +201,7 @@ func TestPositionAppendNil(t *testing.T) { func TestPositionAppendToZero(t *testing.T) { input1 := Position{} input2 := MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234} - want := Position{GTIDSet: MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}} + want := Position{GTIDSet: MariadbGTIDSet{MariadbGTID{Domain: 3, Server: 5555, Sequence: 1234}}} if got := AppendGTID(input1, input2); !got.Equal(want) { t.Errorf("AppendGTID(%#v, %#v) = %#v, want %#v", input1, input2, got, want) diff --git a/go/mysql/server.go b/go/mysql/server.go index 37d5e2b3e5b..f32977ae46a 100644 --- a/go/mysql/server.go +++ b/go/mysql/server.go @@ -17,7 +17,7 @@ limitations under the License. package mysql import ( - "crypto/tls" + tls "crypto/tls" "fmt" "io" "net" @@ -37,8 +37,14 @@ const ( DefaultServerVersion = "5.5.10-Vitess" // timing metric keys - connectTimingKey = "Connect" - queryTimingKey = "Query" + connectTimingKey = "Connect" + queryTimingKey = "Query" + versionSSL30 = "SSL30" + versionTLS10 = "TLS10" + versionTLS11 = "TLS11" + versionTLS12 = "TLS12" + versionTLSUnknown = "UnknownTLSVersion" + versionNoTLS = "None" ) var ( @@ -48,8 +54,9 @@ var ( connAccept = stats.NewCounter("MysqlServerConnAccepted", "Connections accepted by MySQL server") connSlow = stats.NewCounter("MysqlServerConnSlow", "Connections that took more than the configured mysql_slow_connect_warn_threshold to establish") - connCountPerUser = stats.NewGaugesWithSingleLabel("MysqlServerConnCountPerUser", "Active MySQL server connections per user", "count") - _ = stats.NewGaugeFunc("MysqlServerConnCountUnauthenticated", "Active MySQL server connections that haven't authenticated yet", func() int64 { + connCountByTLSVer = stats.NewGaugesWithSingleLabel("MysqlServerConnCountByTLSVer", "Active MySQL server connections by TLS version", "tls") + connCountPerUser = stats.NewGaugesWithSingleLabel("MysqlServerConnCountPerUser", "Active MySQL server connections per user", "count") + _ = stats.NewGaugeFunc("MysqlServerConnCountUnauthenticated", "Active MySQL server connections that haven't authenticated yet", func() int64 { totalUsers := int64(0) for _, v := range connCountPerUser.Counts() { totalUsers += v @@ -142,6 +149,9 @@ type Listener struct { // shutdown indicates that Shutdown method was called. shutdown sync2.AtomicBool + + // RequireSecureTransport configures the server to reject connections from insecure clients + RequireSecureTransport bool } // NewFromListener creares a new mysql listener from an existing net.Listener @@ -300,6 +310,21 @@ func (l *Listener) handle(conn net.Conn, connectionID uint32, acceptTime time.Ti return } c.recycleReadPacket() + + if con, ok := c.conn.(*tls.Conn); ok { + connState := con.ConnectionState() + tlsVerStr := tlsVersionToString(connState.Version) + if tlsVerStr != "" { + connCountByTLSVer.Add(tlsVerStr, 1) + defer connCountByTLSVer.Add(tlsVerStr, -1) + } + } + } else { + if l.RequireSecureTransport { + c.writeErrorPacketFromError(fmt.Errorf("Server does not allow insecure connections, client must use SSL/TLS")) + } + connCountByTLSVer.Add(versionNoTLS, 1) + defer connCountByTLSVer.Add(versionNoTLS, -1) } // See what auth method the AuthServer wants to use for that user. @@ -326,10 +351,34 @@ func (l *Listener) handle(conn net.Conn, connectionID uint32, acceptTime time.Ti case authServerMethod == MysqlNativePassword: // The server really wants to use MysqlNativePassword, - // but the client returned a result for something else: - // not sure this can happen, so not supporting this now. - c.writeErrorPacket(CRServerHandshakeErr, SSUnknownSQLState, "Client asked for auth %v, but server wants auth mysql_native_password", authMethod) - return + // but the client returned a result for something else. + + salt, err := l.authServer.Salt() + if err != nil { + return + } + data := make([]byte, 21) + data = append(salt, byte(0x00)) + if err := c.writeAuthSwitchRequest(MysqlNativePassword, data); err != nil { + log.Errorf("Error writing auth switch packet for %s: %v", c, err) + return + } + + response, err := c.readEphemeralPacket() + if err != nil { + log.Errorf("Error reading auth switch response for %s: %v", c, err) + return + } + c.recycleReadPacket() + + userData, err := l.authServer.ValidateHash(salt, user, response, conn.RemoteAddr()) + if err != nil { + log.Warningf("Error authenticating user using MySQL native password: %v", err) + c.writeErrorPacketFromError(err) + return + } + c.User = user + c.UserData = userData default: // The server wants to use something else, re-negotiate. @@ -421,7 +470,8 @@ func (c *Conn) writeHandshakeV10(serverVersion string, authServer AuthServer, en CapabilityClientMultiResults | CapabilityClientPluginAuth | CapabilityClientPluginAuthLenencClientData | - CapabilityClientDeprecateEOF + CapabilityClientDeprecateEOF | + CapabilityClientConnAttr if enableTLS { capabilities |= CapabilityClientSSL } @@ -620,11 +670,66 @@ func (l *Listener) parseClientHandshakePacket(c *Conn, firstTime bool, data []by authMethod = MysqlNativePassword } - // FIXME(alainjobart) Add CLIENT_CONNECT_ATTRS parsing if we need it. + // Decode connection attributes send by the client + if clientFlags&CapabilityClientConnAttr != 0 { + var attrs map[string]string + var err error + attrs, pos, err = parseConnAttrs(data, pos) + if err != nil { + return "", "", nil, err + } + log.Infof("Connection Attributes: %-v", attrs) + } return username, authMethod, authResponse, nil } +func parseConnAttrs(data []byte, pos int) (map[string]string, int, error) { + var attrLen uint64 + + attrLen, pos, ok := readLenEncInt(data, pos) + if !ok { + return nil, 0, fmt.Errorf("parseClientHandshakePacket: can't read connection attributes variable length") + } + + var attrLenRead uint64 + + attrs := make(map[string]string) + + for attrLenRead < attrLen { + var keyLen byte + keyLen, pos, ok = readByte(data, pos) + if !ok { + return nil, 0, fmt.Errorf("parseClientHandshakePacket: can't read connection attribute key length") + } + attrLenRead += uint64(keyLen) + 1 + + var connAttrKey []byte + connAttrKey, pos, ok = readBytesCopy(data, pos, int(keyLen)) + if !ok { + return nil, 0, fmt.Errorf("parseClientHandshakePacket: can't read connection attribute key") + } + + var valLen byte + valLen, pos, ok = readByte(data, pos) + if !ok { + return nil, 0, fmt.Errorf("parseClientHandshakePacket: can't read connection attribute value length") + } + attrLenRead += uint64(valLen) + 1 + + var connAttrVal []byte + connAttrVal, pos, ok = readBytesCopy(data, pos, int(valLen)) + if !ok { + return nil, 0, fmt.Errorf("parseClientHandshakePacket: can't read connection attribute value") + } + + attrs[string(connAttrKey[:])] = string(connAttrVal[:]) + } + + return attrs, pos, nil + +} + // writeAuthSwitchRequest writes an auth switch request packet. func (c *Conn) writeAuthSwitchRequest(pluginName string, pluginData []byte) error { length := 1 + // AuthSwitchRequestPacket @@ -649,3 +754,19 @@ func (c *Conn) writeAuthSwitchRequest(pluginName string, pluginData []byte) erro } return c.writeEphemeralPacket() } + +// Whenever we move to a new version of go, we will need add any new supported TLS versions here +func tlsVersionToString(version uint16) string { + switch version { + case tls.VersionSSL30: + return versionSSL30 + case tls.VersionTLS10: + return versionTLS10 + case tls.VersionTLS11: + return versionTLS11 + case tls.VersionTLS12: + return versionTLS12 + default: + return versionTLSUnknown + } +} diff --git a/go/mysql/server_test.go b/go/mysql/server_test.go index 7023df61bb0..8895425a7cd 100644 --- a/go/mysql/server_test.go +++ b/go/mysql/server_test.go @@ -930,6 +930,103 @@ func TestTLSServer(t *testing.T) { if results.Rows[0][0].ToString() != "ON" { t.Errorf("Unexpected output for 'ssl echo': %v", results) } + + checkCountForTLSVer(t, versionTLS12, 1) + checkCountForTLSVer(t, versionNoTLS, 0) + conn.Close() + +} + +// TestTLSRequired creates a Server with TLS required, then tests that an insecure mysql +// client is rejected +func TestTLSRequired(t *testing.T) { + th := &testHandler{} + + authServer := NewAuthServerStatic() + authServer.Entries["user1"] = []*AuthServerStaticEntry{{ + Password: "password1", + }} + + // Create the listener, so we can get its host. + // Below, we are enabling --ssl-verify-server-cert, which adds + // a check that the common name of the certificate matches the + // server host name we connect to. + l, err := NewListener("tcp", ":0", authServer, th, 0, 0) + if err != nil { + t.Fatalf("NewListener failed: %v", err) + } + defer l.Close() + + // Make sure hostname is added as an entry to /etc/hosts, otherwise ssl handshake will fail + host, err := os.Hostname() + if err != nil { + t.Fatalf("Failed to get os Hostname: %v", err) + } + + port := l.Addr().(*net.TCPAddr).Port + + // Create the certs. + root, err := ioutil.TempDir("", "TestTLSRequired") + if err != nil { + t.Fatalf("TempDir failed: %v", err) + } + defer os.RemoveAll(root) + tlstest.CreateCA(root) + tlstest.CreateSignedCert(root, tlstest.CA, "01", "server", host) + + // Create the server with TLS config. + serverConfig, err := vttls.ServerConfig( + path.Join(root, "server-cert.pem"), + path.Join(root, "server-key.pem"), + path.Join(root, "ca-cert.pem")) + if err != nil { + t.Fatalf("TLSServerConfig failed: %v", err) + } + l.TLSConfig = serverConfig + l.RequireSecureTransport = true + go l.Accept() + + // Setup conn params without SSL. + params := &ConnParams{ + Host: host, + Port: port, + Uname: "user1", + Pass: "password1", + } + conn, err := Connect(context.Background(), params) + if err == nil { + t.Fatal("mysql should have failed") + } + if conn != nil { + conn.Close() + } + + // setup conn params with TLS + tlstest.CreateSignedCert(root, tlstest.CA, "02", "client", "Client Cert") + params.Flags = CapabilityClientSSL + params.SslCa = path.Join(root, "ca-cert.pem") + params.SslCert = path.Join(root, "client-cert.pem") + params.SslKey = path.Join(root, "client-key.pem") + + conn, err = Connect(context.Background(), params) + if err != nil { + t.Fatalf("mysql failed: %v", err) + } + if conn != nil { + conn.Close() + } +} + +func checkCountForTLSVer(t *testing.T, version string, expected int64) { + connCounts := connCountByTLSVer.Counts() + count, ok := connCounts[version] + if ok { + if count != expected { + t.Errorf("Expected connection count for version %s to be %d, got %d", version, expected, count) + } + } else { + t.Errorf("No count found for version %s", version) + } } func TestErrorCodes(t *testing.T) { @@ -1172,3 +1269,39 @@ func TestListenerShutdown(t *testing.T) { t.Fatalf("Ping should fail after shutdown") } } + +func TestParseConnAttrs(t *testing.T) { + expected := map[string]string{ + "_client_version": "8.0.11", + "program_name": "mysql", + "_pid": "22850", + "_platform": "x86_64", + "_os": "linux-glibc2.12", + "_client_name": "libmysql", + } + + data := []byte{0x70, 0x04, 0x5f, 0x70, 0x69, 0x64, 0x05, 0x32, 0x32, 0x38, 0x35, 0x30, 0x09, 0x5f, 0x70, 0x6c, + 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x06, 0x78, 0x38, 0x36, 0x5f, 0x36, 0x34, 0x03, 0x5f, 0x6f, + 0x73, 0x0f, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x2d, 0x67, 0x6c, 0x69, 0x62, 0x63, 0x32, 0x2e, 0x31, + 0x32, 0x0c, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x08, 0x6c, + 0x69, 0x62, 0x6d, 0x79, 0x73, 0x71, 0x6c, 0x0f, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x06, 0x38, 0x2e, 0x30, 0x2e, 0x31, 0x31, 0x0c, 0x70, + 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x05, 0x6d, 0x79, 0x73, 0x71, 0x6c} + + attrs, pos, err := parseConnAttrs(data, 0) + if err != nil { + t.Fatalf("Failed to read connection attributes: %v", err) + } + if pos != 113 { + t.Fatalf("Unexpeded pos after reading connection attributes: %d intead of 113", pos) + } + for k, v := range expected { + if val, ok := attrs[k]; ok { + if val != v { + t.Fatalf("Unexpected value found in attrs for key %s: got %s expected %s", k, val, v) + } + } else { + t.Fatalf("Error reading key %s from connection attributes: attrs: %-v", k, attrs) + } + } +} diff --git a/go/vt/binlog/binlog_streamer_rbr_test.go b/go/vt/binlog/binlog_streamer_rbr_test.go index 9a8e1a77925..d30255d4f52 100644 --- a/go/vt/binlog/binlog_streamer_rbr_test.go +++ b/go/vt/binlog/binlog_streamer_rbr_test.go @@ -237,10 +237,12 @@ func TestStreamerParseRBREvents(t *testing.T) { eventToken: &querypb.EventToken{ Timestamp: 1407805592, Position: mysql.EncodePosition(mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 0, - Server: 62344, - Sequence: 0x0d, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 0, + Server: 62344, + Sequence: 0x0d, + }, }, }), }, @@ -477,10 +479,12 @@ func TestStreamerParseRBRNameEscapes(t *testing.T) { eventToken: &querypb.EventToken{ Timestamp: 1407805592, Position: mysql.EncodePosition(mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 0, - Server: 62344, - Sequence: 0x0d, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 0, + Server: 62344, + Sequence: 0x0d, + }, }, }), }, diff --git a/go/vt/binlog/binlog_streamer_test.go b/go/vt/binlog/binlog_streamer_test.go index fc3fcb0f856..576e7b5620c 100644 --- a/go/vt/binlog/binlog_streamer_test.go +++ b/go/vt/binlog/binlog_streamer_test.go @@ -103,10 +103,12 @@ func TestStreamerParseEventsXID(t *testing.T) { EventToken: &querypb.EventToken{ Timestamp: 1407805592, Position: mysql.EncodePosition(mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 0, - Server: 62344, - Sequence: 0x0d, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 0, + Server: 62344, + Sequence: 0x0d, + }, }, }), }, @@ -157,10 +159,12 @@ func TestStreamerParseEventsCommit(t *testing.T) { EventToken: &querypb.EventToken{ Timestamp: 1407805592, Position: mysql.EncodePosition(mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 0, - Server: 62344, - Sequence: 0x0d, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 0, + Server: 62344, + Sequence: 0x0d, + }, }, }), }, @@ -504,10 +508,12 @@ func TestStreamerParseEventsRollback(t *testing.T) { EventToken: &querypb.EventToken{ Timestamp: 1407805592, Position: mysql.EncodePosition(mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 0, - Server: 62344, - Sequence: 0x0d, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 0, + Server: 62344, + Sequence: 0x0d, + }, }, }), }, @@ -520,10 +526,12 @@ func TestStreamerParseEventsRollback(t *testing.T) { EventToken: &querypb.EventToken{ Timestamp: 1407805592, Position: mysql.EncodePosition(mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 0, - Server: 62344, - Sequence: 0x0d, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 0, + Server: 62344, + Sequence: 0x0d, + }, }, }), }, @@ -568,10 +576,12 @@ func TestStreamerParseEventsDMLWithoutBegin(t *testing.T) { EventToken: &querypb.EventToken{ Timestamp: 1407805592, Position: mysql.EncodePosition(mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 0, - Server: 62344, - Sequence: 0x0d, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 0, + Server: 62344, + Sequence: 0x0d, + }, }, }), }, @@ -581,10 +591,12 @@ func TestStreamerParseEventsDMLWithoutBegin(t *testing.T) { EventToken: &querypb.EventToken{ Timestamp: 1407805592, Position: mysql.EncodePosition(mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 0, - Server: 62344, - Sequence: 0x0d, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 0, + Server: 62344, + Sequence: 0x0d, + }, }, }), }, @@ -632,10 +644,12 @@ func TestStreamerParseEventsBeginWithoutCommit(t *testing.T) { EventToken: &querypb.EventToken{ Timestamp: 1407805592, Position: mysql.EncodePosition(mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 0, - Server: 62344, - Sequence: 0x0d, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 0, + Server: 62344, + Sequence: 0x0d, + }, }, }), }, @@ -645,10 +659,12 @@ func TestStreamerParseEventsBeginWithoutCommit(t *testing.T) { EventToken: &querypb.EventToken{ Timestamp: 1407805592, Position: mysql.EncodePosition(mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 0, - Server: 62344, - Sequence: 0x0d, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 0, + Server: 62344, + Sequence: 0x0d, + }, }, }), }, @@ -698,10 +714,12 @@ func TestStreamerParseEventsSetInsertID(t *testing.T) { EventToken: &querypb.EventToken{ Timestamp: 1407805592, Position: mysql.EncodePosition(mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 0, - Server: 62344, - Sequence: 0x0d, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 0, + Server: 62344, + Sequence: 0x0d, + }, }, }), }, @@ -788,10 +806,12 @@ func TestStreamerParseEventsOtherDB(t *testing.T) { EventToken: &querypb.EventToken{ Timestamp: 1407805592, Position: mysql.EncodePosition(mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 0, - Server: 62344, - Sequence: 0x0d, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 0, + Server: 62344, + Sequence: 0x0d, + }, }, }), }, @@ -842,10 +862,12 @@ func TestStreamerParseEventsOtherDBBegin(t *testing.T) { EventToken: &querypb.EventToken{ Timestamp: 1407805592, Position: mysql.EncodePosition(mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 0, - Server: 62344, - Sequence: 0x0d, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 0, + Server: 62344, + Sequence: 0x0d, + }, }, }), }, @@ -938,10 +960,12 @@ func TestStreamerParseEventsMariadbBeginGTID(t *testing.T) { EventToken: &querypb.EventToken{ Timestamp: 1409892744, Position: mysql.EncodePosition(mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 0, - Server: 62344, - Sequence: 10, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 0, + Server: 62344, + Sequence: 10, + }, }, }), }, @@ -989,10 +1013,12 @@ func TestStreamerParseEventsMariadbStandaloneGTID(t *testing.T) { EventToken: &querypb.EventToken{ Timestamp: 1409892744, Position: mysql.EncodePosition(mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 0, - Server: 62344, - Sequence: 9, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 0, + Server: 62344, + Sequence: 9, + }, }, }), }, diff --git a/go/vt/discovery/healthcheck.go b/go/vt/discovery/healthcheck.go index 0719d90308d..7fde2774bd4 100644 --- a/go/vt/discovery/healthcheck.go +++ b/go/vt/discovery/healthcheck.go @@ -53,6 +53,7 @@ import ( "golang.org/x/net/context" "vitess.io/vitess/go/netutil" "vitess.io/vitess/go/stats" + "vitess.io/vitess/go/sync2" "vitess.io/vitess/go/vt/grpcclient" "vitess.io/vitess/go/vt/log" "vitess.io/vitess/go/vt/topo/topoproto" @@ -204,6 +205,12 @@ func (e *TabletStats) DeepEqual(f *TabletStats) bool { (e.LastError != nil && f.LastError != nil && e.LastError.Error() == f.LastError.Error())) } +// Copy produces a copy of TabletStats. +func (e *TabletStats) Copy() *TabletStats { + ts := *e + return &ts +} + // GetTabletHostPort formats a tablet host port address. func (e TabletStats) GetTabletHostPort() string { vtPort := e.Tablet.PortMap["vt"] @@ -289,49 +296,58 @@ type HealthCheck interface { Close() error } -// healthCheckConn contains details about a tablet. -// It is used internally by HealthCheckImpl to keep all the info -// about a tablet. -type healthCheckConn struct { - // set at construction time - ctx context.Context - cancelFunc context.CancelFunc - - // mu protects all the following fields. - // When locking both mutex from HealthCheck and healthCheckConn, - // HealthCheck.mu goes first. - // Note tabletStats.Tablet and tabletStats.Name are immutable. - mu sync.RWMutex - conn queryservice.QueryService - streamCancelFunc context.CancelFunc - tabletStats TabletStats - loggedServingState bool - lastResponseTimestamp time.Time // timestamp of the last healthcheck response -} - // HealthCheckImpl performs health checking and notifies downstream components about any changes. +// It contains a map of tabletHealth objects, each of which stores the health information for +// a tablet. A checkConn goroutine is spawned for each tabletHealth, which is responsible for +// keeping that tabletHealth up-to-date. This is done through callbacks to updateHealth. +// If checkConn terminates for any reason, it updates tabletHealth.Up as false. If a tabletHealth +// gets removed from the map, its cancelFunc gets called, which ensures that the associated +// checkConn goroutine eventually terminates. type HealthCheckImpl struct { // Immutable fields set at construction time. listener HealthCheckStatsListener sendDownEvents bool retryDelay time.Duration healthCheckTimeout time.Duration - closeChan chan struct{} // signals the process gorouting to terminate - // wg keeps track of all launched Go routines. - wg sync.WaitGroup + // connsWG keeps track of all launched Go routines that monitor tablet connections. + connsWG sync.WaitGroup // mu protects all the following fields. - // When locking both mutex from HealthCheck and healthCheckConn, - // HealthCheck.mu goes first. - mu sync.RWMutex + mu sync.Mutex - // addrToConns maps from address to the healthCheckConn object. - addrToConns map[string]*healthCheckConn + // addrToHealth maps from address to tabletHealth. + addrToHealth map[string]*tabletHealth // Wait group that's used to wait until all initial StatsUpdate() calls are made after the AddTablet() calls. initialUpdatesWG sync.WaitGroup } +// healthCheckConn is a structure that lives within the scope of +// the checkConn goroutine to maintain its internal state. Therefore, +// it does not require synchronization. Changes that are relevant to +// healthcheck are transmitted through calls to HealthCheckImpl.updateHealth. +// TODO(sougou): move this and associated functions to a separate file. +type healthCheckConn struct { + ctx context.Context + + conn queryservice.QueryService + tabletStats TabletStats + loggedServingState bool + lastResponseTimestamp time.Time // timestamp of the last healthcheck response +} + +// tabletHealth maintains the health status of a tablet. A map of this +// structure is maintained in HealthCheckImpl. +type tabletHealth struct { + // cancelFunc must be called before discarding tabletHealth. + // This will ensure that the associated checkConn goroutine will terminate. + cancelFunc context.CancelFunc + // conn is the connection associated with the tablet. + conn queryservice.QueryService + // latestTabletStats stores the latest health stats of the tablet. + latestTabletStats TabletStats +} + // NewDefaultHealthCheck creates a new HealthCheck object with a default configuration. func NewDefaultHealthCheck() HealthCheck { return NewHealthCheck(DefaultHealthCheckRetryDelay, DefaultHealthCheckTimeout) @@ -348,37 +364,11 @@ func NewDefaultHealthCheck() HealthCheck { // not healthy. func NewHealthCheck(retryDelay, healthCheckTimeout time.Duration) HealthCheck { hc := &HealthCheckImpl{ - addrToConns: make(map[string]*healthCheckConn), + addrToHealth: make(map[string]*tabletHealth), retryDelay: retryDelay, healthCheckTimeout: healthCheckTimeout, - closeChan: make(chan struct{}), } - hc.wg.Add(1) - go func() { - defer hc.wg.Done() - // Start another go routine to check timeout. - // Currently vttablet sends healthcheck response every 20 seconds. - // We set the default timeout to 1 minute (20s * 3), - // and also perform the timeout check in sync with vttablet frequency. - // When we change the healthcheck frequency on vttablet, - // we should also adjust here. - t := time.NewTicker(healthCheckTimeout / 3) - defer t.Stop() - for { - select { - case <-hc.closeChan: - return - case _, ok := <-t.C: - if !ok { - // the ticker stoped - return - } - hc.checkHealthCheckTimeout() - } - } - }() - healthcheckOnce.Do(func() { http.Handle("/debug/gateway", hc) }) @@ -418,16 +408,13 @@ func (hc *HealthCheckImpl) ServeHTTP(w http.ResponseWriter, _ *http.Request) { // servingConnStats returns the number of serving tablets per keyspace/shard/tablet type. func (hc *HealthCheckImpl) servingConnStats() map[string]int64 { res := make(map[string]int64) - hc.mu.RLock() - defer hc.mu.RUnlock() - for _, hcc := range hc.addrToConns { - hcc.mu.RLock() - if !hcc.tabletStats.Up || !hcc.tabletStats.Serving || hcc.tabletStats.LastError != nil { - hcc.mu.RUnlock() + hc.mu.Lock() + defer hc.mu.Unlock() + for _, th := range hc.addrToHealth { + if !th.latestTabletStats.Up || !th.latestTabletStats.Serving || th.latestTabletStats.LastError != nil { continue } - key := fmt.Sprintf("%s.%s.%s", hcc.tabletStats.Target.Keyspace, hcc.tabletStats.Target.Shard, topoproto.TabletTypeLString(hcc.tabletStats.Target.TabletType)) - hcc.mu.RUnlock() + key := fmt.Sprintf("%s.%s.%s", th.latestTabletStats.Target.Keyspace, th.latestTabletStats.Target.Shard, topoproto.TabletTypeLString(th.latestTabletStats.Target.TabletType)) res[key]++ } return res @@ -455,59 +442,139 @@ func (hc *HealthCheckImpl) stateChecksum() int64 { return int64(crc32.ChecksumIEEE(buf.Bytes())) } +// updateHealth updates the tabletHealth record and transmits the tablet stats +// to the listener. +func (hc *HealthCheckImpl) updateHealth(ts *TabletStats, conn queryservice.QueryService) { + // Unconditionally send the received update at the end. + defer func() { + if hc.listener != nil { + hc.listener.StatsUpdate(ts) + } + }() + + hc.mu.Lock() + th, ok := hc.addrToHealth[ts.Key] + if !ok { + // This can happen on delete because the entry is removed first, + // or if HealthCheckImpl has been closed. + hc.mu.Unlock() + return + } + oldts := th.latestTabletStats + th.latestTabletStats = *ts + th.conn = conn + hc.mu.Unlock() + + // In the case where a tablet changes type (but not for the + // initial message), we want to log it, and maybe advertise it too. + if oldts.Target.TabletType != topodatapb.TabletType_UNKNOWN && oldts.Target.TabletType != ts.Target.TabletType { + // Log and maybe notify + log.Infof("HealthCheckUpdate(Type Change): %v, tablet: %s, target %+v => %+v, reparent time: %v", + oldts.Name, topotools.TabletIdent(oldts.Tablet), topotools.TargetIdent(oldts.Target), topotools.TargetIdent(ts.Target), ts.TabletExternallyReparentedTimestamp) + if hc.listener != nil && hc.sendDownEvents { + oldts.Up = false + hc.listener.StatsUpdate(&oldts) + } + + // Track how often a tablet gets promoted to master. It is used for + // comparing against the variables in go/vtgate/buffer/variables.go. + if oldts.Target.TabletType != topodatapb.TabletType_MASTER && ts.Target.TabletType == topodatapb.TabletType_MASTER { + hcMasterPromotedCounters.Add([]string{ts.Target.Keyspace, ts.Target.Shard}, 1) + } + } +} + // finalizeConn closes the health checking connection and sends the final // notification about the tablet to downstream. To be called only on exit from // checkConn(). func (hc *HealthCheckImpl) finalizeConn(hcc *healthCheckConn) { - hcc.mu.Lock() - hccConn := hcc.conn - hccCtx := hcc.ctx - hcc.conn = nil hcc.tabletStats.Up = false hcc.setServingState(false, "finalizeConn closing connection") // Note: checkConn() exits only when hcc.ctx.Done() is closed. Thus it's // safe to simply get Err() value here and assign to LastError. hcc.tabletStats.LastError = hcc.ctx.Err() - ts := hcc.tabletStats - hcc.mu.Unlock() - - if hccConn != nil { - hccConn.Close(hccCtx) - } - - if hc.listener != nil { - hc.listener.StatsUpdate(&ts) + hc.updateHealth(hcc.tabletStats.Copy(), nil) + if hcc.conn != nil { + // Don't use hcc.ctx because it's already closed. + // Use a separate context, and add a timeout to prevent unbounded waits. + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + hcc.conn.Close(ctx) + hcc.conn = nil } } // checkConn performs health checking on the given tablet. func (hc *HealthCheckImpl) checkConn(hcc *healthCheckConn, name string) { - defer hc.wg.Done() + defer hc.connsWG.Done() defer hc.finalizeConn(hcc) // Initial notification for downstream about the tablet existence. - hcc.mu.Lock() - ts := hcc.tabletStats - hcc.mu.Unlock() - if hc.listener != nil { - hc.listener.StatsUpdate(&ts) - } + hc.updateHealth(hcc.tabletStats.Copy(), hcc.conn) hc.initialUpdatesWG.Done() retryDelay := hc.retryDelay for { - ctx, cancel := context.WithCancel(hcc.ctx) - hcc.mu.Lock() - hcc.streamCancelFunc = cancel - hcc.mu.Unlock() + streamCtx, streamCancel := context.WithCancel(hcc.ctx) + + // Setup a watcher that restarts the timer every time an update is received. + // If a timeout occurs for a serving tablet, we make it non-serving and send + // a status update. The stream is also terminated so it can be retried. + // servingStatus feeds into the serving var, which keeps track of the serving + // status transmitted by the tablet. + servingStatus := make(chan bool, 1) + // timedout is accessed atomically because there could be a race + // between the goroutine that sets it and the check for its value + // later. + timedout := sync2.NewAtomicBool(false) + serving := hcc.tabletStats.Serving + go func() { + for { + select { + case serving = <-servingStatus: + continue + case <-time.After(hc.healthCheckTimeout): + // Ignore if not serving. + if !serving { + continue + } + timedout.Set(true) + streamCancel() + return + case <-streamCtx.Done(): + // If stream returns while serving is false, the function + // will get stuck in an infinite loop. This code path + // breaks the loop. + return + } + } + }() // Read stream health responses. - hcc.stream(ctx, hc, func(shr *querypb.StreamHealthResponse) error { + hcc.stream(streamCtx, hc, func(shr *querypb.StreamHealthResponse) error { // We received a message. Reset the back-off. retryDelay = hc.retryDelay + // Don't block on send to avoid deadlocks. + select { + case servingStatus <- shr.Serving: + default: + } return hcc.processResponse(hc, shr) }) + // streamCancel to make sure the watcher goroutine terminates. + streamCancel() + + // If there was a timeout send an error. We do this after stream has returned. + // This will ensure that this update prevails over any previous message that + // stream could have sent. + if timedout.Get() { + hcc.tabletStats.LastError = fmt.Errorf("healthcheck timed out (latest %v)", hcc.lastResponseTimestamp) + hcc.setServingState(false, hcc.tabletStats.LastError.Error()) + hc.updateHealth(hcc.tabletStats.Copy(), hcc.conn) + hcErrorCounters.Add([]string{hcc.tabletStats.Target.Keyspace, hcc.tabletStats.Target.Shard, topoproto.TabletTypeLString(hcc.tabletStats.Target.TabletType)}, 1) + } + // Streaming RPC failed e.g. because vttablet was restarted or took too long. // Sleep until the next retry is up or the context is done/canceled. select { @@ -548,41 +615,24 @@ func (hcc *healthCheckConn) setServingState(serving bool, reason string) { // stream streams healthcheck responses to callback. func (hcc *healthCheckConn) stream(ctx context.Context, hc *HealthCheckImpl, callback func(*querypb.StreamHealthResponse) error) { - hcc.mu.Lock() - conn := hcc.conn - hcc.mu.Unlock() - - if conn == nil { - var err error - conn, err = tabletconn.GetDialer()(hcc.tabletStats.Tablet, grpcclient.FailFast(true)) + if hcc.conn == nil { + conn, err := tabletconn.GetDialer()(hcc.tabletStats.Tablet, grpcclient.FailFast(true)) if err != nil { - hcc.mu.Lock() hcc.tabletStats.LastError = err - hcc.mu.Unlock() return } - - hcc.mu.Lock() hcc.conn = conn hcc.tabletStats.LastError = nil - hcc.mu.Unlock() } - if err := conn.StreamHealth(ctx, callback); err != nil { - hcc.mu.Lock() - hcc.conn = nil + if err := hcc.conn.StreamHealth(ctx, callback); err != nil { hcc.setServingState(false, err.Error()) hcc.tabletStats.LastError = err - ts := hcc.tabletStats - hcc.mu.Unlock() - // notify downstream for serving status change - if hc.listener != nil { - hc.listener.StatsUpdate(&ts) - } - conn.Close(ctx) - return + // Send nil because we intend to close the connection. + hc.updateHealth(hcc.tabletStats.Copy(), nil) + hcc.conn.Close(ctx) + hcc.conn = nil } - return } // processResponse reads one health check response, and notifies HealthCheckStatsListener. @@ -598,10 +648,6 @@ func (hcc *healthCheckConn) processResponse(hc *HealthCheckImpl, shr *querypb.St return fmt.Errorf("health stats is not valid: %v", shr) } - hcc.mu.RLock() - oldTs := hcc.tabletStats - hcc.mu.RUnlock() - // an app-level error from tablet, force serving state. var healthErr error serving := shr.Serving @@ -610,28 +656,10 @@ func (hcc *healthCheckConn) processResponse(hc *HealthCheckImpl, shr *querypb.St serving = false } - // oldTs.Tablet.Alias.Uid may be 0 because the youtube internal mechanism uses a different + // hcc.TabletStats.Tablet.Alias.Uid may be 0 because the youtube internal mechanism uses a different // code path to initialize this value. If so, we should skip this check. - if shr.TabletAlias != nil && oldTs.Tablet.Alias.Uid != 0 && !proto.Equal(shr.TabletAlias, oldTs.Tablet.Alias) { - return fmt.Errorf("health stats mismatch, tablet %+v alias does not match response alias %v", oldTs.Tablet, shr.TabletAlias) - } - - // In the case where a tablet changes type (but not for the - // initial message), we want to log it, and maybe advertise it too. - if hcc.tabletStats.Target.TabletType != topodatapb.TabletType_UNKNOWN && hcc.tabletStats.Target.TabletType != shr.Target.TabletType { - // Log and maybe notify - log.Infof("HealthCheckUpdate(Type Change): %v, tablet: %s, target %+v => %+v, reparent time: %v", - oldTs.Name, topotools.TabletIdent(oldTs.Tablet), topotools.TargetIdent(oldTs.Target), topotools.TargetIdent(shr.Target), shr.TabletExternallyReparentedTimestamp) - if hc.listener != nil && hc.sendDownEvents { - oldTs.Up = false - hc.listener.StatsUpdate(&oldTs) - } - - // Track how often a tablet gets promoted to master. It is used for - // comparing against the variables in go/vtgate/buffer/variables.go. - if oldTs.Target.TabletType != topodatapb.TabletType_MASTER && shr.Target.TabletType == topodatapb.TabletType_MASTER { - hcMasterPromotedCounters.Add([]string{shr.Target.Keyspace, shr.Target.Shard}, 1) - } + if shr.TabletAlias != nil && hcc.tabletStats.Tablet.Alias.Uid != 0 && !proto.Equal(shr.TabletAlias, hcc.tabletStats.Tablet.Alias) { + return fmt.Errorf("health stats mismatch, tablet %+v alias does not match response alias %v", hcc.tabletStats.Tablet, shr.TabletAlias) } // In this case where a new tablet is initialized or a tablet type changes, we want to @@ -642,18 +670,6 @@ func (hcc *healthCheckConn) processResponse(hc *HealthCheckImpl, shr *querypb.St // Update our record, and notify downstream for tabletType and // realtimeStats change. - ts := hcc.update(shr, serving, healthErr) - if hc.listener != nil { - hc.listener.StatsUpdate(&ts) - } - return nil -} - -// update updates the stats of a healthCheckConn, and returns a copy -// of its tabletStats. -func (hcc *healthCheckConn) update(shr *querypb.StreamHealthResponse, serving bool, healthErr error) TabletStats { - hcc.mu.Lock() - defer hcc.mu.Unlock() hcc.lastResponseTimestamp = time.Now() hcc.tabletStats.Target = shr.Target hcc.tabletStats.TabletExternallyReparentedTimestamp = shr.TabletExternallyReparentedTimestamp @@ -664,55 +680,8 @@ func (hcc *healthCheckConn) update(shr *querypb.StreamHealthResponse, serving bo reason = "healthCheck update error: " + healthErr.Error() } hcc.setServingState(serving, reason) - return hcc.tabletStats -} - -func (hc *HealthCheckImpl) checkHealthCheckTimeout() { - hc.mu.RLock() - list := make([]*healthCheckConn, 0, len(hc.addrToConns)) - for _, hcc := range hc.addrToConns { - list = append(list, hcc) - } - hc.mu.RUnlock() - for _, hcc := range list { - hcc.mu.RLock() - if !hcc.tabletStats.Serving { - // ignore non-serving tablet - hcc.mu.RUnlock() - continue - } - if time.Now().Sub(hcc.lastResponseTimestamp) < hc.healthCheckTimeout { - // received a healthcheck response recently - hcc.mu.RUnlock() - continue - } - hcc.mu.RUnlock() - // mark the tablet non-serving as we have not seen a health check response for a long time - hcc.mu.Lock() - // check again to avoid race condition - if !hcc.tabletStats.Serving { - // ignore non-serving tablet - hcc.mu.Unlock() - continue - } - if time.Now().Sub(hcc.lastResponseTimestamp) < hc.healthCheckTimeout { - // received a healthcheck response recently - hcc.mu.Unlock() - continue - } - - //Timeout detected. Cancel the current streaming RPC and let checkConn() restart it. - hcc.streamCancelFunc() - hcc.tabletStats.LastError = fmt.Errorf("healthcheck timed out (latest %v)", hcc.lastResponseTimestamp) - hcc.setServingState(false, hcc.tabletStats.LastError.Error()) - ts := hcc.tabletStats - hcc.mu.Unlock() - // notify downstream for serving status change - if hc.listener != nil { - hc.listener.StatsUpdate(&ts) - } - hcErrorCounters.Add([]string{ts.Target.Keyspace, ts.Target.Shard, topoproto.TabletTypeLString(ts.Target.TabletType)}, 1) - } + hc.updateHealth(hcc.tabletStats.Copy(), hcc.conn) + return nil } func (hc *HealthCheckImpl) deleteConn(tablet *topodatapb.Tablet) { @@ -720,16 +689,13 @@ func (hc *HealthCheckImpl) deleteConn(tablet *topodatapb.Tablet) { defer hc.mu.Unlock() key := TabletToMapKey(tablet) - hcc, ok := hc.addrToConns[key] + th, ok := hc.addrToHealth[key] if !ok { - log.Warningf("deleting unknown tablet: %+v", tablet) return } - hcc.mu.Lock() - hcc.tabletStats.Up = false - hcc.mu.Unlock() - hcc.cancelFunc() - delete(hc.addrToConns, key) + th.latestTabletStats.Up = false + th.cancelFunc() + delete(hc.addrToHealth, key) } // SetListener sets the listener for healthcheck updates. @@ -742,7 +708,7 @@ func (hc *HealthCheckImpl) SetListener(listener HealthCheckStatsListener, sendDo hc.mu.Lock() defer hc.mu.Unlock() - if len(hc.addrToConns) > 0 { + if len(hc.addrToHealth) > 0 { panic("must not call SetListener after tablets were added") } @@ -757,8 +723,7 @@ func (hc *HealthCheckImpl) AddTablet(tablet *topodatapb.Tablet, name string) { ctx, cancelFunc := context.WithCancel(context.Background()) key := TabletToMapKey(tablet) hcc := &healthCheckConn{ - ctx: ctx, - cancelFunc: cancelFunc, + ctx: ctx, tabletStats: TabletStats{ Key: key, Tablet: tablet, @@ -768,16 +733,24 @@ func (hc *HealthCheckImpl) AddTablet(tablet *topodatapb.Tablet, name string) { }, } hc.mu.Lock() - if _, ok := hc.addrToConns[key]; ok { + if hc.addrToHealth == nil { + // already closed. + hc.mu.Unlock() + return + } + if _, ok := hc.addrToHealth[key]; ok { hc.mu.Unlock() log.Warningf("adding duplicate tablet %v for %v: %+v", name, tablet.Alias.Cell, tablet) return } - hc.addrToConns[key] = hcc + hc.addrToHealth[key] = &tabletHealth{ + cancelFunc: cancelFunc, + latestTabletStats: hcc.tabletStats, + } hc.initialUpdatesWG.Add(1) + hc.connsWG.Add(1) hc.mu.Unlock() - hc.wg.Add(1) go hc.checkConn(hcc, name) } @@ -803,16 +776,14 @@ func (hc *HealthCheckImpl) WaitForInitialStatsUpdates() { // GetConnection returns the TabletConn of the given tablet. func (hc *HealthCheckImpl) GetConnection(key string) queryservice.QueryService { - hc.mu.RLock() - hcc := hc.addrToConns[key] - if hcc == nil { - hc.mu.RUnlock() + hc.mu.Lock() + defer hc.mu.Unlock() + + th := hc.addrToHealth[key] + if th == nil { return nil } - hc.mu.RUnlock() - hcc.mu.RLock() - defer hcc.mu.RUnlock() - return hcc.conn + return th.conn } // TabletsCacheStatus is the current tablets for a cell/target. @@ -912,22 +883,20 @@ func (hc *HealthCheckImpl) CacheStatus() TabletsCacheStatusList { func (hc *HealthCheckImpl) cacheStatusMap() map[string]*TabletsCacheStatus { tcsMap := make(map[string]*TabletsCacheStatus) - hc.mu.RLock() - defer hc.mu.RUnlock() - for _, hcc := range hc.addrToConns { - hcc.mu.RLock() - key := fmt.Sprintf("%v.%v.%v.%v", hcc.tabletStats.Tablet.Alias.Cell, hcc.tabletStats.Target.Keyspace, hcc.tabletStats.Target.Shard, hcc.tabletStats.Target.TabletType.String()) + hc.mu.Lock() + defer hc.mu.Unlock() + for _, th := range hc.addrToHealth { + key := fmt.Sprintf("%v.%v.%v.%v", th.latestTabletStats.Tablet.Alias.Cell, th.latestTabletStats.Target.Keyspace, th.latestTabletStats.Target.Shard, th.latestTabletStats.Target.TabletType.String()) var tcs *TabletsCacheStatus var ok bool if tcs, ok = tcsMap[key]; !ok { tcs = &TabletsCacheStatus{ - Cell: hcc.tabletStats.Tablet.Alias.Cell, - Target: hcc.tabletStats.Target, + Cell: th.latestTabletStats.Tablet.Alias.Cell, + Target: th.latestTabletStats.Target, } tcsMap[key] = tcs } - stats := hcc.tabletStats - hcc.mu.RUnlock() + stats := th.latestTabletStats tcs.TabletsStats = append(tcs.TabletsStats, &stats) } return tcsMap @@ -938,18 +907,17 @@ func (hc *HealthCheckImpl) cacheStatusMap() map[string]*TabletsCacheStatus { // currently executing and won't be called again. func (hc *HealthCheckImpl) Close() error { hc.mu.Lock() - close(hc.closeChan) - for _, hcc := range hc.addrToConns { - hcc.cancelFunc() + for _, th := range hc.addrToHealth { + th.cancelFunc() } - hc.addrToConns = make(map[string]*healthCheckConn) + hc.addrToHealth = nil // Release the lock early or a pending checkHealthCheckTimeout // cannot get a read lock on it. hc.mu.Unlock() // Wait for the checkHealthCheckTimeout Go routine and each Go // routine per tablet. - hc.wg.Wait() + hc.connsWG.Wait() return nil } diff --git a/go/vt/discovery/healthcheck_test.go b/go/vt/discovery/healthcheck_test.go index 1bb7d570145..616a15dfdff 100644 --- a/go/vt/discovery/healthcheck_test.go +++ b/go/vt/discovery/healthcheck_test.go @@ -545,6 +545,25 @@ func TestHealthCheckTimeout(t *testing.T) { t.Errorf("StreamHealth should be canceled after timeout, but is not") } + // repeat the wait. There should be no error or cancelation. + fc.resetCanceledFlag() + time.Sleep(2 * timeout) + t.Logf(`Sleep(2 * timeout)`) + + select { + case res = <-l.output: + t.Errorf(`<-l.output: %+v; want not message`, res) + default: + } + + if err := checkErrorCounter("k", "s", topodatapb.TabletType_MASTER, 1); err != nil { + t.Errorf("%v", err) + } + + if fc.isCanceled() { + t.Errorf("StreamHealth should not be canceled after timeout") + } + // send a healthcheck response, it should be serving again input <- shr t.Logf(`input <- {{Keyspace: "k", Shard: "s", TabletType: MASTER}, Serving: true, TabletExternallyReparentedTimestamp: 10, {SecondsBehindMaster: 1, CpuUsage: 0.2}}`) @@ -694,6 +713,12 @@ func (fc *fakeConn) isCanceled() bool { return fc.canceled } +func (fc *fakeConn) resetCanceledFlag() { + fc.mu.Lock() + defer fc.mu.Unlock() + fc.canceled = false +} + func checkErrorCounter(keyspace, shard string, tabletType topodatapb.TabletType, want int64) error { statsKey := []string{keyspace, shard, topoproto.TabletTypeLString(tabletType)} name := strings.Join(statsKey, ".") diff --git a/go/vt/hook/hook.go b/go/vt/hook/hook.go index c2844b3f362..db65ef497ae 100644 --- a/go/vt/hook/hook.go +++ b/go/vt/hook/hook.go @@ -85,6 +85,11 @@ func NewSimpleHook(name string) *Hook { return &Hook{Name: name} } +// NewHookWithEnv returns a Hook object with the provided name, params and ExtraEnv. +func NewHookWithEnv(name string, params []string, env map[string]string) *Hook { + return &Hook{Name: name, Parameters: params, ExtraEnv: env} +} + // findHook trie to locate the hook, and returns the exec.Cmd for it. func (hook *Hook) findHook() (*exec.Cmd, int, error) { // Check the hook path. diff --git a/go/vt/logutil/level.go b/go/vt/logutil/level.go index bf59a42dde6..86c61c868ea 100644 --- a/go/vt/logutil/level.go +++ b/go/vt/logutil/level.go @@ -23,11 +23,13 @@ import ( func init() { threshold := flag.Lookup("stderrthreshold") if threshold == nil { - panic("the logging module doesn't specify a stderrthreshold flag") + // the logging module doesn't specify a stderrthreshold flag + return } + const warningLevel = "1" if err := threshold.Value.Set(warningLevel); err != nil { - panic(err) + return } threshold.DefValue = warningLevel } diff --git a/go/vt/mysqlctl/mycnf_test.go b/go/vt/mysqlctl/mycnf_test.go index 082ea5ecc3c..5bc438c3c84 100644 --- a/go/vt/mysqlctl/mycnf_test.go +++ b/go/vt/mysqlctl/mycnf_test.go @@ -23,7 +23,9 @@ import ( "strings" "testing" + "vitess.io/vitess/go/vt/dbconfigs" "vitess.io/vitess/go/vt/env" + "vitess.io/vitess/go/vt/servenv" ) var MycnfPath = "/tmp/my.cnf" @@ -76,3 +78,82 @@ func TestMycnf(t *testing.T) { t.Errorf("mycnf.ServerID = %v, want %v", got, want) } } + +// Run this test if any changes are made to hook handling / make_mycnf hook +// other tests fail if we keep the hook around +// 1. ln -snf $VTTOP/test/vthook-make_mycnf $VTROOT/vthook/make_mycnf +// 2. Remove "No" prefix from func name +// 3. go test +// 4. \rm $VTROOT/vthook/make_mycnf +// 5. Add No Prefix back + +func NoTestMycnfHook(t *testing.T) { + os.Setenv("MYSQL_FLAVOR", "MariaDB") + uid := uint32(11111) + cnf := NewMycnf(uid, 6802) + // Assigning ServerID to be different from tablet UID to make sure that there are no + // assumptions in the code that those IDs are the same. + cnf.ServerID = 22222 + + // expect these in the output my.cnf + os.Setenv("KEYSPACE", "test-messagedb") + os.Setenv("SHARD", "0") + os.Setenv("TABLET_TYPE", "MASTER") + os.Setenv("TABLET_ID", "11111") + os.Setenv("TABLET_DIR", TabletDir(uid)) + os.Setenv("MYSQL_PORT", "15306") + // this is not being passed, so it should be nil + os.Setenv("MY_VAR", "myvalue") + + dbcfgs, err := dbconfigs.Init(cnf.SocketFile) + mysqld := NewMysqld(dbcfgs) + servenv.OnClose(mysqld.Close) + + err = mysqld.InitConfig(cnf) + if err != nil { + t.Errorf("err: %v", err) + } + _, err = ioutil.ReadFile(cnf.path) + if err != nil { + t.Errorf("failed reading, err %v", err) + return + } + mycnf := NewMycnf(uid, 0) + mycnf.path = cnf.path + mycnf, err = ReadMycnf(mycnf) + if err != nil { + t.Errorf("failed reading, err %v", err) + } else { + t.Logf("socket file %v", mycnf.SocketFile) + } + // Tablet UID should be 11111, which determines tablet/data dir. + if got, want := mycnf.DataDir, "/vt_0000011111/"; !strings.Contains(got, want) { + t.Errorf("mycnf.DataDir = %v, want *%v*", got, want) + } + // MySQL server-id should be 22222, different from Tablet UID. + if got, want := mycnf.ServerID, uint32(22222); got != want { + t.Errorf("mycnf.ServerID = %v, want %v", got, want) + } + // check that the env variables we set were passed correctly to the hook + if got, want := mycnf.lookup("KEYSPACE"), "test-messagedb"; got != want { + t.Errorf("Error passing env %v, got %v, want %v", "KEYSPACE", got, want) + } + if got, want := mycnf.lookup("SHARD"), "0"; got != want { + t.Errorf("Error passing env %v, got %v, want %v", "SHARD", got, want) + } + if got, want := mycnf.lookup("TABLET_TYPE"), "MASTER"; got != want { + t.Errorf("Error passing env %v, got %v, want %v", "TABLET_TYPE", got, want) + } + if got, want := mycnf.lookup("TABLET_ID"), "11111"; got != want { + t.Errorf("Error passing env %v, got %v, want %v", "TABLET_ID", got, want) + } + if got, want := mycnf.lookup("TABLET_DIR"), "/vt_0000011111"; !strings.Contains(got, want) { + t.Errorf("Error passing env %v, got %v, want %v", "TABLET_DIR", got, want) + } + if got, want := mycnf.lookup("MYSQL_PORT"), "15306"; got != want { + t.Errorf("Error passing env %v, got %v, want %v", "MYSQL_PORT", got, want) + } + if got := mycnf.lookup("MY_VAR"); got != "" { + t.Errorf("Unexpected env %v set to %v", "MY_VAR", got) + } +} diff --git a/go/vt/mysqlctl/mysqld.go b/go/vt/mysqlctl/mysqld.go index c754d0cafd7..03a3b10eea4 100644 --- a/go/vt/mysqlctl/mysqld.go +++ b/go/vt/mysqlctl/mysqld.go @@ -595,7 +595,13 @@ func (mysqld *Mysqld) initConfig(root string, cnf *Mycnf, outFile string) error var err error var configData string - switch hr := hook.NewSimpleHook("make_mycnf").Execute(); hr.ExitStatus { + env := make(map[string]string) + envVars := []string{"KEYSPACE", "SHARD", "TABLET_TYPE", "TABLET_ID", "TABLET_DIR", "MYSQL_PORT"} + for _, v := range envVars { + env[v] = os.Getenv(v) + } + + switch hr := hook.NewHookWithEnv("make_mycnf", nil, env).Execute(); hr.ExitStatus { case hook.HOOK_DOES_NOT_EXIST: log.Infof("make_mycnf hook doesn't exist, reading template files") configData, err = cnf.makeMycnf(getMycnfTemplates(root)) diff --git a/go/vt/mysqlctl/s3backupstorage/s3.go b/go/vt/mysqlctl/s3backupstorage/s3.go index fe63c6c5191..40260884122 100644 --- a/go/vt/mysqlctl/s3backupstorage/s3.go +++ b/go/vt/mysqlctl/s3backupstorage/s3.go @@ -51,7 +51,7 @@ var ( region = flag.String("s3_backup_aws_region", "us-east-1", "AWS region to use") // AWS endpoint, defaults to amazonaws.com but appliances may use a different location - endpoint = flag.String("s3_backup_aws_endpoint", "amazonaws.com", "endpoint of the S3 backend (region must be provided)") + endpoint = flag.String("s3_backup_aws_endpoint", "", "endpoint of the S3 backend (region must be provided)") // bucket is where the backups will go. bucket = flag.String("s3_backup_storage_bucket", "", "S3 bucket to use for backups") diff --git a/go/vt/proto/vschema/vschema.pb.go b/go/vt/proto/vschema/vschema.pb.go index 938a617d117..da4101a3090 100644 --- a/go/vt/proto/vschema/vschema.pb.go +++ b/go/vt/proto/vschema/vschema.pb.go @@ -34,7 +34,7 @@ func (m *Keyspace) Reset() { *m = Keyspace{} } func (m *Keyspace) String() string { return proto.CompactTextString(m) } func (*Keyspace) ProtoMessage() {} func (*Keyspace) Descriptor() ([]byte, []int) { - return fileDescriptor_vschema_f0f3bc4bc6c5c748, []int{0} + return fileDescriptor_vschema_5ecfaf46981fe072, []int{0} } func (m *Keyspace) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Keyspace.Unmarshal(m, b) @@ -99,7 +99,7 @@ func (m *Vindex) Reset() { *m = Vindex{} } func (m *Vindex) String() string { return proto.CompactTextString(m) } func (*Vindex) ProtoMessage() {} func (*Vindex) Descriptor() ([]byte, []int) { - return fileDescriptor_vschema_f0f3bc4bc6c5c748, []int{1} + return fileDescriptor_vschema_5ecfaf46981fe072, []int{1} } func (m *Vindex) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Vindex.Unmarshal(m, b) @@ -156,17 +156,21 @@ type Table struct { // shard, as dictated by the keyspace id. // The keyspace id is represened in hex form // like in keyranges. - Pinned string `protobuf:"bytes,5,opt,name=pinned" json:"pinned,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Pinned string `protobuf:"bytes,5,opt,name=pinned" json:"pinned,omitempty"` + // column_list_authoritative is set to true if columns is + // an authoritative list for the table. This allows + // us to expand 'select *' expressions. + ColumnListAuthoritative bool `protobuf:"varint,6,opt,name=column_list_authoritative,json=columnListAuthoritative" json:"column_list_authoritative,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *Table) Reset() { *m = Table{} } func (m *Table) String() string { return proto.CompactTextString(m) } func (*Table) ProtoMessage() {} func (*Table) Descriptor() ([]byte, []int) { - return fileDescriptor_vschema_f0f3bc4bc6c5c748, []int{2} + return fileDescriptor_vschema_5ecfaf46981fe072, []int{2} } func (m *Table) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Table.Unmarshal(m, b) @@ -221,6 +225,13 @@ func (m *Table) GetPinned() string { return "" } +func (m *Table) GetColumnListAuthoritative() bool { + if m != nil { + return m.ColumnListAuthoritative + } + return false +} + // ColumnVindex is used to associate a column to a vindex. type ColumnVindex struct { // Legacy implemenation, moving forward all vindexes should define a list of columns. @@ -238,7 +249,7 @@ func (m *ColumnVindex) Reset() { *m = ColumnVindex{} } func (m *ColumnVindex) String() string { return proto.CompactTextString(m) } func (*ColumnVindex) ProtoMessage() {} func (*ColumnVindex) Descriptor() ([]byte, []int) { - return fileDescriptor_vschema_f0f3bc4bc6c5c748, []int{3} + return fileDescriptor_vschema_5ecfaf46981fe072, []int{3} } func (m *ColumnVindex) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ColumnVindex.Unmarshal(m, b) @@ -293,7 +304,7 @@ func (m *AutoIncrement) Reset() { *m = AutoIncrement{} } func (m *AutoIncrement) String() string { return proto.CompactTextString(m) } func (*AutoIncrement) ProtoMessage() {} func (*AutoIncrement) Descriptor() ([]byte, []int) { - return fileDescriptor_vschema_f0f3bc4bc6c5c748, []int{4} + return fileDescriptor_vschema_5ecfaf46981fe072, []int{4} } func (m *AutoIncrement) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_AutoIncrement.Unmarshal(m, b) @@ -340,7 +351,7 @@ func (m *Column) Reset() { *m = Column{} } func (m *Column) String() string { return proto.CompactTextString(m) } func (*Column) ProtoMessage() {} func (*Column) Descriptor() ([]byte, []int) { - return fileDescriptor_vschema_f0f3bc4bc6c5c748, []int{5} + return fileDescriptor_vschema_5ecfaf46981fe072, []int{5} } func (m *Column) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Column.Unmarshal(m, b) @@ -387,7 +398,7 @@ func (m *SrvVSchema) Reset() { *m = SrvVSchema{} } func (m *SrvVSchema) String() string { return proto.CompactTextString(m) } func (*SrvVSchema) ProtoMessage() {} func (*SrvVSchema) Descriptor() ([]byte, []int) { - return fileDescriptor_vschema_f0f3bc4bc6c5c748, []int{6} + return fileDescriptor_vschema_5ecfaf46981fe072, []int{6} } func (m *SrvVSchema) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_SrvVSchema.Unmarshal(m, b) @@ -428,42 +439,44 @@ func init() { proto.RegisterMapType((map[string]*Keyspace)(nil), "vschema.SrvVSchema.KeyspacesEntry") } -func init() { proto.RegisterFile("vschema.proto", fileDescriptor_vschema_f0f3bc4bc6c5c748) } - -var fileDescriptor_vschema_f0f3bc4bc6c5c748 = []byte{ - // 530 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x54, 0xcd, 0x6e, 0xd3, 0x40, - 0x10, 0x96, 0x93, 0xc6, 0x49, 0xc6, 0x24, 0x81, 0x55, 0xa9, 0x2c, 0x23, 0xd4, 0xc8, 0x2a, 0x10, - 0x2e, 0x8e, 0x94, 0x0a, 0x89, 0x1f, 0x15, 0x01, 0x11, 0x87, 0x0a, 0x24, 0x90, 0x1b, 0xf5, 0xc0, - 0xa5, 0xda, 0x3a, 0x23, 0x6a, 0x35, 0x5e, 0xbb, 0x5e, 0xdb, 0xe0, 0xa7, 0x41, 0xe2, 0x0d, 0x78, - 0x20, 0xde, 0x05, 0x79, 0x77, 0xed, 0xae, 0xd3, 0x70, 0xdb, 0xcf, 0x33, 0xdf, 0x37, 0xdf, 0xce, - 0xce, 0x18, 0x46, 0x05, 0x0f, 0xae, 0x30, 0xa2, 0x5e, 0x92, 0xc6, 0x59, 0x4c, 0xfa, 0x0a, 0x3a, - 0xd6, 0x4d, 0x8e, 0x69, 0x29, 0xbf, 0xba, 0x7f, 0x3a, 0x30, 0xf8, 0x84, 0x25, 0x4f, 0x68, 0x80, - 0xc4, 0x86, 0x3e, 0xbf, 0xa2, 0xe9, 0x1a, 0xd7, 0xb6, 0x31, 0x35, 0x66, 0x03, 0xbf, 0x86, 0xe4, - 0x0d, 0x0c, 0x8a, 0x90, 0xad, 0xf1, 0x27, 0x72, 0xbb, 0x33, 0xed, 0xce, 0xac, 0xc5, 0xa1, 0x57, - 0xcb, 0xd7, 0x74, 0xef, 0x5c, 0x65, 0x7c, 0x64, 0x59, 0x5a, 0xfa, 0x0d, 0x81, 0xbc, 0x00, 0x33, - 0xa3, 0x97, 0x1b, 0xe4, 0x76, 0x57, 0x50, 0x1f, 0xdf, 0xa5, 0xae, 0x44, 0x5c, 0x12, 0x55, 0xb2, - 0xf3, 0x19, 0x46, 0x2d, 0x45, 0x72, 0x1f, 0xba, 0xd7, 0x58, 0x0a, 0x6b, 0x43, 0xbf, 0x3a, 0x92, - 0x27, 0xd0, 0x2b, 0xe8, 0x26, 0x47, 0xbb, 0x33, 0x35, 0x66, 0xd6, 0x62, 0xd2, 0x08, 0x4b, 0xa2, - 0x2f, 0xa3, 0xaf, 0x3b, 0x2f, 0x0d, 0xe7, 0x14, 0x2c, 0xad, 0xc8, 0x0e, 0xad, 0xa3, 0xb6, 0xd6, - 0xb8, 0xd1, 0x12, 0x34, 0x4d, 0xca, 0xfd, 0x6d, 0x80, 0x29, 0x0b, 0x10, 0x02, 0x7b, 0x59, 0x99, - 0xa0, 0xd2, 0x11, 0x67, 0x72, 0x0c, 0x66, 0x42, 0x53, 0x1a, 0xd5, 0x9d, 0x7a, 0xb4, 0xe5, 0xca, - 0xfb, 0x2a, 0xa2, 0xea, 0xb2, 0x32, 0x95, 0xec, 0x43, 0x2f, 0xfe, 0xc1, 0x30, 0xb5, 0xbb, 0x42, - 0x49, 0x02, 0xe7, 0x15, 0x58, 0x5a, 0xf2, 0x0e, 0xd3, 0xfb, 0xba, 0xe9, 0xa1, 0x6e, 0xf2, 0xaf, - 0x01, 0x3d, 0xe1, 0x7c, 0xa7, 0xc7, 0xb7, 0x30, 0x09, 0xe2, 0x4d, 0x1e, 0xb1, 0x8b, 0xad, 0x67, - 0x7d, 0xd8, 0x98, 0x5d, 0x8a, 0xb8, 0x6a, 0xe4, 0x38, 0xd0, 0x10, 0x72, 0x72, 0x02, 0x63, 0x9a, - 0x67, 0xf1, 0x45, 0xc8, 0x82, 0x14, 0x23, 0x64, 0x99, 0xf0, 0x6d, 0x2d, 0x0e, 0x1a, 0xfa, 0xfb, - 0x3c, 0x8b, 0x4f, 0xeb, 0xa8, 0x3f, 0xa2, 0x3a, 0x24, 0xcf, 0xa1, 0x2f, 0x05, 0xb9, 0xbd, 0x27, - 0xca, 0x4e, 0xb6, 0xca, 0xfa, 0x75, 0x9c, 0x1c, 0x80, 0x99, 0x84, 0x8c, 0xe1, 0xda, 0xee, 0x09, - 0xff, 0x0a, 0xb9, 0x2b, 0xb8, 0xa7, 0x3b, 0xac, 0xf2, 0x24, 0x45, 0xdd, 0x53, 0xa1, 0xea, 0xf6, - 0x8c, 0x46, 0x75, 0x83, 0xc4, 0xb9, 0x9a, 0xf3, 0xba, 0x7c, 0x35, 0x91, 0xc3, 0xa6, 0x9a, 0xbb, - 0x84, 0x51, 0xcb, 0xf8, 0x7f, 0x65, 0x1d, 0x18, 0x70, 0xbc, 0xc9, 0x91, 0x05, 0xb5, 0x74, 0x83, - 0xdd, 0x13, 0x30, 0x97, 0xed, 0xe2, 0x86, 0x56, 0xfc, 0x50, 0x3d, 0x47, 0xc5, 0x1a, 0x2f, 0x2c, - 0x4f, 0x6e, 0xe3, 0xaa, 0x4c, 0x50, 0xbe, 0x8d, 0xfb, 0xcb, 0x00, 0x38, 0x4b, 0x8b, 0xf3, 0x33, - 0xd1, 0x10, 0xf2, 0x0e, 0x86, 0xd7, 0x6a, 0x4d, 0xb8, 0x6d, 0x88, 0x6e, 0xb9, 0x4d, 0xb7, 0x6e, - 0xf3, 0x9a, 0x5d, 0x52, 0x83, 0x75, 0x4b, 0x72, 0xbe, 0xc0, 0xb8, 0x1d, 0xdc, 0x31, 0x48, 0xcf, - 0xda, 0xd3, 0xff, 0xe0, 0xce, 0x8a, 0x6a, 0xb3, 0xf5, 0xe1, 0xe9, 0xb7, 0xa3, 0x22, 0xcc, 0x90, - 0x73, 0x2f, 0x8c, 0xe7, 0xf2, 0x34, 0xff, 0x1e, 0xcf, 0x8b, 0x6c, 0x2e, 0x7e, 0x2a, 0x73, 0xc5, - 0xbd, 0x34, 0x05, 0x3c, 0xfe, 0x17, 0x00, 0x00, 0xff, 0xff, 0x31, 0x37, 0xa1, 0xa9, 0x8a, 0x04, +func init() { proto.RegisterFile("vschema.proto", fileDescriptor_vschema_5ecfaf46981fe072) } + +var fileDescriptor_vschema_5ecfaf46981fe072 = []byte{ + // 562 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x54, 0x41, 0x6f, 0xd3, 0x4c, + 0x10, 0x95, 0x93, 0xc6, 0x4d, 0xc6, 0x5f, 0xd2, 0x8f, 0x55, 0x29, 0xc6, 0x08, 0x35, 0xb2, 0x0a, + 0x84, 0x8b, 0x23, 0xa5, 0x42, 0x82, 0xa2, 0x22, 0x4a, 0xc4, 0xa1, 0xa2, 0x12, 0xc8, 0x8d, 0x7a, + 0xe0, 0x12, 0x6d, 0x9d, 0x11, 0xb1, 0x9a, 0xd8, 0xae, 0x77, 0x6d, 0xf0, 0x4f, 0xe1, 0x84, 0xc4, + 0x3f, 0xe0, 0x1f, 0x22, 0xef, 0xae, 0xdd, 0x75, 0x1a, 0x6e, 0xfb, 0x3c, 0xf3, 0xde, 0xbc, 0x9d, + 0x9d, 0x31, 0xf4, 0x73, 0x16, 0x2c, 0x71, 0x4d, 0xbd, 0x24, 0x8d, 0x79, 0x4c, 0x76, 0x15, 0x74, + 0xac, 0xdb, 0x0c, 0xd3, 0x42, 0x7e, 0x75, 0xff, 0xb4, 0xa0, 0xfb, 0x09, 0x0b, 0x96, 0xd0, 0x00, + 0x89, 0x0d, 0xbb, 0x6c, 0x49, 0xd3, 0x05, 0x2e, 0x6c, 0x63, 0x68, 0x8c, 0xba, 0x7e, 0x05, 0xc9, + 0x5b, 0xe8, 0xe6, 0x61, 0xb4, 0xc0, 0x1f, 0xc8, 0xec, 0xd6, 0xb0, 0x3d, 0xb2, 0x26, 0x87, 0x5e, + 0x25, 0x5f, 0xd1, 0xbd, 0x2b, 0x95, 0xf1, 0x31, 0xe2, 0x69, 0xe1, 0xd7, 0x04, 0xf2, 0x0a, 0x4c, + 0x4e, 0xaf, 0x57, 0xc8, 0xec, 0xb6, 0xa0, 0x3e, 0xbd, 0x4f, 0x9d, 0x89, 0xb8, 0x24, 0xaa, 0x64, + 0xe7, 0x02, 0xfa, 0x0d, 0x45, 0xf2, 0x3f, 0xb4, 0x6f, 0xb0, 0x10, 0xd6, 0x7a, 0x7e, 0x79, 0x24, + 0xcf, 0xa0, 0x93, 0xd3, 0x55, 0x86, 0x76, 0x6b, 0x68, 0x8c, 0xac, 0xc9, 0x5e, 0x2d, 0x2c, 0x89, + 0xbe, 0x8c, 0x9e, 0xb4, 0x5e, 0x1b, 0xce, 0x39, 0x58, 0x5a, 0x91, 0x2d, 0x5a, 0x47, 0x4d, 0xad, + 0x41, 0xad, 0x25, 0x68, 0x9a, 0x94, 0xfb, 0xdb, 0x00, 0x53, 0x16, 0x20, 0x04, 0x76, 0x78, 0x91, + 0xa0, 0xd2, 0x11, 0x67, 0x72, 0x0c, 0x66, 0x42, 0x53, 0xba, 0xae, 0x3a, 0xf5, 0x64, 0xc3, 0x95, + 0xf7, 0x45, 0x44, 0xd5, 0x65, 0x65, 0x2a, 0xd9, 0x87, 0x4e, 0xfc, 0x3d, 0xc2, 0xd4, 0x6e, 0x0b, + 0x25, 0x09, 0x9c, 0x37, 0x60, 0x69, 0xc9, 0x5b, 0x4c, 0xef, 0xeb, 0xa6, 0x7b, 0xba, 0xc9, 0x9f, + 0x2d, 0xe8, 0x08, 0xe7, 0x5b, 0x3d, 0xbe, 0x83, 0xbd, 0x20, 0x5e, 0x65, 0xeb, 0x68, 0xbe, 0xf1, + 0xac, 0x0f, 0x6b, 0xb3, 0x53, 0x11, 0x57, 0x8d, 0x1c, 0x04, 0x1a, 0x42, 0x46, 0x4e, 0x61, 0x40, + 0x33, 0x1e, 0xcf, 0xc3, 0x28, 0x48, 0x71, 0x8d, 0x11, 0x17, 0xbe, 0xad, 0xc9, 0x41, 0x4d, 0x3f, + 0xcb, 0x78, 0x7c, 0x5e, 0x45, 0xfd, 0x3e, 0xd5, 0x21, 0x79, 0x09, 0xbb, 0x52, 0x90, 0xd9, 0x3b, + 0xa2, 0xec, 0xde, 0x46, 0x59, 0xbf, 0x8a, 0x93, 0x03, 0x30, 0x93, 0x30, 0x8a, 0x70, 0x61, 0x77, + 0x84, 0x7f, 0x85, 0xc8, 0x09, 0x3c, 0x56, 0x37, 0x58, 0x85, 0x8c, 0xcf, 0x69, 0xc6, 0x97, 0x71, + 0x1a, 0x72, 0xca, 0xc3, 0x1c, 0x6d, 0x53, 0x4c, 0xef, 0x23, 0x99, 0x70, 0x11, 0x32, 0x7e, 0xa6, + 0x87, 0xdd, 0x19, 0xfc, 0xa7, 0xdf, 0xae, 0xac, 0x21, 0x53, 0x55, 0x8f, 0x14, 0x2a, 0x3b, 0x17, + 0xd1, 0x75, 0xd5, 0x5c, 0x71, 0x2e, 0x77, 0xa4, 0xb2, 0x5e, 0x4e, 0x73, 0xaf, 0x76, 0xea, 0x4e, + 0xa1, 0xdf, 0xb8, 0xf4, 0x3f, 0x65, 0x1d, 0xe8, 0x32, 0xbc, 0xcd, 0x30, 0x0a, 0x2a, 0xe9, 0x1a, + 0xbb, 0xa7, 0x60, 0x4e, 0x9b, 0xc5, 0x0d, 0xad, 0xf8, 0xa1, 0x7a, 0xca, 0x92, 0x35, 0x98, 0x58, + 0x9e, 0xdc, 0xe4, 0x59, 0x91, 0xa0, 0x7c, 0x57, 0xf7, 0x97, 0x01, 0x70, 0x99, 0xe6, 0x57, 0x97, + 0xa2, 0x99, 0xe4, 0x3d, 0xf4, 0x6e, 0xd4, 0x8a, 0x31, 0xdb, 0x10, 0x9d, 0x76, 0xeb, 0x4e, 0xdf, + 0xe5, 0xd5, 0x7b, 0xa8, 0x86, 0xf2, 0x8e, 0xe4, 0x7c, 0x86, 0x41, 0x33, 0xb8, 0x65, 0x08, 0x5f, + 0x34, 0x37, 0xe7, 0xc1, 0xbd, 0xf5, 0xd6, 0xe6, 0xf2, 0xc3, 0xf3, 0xaf, 0x47, 0x79, 0xc8, 0x91, + 0x31, 0x2f, 0x8c, 0xc7, 0xf2, 0x34, 0xfe, 0x16, 0x8f, 0x73, 0x3e, 0x16, 0x3f, 0xa4, 0xb1, 0xe2, + 0x5e, 0x9b, 0x02, 0x1e, 0xff, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x41, 0x18, 0x64, 0xd4, 0xc6, 0x04, 0x00, 0x00, } diff --git a/go/vt/schemamanager/schemamanager_test.go b/go/vt/schemamanager/schemamanager_test.go index c67b2bc2f60..18a44a006b3 100644 --- a/go/vt/schemamanager/schemamanager_test.go +++ b/go/vt/schemamanager/schemamanager_test.go @@ -316,6 +316,29 @@ func newFakeTopo(t *testing.T) *topo.Server { t.Fatalf("UpdateShardFields failed: %v", err) } } + if err := ts.CreateKeyspace(ctx, "unsharded_keyspace", &topodatapb.Keyspace{}); err != nil { + t.Fatalf("CreateKeyspace failed: %v", err) + } + if err := ts.CreateShard(ctx, "unsharded_keyspace", "0"); err != nil { + t.Fatalf("CreateShard(%v) failed: %v", "0", err) + } + tablet := &topodatapb.Tablet{ + Alias: &topodatapb.TabletAlias{ + Cell: "test_cell", + Uid: uint32(4), + }, + Keyspace: "test_keyspace", + Shard: "0", + } + if err := ts.CreateTablet(ctx, tablet); err != nil { + t.Fatalf("CreateTablet failed: %v", err) + } + if _, err := ts.UpdateShardFields(ctx, "unsharded_keyspace", "0", func(si *topo.ShardInfo) error { + si.Shard.MasterAlias = tablet.Alias + return nil + }); err != nil { + t.Fatalf("UpdateShardFields failed: %v", err) + } return ts } diff --git a/go/vt/schemamanager/tablet_executor.go b/go/vt/schemamanager/tablet_executor.go index dae77dfc369..6346e6f913b 100644 --- a/go/vt/schemamanager/tablet_executor.go +++ b/go/vt/schemamanager/tablet_executor.go @@ -24,11 +24,9 @@ import ( "golang.org/x/net/context" "vitess.io/vitess/go/sync2" - "vitess.io/vitess/go/vt/mysqlctl/tmutils" "vitess.io/vitess/go/vt/sqlparser" "vitess.io/vitess/go/vt/wrangler" - tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata" topodatapb "vitess.io/vitess/go/vt/proto/topodata" ) @@ -36,7 +34,6 @@ import ( type TabletExecutor struct { wr *wrangler.Wrangler tablets []*topodatapb.Tablet - schemaDiffs []*tabletmanagerdatapb.SchemaChangeResult isClosed bool allowBigSchemaChange bool keyspace string @@ -98,29 +95,13 @@ func (exec *TabletExecutor) Open(ctx context.Context, keyspace string) error { return nil } -func parseDDLs(sqls []string) ([]*sqlparser.DDL, error) { - parsedDDLs := make([]*sqlparser.DDL, len(sqls)) - for i, sql := range sqls { - stat, err := sqlparser.Parse(sql) - if err != nil { - return nil, fmt.Errorf("failed to parse sql: %s, got error: %v", sql, err) - } - ddl, ok := stat.(*sqlparser.DDL) - if !ok { - return nil, fmt.Errorf("schema change works for DDLs only, but get non DDL statement: %s", sql) - } - parsedDDLs[i] = ddl - } - return parsedDDLs, nil -} - // Validate validates a list of sql statements. func (exec *TabletExecutor) Validate(ctx context.Context, sqls []string) error { if exec.isClosed { return fmt.Errorf("executor is closed") } - parsedDDLs, err := parseDDLs(sqls) + parsedDDLs, err := exec.parseDDLs(sqls) if err != nil { return err } @@ -133,6 +114,25 @@ func (exec *TabletExecutor) Validate(ctx context.Context, sqls []string) error { return err } +func (exec *TabletExecutor) parseDDLs(sqls []string) ([]*sqlparser.DDL, error) { + parsedDDLs := make([]*sqlparser.DDL, 0, len(sqls)) + for _, sql := range sqls { + stat, err := sqlparser.Parse(sql) + if err != nil { + return nil, fmt.Errorf("failed to parse sql: %s, got error: %v", sql, err) + } + ddl, ok := stat.(*sqlparser.DDL) + if !ok { + if len(exec.tablets) != 1 { + return nil, fmt.Errorf("non-ddl statements can only be executed for single shard keyspaces: %s", sql) + } + continue + } + parsedDDLs = append(parsedDDLs, ddl) + } + return parsedDDLs, nil +} + // a schema change that satisfies any following condition is considered // to be a big schema change and will be rejected. // 1. Alter more than 100,000 rows. @@ -153,7 +153,7 @@ func (exec *TabletExecutor) detectBigSchemaChanges(ctx context.Context, parsedDD } for _, ddl := range parsedDDLs { switch ddl.Action { - case sqlparser.DropStr, sqlparser.CreateStr, sqlparser.TruncateStr: + case sqlparser.DropStr, sqlparser.CreateStr, sqlparser.TruncateStr, sqlparser.RenameStr: continue } tableName := ddl.Table.Name.String() @@ -172,32 +172,8 @@ func (exec *TabletExecutor) detectBigSchemaChanges(ctx context.Context, parsedDD } func (exec *TabletExecutor) preflightSchemaChanges(ctx context.Context, sqls []string) error { - schemaDiffs, err := exec.wr.TabletManagerClient().PreflightSchema(ctx, exec.tablets[0], sqls) - if err != nil { - return err - } - - parsedDDLs, err := parseDDLs(sqls) - if err != nil { - return err - } - - for i, schemaDiff := range schemaDiffs { - diffs := tmutils.DiffSchemaToArray( - "BeforeSchema", - schemaDiff.BeforeSchema, - "AfterSchema", - schemaDiff.AfterSchema) - if len(diffs) == 0 { - if parsedDDLs[i].Action == sqlparser.DropStr && parsedDDLs[i].IfExists { - // DROP IF EXISTS on a nonexistent table does not change the schema. It's safe to ignore. - continue - } - return fmt.Errorf("schema change: '%s' does not introduce any table definition change", sqls[i]) - } - } - exec.schemaDiffs = schemaDiffs - return nil + _, err := exec.wr.TabletManagerClient().PreflightSchema(ctx, exec.tablets[0], sqls) + return err } // Execute applies schema changes diff --git a/go/vt/schemamanager/tablet_executor_test.go b/go/vt/schemamanager/tablet_executor_test.go index 95e953fdf97..13cae66fdb1 100644 --- a/go/vt/schemamanager/tablet_executor_test.go +++ b/go/vt/schemamanager/tablet_executor_test.go @@ -135,13 +135,6 @@ func TestTabletExecutorValidate(t *testing.T) { t.Fatalf("executor.Validate should fail, alter a table more than 100,000 rows") } - // change a table with more than 2,000,000 rows - if err := executor.Validate(ctx, []string{ - "RENAME TABLE test_table_04 TO test_table_05", - }); err == nil { - t.Fatalf("executor.Validate should fail, change a table more than 2,000,000 rows") - } - if err := executor.Validate(ctx, []string{ "TRUNCATE TABLE test_table_04", }); err != nil { @@ -170,27 +163,54 @@ func TestTabletExecutorValidate(t *testing.T) { } } -func TestTabletExecutorExecute(t *testing.T) { - executor := newFakeExecutor(t) +func TestTabletExecutorDML(t *testing.T) { + fakeTmc := newFakeTabletManagerClient() + + fakeTmc.AddSchemaDefinition("vt_test_keyspace", &tabletmanagerdatapb.SchemaDefinition{ + DatabaseSchema: "CREATE DATABASE `{{.DatabaseName}}` /*!40100 DEFAULT CHARACTER SET utf8 */", + TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ + { + Name: "test_table", + Schema: "table schema", + Type: tmutils.TableBaseTable, + }, + { + Name: "test_table_03", + Schema: "table schema", + Type: tmutils.TableBaseTable, + RowCount: 200000, + }, + { + Name: "test_table_04", + Schema: "table schema", + Type: tmutils.TableBaseTable, + RowCount: 3000000, + }, + }, + }) + + wr := wrangler.New(logutil.NewConsoleLogger(), newFakeTopo(t), fakeTmc) + executor := NewTabletExecutor(wr, testWaitSlaveTimeout) ctx := context.Background() - sqls := []string{"DROP TABLE unknown_table"} + executor.Open(ctx, "unsharded_keyspace") + defer executor.Close() - result := executor.Execute(ctx, sqls) - if result.ExecutorErr == "" { - t.Fatalf("execute should fail, call execute.Open first") + // schema changes with DMLs should fail + if err := executor.Validate(ctx, []string{ + "INSERT INTO test_table VALUES(1)"}); err != nil { + t.Fatalf("executor.Validate should succeed, for DML to unsharded keyspace") } } -func TestTabletExecutorExecute_PreflightWithoutChangesIsAnError(t *testing.T) { +func TestTabletExecutorExecute(t *testing.T) { executor := newFakeExecutor(t) ctx := context.Background() - executor.Open(ctx, "test_keyspace") - defer executor.Close() sqls := []string{"DROP TABLE unknown_table"} + result := executor.Execute(ctx, sqls) if result.ExecutorErr == "" { - t.Fatalf("execute should fail, ddl does not introduce any table schema change") + t.Fatalf("execute should fail, call execute.Open first") } } diff --git a/go/vt/servenv/grpc_server_auth_static.go b/go/vt/servenv/grpc_server_auth_static.go index adf0a719266..27fa9d231eb 100644 --- a/go/vt/servenv/grpc_server_auth_static.go +++ b/go/vt/servenv/grpc_server_auth_static.go @@ -73,7 +73,7 @@ func (sa *StaticAuthPlugin) Authenticate(ctx context.Context, fullMethod string) func staticAuthPluginInitializer() (Authenticator, error) { entries := make([]StaticAuthConfigEntry, 0) if *credsFile == "" { - err := fmt.Errorf("failed to load static auth plugin. Plugin configured but grpc_server_auth_static_file not provided") + err := fmt.Errorf("failed to load static auth plugin. Plugin configured but grpc_auth_static_password_file not provided") return nil, err } @@ -91,7 +91,7 @@ func staticAuthPluginInitializer() (Authenticator, error) { staticAuthPlugin := &StaticAuthPlugin{ entries: entries, } - log.Info("static auth plugin have initialized successfully with config from grpc_server_auth_static_file") + log.Info("static auth plugin have initialized successfully with config from grpc_auth_static_password_file") return staticAuthPlugin, nil } diff --git a/go/vt/sqlparser/ast.go b/go/vt/sqlparser/ast.go index e0107dd1cfe..0ecff77a306 100644 --- a/go/vt/sqlparser/ast.go +++ b/go/vt/sqlparser/ast.go @@ -89,6 +89,9 @@ func Parse(sql string) (Statement, error) { tokenizer := NewStringTokenizer(sql) if yyParsePooled(tokenizer) != 0 { if tokenizer.partialDDL != nil { + if typ, val := tokenizer.Scan(); typ != 0 { + return nil, fmt.Errorf("extra characters encountered after end of DDL: '%s'", string(val)) + } log.Warningf("ignoring error parsing DDL '%s': %v", sql, tokenizer.LastError) tokenizer.ParseTree = tokenizer.partialDDL return tokenizer.ParseTree, nil @@ -715,21 +718,30 @@ func (node *DBDDL) walkSubtree(visit Visit) error { return nil } -// DDL represents a CREATE, ALTER, DROP, RENAME or TRUNCATE statement. -// Table is set for AlterStr, DropStr, RenameStr, TruncateStr -// NewName is set for AlterStr, CreateStr, RenameStr. -// VindexSpec is set for CreateVindexStr, DropVindexStr, AddColVindexStr, DropColVindexStr -// VindexCols is set for AddColVindexStr +// DDL represents a CREATE, ALTER, DROP, RENAME, TRUNCATE or ANALYZE statement. type DDL struct { - Action string - Table TableName - NewName TableName + Action string + + // FromTables is set if Action is RenameStr or DropStr. + FromTables TableNames + + // ToTables is set if Action is RenameStr. + ToTables TableNames + + // Table is set if Action is other than RenameStr or DropStr. + Table TableName + + // The following fields are set if a DDL was fully analyzed. IfExists bool TableSpec *TableSpec OptLike *OptLike PartitionSpec *PartitionSpec - VindexSpec *VindexSpec - VindexCols []ColIdent + + // VindexSpec is set for CreateVindexStr, DropVindexStr, AddColVindexStr, DropColVindexStr. + VindexSpec *VindexSpec + + // VindexCols is set for AddColVindexStr. + VindexCols []ColIdent } // DDL strings. @@ -753,20 +765,23 @@ func (node *DDL) Format(buf *TrackedBuffer) { switch node.Action { case CreateStr: if node.OptLike != nil { - buf.Myprintf("%s table %v %v", node.Action, node.NewName, node.OptLike) + buf.Myprintf("%s table %v %v", node.Action, node.Table, node.OptLike) } else if node.TableSpec != nil { - buf.Myprintf("%s table %v %v", node.Action, node.NewName, node.TableSpec) + buf.Myprintf("%s table %v %v", node.Action, node.Table, node.TableSpec) } else { - buf.Myprintf("%s table %v", node.Action, node.NewName) + buf.Myprintf("%s table %v", node.Action, node.Table) } case DropStr: exists := "" if node.IfExists { exists = " if exists" } - buf.Myprintf("%s table%s %v", node.Action, exists, node.Table) + buf.Myprintf("%s table%s %v", node.Action, exists, node.FromTables) case RenameStr: - buf.Myprintf("%s table %v to %v", node.Action, node.Table, node.NewName) + buf.Myprintf("%s table %v to %v", node.Action, node.FromTables[0], node.ToTables[0]) + for i := 1; i < len(node.FromTables); i++ { + buf.Myprintf(", %v to %v", node.FromTables[i], node.ToTables[i]) + } case AlterStr: if node.PartitionSpec != nil { buf.Myprintf("%s table %v %v", node.Action, node.Table, node.PartitionSpec) @@ -801,11 +816,23 @@ func (node *DDL) walkSubtree(visit Visit) error { if node == nil { return nil } - return Walk( - visit, - node.Table, - node.NewName, - ) + for _, t := range node.AffectedTables() { + if err := Walk(visit, t); err != nil { + return err + } + } + return nil +} + +// AffectedTables returns the list table names affected by the DDL. +func (node *DDL) AffectedTables() TableNames { + if node.Action == RenameStr || node.Action == DropStr { + list := make(TableNames, 0, len(node.FromTables)+len(node.ToTables)) + list = append(list, node.FromTables...) + list = append(list, node.ToTables...) + return list + } + return TableNames{node.Table} } // Partition strings @@ -2971,20 +2998,30 @@ func (node *ValuesFuncExpr) replace(from, to Expr) bool { } // SubstrExpr represents a call to SubstrExpr(column, value_expression) or SubstrExpr(column, value_expression,value_expression) -// also supported syntax SubstrExpr(column from value_expression for value_expression) +// also supported syntax SubstrExpr(column from value_expression for value_expression). +// Additionally to column names, SubstrExpr is also supported for string values, e.g.: +// SubstrExpr('static string value', value_expression, value_expression) +// In this case StrVal will be set instead of Name. type SubstrExpr struct { - Name *ColName - From Expr - To Expr + Name *ColName + StrVal *SQLVal + From Expr + To Expr } // Format formats the node. func (node *SubstrExpr) Format(buf *TrackedBuffer) { + var val interface{} + if node.Name != nil { + val = node.Name + } else { + val = node.StrVal + } if node.To == nil { - buf.Myprintf("substr(%v, %v)", node.Name, node.From) + buf.Myprintf("substr(%v, %v)", val, node.From) } else { - buf.Myprintf("substr(%v, %v, %v)", node.Name, node.From, node.To) + buf.Myprintf("substr(%v, %v, %v)", val, node.From, node.To) } } @@ -2993,7 +3030,7 @@ func (node *SubstrExpr) replace(from, to Expr) bool { } func (node *SubstrExpr) walkSubtree(visit Visit) error { - if node == nil { + if node == nil || node.Name == nil { return nil } return Walk( @@ -3068,6 +3105,7 @@ type ConvertType struct { // this string is "character set" and this comment is required const ( CharacterSetStr = " character set" + CharsetStr = "charset" ) // Format formats the node. diff --git a/go/vt/sqlparser/ast_test.go b/go/vt/sqlparser/ast_test.go index cf1d8d9c375..08bd05d96d8 100644 --- a/go/vt/sqlparser/ast_test.go +++ b/go/vt/sqlparser/ast_test.go @@ -191,6 +191,82 @@ func TestSetLimit(t *testing.T) { } } +func TestDDL(t *testing.T) { + testcases := []struct { + query string + output *DDL + affected []string + }{{ + query: "create table a", + output: &DDL{ + Action: CreateStr, + Table: TableName{Name: NewTableIdent("a")}, + }, + affected: []string{"a"}, + }, { + query: "rename table a to b", + output: &DDL{ + Action: RenameStr, + FromTables: TableNames{ + TableName{Name: NewTableIdent("a")}, + }, + ToTables: TableNames{ + TableName{Name: NewTableIdent("b")}, + }, + }, + affected: []string{"a", "b"}, + }, { + query: "rename table a to b, c to d", + output: &DDL{ + Action: RenameStr, + FromTables: TableNames{ + TableName{Name: NewTableIdent("a")}, + TableName{Name: NewTableIdent("c")}, + }, + ToTables: TableNames{ + TableName{Name: NewTableIdent("b")}, + TableName{Name: NewTableIdent("d")}, + }, + }, + affected: []string{"a", "c", "b", "d"}, + }, { + query: "drop table a", + output: &DDL{ + Action: DropStr, + FromTables: TableNames{ + TableName{Name: NewTableIdent("a")}, + }, + }, + affected: []string{"a"}, + }, { + query: "drop table a, b", + output: &DDL{ + Action: DropStr, + FromTables: TableNames{ + TableName{Name: NewTableIdent("a")}, + TableName{Name: NewTableIdent("b")}, + }, + }, + affected: []string{"a", "b"}, + }} + for _, tcase := range testcases { + got, err := Parse(tcase.query) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(got, tcase.output) { + t.Errorf("%s: %v, want %v", tcase.query, got, tcase.output) + } + want := make(TableNames, 0, len(tcase.affected)) + for _, t := range tcase.affected { + want = append(want, TableName{Name: NewTableIdent(t)}) + } + if affected := got.(*DDL).AffectedTables(); !reflect.DeepEqual(affected, want) { + t.Errorf("Affected(%s): %v, want %v", tcase.query, affected, want) + } + } +} + func TestSetAutocommitON(t *testing.T) { stmt, err := Parse("SET autocommit=ON") if err != nil { diff --git a/go/vt/sqlparser/parse_next_test.go b/go/vt/sqlparser/parse_next_test.go index 543854cadf5..c1dd85f5498 100644 --- a/go/vt/sqlparser/parse_next_test.go +++ b/go/vt/sqlparser/parse_next_test.go @@ -139,7 +139,7 @@ func TestParseNextEdgeCases(t *testing.T) { input: "select 1 from a; update a set b = 2 ; ", want: []string{"select 1 from a", "update a set b = 2"}, }, { - name: "Handle ForceEOF statements", + name: "Handle SkipToEnd statements", input: "set character set utf8; select 1 from a", want: []string{"set charset 'utf8'", "select 1 from a"}, }, { diff --git a/go/vt/sqlparser/parse_test.go b/go/vt/sqlparser/parse_test.go index 57a897550ed..0eb0e7d54da 100644 --- a/go/vt/sqlparser/parse_test.go +++ b/go/vt/sqlparser/parse_test.go @@ -979,12 +979,21 @@ var ( }, { input: "alter view a", output: "alter table a", + }, { + input: "rename table a to b", + output: "rename table a to b", + }, { + input: "rename table a to b, b to c", + output: "rename table a to b, b to c", }, { input: "drop view a", output: "drop table a", }, { input: "drop table a", output: "drop table a", + }, { + input: "drop table a, b", + output: "drop table a, b", }, { input: "drop table if exists a", output: "drop table if exists a", @@ -1011,10 +1020,16 @@ var ( output: "show binlog", }, { input: "show character set", - output: "show character set", + output: "show charset", }, { input: "show character set like '%foo'", - output: "show character set", + output: "show charset", + }, { + input: "show charset", + output: "show charset", + }, { + input: "show charset like '%foo'", + output: "show charset", }, { input: "show collation", output: "show collation", @@ -1740,7 +1755,7 @@ func TestSubStr(t *testing.T) { input string output string }{{ - input: "select substr(a, 1) from t", + input: `select substr('foobar', 1) from t`, }, { input: "select substr(a, 1, 6) from t", }, { @@ -1755,6 +1770,12 @@ func TestSubStr(t *testing.T) { }, { input: "select substring(a from 1 for 6) from t", output: "select substr(a, 1, 6) from t", + }, { + input: `select substr("foo" from 1 for 2) from t`, + output: `select substr('foo', 1, 2) from t`, + }, { + input: `select substring("foo", 1, 2) from t`, + output: `select substr('foo', 1, 2) from t`, }} for _, tcase := range validSQL { @@ -2243,6 +2264,40 @@ func TestErrors(t *testing.T) { } } +// TestSkipToEnd tests that the skip to end functionality +// does not skip past a ';'. If any tokens exist after that, Parse +// should return an error. +func TestSkipToEnd(t *testing.T) { + testcases := []struct { + input string + output string + }{{ + // This is the case where the partial ddl will be reset + // because of a premature ';'. + input: "create table a(id; select * from t", + output: "syntax error at position 19", + }, { + // Partial DDL should get reset for valid DDLs also. + input: "create table a(id int); select * from t", + output: "syntax error at position 31 near 'select'", + }, { + // Partial DDL does not get reset here. But we allow the + // DDL only if there are no new tokens after skipping to end. + input: "create table a bb cc; select * from t", + output: "extra characters encountered after end of DDL: 'select'", + }, { + // Test that we don't step at ';' inside strings. + input: "create table a bb 'a;'; select * from t", + output: "extra characters encountered after end of DDL: 'select'", + }} + for _, tcase := range testcases { + _, err := Parse(tcase.input) + if err == nil || err.Error() != tcase.output { + t.Errorf("%s: %v, want %s", tcase.input, err, tcase.output) + } + } +} + // Benchmark run on 6/23/17, prior to improvements: // BenchmarkParse1-4 100000 16334 ns/op // BenchmarkParse2-4 30000 44121 ns/op diff --git a/go/vt/sqlparser/sql.go b/go/vt/sqlparser/sql.go index 7004dd4feba..bff8ec3af25 100644 --- a/go/vt/sqlparser/sql.go +++ b/go/vt/sqlparser/sql.go @@ -30,11 +30,11 @@ func decNesting(yylex interface{}) { yylex.(*Tokenizer).nesting-- } -// forceEOF forces the lexer to end prematurely. Not all SQL statements -// are supported by the Parser, thus calling forceEOF will make the lexer +// skipToEnd forces the lexer to end prematurely. Not all SQL statements +// are supported by the Parser, thus calling skipToEnd will make the lexer // return EOF early. -func forceEOF(yylex interface{}) { - yylex.(*Tokenizer).ForceEOF = true +func skipToEnd(yylex interface{}) { + yylex.(*Tokenizer).SkipToEnd = true } //line sql.y:53 @@ -310,43 +310,45 @@ const FULL = 57547 const PROCESSLIST = 57548 const COLUMNS = 57549 const FIELDS = 57550 -const NAMES = 57551 -const CHARSET = 57552 -const GLOBAL = 57553 -const SESSION = 57554 -const ISOLATION = 57555 -const LEVEL = 57556 -const READ = 57557 -const WRITE = 57558 -const ONLY = 57559 -const REPEATABLE = 57560 -const COMMITTED = 57561 -const UNCOMMITTED = 57562 -const SERIALIZABLE = 57563 -const CURRENT_TIMESTAMP = 57564 -const DATABASE = 57565 -const CURRENT_DATE = 57566 -const CURRENT_TIME = 57567 -const LOCALTIME = 57568 -const LOCALTIMESTAMP = 57569 -const UTC_DATE = 57570 -const UTC_TIME = 57571 -const UTC_TIMESTAMP = 57572 -const REPLACE = 57573 -const CONVERT = 57574 -const CAST = 57575 -const SUBSTR = 57576 -const SUBSTRING = 57577 -const GROUP_CONCAT = 57578 -const SEPARATOR = 57579 -const MATCH = 57580 -const AGAINST = 57581 -const BOOLEAN = 57582 -const LANGUAGE = 57583 -const WITH = 57584 -const QUERY = 57585 -const EXPANSION = 57586 -const UNUSED = 57587 +const ENGINES = 57551 +const PLUGINS = 57552 +const NAMES = 57553 +const CHARSET = 57554 +const GLOBAL = 57555 +const SESSION = 57556 +const ISOLATION = 57557 +const LEVEL = 57558 +const READ = 57559 +const WRITE = 57560 +const ONLY = 57561 +const REPEATABLE = 57562 +const COMMITTED = 57563 +const UNCOMMITTED = 57564 +const SERIALIZABLE = 57565 +const CURRENT_TIMESTAMP = 57566 +const DATABASE = 57567 +const CURRENT_DATE = 57568 +const CURRENT_TIME = 57569 +const LOCALTIME = 57570 +const LOCALTIMESTAMP = 57571 +const UTC_DATE = 57572 +const UTC_TIME = 57573 +const UTC_TIMESTAMP = 57574 +const REPLACE = 57575 +const CONVERT = 57576 +const CAST = 57577 +const SUBSTR = 57578 +const SUBSTRING = 57579 +const GROUP_CONCAT = 57580 +const SEPARATOR = 57581 +const MATCH = 57582 +const AGAINST = 57583 +const BOOLEAN = 57584 +const LANGUAGE = 57585 +const WITH = 57586 +const QUERY = 57587 +const EXPANSION = 57588 +const UNUSED = 57589 var yyToknames = [...]string{ "$end", @@ -574,6 +576,8 @@ var yyToknames = [...]string{ "PROCESSLIST", "COLUMNS", "FIELDS", + "ENGINES", + "PLUGINS", "NAMES", "CHARSET", "GLOBAL", @@ -628,888 +632,902 @@ var yyExca = [...]int{ 5, 29, -2, 4, -1, 37, - 159, 292, - 160, 292, - -2, 282, - -1, 262, - 112, 622, - -2, 618, - -1, 263, - 112, 623, - -2, 619, - -1, 326, - 82, 791, + 159, 297, + 160, 297, + -2, 287, + -1, 267, + 112, 633, + -2, 629, + -1, 268, + 112, 634, + -2, 630, + -1, 331, + 82, 804, -2, 60, - -1, 327, - 82, 749, - -2, 61, -1, 332, - 82, 729, - -2, 584, - -1, 334, - 82, 770, - -2, 586, - -1, 586, - 1, 344, - 5, 344, - 12, 344, - 13, 344, - 14, 344, - 15, 344, - 17, 344, - 19, 344, - 30, 344, - 31, 344, - 42, 344, - 43, 344, - 44, 344, - 45, 344, - 46, 344, - 48, 344, - 49, 344, - 52, 344, - 53, 344, - 55, 344, - 56, 344, - 263, 344, - -2, 362, - -1, 589, + 82, 761, + -2, 61, + -1, 337, + 82, 740, + -2, 595, + -1, 339, + 82, 782, + -2, 597, + -1, 593, + 1, 349, + 5, 349, + 12, 349, + 13, 349, + 14, 349, + 15, 349, + 17, 349, + 19, 349, + 30, 349, + 31, 349, + 42, 349, + 43, 349, + 44, 349, + 45, 349, + 46, 349, + 48, 349, + 49, 349, + 52, 349, + 53, 349, + 55, 349, + 56, 349, + 265, 349, + -2, 367, + -1, 596, 53, 43, 55, 43, -2, 45, - -1, 725, - 112, 625, - -2, 621, - -1, 937, + -1, 733, + 112, 636, + -2, 632, + -1, 948, 5, 30, - -2, 428, - -1, 962, + -2, 433, + -1, 977, 5, 29, - -2, 558, - -1, 1202, + -2, 569, + -1, 1229, 5, 30, - -2, 559, - -1, 1252, + -2, 570, + -1, 1283, 5, 29, - -2, 561, - -1, 1325, + -2, 572, + -1, 1360, 5, 30, - -2, 562, + -2, 573, } const yyPrivate = 57344 -const yyLast = 11302 +const yyLast = 11934 var yyAct = [...]int{ - 263, 1360, 1350, 1316, 876, 661, 965, 551, 1264, 1103, - 983, 241, 804, 1137, 1034, 1100, 966, 267, 1104, 903, - 850, 870, 293, 989, 836, 826, 57, 802, 269, 1008, - 1222, 1110, 82, 1116, 856, 750, 204, 1077, 260, 204, - 690, 929, 1037, 760, 550, 3, 1025, 823, 806, 599, - 840, 483, 776, 727, 866, 791, 489, 827, 430, 495, - 784, 294, 51, 232, 503, 598, 322, 204, 82, 325, - 265, 582, 204, 911, 204, 331, 82, 250, 320, 56, - 1353, 1337, 1348, 1323, 1345, 877, 757, 1336, 1322, 583, - 1095, 1196, 565, 435, 1275, 1131, 240, 1145, 1146, 1147, - 1132, 1133, 818, 819, 817, 1150, 1148, 254, 233, 234, - 235, 236, 996, 51, 239, 995, 238, 600, 997, 601, - 456, 246, 759, 24, 25, 52, 27, 28, 1291, 516, - 515, 525, 526, 518, 519, 520, 521, 522, 523, 524, - 517, 237, 43, 527, 1016, 849, 61, 29, 48, 49, - 199, 195, 196, 197, 474, 1225, 1241, 857, 1185, 1183, - 231, 475, 472, 473, 467, 468, 1347, 38, 1344, 461, - 1317, 54, 63, 64, 65, 66, 67, 1058, 785, 841, - 1307, 1265, 1368, 1273, 445, 438, 458, 193, 460, 444, - 984, 986, 669, 204, 1267, 204, 1055, 192, 843, 193, - 660, 204, 1057, 1126, 1125, 1124, 433, 441, 204, 207, - 194, 905, 82, 82, 82, 82, 1296, 82, 457, 459, - 539, 540, 1364, 1205, 1064, 843, 945, 82, 923, 824, - 699, 507, 31, 32, 34, 33, 36, 451, 50, 516, - 515, 525, 526, 518, 519, 520, 521, 522, 523, 524, - 517, 527, 696, 527, 1009, 1154, 502, 82, 1305, 37, - 44, 45, 1266, 70, 46, 47, 35, 985, 198, 517, - 1164, 491, 527, 462, 462, 462, 462, 1149, 462, 39, - 40, 588, 41, 42, 1274, 1272, 857, 1114, 462, 904, - 481, 492, 930, 537, 842, 1292, 1321, 602, 1056, 71, - 1054, 479, 480, 455, 691, 1097, 1155, 777, 51, 541, - 542, 543, 544, 545, 546, 547, 548, 201, 204, 204, - 204, 842, 843, 536, 82, 664, 538, 500, 1362, 1014, - 82, 1363, 1193, 1361, 518, 519, 520, 521, 522, 523, - 524, 517, 1310, 502, 527, 497, 581, 586, 321, 1327, - 777, 431, 952, 432, 549, 434, 553, 554, 555, 556, - 557, 558, 559, 560, 561, 53, 564, 566, 566, 566, - 566, 566, 566, 566, 566, 574, 575, 576, 577, 1232, - 587, 191, 464, 465, 466, 692, 469, 447, 448, 449, - 596, 846, 431, 1231, 493, 1329, 478, 847, 590, 567, - 568, 569, 570, 571, 572, 573, 516, 515, 525, 526, - 518, 519, 520, 521, 522, 523, 524, 517, 842, 54, - 527, 1029, 734, 839, 837, 429, 838, 501, 500, 730, - 204, 835, 841, 942, 1099, 82, 732, 733, 731, 1369, - 1028, 204, 204, 82, 502, 204, 317, 318, 204, 702, - 703, 1017, 204, 437, 82, 82, 82, 82, 82, 82, - 82, 82, 520, 521, 522, 523, 524, 517, 82, 82, - 527, 941, 1303, 940, 442, 1306, 443, 204, 1370, 463, - 1248, 22, 450, 501, 500, 501, 500, 1229, 1061, 452, - 501, 500, 920, 921, 922, 82, 462, 501, 500, 204, - 502, 1026, 502, 704, 462, 82, 751, 502, 752, 998, - 1009, 999, 1004, 698, 502, 462, 462, 462, 462, 462, - 462, 462, 462, 1270, 1346, 676, 879, 678, 753, 462, - 462, 439, 440, 728, 717, 719, 720, 675, 292, 729, - 718, 245, 1333, 482, 1270, 1314, 482, 328, 726, 82, - 697, 735, 736, 737, 738, 739, 740, 741, 742, 743, - 744, 745, 746, 747, 748, 749, 674, 501, 500, 721, - 80, 706, 1270, 482, 1270, 1297, 769, 772, 1270, 1269, - 204, 723, 778, 725, 502, 1220, 1219, 1330, 204, 204, - 665, 663, 204, 204, 1207, 482, 82, 658, 764, 580, - 51, 589, 1204, 482, 659, 453, 330, 1161, 1160, 82, - 754, 755, 668, 446, 436, 553, 586, 1157, 1158, 812, - 586, 774, 1280, 679, 680, 681, 682, 683, 684, 685, - 686, 1157, 1156, 935, 482, 788, 482, 687, 688, 852, - 853, 854, 855, 1078, 762, 482, 609, 608, 24, 803, - 810, 1279, 1276, 587, 24, 863, 864, 865, 858, 859, - 860, 204, 781, 815, 82, 814, 82, 1151, 831, 990, - 204, 990, 960, 204, 82, 844, 961, 765, 766, 872, - 1080, 1101, 1251, 773, 1113, 811, 593, 592, 1067, 1113, - 204, 58, 204, 204, 762, 787, 54, 780, 1200, 782, - 783, 788, 54, 662, 1163, 947, 1159, 1000, 816, 868, - 869, 610, 788, 1082, 1113, 1086, 24, 1081, 944, 1079, - 788, 935, 666, 667, 1084, 462, 670, 462, 594, 673, - 592, 935, 935, 1083, 595, 462, 525, 526, 518, 519, - 520, 521, 522, 523, 524, 517, 1085, 1087, 527, 946, - 330, 330, 330, 330, 728, 330, 913, 912, 693, 700, - 729, 874, 943, 247, 54, 330, 54, 1338, 1313, 1236, - 1227, 926, 927, 928, 851, 725, 1212, 871, 1142, 1003, - 713, 1117, 1118, 705, 867, 925, 924, 862, 861, 204, - 204, 204, 204, 204, 1355, 505, 1351, 1144, 1120, 1101, - 1030, 204, 672, 328, 204, 967, 476, 712, 204, 1045, - 977, 54, 204, 1123, 1122, 978, 974, 586, 586, 586, - 586, 586, 973, 1342, 1335, 975, 1063, 82, 951, 962, - 976, 908, 586, 880, 919, 882, 992, 1341, 1043, 991, - 586, 761, 763, 901, 251, 252, 963, 964, 764, 496, - 587, 587, 587, 587, 587, 980, 988, 779, 918, 917, - 968, 786, 330, 971, 494, 803, 993, 987, 604, 979, - 1010, 797, 798, 587, 813, 82, 82, 1021, 82, 969, - 970, 934, 972, 484, 1001, 607, 454, 1018, 1019, 1006, - 1007, 1013, 1237, 1312, 256, 485, 1311, 949, 1249, 1011, - 1005, 82, 1198, 881, 1020, 1044, 1022, 1023, 1024, 1027, - 1049, 1046, 1039, 1047, 1042, 204, 671, 801, 1040, 1041, - 248, 249, 496, 916, 82, 242, 1285, 243, 1050, 58, - 1284, 915, 1048, 1239, 990, 477, 689, 462, 1051, 1357, - 1356, 1357, 875, 1060, 498, 1293, 1226, 1036, 695, 60, - 62, 899, 591, 55, 900, 1, 1349, 878, 1033, 887, - 1315, 1263, 462, 1136, 82, 82, 834, 1102, 1071, 1073, - 1074, 902, 825, 330, 69, 428, 1096, 1088, 967, 1076, - 1089, 330, 1090, 1091, 1105, 1093, 1094, 724, 82, 1112, - 68, 1304, 330, 330, 330, 330, 330, 330, 330, 330, - 833, 82, 832, 82, 82, 1121, 330, 330, 1107, 1271, - 1224, 1070, 845, 1015, 848, 1130, 1128, 1143, 1309, 1127, - 1012, 725, 615, 1106, 613, 51, 1134, 614, 612, 1139, - 617, 204, 616, 708, 611, 215, 1140, 1141, 323, 82, - 800, 603, 873, 505, 932, 1032, 330, 499, 933, 72, - 1053, 1052, 82, 204, 883, 937, 938, 939, 470, 82, - 1135, 471, 217, 535, 948, 82, 1152, 1153, 204, 954, - 1059, 955, 956, 957, 958, 328, 914, 994, 329, 1108, - 1172, 701, 488, 1283, 1238, 950, 562, 756, 828, 775, - 1173, 268, 1190, 982, 716, 1174, 586, 770, 770, 1178, - 1179, 1165, 1180, 770, 1181, 1182, 281, 1184, 278, 280, - 279, 1176, 707, 959, 1167, 509, 266, 1170, 1199, 258, - 585, 578, 792, 283, 282, 285, 286, 287, 288, 587, - 1209, 967, 284, 289, 330, 82, 790, 486, 490, 1208, - 1218, 789, 1119, 82, 1115, 584, 1066, 330, 1195, 793, - 796, 797, 798, 794, 508, 795, 799, 1194, 1290, 1221, - 711, 26, 59, 82, 82, 82, 516, 515, 525, 526, - 518, 519, 520, 521, 522, 523, 524, 517, 1234, 724, - 527, 253, 19, 18, 17, 20, 1214, 1215, 1216, 552, - 16, 1228, 1001, 1230, 15, 14, 1065, 30, 563, 21, - 13, 12, 330, 11, 330, 82, 82, 10, 82, 9, - 8, 7, 330, 82, 1240, 82, 82, 82, 204, 1250, - 1257, 82, 1235, 1105, 462, 1075, 6, 5, 1242, 1243, - 1262, 1244, 1245, 1246, 1268, 4, 244, 82, 330, 23, - 893, 2, 0, 1258, 0, 1259, 1260, 1261, 1252, 793, - 796, 797, 798, 794, 892, 795, 799, 0, 0, 1117, - 1118, 1294, 1106, 0, 0, 1253, 0, 1281, 0, 0, - 0, 82, 1302, 1301, 1105, 0, 1277, 0, 1278, 0, - 0, 0, 897, 0, 0, 0, 0, 0, 0, 0, - 0, 891, 0, 1319, 0, 0, 0, 82, 1295, 1282, - 1324, 0, 0, 0, 0, 0, 828, 0, 204, 0, - 0, 967, 1162, 1106, 0, 51, 0, 0, 82, 0, - 1331, 0, 0, 0, 0, 0, 770, 0, 0, 0, - 0, 0, 1233, 0, 1169, 0, 1339, 1340, 0, 82, - 0, 888, 885, 886, 0, 884, 0, 0, 0, 0, - 0, 0, 1354, 0, 1045, 0, 0, 1035, 0, 1365, - 0, 1175, 0, 0, 0, 330, 487, 0, 1177, 1343, - 0, 0, 694, 0, 0, 0, 895, 898, 0, 1186, - 1187, 1188, 0, 1043, 1191, 0, 0, 0, 0, 0, - 0, 1358, 0, 0, 0, 714, 715, 1201, 1202, 1203, - 0, 1206, 202, 1069, 0, 230, 0, 0, 0, 1352, - 0, 0, 890, 1031, 330, 0, 330, 0, 1217, 0, - 0, 0, 0, 0, 0, 1092, 0, 0, 0, 0, - 257, 0, 0, 202, 889, 0, 0, 0, 202, 330, - 202, 0, 0, 0, 0, 0, 0, 0, 552, 0, - 1044, 767, 768, 0, 0, 1049, 1046, 1039, 1047, 1042, - 0, 0, 330, 1040, 1041, 0, 1192, 482, 0, 0, - 0, 0, 0, 0, 894, 0, 0, 1048, 0, 0, - 828, 0, 828, 1038, 330, 0, 1247, 896, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 822, 0, 770, - 0, 0, 1109, 1111, 516, 515, 525, 526, 518, 519, - 520, 521, 522, 523, 524, 517, 0, 0, 527, 0, - 0, 0, 0, 0, 0, 0, 1111, 0, 0, 0, - 0, 0, 0, 1286, 1287, 1288, 1289, 0, 213, 330, - 0, 330, 1138, 0, 1069, 0, 0, 0, 0, 0, - 1298, 1299, 1300, 0, 0, 0, 0, 0, 0, 202, - 0, 202, 226, 0, 0, 0, 0, 202, 0, 0, - 0, 0, 0, 0, 202, 0, 0, 1166, 0, 0, - 0, 1320, 0, 0, 0, 0, 1325, 0, 0, 1328, - 1168, 0, 909, 910, 482, 490, 0, 1171, 0, 0, - 0, 0, 0, 330, 0, 0, 1332, 0, 0, 0, - 0, 0, 0, 208, 828, 0, 0, 0, 0, 0, - 210, 0, 0, 0, 0, 0, 0, 0, 216, 212, - 0, 516, 515, 525, 526, 518, 519, 520, 521, 522, - 523, 524, 517, 1035, 828, 527, 0, 0, 0, 1366, - 1367, 0, 770, 0, 0, 0, 0, 936, 0, 0, - 214, 0, 0, 218, 0, 0, 225, 0, 0, 1189, - 482, 0, 953, 330, 0, 0, 0, 0, 0, 0, - 0, 1223, 0, 0, 202, 202, 202, 0, 0, 0, - 0, 0, 209, 0, 0, 0, 0, 0, 0, 0, - 0, 330, 330, 330, 0, 0, 0, 516, 515, 525, - 526, 518, 519, 520, 521, 522, 523, 524, 517, 219, - 211, 527, 220, 221, 222, 224, 223, 229, 0, 0, - 0, 0, 0, 228, 227, 0, 0, 0, 0, 0, - 0, 0, 0, 1254, 1255, 0, 1256, 0, 0, 0, - 0, 1223, 0, 1223, 1223, 1223, 0, 0, 0, 1138, - 511, 0, 514, 0, 0, 0, 0, 0, 528, 529, - 530, 531, 532, 533, 534, 1223, 512, 513, 510, 516, - 515, 525, 526, 518, 519, 520, 521, 522, 523, 524, - 517, 0, 0, 527, 0, 0, 202, 0, 0, 1072, - 1062, 0, 0, 0, 0, 0, 0, 202, 202, 1308, - 0, 202, 0, 0, 202, 0, 0, 0, 677, 516, - 515, 525, 526, 518, 519, 520, 521, 522, 523, 524, - 517, 0, 770, 527, 0, 1326, 0, 0, 0, 0, - 0, 0, 0, 202, 0, 0, 0, 1098, 0, 0, - 0, 0, 0, 0, 0, 0, 1334, 0, 0, 0, - 0, 0, 0, 0, 0, 202, 931, 0, 0, 0, - 0, 0, 0, 0, 677, 0, 0, 1223, 0, 0, - 0, 0, 0, 0, 0, 1129, 516, 515, 525, 526, - 518, 519, 520, 521, 522, 523, 524, 517, 0, 0, - 527, 516, 515, 525, 526, 518, 519, 520, 521, 522, - 523, 524, 517, 0, 0, 527, 257, 0, 0, 0, - 0, 257, 257, 0, 0, 771, 771, 257, 0, 0, - 0, 771, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 257, 257, 257, 257, 632, 202, 0, 0, 0, - 0, 0, 0, 0, 202, 808, 0, 0, 202, 202, - 515, 525, 526, 518, 519, 520, 521, 522, 523, 524, - 517, 0, 0, 527, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1197, 0, 0, 0, 0, 0, - 0, 552, 0, 0, 0, 0, 0, 0, 0, 1210, - 0, 0, 1211, 0, 0, 1213, 0, 0, 0, 0, - 0, 0, 0, 620, 0, 0, 0, 202, 0, 0, - 0, 0, 0, 0, 0, 0, 202, 0, 0, 202, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 202, 0, 906, 907, - 633, 0, 0, 0, 0, 0, 677, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 257, 0, - 0, 0, 646, 647, 648, 649, 650, 651, 652, 0, - 653, 654, 655, 656, 657, 634, 635, 636, 637, 618, - 619, 0, 0, 621, 0, 622, 623, 624, 625, 626, - 627, 628, 629, 630, 631, 638, 639, 640, 641, 642, - 643, 644, 645, 0, 0, 257, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 257, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 771, 202, 202, 202, 202, 202, - 0, 0, 0, 0, 0, 0, 0, 981, 0, 0, - 202, 0, 0, 0, 808, 0, 0, 0, 202, 1318, - 552, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 268, 1395, 1351, 886, 668, 980, 558, 998, 1385, 1295, + 272, 298, 1123, 833, 814, 1049, 981, 837, 1124, 1120, + 1157, 246, 880, 1249, 57, 557, 3, 1023, 866, 914, + 1004, 846, 82, 1130, 1136, 237, 206, 836, 1093, 206, + 758, 765, 698, 1052, 940, 1040, 606, 812, 299, 51, + 850, 801, 786, 816, 768, 735, 490, 496, 589, 435, + 767, 876, 605, 336, 502, 794, 330, 206, 82, 922, + 270, 461, 206, 510, 206, 325, 82, 245, 255, 327, + 238, 239, 240, 241, 56, 1388, 244, 1372, 1383, 572, + 1358, 1380, 887, 1371, 1115, 1357, 463, 1223, 440, 1306, + 51, 590, 1151, 1165, 1166, 1167, 827, 595, 251, 465, + 243, 1170, 1168, 201, 197, 198, 199, 242, 259, 1152, + 1153, 1326, 523, 522, 532, 533, 525, 526, 527, 528, + 529, 530, 531, 524, 1011, 1031, 534, 1010, 859, 61, + 1012, 828, 829, 203, 607, 481, 608, 903, 1252, 867, + 1206, 1268, 482, 479, 480, 1094, 1204, 236, 474, 475, + 1382, 902, 1379, 1352, 333, 63, 64, 65, 66, 67, + 1073, 795, 1342, 450, 326, 467, 851, 469, 1403, 437, + 195, 439, 451, 999, 1001, 443, 1399, 1074, 1296, 907, + 677, 194, 1096, 195, 1070, 206, 667, 206, 901, 1146, + 1072, 1298, 1145, 206, 1144, 1304, 438, 466, 468, 446, + 206, 209, 853, 82, 196, 82, 853, 82, 82, 1331, + 82, 1232, 82, 546, 547, 1098, 1080, 1102, 958, 1097, + 934, 1095, 82, 200, 707, 853, 1100, 514, 457, 742, + 524, 1024, 704, 534, 834, 1099, 534, 699, 898, 895, + 896, 70, 894, 740, 741, 739, 507, 509, 1101, 1103, + 1000, 462, 82, 462, 436, 462, 462, 1340, 462, 1297, + 462, 1174, 509, 1184, 498, 1134, 436, 499, 486, 487, + 462, 867, 609, 905, 908, 1169, 470, 71, 471, 472, + 1327, 473, 1397, 476, 464, 1398, 1071, 1396, 1069, 1117, + 51, 916, 447, 485, 449, 1356, 1305, 1303, 852, 434, + 456, 787, 852, 967, 1060, 543, 488, 458, 545, 900, + 787, 856, 1175, 206, 206, 206, 671, 857, 700, 82, + 1029, 852, 261, 710, 711, 82, 849, 847, 1345, 848, + 504, 899, 1362, 1058, 845, 851, 556, 54, 560, 561, + 562, 563, 564, 565, 566, 567, 568, 738, 571, 573, + 573, 573, 573, 573, 573, 573, 573, 581, 582, 583, + 584, 588, 594, 453, 454, 455, 193, 442, 1259, 915, + 1258, 508, 507, 904, 525, 526, 527, 528, 529, 530, + 531, 524, 500, 952, 534, 951, 906, 1044, 509, 1043, + 597, 574, 575, 576, 577, 578, 579, 580, 603, 759, + 1059, 760, 508, 507, 274, 1064, 1061, 1054, 1062, 1057, + 1404, 508, 507, 1055, 1056, 333, 1032, 22, 1119, 509, + 587, 1364, 596, 508, 507, 206, 706, 1063, 509, 1341, + 82, 322, 323, 1066, 1279, 1256, 206, 206, 82, 1077, + 509, 206, 1041, 1338, 206, 444, 445, 1024, 206, 1405, + 82, 82, 931, 932, 933, 82, 82, 82, 82, 82, + 82, 1019, 1013, 705, 1014, 82, 82, 527, 528, 529, + 530, 531, 524, 953, 206, 534, 889, 250, 462, 761, + 508, 507, 725, 727, 728, 683, 462, 682, 726, 1301, + 1381, 489, 82, 1368, 489, 1365, 206, 509, 462, 462, + 672, 666, 82, 462, 462, 462, 462, 462, 462, 675, + 712, 686, 670, 462, 462, 1311, 678, 684, 665, 736, + 297, 687, 688, 508, 507, 459, 689, 690, 691, 692, + 693, 694, 617, 1301, 1349, 1310, 695, 696, 1301, 489, + 509, 1301, 1332, 673, 674, 452, 82, 1307, 326, 1301, + 1300, 681, 80, 532, 533, 525, 526, 527, 528, 529, + 530, 531, 524, 1247, 1246, 534, 777, 781, 733, 714, + 493, 497, 788, 1171, 731, 729, 772, 206, 1234, 489, + 854, 701, 1231, 489, 51, 206, 206, 515, 335, 206, + 206, 1181, 1180, 82, 1177, 1178, 441, 1177, 1176, 560, + 1133, 732, 600, 721, 946, 489, 82, 762, 763, 798, + 489, 1005, 773, 774, 791, 770, 489, 1005, 783, 616, + 615, 1121, 559, 784, 1133, 58, 1083, 24, 821, 770, + 599, 570, 790, 813, 792, 793, 822, 594, 288, 287, + 290, 291, 292, 293, 601, 24, 599, 289, 294, 868, + 869, 870, 820, 860, 798, 1282, 962, 825, 206, 824, + 1133, 82, 960, 82, 957, 946, 841, 206, 206, 975, + 946, 206, 82, 976, 544, 54, 797, 882, 1227, 955, + 798, 24, 1183, 1179, 796, 1015, 826, 946, 206, 333, + 206, 206, 599, 54, 1390, 602, 708, 823, 676, 1386, + 961, 798, 838, 54, 1373, 252, 959, 669, 956, 462, + 1348, 462, 1263, 878, 879, 803, 806, 807, 808, 804, + 462, 805, 809, 954, 1254, 1137, 1138, 884, 593, 54, + 861, 1239, 890, 335, 892, 335, 881, 335, 335, 1162, + 335, 736, 335, 912, 803, 806, 807, 808, 804, 1018, + 805, 809, 335, 54, 877, 872, 923, 1137, 1138, 924, + 871, 733, 1164, 1140, 1121, 885, 1045, 680, 483, 720, + 930, 935, 992, 1143, 909, 910, 990, 993, 911, 1142, + 989, 991, 512, 988, 994, 936, 807, 808, 1377, 206, + 206, 206, 206, 206, 732, 913, 256, 257, 982, 1370, + 1079, 206, 919, 503, 206, 1376, 713, 702, 206, 929, + 977, 928, 206, 491, 1036, 614, 460, 945, 501, 1028, + 1347, 1346, 891, 1280, 1026, 492, 1020, 82, 966, 772, + 722, 723, 1225, 978, 979, 964, 1264, 594, 594, 594, + 594, 594, 679, 1007, 1016, 811, 503, 983, 265, 335, + 986, 995, 813, 1316, 1002, 611, 253, 254, 927, 1006, + 594, 1003, 247, 1008, 769, 771, 926, 248, 1025, 58, + 1035, 1315, 1037, 1038, 1039, 82, 82, 1266, 82, 1005, + 789, 1033, 1034, 559, 484, 697, 775, 776, 505, 1021, + 1022, 984, 985, 1392, 987, 1328, 1219, 489, 1392, 1391, + 1253, 703, 82, 60, 62, 598, 55, 1, 1042, 1384, + 888, 1048, 897, 1350, 1294, 1156, 206, 844, 835, 1051, + 69, 433, 737, 838, 462, 82, 68, 1339, 843, 1065, + 842, 1302, 832, 1251, 523, 522, 532, 533, 525, 526, + 527, 528, 529, 530, 531, 524, 1076, 1047, 534, 855, + 462, 1030, 858, 1163, 1344, 1027, 622, 620, 621, 619, + 335, 624, 623, 618, 220, 328, 810, 1086, 335, 82, + 82, 1122, 1087, 1075, 1050, 610, 982, 883, 1125, 1116, + 335, 335, 1105, 1104, 506, 335, 335, 335, 335, 335, + 335, 1092, 72, 82, 1127, 335, 335, 1068, 1067, 593, + 893, 1132, 477, 593, 478, 222, 82, 542, 82, 82, + 925, 1141, 733, 1009, 334, 1126, 1128, 51, 709, 1148, + 495, 1085, 716, 1081, 1314, 1155, 1147, 1150, 920, 921, + 1265, 497, 512, 965, 569, 335, 206, 1172, 1173, 1160, + 1161, 1159, 785, 1154, 82, 1110, 522, 532, 533, 525, + 526, 527, 528, 529, 530, 531, 524, 82, 206, 534, + 273, 724, 286, 283, 82, 206, 285, 284, 943, 715, + 974, 82, 944, 516, 206, 271, 764, 263, 1185, 948, + 949, 950, 592, 585, 802, 800, 779, 779, 799, 1139, + 963, 1187, 779, 947, 1190, 969, 1193, 970, 971, 972, + 973, 1135, 838, 1194, 838, 591, 1082, 1222, 1325, 719, + 968, 1202, 26, 59, 258, 19, 18, 17, 20, 997, + 16, 1195, 594, 335, 548, 549, 550, 551, 552, 553, + 554, 555, 1226, 15, 982, 14, 335, 448, 30, 21, + 1236, 13, 12, 1182, 737, 82, 11, 1235, 10, 9, + 8, 7, 6, 82, 1221, 5, 4, 249, 23, 2, + 1245, 0, 1016, 0, 0, 1189, 0, 1085, 0, 0, + 0, 0, 1192, 82, 82, 82, 0, 0, 0, 0, + 0, 0, 0, 1241, 1242, 1243, 0, 0, 0, 1261, + 0, 335, 1262, 335, 0, 1255, 0, 1257, 0, 0, + 0, 0, 335, 593, 593, 593, 593, 593, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 593, 1267, + 0, 462, 82, 82, 0, 82, 593, 0, 335, 1125, + 82, 0, 82, 82, 82, 206, 1281, 0, 82, 1078, + 0, 838, 0, 0, 1260, 0, 1283, 1288, 0, 1293, + 0, 0, 1091, 1289, 82, 1290, 1291, 1292, 1299, 1308, + 0, 1309, 0, 0, 0, 0, 1126, 0, 0, 1284, + 1050, 838, 0, 0, 0, 0, 0, 1312, 0, 862, + 863, 864, 865, 0, 1125, 1329, 0, 0, 0, 0, + 1118, 1336, 82, 0, 1337, 873, 874, 875, 0, 0, + 1330, 0, 0, 1313, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1354, 0, 0, 0, 0, 779, 0, + 0, 1126, 82, 51, 1359, 0, 0, 0, 1149, 982, + 0, 0, 0, 206, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 82, 1366, 523, 522, 532, 533, 525, + 526, 527, 528, 529, 530, 531, 524, 335, 0, 534, + 1374, 1375, 0, 0, 82, 734, 0, 0, 743, 744, + 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, + 755, 756, 757, 1389, 1400, 0, 0, 1378, 1060, 0, + 0, 0, 0, 1196, 0, 0, 0, 0, 941, 0, + 1198, 0, 0, 0, 0, 1046, 335, 0, 335, 494, + 0, 1207, 1208, 1209, 0, 1212, 0, 1058, 1215, 0, + 1218, 1387, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 335, 1228, 1229, 1230, 0, 1233, 0, 0, + 1363, 0, 1224, 0, 0, 204, 0, 0, 235, 559, + 0, 0, 0, 0, 1244, 335, 0, 1237, 0, 0, + 1238, 0, 0, 1240, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 262, 0, 0, 204, 0, 0, 335, + 217, 204, 0, 204, 1059, 0, 0, 0, 593, 1064, + 1061, 1054, 1062, 1057, 0, 0, 779, 1055, 1056, 1129, + 1131, 0, 0, 0, 231, 1216, 489, 0, 0, 0, + 0, 1063, 0, 0, 0, 0, 0, 1053, 0, 0, + 0, 0, 0, 1131, 0, 0, 0, 0, 0, 1278, + 0, 0, 0, 0, 0, 0, 335, 0, 335, 1158, + 0, 0, 0, 523, 522, 532, 533, 525, 526, 527, + 528, 529, 530, 531, 524, 210, 0, 534, 0, 0, + 0, 0, 213, 0, 0, 0, 0, 0, 0, 0, + 221, 216, 0, 0, 1186, 0, 1317, 1318, 1319, 1320, + 1321, 1322, 1323, 1324, 0, 0, 0, 1188, 0, 937, + 938, 939, 0, 0, 1191, 0, 0, 1333, 1334, 1335, + 0, 335, 219, 0, 204, 223, 204, 0, 230, 0, + 0, 0, 204, 0, 0, 0, 0, 0, 0, 204, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1355, 0, 211, 0, 0, 1360, 1353, 559, + 0, 0, 0, 0, 0, 0, 1213, 489, 0, 0, + 0, 0, 0, 0, 779, 0, 0, 1367, 0, 0, + 0, 224, 214, 0, 225, 226, 227, 229, 228, 234, + 1210, 489, 0, 215, 218, 335, 212, 233, 232, 0, + 0, 0, 0, 1250, 523, 522, 532, 533, 525, 526, + 527, 528, 529, 530, 531, 524, 0, 0, 534, 489, + 1401, 1402, 0, 335, 335, 335, 0, 0, 523, 522, + 532, 533, 525, 526, 527, 528, 529, 530, 531, 524, + 0, 0, 534, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 204, 204, 204, 0, 523, 522, 532, 533, + 525, 526, 527, 528, 529, 530, 531, 524, 1199, 1200, + 534, 1201, 1285, 1286, 1203, 1287, 1205, 1220, 0, 0, + 1250, 0, 1250, 1250, 1250, 0, 0, 0, 1158, 24, + 25, 52, 27, 28, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1250, 0, 0, 0, 43, 0, + 1089, 1090, 0, 29, 48, 49, 0, 0, 0, 0, + 0, 0, 0, 1106, 1107, 1108, 1109, 0, 1111, 1112, + 1113, 1114, 1248, 38, 0, 0, 0, 54, 0, 0, + 0, 0, 1343, 0, 0, 0, 0, 0, 0, 0, + 0, 523, 522, 532, 533, 525, 526, 527, 528, 529, + 530, 531, 524, 0, 204, 534, 0, 0, 0, 779, + 0, 0, 1361, 0, 0, 204, 204, 0, 0, 0, + 204, 0, 0, 204, 0, 0, 0, 685, 0, 0, + 0, 0, 0, 1369, 0, 0, 0, 0, 31, 32, + 34, 33, 36, 0, 50, 0, 0, 0, 0, 0, + 0, 0, 0, 204, 1250, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 37, 44, 45, 0, 0, + 46, 47, 35, 0, 0, 204, 0, 0, 0, 0, + 0, 0, 0, 0, 685, 39, 40, 0, 41, 42, + 518, 0, 521, 0, 0, 0, 0, 1197, 535, 536, + 537, 538, 539, 540, 541, 1217, 519, 520, 517, 523, + 522, 532, 533, 525, 526, 527, 528, 529, 530, 531, + 524, 1214, 0, 534, 0, 0, 262, 0, 0, 0, + 0, 262, 262, 0, 0, 780, 780, 262, 0, 0, + 0, 780, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 262, 262, 262, 262, 0, 204, 639, 0, 0, + 0, 0, 0, 0, 204, 818, 0, 0, 204, 204, + 0, 0, 0, 53, 0, 0, 0, 1211, 0, 523, + 522, 532, 533, 525, 526, 527, 528, 529, 530, 531, + 524, 0, 0, 534, 0, 523, 522, 532, 533, 525, + 526, 527, 528, 529, 530, 531, 524, 0, 0, 534, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1269, + 1270, 0, 1271, 1272, 0, 1273, 1274, 0, 1275, 1276, + 1277, 0, 0, 0, 0, 627, 0, 204, 0, 0, + 0, 0, 0, 0, 0, 0, 204, 204, 0, 0, + 204, 523, 522, 532, 533, 525, 526, 527, 528, 529, + 530, 531, 524, 0, 0, 534, 0, 204, 0, 917, + 918, 0, 640, 0, 0, 0, 0, 685, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 262, + 0, 0, 0, 0, 653, 654, 655, 656, 657, 658, + 659, 0, 660, 661, 662, 663, 664, 641, 642, 643, + 644, 625, 626, 0, 0, 628, 0, 629, 630, 631, + 632, 633, 634, 635, 636, 637, 638, 645, 646, 647, + 648, 649, 650, 651, 652, 0, 262, 523, 522, 532, + 533, 525, 526, 527, 528, 529, 530, 531, 524, 0, + 0, 534, 0, 0, 262, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 780, 204, 204, + 204, 204, 204, 0, 0, 0, 0, 1088, 0, 0, + 996, 0, 0, 204, 0, 0, 0, 818, 0, 0, + 0, 204, 942, 0, 0, 0, 1393, 523, 522, 532, + 533, 525, 526, 527, 528, 529, 530, 531, 524, 0, + 0, 534, 523, 522, 532, 533, 525, 526, 527, 528, + 529, 530, 531, 524, 0, 0, 534, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 204, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 262, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 262, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 685, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 780, 0, 139, 0, 0, + 0, 0, 0, 0, 0, 0, 103, 0, 0, 0, + 0, 0, 120, 0, 122, 0, 0, 159, 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 202, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 257, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 257, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 677, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 138, 0, 0, 771, 0, 0, - 0, 0, 0, 103, 0, 0, 0, 0, 0, 119, - 0, 121, 0, 0, 157, 130, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 81, 0, 0, 0, + 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 81, 0, 0, 0, 0, 0, 0, - 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 204, 0, 0, 0, 0, + 0, 0, 523, 522, 532, 533, 525, 526, 527, 528, + 529, 530, 531, 524, 0, 0, 534, 204, 0, 0, + 0, 0, 0, 0, 204, 0, 0, 0, 0, 0, + 109, 0, 0, 204, 207, 0, 0, 0, 0, 146, + 0, 162, 111, 119, 84, 90, 0, 110, 137, 151, + 155, 0, 0, 0, 99, 0, 153, 141, 175, 0, + 142, 152, 123, 167, 147, 174, 208, 182, 164, 181, + 188, 85, 163, 173, 97, 156, 87, 171, 161, 129, + 115, 116, 86, 780, 150, 102, 107, 101, 138, 168, + 169, 100, 191, 91, 180, 89, 92, 179, 136, 166, + 172, 130, 127, 88, 170, 128, 126, 118, 105, 112, + 144, 125, 145, 113, 133, 132, 134, 0, 0, 0, + 160, 177, 192, 94, 0, 165, 183, 184, 185, 186, + 187, 0, 0, 95, 108, 104, 143, 135, 93, 114, + 157, 117, 124, 149, 190, 140, 154, 98, 176, 158, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 202, 0, 516, - 515, 525, 526, 518, 519, 520, 521, 522, 523, 524, - 517, 0, 0, 527, 0, 0, 0, 0, 0, 202, - 0, 0, 0, 0, 0, 0, 0, 108, 0, 0, - 0, 205, 0, 0, 202, 0, 144, 0, 160, 110, - 118, 84, 90, 0, 109, 136, 149, 153, 0, 0, - 0, 99, 0, 151, 140, 173, 0, 141, 150, 122, - 165, 145, 172, 206, 180, 162, 179, 186, 85, 161, - 171, 97, 154, 87, 169, 159, 128, 114, 115, 86, - 771, 148, 102, 106, 101, 137, 166, 167, 100, 189, - 91, 178, 89, 92, 177, 135, 164, 170, 129, 126, - 88, 168, 127, 125, 117, 104, 111, 142, 124, 143, - 112, 132, 131, 133, 0, 0, 0, 158, 175, 190, - 94, 0, 163, 181, 182, 183, 184, 185, 0, 0, - 95, 107, 134, 93, 113, 155, 116, 123, 147, 188, - 139, 152, 98, 174, 156, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 83, 0, 121, + 189, 148, 106, 178, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 83, 0, 120, 187, 146, 105, 176, 416, - 407, 0, 378, 419, 356, 370, 427, 371, 372, 400, - 342, 386, 138, 368, 808, 359, 337, 365, 338, 357, - 380, 103, 383, 355, 409, 389, 418, 119, 425, 121, - 394, 0, 157, 130, 0, 0, 382, 411, 384, 405, - 377, 401, 347, 393, 420, 369, 398, 421, 0, 0, - 0, 81, 0, 829, 830, 0, 0, 0, 0, 0, - 96, 0, 396, 415, 367, 397, 399, 336, 395, 0, - 340, 343, 426, 413, 362, 363, 1002, 0, 0, 0, - 0, 0, 0, 381, 385, 402, 375, 0, 0, 0, - 771, 0, 0, 0, 0, 360, 0, 392, 0, 0, - 0, 344, 341, 0, 202, 379, 0, 0, 0, 346, - 0, 361, 403, 0, 335, 108, 406, 412, 376, 205, - 414, 374, 373, 417, 144, 0, 160, 110, 118, 84, - 90, 0, 109, 136, 149, 153, 410, 358, 366, 99, - 364, 151, 140, 173, 391, 141, 150, 122, 165, 145, - 172, 206, 180, 162, 179, 186, 85, 161, 171, 97, - 154, 87, 169, 159, 128, 114, 115, 86, 0, 148, - 102, 106, 101, 137, 166, 167, 100, 189, 91, 178, - 89, 92, 177, 135, 164, 170, 129, 126, 88, 168, - 127, 125, 117, 104, 111, 142, 124, 143, 112, 132, - 131, 133, 0, 339, 0, 158, 175, 190, 94, 354, - 163, 181, 182, 183, 184, 185, 0, 0, 95, 107, - 134, 93, 113, 155, 116, 123, 147, 188, 139, 152, - 98, 174, 156, 350, 353, 348, 349, 387, 388, 422, - 423, 424, 404, 345, 0, 351, 352, 0, 408, 390, - 83, 0, 120, 187, 146, 105, 176, 416, 407, 0, - 378, 419, 356, 370, 427, 371, 372, 400, 342, 386, - 138, 368, 0, 359, 337, 365, 338, 357, 380, 103, - 383, 355, 409, 389, 418, 119, 425, 121, 394, 0, - 157, 130, 0, 0, 382, 411, 384, 405, 377, 401, - 347, 393, 420, 369, 398, 421, 0, 0, 0, 81, - 0, 829, 830, 0, 0, 0, 0, 0, 96, 0, - 396, 415, 367, 397, 399, 336, 395, 0, 340, 343, - 426, 413, 362, 363, 0, 0, 0, 0, 0, 0, - 0, 381, 385, 402, 375, 0, 0, 0, 0, 0, - 0, 0, 0, 360, 0, 392, 0, 0, 0, 344, - 341, 0, 0, 379, 0, 0, 0, 346, 0, 361, - 403, 0, 335, 108, 406, 412, 376, 205, 414, 374, - 373, 417, 144, 0, 160, 110, 118, 84, 90, 0, - 109, 136, 149, 153, 410, 358, 366, 99, 364, 151, - 140, 173, 391, 141, 150, 122, 165, 145, 172, 206, - 180, 162, 179, 186, 85, 161, 171, 97, 154, 87, - 169, 159, 128, 114, 115, 86, 0, 148, 102, 106, - 101, 137, 166, 167, 100, 189, 91, 178, 89, 92, - 177, 135, 164, 170, 129, 126, 88, 168, 127, 125, - 117, 104, 111, 142, 124, 143, 112, 132, 131, 133, - 0, 339, 0, 158, 175, 190, 94, 354, 163, 181, - 182, 183, 184, 185, 0, 0, 95, 107, 134, 93, - 113, 155, 116, 123, 147, 188, 139, 152, 98, 174, - 156, 350, 353, 348, 349, 387, 388, 422, 423, 424, - 404, 345, 0, 351, 352, 0, 408, 390, 83, 0, - 120, 187, 146, 105, 176, 416, 407, 0, 378, 419, - 356, 370, 427, 371, 372, 400, 342, 386, 138, 368, - 0, 359, 337, 365, 338, 357, 380, 103, 383, 355, - 409, 389, 418, 119, 425, 121, 394, 0, 157, 130, - 0, 0, 382, 411, 384, 405, 377, 401, 347, 393, - 420, 369, 398, 421, 54, 0, 0, 81, 0, 0, - 0, 0, 0, 0, 0, 0, 96, 0, 396, 415, - 367, 397, 399, 336, 395, 0, 340, 343, 426, 413, - 362, 363, 0, 0, 0, 0, 0, 0, 0, 381, - 385, 402, 375, 0, 0, 0, 0, 0, 0, 0, - 0, 360, 0, 392, 0, 0, 0, 344, 341, 0, - 0, 379, 0, 0, 0, 346, 0, 361, 403, 0, - 335, 108, 406, 412, 376, 205, 414, 374, 373, 417, - 144, 0, 160, 110, 118, 84, 90, 0, 109, 136, - 149, 153, 410, 358, 366, 99, 364, 151, 140, 173, - 391, 141, 150, 122, 165, 145, 172, 206, 180, 162, - 179, 186, 85, 161, 171, 97, 154, 87, 169, 159, - 128, 114, 115, 86, 0, 148, 102, 106, 101, 137, - 166, 167, 100, 189, 91, 178, 89, 92, 177, 135, - 164, 170, 129, 126, 88, 168, 127, 125, 117, 104, - 111, 142, 124, 143, 112, 132, 131, 133, 0, 339, - 0, 158, 175, 190, 94, 354, 163, 181, 182, 183, - 184, 185, 0, 0, 95, 107, 134, 93, 113, 155, - 116, 123, 147, 188, 139, 152, 98, 174, 156, 350, - 353, 348, 349, 387, 388, 422, 423, 424, 404, 345, - 0, 351, 352, 0, 408, 390, 83, 0, 120, 187, - 146, 105, 176, 416, 407, 0, 378, 419, 356, 370, - 427, 371, 372, 400, 342, 386, 138, 368, 0, 359, - 337, 365, 338, 357, 380, 103, 383, 355, 409, 389, - 418, 119, 425, 121, 394, 0, 157, 130, 0, 0, - 382, 411, 384, 405, 377, 401, 347, 393, 420, 369, - 398, 421, 0, 0, 0, 81, 0, 0, 0, 0, - 0, 0, 0, 0, 96, 0, 396, 415, 367, 397, - 399, 336, 395, 0, 340, 343, 426, 413, 362, 363, - 0, 0, 0, 0, 0, 0, 0, 381, 385, 402, - 375, 0, 0, 0, 0, 0, 0, 1068, 0, 360, - 0, 392, 0, 0, 0, 344, 341, 0, 0, 379, - 0, 0, 0, 346, 0, 361, 403, 0, 335, 108, - 406, 412, 376, 205, 414, 374, 373, 417, 144, 0, - 160, 110, 118, 84, 90, 0, 109, 136, 149, 153, - 410, 358, 366, 99, 364, 151, 140, 173, 391, 141, - 150, 122, 165, 145, 172, 206, 180, 162, 179, 186, - 85, 161, 171, 97, 154, 87, 169, 159, 128, 114, - 115, 86, 0, 148, 102, 106, 101, 137, 166, 167, - 100, 189, 91, 178, 89, 92, 177, 135, 164, 170, - 129, 126, 88, 168, 127, 125, 117, 104, 111, 142, - 124, 143, 112, 132, 131, 133, 0, 339, 0, 158, - 175, 190, 94, 354, 163, 181, 182, 183, 184, 185, - 0, 0, 95, 107, 134, 93, 113, 155, 116, 123, - 147, 188, 139, 152, 98, 174, 156, 350, 353, 348, - 349, 387, 388, 422, 423, 424, 404, 345, 0, 351, - 352, 0, 408, 390, 83, 0, 120, 187, 146, 105, - 176, 416, 407, 0, 378, 419, 356, 370, 427, 371, - 372, 400, 342, 386, 138, 368, 0, 359, 337, 365, - 338, 357, 380, 103, 383, 355, 409, 389, 418, 119, - 425, 121, 394, 0, 157, 130, 0, 0, 382, 411, - 384, 405, 377, 401, 347, 393, 420, 369, 398, 421, - 0, 0, 0, 262, 0, 0, 0, 0, 0, 0, - 0, 0, 96, 0, 396, 415, 367, 397, 399, 336, - 395, 0, 340, 343, 426, 413, 362, 363, 0, 0, - 0, 0, 0, 0, 0, 381, 385, 402, 375, 0, - 0, 0, 0, 0, 0, 722, 0, 360, 0, 392, - 0, 0, 0, 344, 341, 0, 0, 379, 0, 0, - 0, 346, 0, 361, 403, 0, 335, 108, 406, 412, - 376, 205, 414, 374, 373, 417, 144, 0, 160, 110, - 118, 84, 90, 0, 109, 136, 149, 153, 410, 358, - 366, 99, 364, 151, 140, 173, 391, 141, 150, 122, - 165, 145, 172, 206, 180, 162, 179, 186, 85, 161, - 171, 97, 154, 87, 169, 159, 128, 114, 115, 86, - 0, 148, 102, 106, 101, 137, 166, 167, 100, 189, - 91, 178, 89, 92, 177, 135, 164, 170, 129, 126, - 88, 168, 127, 125, 117, 104, 111, 142, 124, 143, - 112, 132, 131, 133, 0, 339, 0, 158, 175, 190, - 94, 354, 163, 181, 182, 183, 184, 185, 0, 0, - 95, 107, 134, 93, 113, 155, 116, 123, 147, 188, - 139, 152, 98, 174, 156, 350, 353, 348, 349, 387, - 388, 422, 423, 424, 404, 345, 0, 351, 352, 0, - 408, 390, 83, 0, 120, 187, 146, 105, 176, 416, - 407, 0, 378, 419, 356, 370, 427, 371, 372, 400, - 342, 386, 138, 368, 0, 359, 337, 365, 338, 357, - 380, 103, 383, 355, 409, 389, 418, 119, 425, 121, - 394, 0, 157, 130, 0, 0, 382, 411, 384, 405, - 377, 401, 347, 393, 420, 369, 398, 421, 0, 0, - 0, 81, 0, 0, 0, 0, 0, 0, 0, 0, - 96, 0, 396, 415, 367, 397, 399, 336, 395, 0, - 340, 343, 426, 413, 362, 363, 0, 0, 0, 0, - 0, 0, 0, 381, 385, 402, 375, 0, 0, 0, - 0, 0, 0, 0, 0, 360, 0, 392, 0, 0, - 0, 344, 341, 0, 0, 379, 0, 0, 0, 346, - 0, 361, 403, 0, 335, 108, 406, 412, 376, 205, - 414, 374, 373, 417, 144, 0, 160, 110, 118, 84, - 90, 0, 109, 136, 149, 153, 410, 358, 366, 99, - 364, 151, 140, 173, 391, 141, 150, 122, 165, 145, - 172, 206, 180, 162, 179, 186, 85, 161, 171, 97, - 154, 87, 169, 159, 128, 114, 115, 86, 0, 148, - 102, 106, 101, 137, 166, 167, 100, 189, 91, 178, - 89, 92, 177, 135, 164, 170, 129, 126, 88, 168, - 127, 125, 117, 104, 111, 142, 124, 143, 112, 132, - 131, 133, 0, 339, 0, 158, 175, 190, 94, 354, - 163, 181, 182, 183, 184, 185, 0, 0, 95, 107, - 134, 93, 113, 155, 116, 123, 147, 188, 139, 152, - 98, 174, 156, 350, 353, 348, 349, 387, 388, 422, - 423, 424, 404, 345, 0, 351, 352, 0, 408, 390, - 83, 0, 120, 187, 146, 105, 176, 416, 407, 0, - 378, 419, 356, 370, 427, 371, 372, 400, 342, 386, - 138, 368, 0, 359, 337, 365, 338, 357, 380, 103, - 383, 355, 409, 389, 418, 119, 425, 121, 394, 0, - 157, 130, 0, 0, 382, 411, 384, 405, 377, 401, - 347, 393, 420, 369, 398, 421, 0, 0, 0, 262, - 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, - 396, 415, 367, 397, 399, 336, 395, 0, 340, 343, - 426, 413, 362, 363, 0, 0, 0, 0, 0, 0, - 0, 381, 385, 402, 375, 0, 0, 0, 0, 0, - 0, 0, 0, 360, 0, 392, 0, 0, 0, 344, - 341, 0, 0, 379, 0, 0, 0, 346, 0, 361, - 403, 0, 335, 108, 406, 412, 376, 205, 414, 374, - 373, 417, 144, 0, 160, 110, 118, 84, 90, 0, - 109, 136, 149, 153, 410, 358, 366, 99, 364, 151, - 140, 173, 391, 141, 150, 122, 165, 145, 172, 206, - 180, 162, 179, 186, 85, 161, 171, 97, 154, 87, - 169, 159, 128, 114, 115, 86, 0, 148, 102, 106, - 101, 137, 166, 167, 100, 189, 91, 178, 89, 92, - 177, 135, 164, 170, 129, 126, 88, 168, 127, 125, - 117, 104, 111, 142, 124, 143, 112, 132, 131, 133, - 0, 339, 0, 158, 175, 190, 94, 354, 163, 181, - 182, 183, 184, 185, 0, 0, 95, 107, 134, 93, - 113, 155, 116, 123, 147, 188, 139, 152, 98, 174, - 156, 350, 353, 348, 349, 387, 388, 422, 423, 424, - 404, 345, 0, 351, 352, 0, 408, 390, 83, 0, - 120, 187, 146, 105, 176, 416, 407, 0, 378, 419, - 356, 370, 427, 371, 372, 400, 342, 386, 138, 368, - 0, 359, 337, 365, 338, 357, 380, 103, 383, 355, - 409, 389, 418, 119, 425, 121, 394, 0, 157, 130, - 0, 0, 382, 411, 384, 405, 377, 401, 347, 393, - 420, 369, 398, 421, 0, 0, 0, 81, 0, 0, - 0, 0, 0, 0, 0, 0, 96, 0, 396, 415, - 367, 397, 399, 336, 395, 0, 340, 343, 426, 413, - 362, 363, 0, 0, 0, 0, 0, 0, 0, 381, - 385, 402, 375, 0, 0, 0, 0, 0, 0, 0, - 0, 360, 0, 392, 0, 0, 0, 344, 341, 0, - 0, 379, 0, 0, 0, 346, 0, 361, 403, 0, - 335, 108, 406, 412, 376, 205, 414, 374, 373, 417, - 144, 0, 160, 110, 118, 84, 90, 0, 109, 136, - 149, 153, 410, 358, 366, 99, 364, 151, 140, 173, - 391, 141, 150, 122, 165, 145, 172, 206, 180, 162, - 179, 186, 85, 161, 171, 97, 154, 87, 169, 159, - 128, 114, 115, 86, 0, 148, 102, 106, 101, 137, - 166, 167, 100, 189, 91, 178, 89, 333, 177, 135, - 164, 170, 129, 126, 88, 168, 127, 125, 117, 104, - 111, 142, 124, 143, 112, 132, 131, 133, 0, 339, - 0, 158, 175, 190, 94, 354, 163, 181, 182, 183, - 184, 185, 0, 0, 95, 107, 334, 332, 113, 155, - 116, 123, 147, 188, 139, 152, 98, 174, 156, 350, - 353, 348, 349, 387, 388, 422, 423, 424, 404, 345, - 0, 351, 352, 0, 408, 390, 83, 0, 120, 187, - 146, 105, 176, 416, 407, 0, 378, 419, 356, 370, - 427, 371, 372, 400, 342, 386, 138, 368, 0, 359, - 337, 365, 338, 357, 380, 103, 383, 355, 409, 389, - 418, 119, 425, 121, 394, 0, 157, 130, 0, 0, - 382, 411, 384, 405, 377, 401, 347, 393, 420, 369, - 398, 421, 0, 0, 0, 203, 0, 0, 0, 0, - 0, 0, 0, 0, 96, 0, 396, 415, 367, 397, - 399, 336, 395, 0, 340, 343, 426, 413, 362, 363, - 0, 0, 0, 0, 0, 0, 0, 381, 385, 402, - 375, 0, 0, 0, 0, 0, 0, 0, 0, 360, - 0, 392, 0, 0, 0, 344, 341, 0, 0, 379, - 0, 0, 0, 346, 0, 361, 403, 0, 335, 108, - 406, 412, 376, 205, 414, 374, 373, 417, 144, 0, - 160, 110, 118, 84, 90, 0, 109, 136, 149, 153, - 410, 358, 366, 99, 364, 151, 140, 173, 391, 141, - 150, 122, 165, 145, 172, 206, 180, 162, 179, 186, - 85, 161, 171, 97, 154, 87, 169, 159, 128, 114, - 115, 86, 0, 148, 102, 106, 101, 137, 166, 167, - 100, 189, 91, 178, 89, 92, 177, 135, 164, 170, - 129, 126, 88, 168, 127, 125, 117, 104, 111, 142, - 124, 143, 112, 132, 131, 133, 0, 339, 0, 158, - 175, 190, 94, 354, 163, 181, 182, 183, 184, 185, - 0, 0, 95, 107, 134, 93, 113, 155, 116, 123, - 147, 188, 139, 152, 98, 174, 156, 350, 353, 348, - 349, 387, 388, 422, 423, 424, 404, 345, 0, 351, - 352, 0, 408, 390, 83, 0, 120, 187, 146, 105, - 176, 416, 407, 0, 378, 419, 356, 370, 427, 371, - 372, 400, 342, 386, 138, 368, 0, 359, 337, 365, - 338, 357, 380, 103, 383, 355, 409, 389, 418, 119, - 425, 121, 394, 0, 157, 130, 0, 0, 382, 411, - 384, 405, 377, 401, 347, 393, 420, 369, 398, 421, - 0, 0, 0, 81, 0, 0, 0, 0, 0, 0, - 0, 0, 96, 0, 396, 415, 367, 397, 399, 336, - 395, 0, 340, 343, 426, 413, 362, 363, 0, 0, - 0, 0, 0, 0, 0, 381, 385, 402, 375, 0, - 0, 0, 0, 0, 0, 0, 0, 360, 0, 392, - 0, 0, 0, 344, 341, 0, 0, 379, 0, 0, - 0, 346, 0, 361, 403, 0, 335, 108, 406, 412, - 376, 205, 414, 374, 373, 417, 144, 0, 160, 110, - 118, 84, 90, 0, 109, 136, 149, 153, 410, 358, - 366, 99, 364, 151, 140, 173, 391, 141, 150, 122, - 165, 145, 172, 206, 180, 162, 179, 186, 85, 161, - 597, 97, 154, 87, 169, 159, 128, 114, 115, 86, - 0, 148, 102, 106, 101, 137, 166, 167, 100, 189, - 91, 178, 89, 333, 177, 135, 164, 170, 129, 126, - 88, 168, 127, 125, 117, 104, 111, 142, 124, 143, - 112, 132, 131, 133, 0, 339, 0, 158, 175, 190, - 94, 354, 163, 181, 182, 183, 184, 185, 0, 0, - 95, 107, 334, 332, 113, 155, 116, 123, 147, 188, - 139, 152, 98, 174, 156, 350, 353, 348, 349, 387, - 388, 422, 423, 424, 404, 345, 0, 351, 352, 0, - 408, 390, 83, 0, 120, 187, 146, 105, 176, 416, - 407, 0, 378, 419, 356, 370, 427, 371, 372, 400, - 342, 386, 138, 368, 0, 359, 337, 365, 338, 357, - 380, 103, 383, 355, 409, 389, 418, 119, 425, 121, - 394, 0, 157, 130, 0, 0, 382, 411, 384, 405, - 377, 401, 347, 393, 420, 369, 398, 421, 0, 0, - 0, 81, 0, 0, 0, 0, 0, 0, 0, 0, - 96, 0, 396, 415, 367, 397, 399, 336, 395, 0, - 340, 343, 426, 413, 362, 363, 0, 0, 0, 0, - 0, 0, 0, 381, 385, 402, 375, 0, 0, 0, - 0, 0, 0, 0, 0, 360, 0, 392, 0, 0, - 0, 344, 341, 0, 0, 379, 0, 0, 0, 346, - 0, 361, 403, 0, 335, 108, 406, 412, 376, 205, - 414, 374, 373, 417, 144, 0, 160, 110, 118, 84, - 90, 0, 109, 136, 149, 153, 410, 358, 366, 99, - 364, 151, 140, 173, 391, 141, 150, 122, 165, 145, - 172, 206, 180, 162, 179, 186, 85, 161, 324, 97, - 154, 87, 169, 159, 128, 114, 115, 86, 0, 148, - 102, 106, 101, 137, 166, 167, 100, 189, 91, 178, - 89, 333, 177, 135, 164, 170, 129, 126, 88, 168, - 127, 125, 117, 104, 111, 142, 124, 143, 112, 132, - 131, 133, 0, 339, 0, 158, 175, 190, 94, 354, - 163, 181, 182, 183, 184, 185, 0, 0, 95, 107, - 334, 332, 327, 326, 116, 123, 147, 188, 139, 152, - 98, 174, 156, 350, 353, 348, 349, 387, 388, 422, - 423, 424, 404, 345, 0, 351, 352, 0, 408, 390, - 83, 0, 120, 187, 146, 105, 176, 138, 0, 0, - 0, 0, 264, 0, 0, 0, 103, 0, 261, 0, - 0, 0, 119, 304, 121, 0, 0, 157, 130, 0, - 0, 0, 0, 295, 296, 0, 0, 0, 0, 0, - 0, 820, 0, 54, 0, 0, 262, 283, 282, 285, - 286, 287, 288, 0, 0, 96, 284, 289, 290, 291, - 821, 0, 0, 259, 276, 0, 303, 0, 0, 0, + 0, 0, 0, 0, 818, 421, 412, 0, 383, 424, + 361, 375, 432, 376, 377, 405, 347, 391, 139, 373, + 0, 364, 342, 370, 343, 362, 385, 103, 388, 360, + 414, 394, 423, 120, 430, 122, 399, 0, 159, 131, + 0, 0, 387, 416, 389, 410, 382, 406, 352, 398, + 425, 374, 403, 426, 0, 0, 0, 81, 0, 839, + 840, 0, 0, 0, 0, 0, 96, 0, 401, 420, + 372, 402, 404, 341, 400, 0, 345, 348, 431, 418, + 367, 368, 1017, 0, 0, 0, 0, 0, 780, 386, + 390, 407, 380, 0, 0, 0, 0, 0, 0, 0, + 0, 365, 204, 397, 0, 0, 0, 349, 346, 0, + 0, 384, 0, 0, 0, 351, 0, 366, 408, 0, + 340, 109, 411, 417, 381, 207, 419, 379, 378, 422, + 146, 0, 162, 111, 119, 84, 90, 0, 110, 137, + 151, 155, 415, 363, 371, 99, 369, 153, 141, 175, + 396, 142, 152, 123, 167, 147, 174, 208, 182, 164, + 181, 188, 85, 163, 173, 97, 156, 87, 171, 161, + 129, 115, 116, 86, 0, 150, 102, 107, 101, 138, + 168, 169, 100, 191, 91, 180, 89, 92, 179, 136, + 166, 172, 130, 127, 88, 170, 128, 126, 118, 105, + 112, 144, 125, 145, 113, 133, 132, 134, 0, 344, + 0, 160, 177, 192, 94, 359, 165, 183, 184, 185, + 186, 187, 0, 0, 95, 108, 104, 143, 135, 93, + 114, 157, 117, 124, 149, 190, 140, 154, 98, 176, + 158, 355, 358, 353, 354, 392, 393, 427, 428, 429, + 409, 350, 0, 356, 357, 0, 413, 395, 83, 0, + 121, 189, 148, 106, 178, 421, 412, 0, 383, 424, + 361, 375, 432, 376, 377, 405, 347, 391, 139, 373, + 0, 364, 342, 370, 343, 362, 385, 103, 388, 360, + 414, 394, 423, 120, 430, 122, 399, 0, 159, 131, + 0, 0, 387, 416, 389, 410, 382, 406, 352, 398, + 425, 374, 403, 426, 0, 0, 0, 81, 0, 839, + 840, 0, 0, 0, 0, 0, 96, 0, 401, 420, + 372, 402, 404, 341, 400, 0, 345, 348, 431, 418, + 367, 368, 0, 0, 0, 0, 0, 0, 0, 386, + 390, 407, 380, 0, 0, 0, 0, 0, 0, 0, + 0, 365, 0, 397, 0, 0, 0, 349, 346, 0, + 0, 384, 0, 0, 0, 351, 0, 366, 408, 0, + 340, 109, 411, 417, 381, 207, 419, 379, 378, 422, + 146, 0, 162, 111, 119, 84, 90, 0, 110, 137, + 151, 155, 415, 363, 371, 99, 369, 153, 141, 175, + 396, 142, 152, 123, 167, 147, 174, 208, 182, 164, + 181, 188, 85, 163, 173, 97, 156, 87, 171, 161, + 129, 115, 116, 86, 0, 150, 102, 107, 101, 138, + 168, 169, 100, 191, 91, 180, 89, 92, 179, 136, + 166, 172, 130, 127, 88, 170, 128, 126, 118, 105, + 112, 144, 125, 145, 113, 133, 132, 134, 0, 344, + 0, 160, 177, 192, 94, 359, 165, 183, 184, 185, + 186, 187, 0, 0, 95, 108, 104, 143, 135, 93, + 114, 157, 117, 124, 149, 190, 140, 154, 98, 176, + 158, 355, 358, 353, 354, 392, 393, 427, 428, 429, + 409, 350, 0, 356, 357, 0, 413, 395, 83, 0, + 121, 189, 148, 106, 178, 421, 412, 0, 383, 424, + 361, 375, 432, 376, 377, 405, 347, 391, 139, 373, + 0, 364, 342, 370, 343, 362, 385, 103, 388, 360, + 414, 394, 423, 120, 430, 122, 399, 0, 159, 131, + 0, 0, 387, 416, 389, 410, 382, 406, 352, 398, + 425, 374, 403, 426, 54, 0, 0, 81, 0, 0, + 0, 0, 0, 0, 0, 0, 96, 0, 401, 420, + 372, 402, 404, 341, 400, 0, 345, 348, 431, 418, + 367, 368, 0, 0, 0, 0, 0, 0, 0, 386, + 390, 407, 380, 0, 0, 0, 0, 0, 0, 0, + 0, 365, 0, 397, 0, 0, 0, 349, 346, 0, + 0, 384, 0, 0, 0, 351, 0, 366, 408, 0, + 340, 109, 411, 417, 381, 207, 419, 379, 378, 422, + 146, 0, 162, 111, 119, 84, 90, 0, 110, 137, + 151, 155, 415, 363, 371, 99, 369, 153, 141, 175, + 396, 142, 152, 123, 167, 147, 174, 208, 182, 164, + 181, 188, 85, 163, 173, 97, 156, 87, 171, 161, + 129, 115, 116, 86, 0, 150, 102, 107, 101, 138, + 168, 169, 100, 191, 91, 180, 89, 92, 179, 136, + 166, 172, 130, 127, 88, 170, 128, 126, 118, 105, + 112, 144, 125, 145, 113, 133, 132, 134, 0, 344, + 0, 160, 177, 192, 94, 359, 165, 183, 184, 185, + 186, 187, 0, 0, 95, 108, 104, 143, 135, 93, + 114, 157, 117, 124, 149, 190, 140, 154, 98, 176, + 158, 355, 358, 353, 354, 392, 393, 427, 428, 429, + 409, 350, 0, 356, 357, 0, 413, 395, 83, 0, + 121, 189, 148, 106, 178, 421, 412, 0, 383, 424, + 361, 375, 432, 376, 377, 405, 347, 391, 139, 373, + 0, 364, 342, 370, 343, 362, 385, 103, 388, 360, + 414, 394, 423, 120, 430, 122, 399, 0, 159, 131, + 0, 0, 387, 416, 389, 410, 382, 406, 352, 398, + 425, 374, 403, 426, 0, 0, 0, 81, 0, 0, + 0, 0, 0, 0, 0, 0, 96, 0, 401, 420, + 372, 402, 404, 341, 400, 0, 345, 348, 431, 418, + 367, 368, 0, 0, 0, 0, 0, 0, 0, 386, + 390, 407, 380, 0, 0, 0, 0, 0, 0, 1084, + 0, 365, 0, 397, 0, 0, 0, 349, 346, 0, + 0, 384, 0, 0, 0, 351, 0, 366, 408, 0, + 340, 109, 411, 417, 381, 207, 419, 379, 378, 422, + 146, 0, 162, 111, 119, 84, 90, 0, 110, 137, + 151, 155, 415, 363, 371, 99, 369, 153, 141, 175, + 396, 142, 152, 123, 167, 147, 174, 208, 182, 164, + 181, 188, 85, 163, 173, 97, 156, 87, 171, 161, + 129, 115, 116, 86, 0, 150, 102, 107, 101, 138, + 168, 169, 100, 191, 91, 180, 89, 92, 179, 136, + 166, 172, 130, 127, 88, 170, 128, 126, 118, 105, + 112, 144, 125, 145, 113, 133, 132, 134, 0, 344, + 0, 160, 177, 192, 94, 359, 165, 183, 184, 185, + 186, 187, 0, 0, 95, 108, 104, 143, 135, 93, + 114, 157, 117, 124, 149, 190, 140, 154, 98, 176, + 158, 355, 358, 353, 354, 392, 393, 427, 428, 429, + 409, 350, 0, 356, 357, 0, 413, 395, 83, 0, + 121, 189, 148, 106, 178, 421, 412, 0, 383, 424, + 361, 375, 432, 376, 377, 405, 347, 391, 139, 373, + 0, 364, 342, 370, 343, 362, 385, 103, 388, 360, + 414, 394, 423, 120, 430, 122, 399, 0, 159, 131, + 0, 0, 387, 416, 389, 410, 382, 406, 352, 398, + 425, 374, 403, 426, 0, 0, 0, 267, 0, 0, + 0, 0, 0, 0, 0, 0, 96, 0, 401, 420, + 372, 402, 404, 341, 400, 0, 345, 348, 431, 418, + 367, 368, 0, 0, 0, 0, 0, 0, 0, 386, + 390, 407, 380, 0, 0, 0, 0, 0, 0, 730, + 0, 365, 0, 397, 0, 0, 0, 349, 346, 0, + 0, 384, 0, 0, 0, 351, 0, 366, 408, 0, + 340, 109, 411, 417, 381, 207, 419, 379, 378, 422, + 146, 0, 162, 111, 119, 84, 90, 0, 110, 137, + 151, 155, 415, 363, 371, 99, 369, 153, 141, 175, + 396, 142, 152, 123, 167, 147, 174, 208, 182, 164, + 181, 188, 85, 163, 173, 97, 156, 87, 171, 161, + 129, 115, 116, 86, 0, 150, 102, 107, 101, 138, + 168, 169, 100, 191, 91, 180, 89, 92, 179, 136, + 166, 172, 130, 127, 88, 170, 128, 126, 118, 105, + 112, 144, 125, 145, 113, 133, 132, 134, 0, 344, + 0, 160, 177, 192, 94, 359, 165, 183, 184, 185, + 186, 187, 0, 0, 95, 108, 104, 143, 135, 93, + 114, 157, 117, 124, 149, 190, 140, 154, 98, 176, + 158, 355, 358, 353, 354, 392, 393, 427, 428, 429, + 409, 350, 0, 356, 357, 0, 413, 395, 83, 0, + 121, 189, 148, 106, 178, 421, 412, 0, 383, 424, + 361, 375, 432, 376, 377, 405, 347, 391, 139, 373, + 0, 364, 342, 370, 343, 362, 385, 103, 388, 360, + 414, 394, 423, 120, 430, 122, 399, 0, 159, 131, + 0, 0, 387, 416, 389, 410, 382, 406, 352, 398, + 425, 374, 403, 426, 0, 0, 0, 81, 0, 0, + 0, 0, 0, 0, 0, 0, 96, 0, 401, 420, + 372, 402, 404, 341, 400, 0, 345, 348, 431, 418, + 367, 368, 0, 0, 0, 0, 0, 0, 0, 386, + 390, 407, 380, 0, 0, 0, 0, 0, 0, 0, + 0, 365, 0, 397, 0, 0, 0, 349, 346, 0, + 0, 384, 0, 0, 0, 351, 0, 366, 408, 0, + 340, 109, 411, 417, 381, 207, 419, 379, 378, 422, + 146, 0, 162, 111, 119, 84, 90, 0, 110, 137, + 151, 155, 415, 363, 371, 99, 369, 153, 141, 175, + 396, 142, 152, 123, 167, 147, 174, 208, 182, 164, + 181, 188, 85, 163, 173, 97, 156, 87, 171, 161, + 129, 115, 116, 86, 0, 150, 102, 107, 101, 138, + 168, 169, 100, 191, 91, 180, 89, 92, 179, 136, + 166, 172, 130, 127, 88, 170, 128, 126, 118, 105, + 112, 144, 125, 145, 113, 133, 132, 134, 0, 344, + 0, 160, 177, 192, 94, 359, 165, 183, 184, 185, + 186, 187, 0, 0, 95, 108, 104, 143, 135, 93, + 114, 157, 117, 124, 149, 190, 140, 154, 98, 176, + 158, 355, 358, 353, 354, 392, 393, 427, 428, 429, + 409, 350, 0, 356, 357, 0, 413, 395, 83, 0, + 121, 189, 148, 106, 178, 421, 412, 0, 383, 424, + 361, 375, 432, 376, 377, 405, 347, 391, 139, 373, + 0, 364, 342, 370, 343, 362, 385, 103, 388, 360, + 414, 394, 423, 120, 430, 122, 399, 0, 159, 131, + 0, 0, 387, 416, 389, 410, 382, 406, 352, 398, + 425, 374, 403, 426, 0, 0, 0, 267, 0, 0, + 0, 0, 0, 0, 0, 0, 96, 0, 401, 420, + 372, 402, 404, 341, 400, 0, 345, 348, 431, 418, + 367, 368, 0, 0, 0, 0, 0, 0, 0, 386, + 390, 407, 380, 0, 0, 0, 0, 0, 0, 0, + 0, 365, 0, 397, 0, 0, 0, 349, 346, 0, + 0, 384, 0, 0, 0, 351, 0, 366, 408, 0, + 340, 109, 411, 417, 381, 207, 419, 379, 378, 422, + 146, 0, 162, 111, 119, 84, 90, 0, 110, 137, + 151, 155, 415, 363, 371, 99, 369, 153, 141, 175, + 396, 142, 152, 123, 167, 147, 174, 208, 182, 164, + 181, 188, 85, 163, 173, 97, 156, 87, 171, 161, + 129, 115, 116, 86, 0, 150, 102, 107, 101, 138, + 168, 169, 100, 191, 91, 180, 89, 92, 179, 136, + 166, 172, 130, 127, 88, 170, 128, 126, 118, 105, + 112, 144, 125, 145, 113, 133, 132, 134, 0, 344, + 0, 160, 177, 192, 94, 359, 165, 183, 184, 185, + 186, 187, 0, 0, 95, 108, 104, 143, 135, 93, + 114, 157, 117, 124, 149, 190, 140, 154, 98, 176, + 158, 355, 358, 353, 354, 392, 393, 427, 428, 429, + 409, 350, 0, 356, 357, 0, 413, 395, 83, 0, + 121, 189, 148, 106, 178, 421, 412, 0, 383, 424, + 361, 375, 432, 376, 377, 405, 347, 391, 139, 373, + 0, 364, 342, 370, 343, 362, 385, 103, 388, 360, + 414, 394, 423, 120, 430, 122, 399, 0, 159, 131, + 0, 0, 387, 416, 389, 410, 382, 406, 352, 398, + 425, 374, 403, 426, 0, 0, 0, 81, 0, 0, + 0, 0, 0, 0, 0, 0, 96, 0, 401, 420, + 372, 402, 404, 341, 400, 0, 345, 348, 431, 418, + 367, 368, 0, 0, 0, 0, 0, 0, 0, 386, + 390, 407, 380, 0, 0, 0, 0, 0, 0, 0, + 0, 365, 0, 397, 0, 0, 0, 349, 346, 0, + 0, 384, 0, 0, 0, 351, 0, 366, 408, 0, + 340, 109, 411, 417, 381, 207, 419, 379, 378, 422, + 146, 0, 162, 111, 119, 84, 90, 0, 110, 137, + 151, 155, 415, 363, 371, 99, 369, 153, 141, 175, + 396, 142, 152, 123, 167, 147, 174, 208, 182, 164, + 181, 188, 85, 163, 173, 97, 156, 87, 171, 161, + 129, 115, 116, 86, 0, 150, 102, 107, 101, 138, + 168, 169, 100, 191, 91, 180, 89, 338, 179, 136, + 166, 172, 130, 127, 88, 170, 128, 126, 118, 105, + 112, 144, 125, 145, 113, 133, 132, 134, 0, 344, + 0, 160, 177, 192, 94, 359, 165, 183, 184, 185, + 186, 187, 0, 0, 95, 108, 104, 143, 339, 337, + 114, 157, 117, 124, 149, 190, 140, 154, 98, 176, + 158, 355, 358, 353, 354, 392, 393, 427, 428, 429, + 409, 350, 0, 356, 357, 0, 413, 395, 83, 0, + 121, 189, 148, 106, 178, 421, 412, 0, 383, 424, + 361, 375, 432, 376, 377, 405, 347, 391, 139, 373, + 0, 364, 342, 370, 343, 362, 385, 103, 388, 360, + 414, 394, 423, 120, 430, 122, 399, 0, 159, 131, + 0, 0, 387, 416, 389, 410, 382, 406, 352, 398, + 425, 374, 403, 426, 0, 0, 0, 205, 0, 0, + 0, 0, 0, 0, 0, 0, 96, 0, 401, 420, + 372, 402, 404, 341, 400, 0, 345, 348, 431, 418, + 367, 368, 0, 0, 0, 0, 0, 0, 0, 386, + 390, 407, 380, 0, 0, 0, 0, 0, 0, 0, + 0, 365, 0, 397, 0, 0, 0, 349, 346, 0, + 0, 384, 0, 0, 0, 351, 0, 366, 408, 0, + 340, 109, 411, 417, 381, 207, 419, 379, 378, 422, + 146, 0, 162, 111, 119, 84, 90, 0, 110, 137, + 151, 155, 415, 363, 371, 99, 369, 153, 141, 175, + 396, 142, 152, 123, 167, 147, 174, 208, 182, 164, + 181, 188, 85, 163, 173, 97, 156, 87, 171, 161, + 129, 115, 116, 86, 0, 150, 102, 107, 101, 138, + 168, 169, 100, 191, 91, 180, 89, 92, 179, 136, + 166, 172, 130, 127, 88, 170, 128, 126, 118, 105, + 112, 144, 125, 145, 113, 133, 132, 134, 0, 344, + 0, 160, 177, 192, 94, 359, 165, 183, 184, 185, + 186, 187, 0, 0, 95, 108, 104, 143, 135, 93, + 114, 157, 117, 124, 149, 190, 140, 154, 98, 176, + 158, 355, 358, 353, 354, 392, 393, 427, 428, 429, + 409, 350, 0, 356, 357, 0, 413, 395, 83, 0, + 121, 189, 148, 106, 178, 421, 412, 0, 383, 424, + 361, 375, 432, 376, 377, 405, 347, 391, 139, 373, + 0, 364, 342, 370, 343, 362, 385, 103, 388, 360, + 414, 394, 423, 120, 430, 122, 399, 0, 159, 131, + 0, 0, 387, 416, 389, 410, 382, 406, 352, 398, + 425, 374, 403, 426, 0, 0, 0, 81, 0, 0, + 0, 0, 0, 0, 0, 0, 96, 0, 401, 420, + 372, 402, 404, 341, 400, 0, 345, 348, 431, 418, + 367, 368, 0, 0, 0, 0, 0, 0, 0, 386, + 390, 407, 380, 0, 0, 0, 0, 0, 0, 0, + 0, 365, 0, 397, 0, 0, 0, 349, 346, 0, + 0, 384, 0, 0, 0, 351, 0, 366, 408, 0, + 340, 109, 411, 417, 381, 207, 419, 379, 378, 422, + 146, 0, 162, 111, 119, 84, 90, 0, 110, 137, + 151, 155, 415, 363, 371, 99, 369, 153, 141, 175, + 396, 142, 152, 123, 167, 147, 174, 208, 182, 164, + 181, 188, 85, 163, 604, 97, 156, 87, 171, 161, + 129, 115, 116, 86, 0, 150, 102, 107, 101, 138, + 168, 169, 100, 191, 91, 180, 89, 338, 179, 136, + 166, 172, 130, 127, 88, 170, 128, 126, 118, 105, + 112, 144, 125, 145, 113, 133, 132, 134, 0, 344, + 0, 160, 177, 192, 94, 359, 165, 183, 184, 185, + 186, 187, 0, 0, 95, 108, 104, 143, 339, 337, + 114, 157, 117, 124, 149, 190, 140, 154, 98, 176, + 158, 355, 358, 353, 354, 392, 393, 427, 428, 429, + 409, 350, 0, 356, 357, 0, 413, 395, 83, 0, + 121, 189, 148, 106, 178, 421, 412, 0, 383, 424, + 361, 375, 432, 376, 377, 405, 347, 391, 139, 373, + 0, 364, 342, 370, 343, 362, 385, 103, 388, 360, + 414, 394, 423, 120, 430, 122, 399, 0, 159, 131, + 0, 0, 387, 416, 389, 410, 382, 406, 352, 398, + 425, 374, 403, 426, 0, 0, 0, 81, 0, 0, + 0, 0, 0, 0, 0, 0, 96, 0, 401, 420, + 372, 402, 404, 341, 400, 0, 345, 348, 431, 418, + 367, 368, 0, 0, 0, 0, 0, 0, 0, 386, + 390, 407, 380, 0, 0, 0, 0, 0, 0, 0, + 0, 365, 0, 397, 0, 0, 0, 349, 346, 0, + 0, 384, 0, 0, 0, 351, 0, 366, 408, 0, + 340, 109, 411, 417, 381, 207, 419, 379, 378, 422, + 146, 0, 162, 111, 119, 84, 90, 0, 110, 137, + 151, 155, 415, 363, 371, 99, 369, 153, 141, 175, + 396, 142, 152, 123, 167, 147, 174, 208, 182, 164, + 181, 188, 85, 163, 329, 97, 156, 87, 171, 161, + 129, 115, 116, 86, 0, 150, 102, 107, 101, 138, + 168, 169, 100, 191, 91, 180, 89, 338, 179, 136, + 166, 172, 130, 127, 88, 170, 128, 126, 118, 105, + 112, 144, 125, 145, 113, 133, 132, 134, 0, 344, + 0, 160, 177, 192, 94, 359, 165, 183, 184, 185, + 186, 187, 0, 0, 95, 108, 104, 143, 339, 337, + 332, 331, 117, 124, 149, 190, 140, 154, 98, 176, + 158, 355, 358, 353, 354, 392, 393, 427, 428, 429, + 409, 350, 0, 356, 357, 0, 413, 395, 83, 0, + 121, 189, 148, 106, 178, 139, 0, 0, 0, 0, + 269, 0, 0, 0, 103, 0, 266, 0, 0, 0, + 120, 309, 122, 0, 0, 159, 131, 0, 0, 0, + 0, 300, 301, 0, 0, 0, 0, 0, 0, 830, + 0, 54, 0, 0, 267, 288, 287, 290, 291, 292, + 293, 0, 0, 96, 289, 294, 295, 296, 831, 0, + 0, 264, 281, 0, 308, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 273, 274, 0, 0, - 0, 0, 315, 0, 275, 0, 0, 270, 271, 272, - 277, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 108, 0, 0, 0, 205, 0, 0, 313, 0, 144, - 0, 160, 110, 118, 84, 90, 0, 109, 136, 149, - 153, 0, 0, 0, 99, 0, 151, 140, 173, 0, - 141, 150, 122, 165, 145, 172, 206, 180, 162, 179, - 186, 85, 161, 171, 97, 154, 87, 169, 159, 128, - 114, 115, 86, 0, 148, 102, 106, 101, 137, 166, - 167, 100, 189, 91, 178, 89, 92, 177, 135, 164, - 170, 129, 126, 88, 168, 127, 125, 117, 104, 111, - 142, 124, 143, 112, 132, 131, 133, 0, 0, 0, - 158, 175, 190, 94, 0, 163, 181, 182, 183, 184, - 185, 0, 0, 95, 107, 134, 93, 113, 155, 116, - 123, 147, 188, 139, 152, 98, 174, 156, 305, 314, - 311, 312, 309, 310, 308, 307, 306, 316, 297, 298, - 299, 300, 302, 0, 301, 83, 0, 120, 187, 146, - 105, 176, 138, 0, 0, 758, 0, 264, 0, 0, - 0, 103, 0, 261, 0, 0, 0, 119, 304, 121, - 0, 0, 157, 130, 0, 0, 0, 0, 295, 296, + 0, 0, 0, 0, 278, 279, 0, 0, 0, 0, + 320, 0, 280, 0, 0, 275, 276, 277, 282, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 109, 0, + 0, 0, 207, 0, 0, 318, 0, 146, 0, 162, + 111, 119, 84, 90, 0, 110, 137, 151, 155, 0, + 0, 0, 99, 0, 153, 141, 175, 0, 142, 152, + 123, 167, 147, 174, 208, 182, 164, 181, 188, 85, + 163, 173, 97, 156, 87, 171, 161, 129, 115, 116, + 86, 0, 150, 102, 107, 101, 138, 168, 169, 100, + 191, 91, 180, 89, 92, 179, 136, 166, 172, 130, + 127, 88, 170, 128, 126, 118, 105, 112, 144, 125, + 145, 113, 133, 132, 134, 0, 0, 0, 160, 177, + 192, 94, 0, 165, 183, 184, 185, 186, 187, 0, + 0, 95, 108, 104, 143, 135, 93, 114, 157, 117, + 124, 149, 190, 140, 154, 98, 176, 158, 310, 319, + 316, 317, 314, 315, 313, 312, 311, 321, 302, 303, + 304, 305, 307, 0, 306, 83, 0, 121, 189, 148, + 106, 178, 139, 0, 0, 766, 0, 269, 0, 0, + 0, 103, 0, 266, 0, 0, 0, 120, 309, 122, + 0, 0, 159, 131, 0, 0, 0, 0, 300, 301, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, - 0, 262, 283, 282, 285, 286, 287, 288, 0, 0, - 96, 284, 289, 290, 291, 0, 0, 0, 259, 276, - 0, 303, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 267, 288, 287, 290, 291, 292, 293, 0, 0, + 96, 289, 294, 295, 296, 0, 0, 0, 264, 281, + 0, 308, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 273, 274, 255, 0, 0, 0, 315, 0, 275, - 0, 0, 270, 271, 272, 277, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 108, 0, 0, 0, 205, - 0, 0, 313, 0, 144, 0, 160, 110, 118, 84, - 90, 0, 109, 136, 149, 153, 0, 0, 0, 99, - 0, 151, 140, 173, 0, 141, 150, 122, 165, 145, - 172, 206, 180, 162, 179, 186, 85, 161, 171, 97, - 154, 87, 169, 159, 128, 114, 115, 86, 0, 148, - 102, 106, 101, 137, 166, 167, 100, 189, 91, 178, - 89, 92, 177, 135, 164, 170, 129, 126, 88, 168, - 127, 125, 117, 104, 111, 142, 124, 143, 112, 132, - 131, 133, 0, 0, 0, 158, 175, 190, 94, 0, - 163, 181, 182, 183, 184, 185, 0, 0, 95, 107, - 134, 93, 113, 155, 116, 123, 147, 188, 139, 152, - 98, 174, 156, 305, 314, 311, 312, 309, 310, 308, - 307, 306, 316, 297, 298, 299, 300, 302, 0, 301, - 83, 0, 120, 187, 146, 105, 176, 138, 0, 0, - 0, 0, 264, 0, 0, 0, 103, 0, 261, 0, - 0, 0, 119, 304, 121, 0, 0, 157, 130, 0, - 0, 0, 0, 295, 296, 0, 0, 0, 0, 0, - 0, 0, 0, 54, 0, 482, 262, 283, 282, 285, - 286, 287, 288, 0, 0, 96, 284, 289, 290, 291, - 0, 0, 0, 259, 276, 0, 303, 0, 0, 0, + 0, 278, 279, 260, 0, 0, 0, 320, 0, 280, + 0, 0, 275, 276, 277, 282, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 109, 0, 0, 0, 207, + 0, 0, 318, 0, 146, 0, 162, 111, 119, 84, + 90, 0, 110, 137, 151, 155, 0, 0, 0, 99, + 0, 153, 141, 175, 0, 142, 152, 123, 167, 147, + 174, 208, 182, 164, 181, 188, 85, 163, 173, 97, + 156, 87, 171, 161, 129, 115, 116, 86, 0, 150, + 102, 107, 101, 138, 168, 169, 100, 191, 91, 180, + 89, 92, 179, 136, 166, 172, 130, 127, 88, 170, + 128, 126, 118, 105, 112, 144, 125, 145, 113, 133, + 132, 134, 0, 0, 0, 160, 177, 192, 94, 0, + 165, 183, 184, 185, 186, 187, 0, 0, 95, 108, + 104, 143, 135, 93, 114, 157, 117, 124, 149, 190, + 140, 154, 98, 176, 158, 310, 319, 316, 317, 314, + 315, 313, 312, 311, 321, 302, 303, 304, 305, 307, + 0, 306, 83, 0, 121, 189, 148, 106, 178, 139, + 0, 0, 0, 0, 269, 0, 0, 0, 103, 0, + 266, 0, 0, 0, 120, 309, 122, 0, 0, 159, + 131, 0, 0, 0, 0, 300, 301, 0, 0, 0, + 0, 0, 0, 0, 0, 54, 0, 489, 267, 288, + 287, 290, 291, 292, 293, 0, 0, 96, 289, 294, + 295, 296, 0, 0, 0, 264, 281, 0, 308, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 273, 274, 0, 0, - 0, 0, 315, 0, 275, 0, 0, 270, 271, 272, - 277, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 108, 0, 0, 0, 205, 0, 0, 313, 0, 144, - 0, 160, 110, 118, 84, 90, 0, 109, 136, 149, - 153, 0, 0, 0, 99, 0, 151, 140, 173, 0, - 141, 150, 122, 165, 145, 172, 206, 180, 162, 179, - 186, 85, 161, 171, 97, 154, 87, 169, 159, 128, - 114, 115, 86, 0, 148, 102, 106, 101, 137, 166, - 167, 100, 189, 91, 178, 89, 92, 177, 135, 164, - 170, 129, 126, 88, 168, 127, 125, 117, 104, 111, - 142, 124, 143, 112, 132, 131, 133, 0, 0, 0, - 158, 175, 190, 94, 0, 163, 181, 182, 183, 184, - 185, 0, 0, 95, 107, 134, 93, 113, 155, 116, - 123, 147, 188, 139, 152, 98, 174, 156, 305, 314, - 311, 312, 309, 310, 308, 307, 306, 316, 297, 298, - 299, 300, 302, 0, 301, 83, 0, 120, 187, 146, - 105, 176, 138, 0, 0, 0, 0, 264, 0, 0, - 0, 103, 0, 261, 0, 0, 0, 119, 304, 121, - 0, 0, 157, 130, 0, 0, 0, 0, 295, 296, - 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, - 0, 262, 283, 282, 285, 286, 287, 288, 0, 0, - 96, 284, 289, 290, 291, 0, 0, 0, 259, 276, - 0, 303, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 278, 279, + 0, 0, 0, 0, 320, 0, 280, 0, 0, 275, + 276, 277, 282, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 109, 0, 0, 0, 207, 0, 0, 318, + 0, 146, 0, 162, 111, 119, 84, 90, 0, 110, + 137, 151, 155, 0, 0, 0, 99, 0, 153, 141, + 175, 0, 142, 152, 123, 167, 147, 174, 208, 182, + 164, 181, 188, 85, 163, 173, 97, 156, 87, 171, + 161, 129, 115, 116, 86, 0, 150, 102, 107, 101, + 138, 168, 169, 100, 191, 91, 180, 89, 92, 179, + 136, 166, 172, 130, 127, 88, 170, 128, 126, 118, + 105, 112, 144, 125, 145, 113, 133, 132, 134, 0, + 0, 0, 160, 177, 192, 94, 0, 165, 183, 184, + 185, 186, 187, 0, 0, 95, 108, 104, 143, 135, + 93, 114, 157, 117, 124, 149, 190, 140, 154, 98, + 176, 158, 310, 319, 316, 317, 314, 315, 313, 312, + 311, 321, 302, 303, 304, 305, 307, 0, 306, 83, + 0, 121, 189, 148, 106, 178, 139, 0, 0, 0, + 0, 269, 0, 0, 0, 103, 0, 266, 0, 0, + 0, 120, 309, 122, 0, 0, 159, 131, 0, 0, + 0, 0, 300, 301, 0, 0, 0, 0, 0, 0, + 0, 0, 54, 0, 0, 267, 288, 287, 290, 291, + 292, 293, 0, 0, 96, 289, 294, 295, 296, 0, + 0, 0, 264, 281, 0, 308, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 273, 274, 255, 0, 0, 0, 315, 0, 275, - 0, 0, 270, 271, 272, 277, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 108, 0, 0, 0, 205, - 0, 0, 313, 0, 144, 0, 160, 110, 118, 84, - 90, 0, 109, 136, 149, 153, 0, 0, 0, 99, - 0, 151, 140, 173, 0, 141, 150, 122, 165, 145, - 172, 206, 180, 162, 179, 186, 85, 161, 171, 97, - 154, 87, 169, 159, 128, 114, 115, 86, 0, 148, - 102, 106, 101, 137, 166, 167, 100, 189, 91, 178, - 89, 92, 177, 135, 164, 170, 129, 126, 88, 168, - 127, 125, 117, 104, 111, 142, 124, 143, 112, 132, - 131, 133, 0, 0, 0, 158, 175, 190, 94, 0, - 163, 181, 182, 183, 184, 185, 0, 0, 95, 107, - 134, 93, 113, 155, 116, 123, 147, 188, 139, 152, - 98, 174, 156, 305, 314, 311, 312, 309, 310, 308, - 307, 306, 316, 297, 298, 299, 300, 302, 24, 301, - 83, 0, 120, 187, 146, 105, 176, 0, 0, 0, - 138, 0, 0, 0, 0, 264, 0, 0, 0, 103, - 0, 261, 0, 0, 0, 119, 304, 121, 0, 0, - 157, 130, 0, 0, 0, 0, 295, 296, 0, 0, - 0, 0, 0, 0, 0, 0, 54, 0, 0, 262, - 283, 282, 285, 286, 287, 288, 0, 0, 96, 284, - 289, 290, 291, 0, 0, 0, 259, 276, 0, 303, + 0, 0, 0, 0, 0, 278, 279, 260, 0, 0, + 0, 320, 0, 280, 0, 0, 275, 276, 277, 282, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 109, + 0, 0, 0, 207, 0, 0, 318, 0, 146, 0, + 162, 111, 119, 84, 90, 0, 110, 137, 151, 155, + 0, 0, 0, 99, 0, 153, 141, 175, 0, 142, + 152, 123, 167, 147, 174, 208, 182, 164, 181, 188, + 85, 163, 173, 97, 156, 87, 171, 161, 129, 115, + 116, 86, 0, 150, 102, 107, 101, 138, 168, 169, + 100, 191, 91, 180, 89, 92, 179, 136, 166, 172, + 130, 127, 88, 170, 128, 126, 118, 105, 112, 144, + 125, 145, 113, 133, 132, 134, 0, 0, 0, 160, + 177, 192, 94, 0, 165, 183, 184, 185, 186, 187, + 0, 0, 95, 108, 104, 143, 135, 93, 114, 157, + 117, 124, 149, 190, 140, 154, 98, 176, 158, 310, + 319, 316, 317, 314, 315, 313, 312, 311, 321, 302, + 303, 304, 305, 307, 24, 306, 83, 0, 121, 189, + 148, 106, 178, 0, 0, 0, 139, 0, 0, 0, + 0, 269, 0, 0, 0, 103, 0, 266, 0, 0, + 0, 120, 309, 122, 0, 0, 159, 131, 0, 0, + 0, 0, 300, 301, 0, 0, 0, 0, 0, 0, + 0, 0, 54, 0, 0, 267, 288, 287, 290, 291, + 292, 293, 0, 0, 96, 289, 294, 295, 296, 0, + 0, 0, 264, 281, 0, 308, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 273, - 274, 0, 0, 0, 0, 315, 0, 275, 0, 0, - 270, 271, 272, 277, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 108, 0, 0, 0, 205, 0, 0, - 313, 0, 144, 0, 160, 110, 118, 84, 90, 0, - 109, 136, 149, 153, 0, 0, 0, 99, 0, 151, - 140, 173, 0, 141, 150, 122, 165, 145, 172, 206, - 180, 162, 179, 186, 85, 161, 171, 97, 154, 87, - 169, 159, 128, 114, 115, 86, 0, 148, 102, 106, - 101, 137, 166, 167, 100, 189, 91, 178, 89, 92, - 177, 135, 164, 170, 129, 126, 88, 168, 127, 125, - 117, 104, 111, 142, 124, 143, 112, 132, 131, 133, - 0, 0, 0, 158, 175, 190, 94, 0, 163, 181, - 182, 183, 184, 185, 0, 0, 95, 107, 134, 93, - 113, 155, 116, 123, 147, 188, 139, 152, 98, 174, - 156, 305, 314, 311, 312, 309, 310, 308, 307, 306, - 316, 297, 298, 299, 300, 302, 0, 301, 83, 0, - 120, 187, 146, 105, 176, 138, 0, 0, 0, 0, - 264, 0, 0, 0, 103, 0, 261, 0, 0, 0, - 119, 304, 121, 0, 0, 157, 130, 0, 0, 0, - 0, 295, 296, 0, 0, 0, 0, 0, 0, 0, - 0, 54, 0, 0, 262, 283, 282, 285, 286, 287, - 288, 0, 0, 96, 284, 289, 290, 291, 0, 0, - 0, 259, 276, 0, 303, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 278, 279, 0, 0, 0, + 0, 320, 0, 280, 0, 0, 275, 276, 277, 282, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 109, + 0, 0, 0, 207, 0, 0, 318, 0, 146, 0, + 162, 111, 119, 84, 90, 0, 110, 137, 151, 155, + 0, 0, 0, 99, 0, 153, 141, 175, 0, 142, + 152, 123, 167, 147, 174, 208, 182, 164, 181, 188, + 85, 163, 173, 97, 156, 87, 171, 161, 129, 115, + 116, 86, 0, 150, 102, 107, 101, 138, 168, 169, + 100, 191, 91, 180, 89, 92, 179, 136, 166, 172, + 130, 127, 88, 170, 128, 126, 118, 105, 112, 144, + 125, 145, 113, 133, 132, 134, 0, 0, 0, 160, + 177, 192, 94, 0, 165, 183, 184, 185, 186, 187, + 0, 0, 95, 108, 104, 143, 135, 93, 114, 157, + 117, 124, 149, 190, 140, 154, 98, 176, 158, 310, + 319, 316, 317, 314, 315, 313, 312, 311, 321, 302, + 303, 304, 305, 307, 0, 306, 83, 0, 121, 189, + 148, 106, 178, 139, 0, 0, 0, 0, 269, 0, + 0, 0, 103, 0, 266, 0, 0, 0, 120, 309, + 122, 0, 0, 159, 131, 0, 0, 0, 0, 300, + 301, 0, 0, 0, 0, 0, 0, 0, 0, 54, + 0, 0, 267, 288, 287, 290, 291, 292, 293, 0, + 0, 96, 289, 294, 295, 296, 0, 0, 0, 264, + 281, 0, 308, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 273, 274, 0, 0, 0, 0, - 315, 0, 275, 0, 0, 270, 271, 272, 277, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 108, 0, - 0, 0, 205, 0, 0, 313, 0, 144, 0, 160, - 110, 118, 84, 90, 0, 109, 136, 149, 153, 0, - 0, 0, 99, 0, 151, 140, 173, 0, 141, 150, - 122, 165, 145, 172, 206, 180, 162, 179, 186, 85, - 161, 171, 97, 154, 87, 169, 159, 128, 114, 115, - 86, 0, 148, 102, 106, 101, 137, 166, 167, 100, - 189, 91, 178, 89, 92, 177, 135, 164, 170, 129, - 126, 88, 168, 127, 125, 117, 104, 111, 142, 124, - 143, 112, 132, 131, 133, 0, 0, 0, 158, 175, - 190, 94, 0, 163, 181, 182, 183, 184, 185, 0, - 0, 95, 107, 134, 93, 113, 155, 116, 123, 147, - 188, 139, 152, 98, 174, 156, 305, 314, 311, 312, - 309, 310, 308, 307, 306, 316, 297, 298, 299, 300, - 302, 138, 301, 83, 0, 120, 187, 146, 105, 176, - 103, 0, 0, 0, 0, 0, 119, 304, 121, 0, - 0, 157, 130, 0, 0, 0, 0, 295, 296, 0, + 0, 0, 278, 279, 0, 0, 0, 0, 320, 0, + 280, 0, 0, 275, 276, 277, 282, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 109, 0, 0, 0, + 207, 0, 0, 318, 0, 146, 0, 162, 111, 119, + 84, 90, 0, 110, 137, 151, 155, 0, 0, 0, + 99, 0, 153, 141, 175, 0, 142, 152, 123, 167, + 147, 174, 208, 182, 164, 181, 188, 85, 163, 173, + 97, 156, 87, 171, 161, 129, 115, 116, 86, 0, + 150, 102, 107, 101, 138, 168, 169, 100, 191, 91, + 180, 89, 92, 179, 136, 166, 172, 130, 127, 88, + 170, 128, 126, 118, 105, 112, 144, 125, 145, 113, + 133, 132, 134, 0, 0, 0, 160, 177, 192, 94, + 0, 165, 183, 184, 185, 186, 187, 0, 0, 95, + 108, 104, 143, 135, 93, 114, 157, 117, 124, 149, + 190, 140, 154, 98, 176, 158, 310, 319, 316, 317, + 314, 315, 313, 312, 311, 321, 302, 303, 304, 305, + 307, 139, 306, 83, 0, 121, 189, 148, 106, 178, + 103, 0, 0, 0, 0, 0, 120, 309, 122, 0, + 0, 159, 131, 0, 0, 0, 0, 300, 301, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, - 262, 283, 282, 285, 286, 287, 288, 0, 0, 96, - 284, 289, 290, 291, 0, 0, 0, 0, 276, 0, - 303, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 273, 274, 0, 0, 0, 0, 315, 0, 275, 0, - 0, 270, 271, 272, 277, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 108, 0, 0, 0, 205, 0, - 0, 313, 0, 144, 0, 160, 110, 118, 84, 90, - 0, 109, 136, 149, 153, 0, 0, 0, 99, 0, - 151, 140, 173, 1359, 141, 150, 122, 165, 145, 172, - 206, 180, 162, 179, 186, 85, 161, 171, 97, 154, - 87, 169, 159, 128, 114, 115, 86, 0, 148, 102, - 106, 101, 137, 166, 167, 100, 189, 91, 178, 89, - 92, 177, 135, 164, 170, 129, 126, 88, 168, 127, - 125, 117, 104, 111, 142, 124, 143, 112, 132, 131, - 133, 0, 0, 0, 158, 175, 190, 94, 0, 163, - 181, 182, 183, 184, 185, 0, 0, 95, 107, 134, - 93, 113, 155, 116, 123, 147, 188, 139, 152, 98, - 174, 156, 305, 314, 311, 312, 309, 310, 308, 307, - 306, 316, 297, 298, 299, 300, 302, 138, 301, 83, - 0, 120, 187, 146, 105, 176, 103, 0, 0, 0, - 0, 0, 119, 304, 121, 0, 0, 157, 130, 0, - 0, 0, 0, 295, 296, 0, 0, 0, 0, 0, - 0, 0, 0, 54, 0, 0, 262, 283, 282, 285, - 286, 287, 288, 0, 0, 96, 284, 289, 290, 291, - 0, 0, 0, 0, 276, 0, 303, 0, 0, 0, + 267, 288, 287, 290, 291, 292, 293, 0, 0, 96, + 289, 294, 295, 296, 0, 0, 0, 0, 281, 0, + 308, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 273, 274, 0, 0, - 0, 0, 315, 0, 275, 0, 0, 270, 271, 272, - 277, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 108, 0, 0, 0, 205, 0, 0, 313, 0, 144, - 0, 160, 110, 118, 84, 90, 0, 109, 136, 149, - 153, 0, 0, 0, 99, 0, 151, 140, 173, 0, - 141, 150, 122, 165, 145, 172, 206, 180, 162, 179, - 186, 85, 161, 171, 97, 154, 87, 169, 159, 128, - 114, 115, 86, 0, 148, 102, 106, 101, 137, 166, - 167, 100, 189, 91, 178, 89, 92, 177, 135, 164, - 170, 129, 126, 88, 168, 127, 125, 117, 104, 111, - 142, 124, 143, 112, 132, 131, 133, 0, 0, 0, - 158, 175, 190, 94, 0, 163, 181, 182, 183, 184, - 185, 0, 0, 95, 107, 134, 93, 113, 155, 116, - 123, 147, 188, 139, 152, 98, 174, 156, 305, 314, - 311, 312, 309, 310, 308, 307, 306, 316, 297, 298, - 299, 300, 302, 0, 301, 83, 0, 120, 187, 146, - 105, 176, 138, 0, 0, 0, 504, 0, 0, 0, - 0, 103, 0, 0, 0, 0, 0, 119, 0, 121, - 0, 0, 157, 130, 0, 0, 0, 0, 0, 0, + 278, 279, 0, 0, 0, 0, 320, 0, 280, 0, + 0, 275, 276, 277, 282, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 109, 0, 0, 0, 207, 0, + 0, 318, 0, 146, 0, 162, 111, 119, 84, 90, + 0, 110, 137, 151, 155, 0, 0, 0, 99, 0, + 153, 141, 175, 1394, 142, 152, 123, 167, 147, 174, + 208, 182, 164, 181, 188, 85, 163, 173, 97, 156, + 87, 171, 161, 129, 115, 116, 86, 0, 150, 102, + 107, 101, 138, 168, 169, 100, 191, 91, 180, 89, + 92, 179, 136, 166, 172, 130, 127, 88, 170, 128, + 126, 118, 105, 112, 144, 125, 145, 113, 133, 132, + 134, 0, 0, 0, 160, 177, 192, 94, 0, 165, + 183, 184, 185, 186, 187, 0, 0, 95, 108, 104, + 143, 135, 93, 114, 157, 117, 124, 149, 190, 140, + 154, 98, 176, 158, 310, 319, 316, 317, 314, 315, + 313, 312, 311, 321, 302, 303, 304, 305, 307, 139, + 306, 83, 0, 121, 189, 148, 106, 178, 103, 0, + 0, 0, 0, 0, 120, 309, 122, 0, 0, 159, + 131, 0, 0, 0, 0, 300, 301, 0, 0, 0, + 0, 0, 0, 0, 0, 54, 0, 0, 267, 288, + 287, 290, 291, 292, 293, 0, 0, 96, 289, 294, + 295, 296, 0, 0, 0, 0, 281, 0, 308, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 81, 0, 506, 0, 0, 0, 0, 0, 0, - 96, 0, 0, 0, 0, 0, 501, 500, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 278, 279, + 0, 0, 0, 0, 320, 0, 280, 0, 0, 275, + 276, 277, 282, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 109, 0, 0, 0, 207, 0, 0, 318, + 0, 146, 0, 162, 111, 119, 84, 90, 0, 110, + 137, 151, 155, 0, 0, 0, 99, 0, 153, 141, + 175, 0, 142, 152, 123, 167, 147, 174, 208, 182, + 164, 181, 188, 85, 163, 173, 97, 156, 87, 171, + 161, 129, 115, 116, 86, 0, 150, 102, 107, 101, + 138, 168, 169, 100, 191, 91, 180, 89, 92, 179, + 136, 166, 172, 130, 127, 88, 170, 128, 126, 118, + 105, 112, 144, 125, 145, 113, 133, 132, 134, 0, + 0, 0, 160, 177, 192, 94, 0, 165, 183, 184, + 185, 186, 187, 0, 0, 95, 108, 104, 143, 135, + 93, 114, 157, 117, 124, 149, 190, 140, 154, 98, + 176, 158, 310, 319, 316, 317, 314, 315, 313, 312, + 311, 321, 302, 303, 304, 305, 307, 0, 306, 83, + 0, 121, 189, 148, 106, 178, 139, 0, 0, 0, + 511, 0, 0, 0, 0, 103, 0, 0, 0, 0, + 0, 120, 0, 122, 0, 0, 159, 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 502, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 81, 0, 513, 0, 0, + 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, + 508, 507, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 509, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 108, 0, 0, 0, 205, - 0, 0, 0, 0, 144, 0, 160, 110, 118, 84, - 90, 0, 109, 136, 149, 153, 0, 0, 0, 99, - 0, 151, 140, 173, 0, 141, 150, 122, 165, 145, - 172, 206, 180, 162, 179, 186, 85, 161, 171, 97, - 154, 87, 169, 159, 128, 114, 115, 86, 0, 148, - 102, 106, 101, 137, 166, 167, 100, 189, 91, 178, - 89, 92, 177, 135, 164, 170, 129, 126, 88, 168, - 127, 125, 117, 104, 111, 142, 124, 143, 112, 132, - 131, 133, 0, 0, 0, 158, 175, 190, 94, 0, - 163, 181, 182, 183, 184, 185, 0, 0, 95, 107, - 134, 93, 113, 155, 116, 123, 147, 188, 139, 152, - 98, 174, 156, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 138, 0, 0, 0, 0, 0, 0, 0, - 83, 103, 120, 187, 146, 105, 176, 119, 0, 121, - 0, 0, 157, 130, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 109, + 0, 0, 0, 207, 0, 0, 0, 0, 146, 0, + 162, 111, 119, 84, 90, 0, 110, 137, 151, 155, + 0, 0, 0, 99, 0, 153, 141, 175, 0, 142, + 152, 123, 167, 147, 174, 208, 182, 164, 181, 188, + 85, 163, 173, 97, 156, 87, 171, 161, 129, 115, + 116, 86, 0, 150, 102, 107, 101, 138, 168, 169, + 100, 191, 91, 180, 89, 92, 179, 136, 166, 172, + 130, 127, 88, 170, 128, 126, 118, 105, 112, 144, + 125, 145, 113, 133, 132, 134, 0, 0, 0, 160, + 177, 192, 94, 0, 165, 183, 184, 185, 186, 187, + 0, 0, 95, 108, 104, 143, 135, 93, 114, 157, + 117, 124, 149, 190, 140, 154, 98, 176, 158, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 139, 0, + 0, 0, 0, 0, 0, 0, 83, 103, 121, 189, + 148, 106, 178, 120, 0, 122, 0, 0, 159, 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 81, 0, 0, 0, 0, 0, 0, 0, 0, - 96, 0, 0, 0, 0, 0, 74, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 81, 0, 0, + 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, + 0, 0, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 108, 77, 78, 0, 73, - 0, 0, 0, 79, 144, 0, 160, 110, 118, 84, - 90, 0, 109, 136, 149, 153, 0, 0, 0, 99, - 0, 151, 140, 173, 0, 141, 150, 122, 165, 145, - 172, 75, 180, 162, 179, 186, 85, 161, 171, 97, - 154, 87, 169, 159, 128, 114, 115, 86, 0, 148, - 102, 106, 101, 137, 166, 167, 100, 189, 91, 178, - 89, 92, 177, 135, 164, 170, 129, 126, 88, 168, - 127, 125, 117, 104, 111, 142, 124, 143, 112, 132, - 131, 133, 0, 0, 0, 158, 175, 190, 94, 0, - 163, 181, 182, 183, 184, 185, 0, 0, 95, 107, - 134, 93, 113, 155, 116, 123, 147, 188, 139, 152, - 98, 174, 156, 0, 76, 0, 0, 0, 0, 0, - 0, 0, 138, 0, 0, 0, 807, 0, 0, 0, - 83, 103, 120, 187, 146, 105, 176, 119, 0, 121, - 0, 0, 157, 130, 0, 0, 0, 0, 0, 0, + 0, 109, 77, 78, 0, 73, 0, 0, 0, 79, + 146, 0, 162, 111, 119, 84, 90, 0, 110, 137, + 151, 155, 0, 0, 0, 99, 0, 153, 141, 175, + 0, 142, 152, 123, 167, 147, 174, 75, 182, 164, + 181, 188, 85, 163, 173, 97, 156, 87, 171, 161, + 129, 115, 116, 86, 0, 150, 102, 107, 101, 138, + 168, 169, 100, 191, 91, 180, 89, 92, 179, 136, + 166, 172, 130, 127, 88, 170, 128, 126, 118, 105, + 112, 144, 125, 145, 113, 133, 132, 134, 0, 0, + 0, 160, 177, 192, 94, 0, 165, 183, 184, 185, + 186, 187, 0, 0, 95, 108, 104, 143, 135, 93, + 114, 157, 117, 124, 149, 190, 140, 154, 98, 176, + 158, 0, 76, 0, 0, 0, 0, 0, 0, 0, + 139, 0, 0, 0, 817, 0, 0, 0, 83, 103, + 121, 189, 148, 106, 178, 120, 0, 122, 0, 0, + 159, 131, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 205, + 0, 819, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 203, 0, 809, 0, 0, 0, 0, 0, 0, - 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 108, 0, 0, 0, 205, - 0, 0, 0, 0, 144, 0, 160, 110, 118, 84, - 90, 0, 109, 136, 149, 153, 0, 0, 0, 99, - 0, 151, 140, 173, 0, 141, 150, 122, 165, 145, - 172, 206, 180, 162, 179, 186, 85, 161, 171, 97, - 154, 87, 169, 159, 128, 114, 115, 86, 0, 148, - 102, 106, 101, 137, 166, 167, 100, 189, 91, 178, - 89, 92, 177, 135, 164, 170, 129, 126, 88, 168, - 127, 125, 117, 104, 111, 142, 124, 143, 112, 132, - 131, 133, 0, 0, 0, 158, 175, 190, 94, 0, - 163, 181, 182, 183, 184, 185, 0, 0, 95, 107, - 134, 93, 113, 155, 116, 123, 147, 188, 139, 152, - 98, 174, 156, 0, 0, 0, 24, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 138, 0, - 83, 0, 120, 187, 146, 105, 176, 103, 0, 0, - 0, 0, 0, 119, 0, 121, 0, 0, 157, 130, + 0, 0, 0, 109, 0, 0, 0, 207, 0, 0, + 0, 0, 146, 0, 162, 111, 119, 84, 90, 0, + 110, 137, 151, 155, 0, 0, 0, 99, 0, 153, + 141, 175, 0, 142, 152, 123, 167, 147, 174, 208, + 182, 164, 181, 188, 85, 163, 173, 97, 156, 87, + 171, 161, 129, 115, 116, 86, 0, 150, 102, 107, + 101, 138, 168, 169, 100, 191, 91, 180, 89, 92, + 179, 136, 166, 172, 130, 127, 88, 170, 128, 126, + 118, 105, 112, 144, 125, 145, 113, 133, 132, 134, + 0, 0, 0, 160, 177, 192, 94, 0, 165, 183, + 184, 185, 186, 187, 0, 0, 95, 108, 104, 143, + 135, 93, 114, 157, 117, 124, 149, 190, 140, 154, + 98, 176, 158, 0, 0, 0, 24, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 139, 0, + 83, 0, 121, 189, 148, 106, 178, 103, 0, 0, + 0, 0, 0, 120, 0, 122, 0, 0, 159, 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 81, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, @@ -1518,333 +1536,382 @@ var yyAct = [...]int{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 108, 0, 0, 0, 205, 0, 0, 0, 0, - 144, 0, 160, 110, 118, 84, 90, 0, 109, 136, - 149, 153, 0, 0, 0, 99, 0, 151, 140, 173, - 0, 141, 150, 122, 165, 145, 172, 206, 180, 162, - 179, 186, 85, 161, 171, 97, 154, 87, 169, 159, - 128, 114, 115, 86, 0, 148, 102, 106, 101, 137, - 166, 167, 100, 189, 91, 178, 89, 92, 177, 135, - 164, 170, 129, 126, 88, 168, 127, 125, 117, 104, - 111, 142, 124, 143, 112, 132, 131, 133, 0, 0, - 0, 158, 175, 190, 94, 0, 163, 181, 182, 183, - 184, 185, 0, 0, 95, 107, 134, 93, 113, 155, - 116, 123, 147, 188, 139, 152, 98, 174, 156, 0, - 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 138, 0, 83, 0, 120, 187, - 146, 105, 176, 103, 0, 0, 0, 0, 0, 119, - 0, 121, 0, 0, 157, 130, 0, 0, 0, 0, + 0, 109, 0, 0, 0, 207, 0, 0, 0, 0, + 146, 0, 162, 111, 119, 84, 90, 0, 110, 137, + 151, 155, 0, 0, 0, 99, 0, 153, 141, 175, + 0, 142, 152, 123, 167, 147, 174, 208, 182, 164, + 181, 188, 85, 163, 173, 97, 156, 87, 171, 161, + 129, 115, 116, 86, 0, 150, 102, 107, 101, 138, + 168, 169, 100, 191, 91, 180, 89, 92, 179, 136, + 166, 172, 130, 127, 88, 170, 128, 126, 118, 105, + 112, 144, 125, 145, 113, 133, 132, 134, 0, 0, + 0, 160, 177, 192, 94, 0, 165, 183, 184, 185, + 186, 187, 0, 0, 95, 108, 104, 143, 135, 93, + 114, 157, 117, 124, 149, 190, 140, 154, 98, 176, + 158, 0, 0, 0, 24, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 139, 0, 83, 0, + 121, 189, 148, 106, 178, 103, 0, 0, 0, 0, + 0, 120, 0, 122, 0, 0, 159, 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 54, 0, 0, 203, 0, 0, 0, 0, 0, 0, - 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 54, 0, 0, 205, 0, 0, 0, 0, + 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 108, 0, 0, - 0, 205, 0, 0, 0, 0, 144, 0, 160, 110, - 118, 84, 90, 0, 109, 136, 149, 153, 0, 0, - 0, 99, 0, 151, 140, 173, 0, 141, 150, 122, - 165, 145, 172, 206, 180, 162, 179, 186, 85, 161, - 171, 97, 154, 87, 169, 159, 128, 114, 115, 86, - 0, 148, 102, 106, 101, 137, 166, 167, 100, 189, - 91, 178, 89, 92, 177, 135, 164, 170, 129, 126, - 88, 168, 127, 125, 117, 104, 111, 142, 124, 143, - 112, 132, 131, 133, 0, 0, 0, 158, 175, 190, - 94, 0, 163, 181, 182, 183, 184, 185, 0, 0, - 95, 107, 134, 93, 113, 155, 116, 123, 147, 188, - 139, 152, 98, 174, 156, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 138, 0, 0, 0, 807, 0, - 0, 0, 83, 103, 120, 187, 146, 105, 176, 119, - 0, 121, 0, 0, 157, 130, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 109, + 0, 0, 0, 207, 0, 0, 0, 0, 146, 0, + 162, 111, 119, 84, 90, 0, 110, 137, 151, 155, + 0, 0, 0, 99, 0, 153, 141, 175, 0, 142, + 152, 123, 167, 147, 174, 208, 182, 164, 181, 188, + 85, 163, 173, 97, 156, 87, 171, 161, 129, 115, + 116, 86, 0, 150, 102, 107, 101, 138, 168, 169, + 100, 191, 91, 180, 89, 92, 179, 136, 166, 172, + 130, 127, 88, 170, 128, 126, 118, 105, 112, 144, + 125, 145, 113, 133, 132, 134, 0, 0, 0, 160, + 177, 192, 94, 0, 165, 183, 184, 185, 186, 187, + 0, 0, 95, 108, 104, 143, 135, 93, 114, 157, + 117, 124, 149, 190, 140, 154, 98, 176, 158, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 139, 0, + 0, 0, 817, 0, 0, 0, 83, 103, 121, 189, + 148, 106, 178, 120, 0, 122, 0, 0, 159, 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 203, 0, 809, 0, 0, 0, 0, - 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 205, 0, 819, + 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 108, 0, 0, - 0, 205, 0, 0, 0, 0, 144, 0, 160, 110, - 118, 84, 90, 0, 109, 136, 149, 153, 0, 0, - 0, 99, 0, 151, 140, 173, 0, 805, 150, 122, - 165, 145, 172, 206, 180, 162, 179, 186, 85, 161, - 171, 97, 154, 87, 169, 159, 128, 114, 115, 86, - 0, 148, 102, 106, 101, 137, 166, 167, 100, 189, - 91, 178, 89, 92, 177, 135, 164, 170, 129, 126, - 88, 168, 127, 125, 117, 104, 111, 142, 124, 143, - 112, 132, 131, 133, 0, 0, 0, 158, 175, 190, - 94, 0, 163, 181, 182, 183, 184, 185, 0, 0, - 95, 107, 134, 93, 113, 155, 116, 123, 147, 188, - 139, 152, 98, 174, 156, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 138, 0, 0, 0, 0, 0, - 0, 0, 83, 103, 120, 187, 146, 105, 176, 119, - 0, 121, 0, 0, 157, 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 81, 0, 0, 709, 0, 0, 710, - 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, + 0, 109, 0, 0, 0, 207, 0, 0, 0, 0, + 146, 0, 162, 111, 119, 84, 90, 0, 110, 137, + 151, 155, 0, 0, 0, 99, 0, 153, 141, 175, + 0, 815, 152, 123, 167, 147, 174, 208, 182, 164, + 181, 188, 85, 163, 173, 97, 156, 87, 171, 161, + 129, 115, 116, 86, 0, 150, 102, 107, 101, 138, + 168, 169, 100, 191, 91, 180, 89, 92, 179, 136, + 166, 172, 130, 127, 88, 170, 128, 126, 118, 105, + 112, 144, 125, 145, 113, 133, 132, 134, 0, 0, + 0, 160, 177, 192, 94, 0, 165, 183, 184, 185, + 186, 187, 0, 0, 95, 108, 104, 143, 135, 93, + 114, 157, 117, 124, 149, 190, 140, 154, 98, 176, + 158, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 139, 0, 0, 0, 0, 0, 0, 0, 83, 103, + 121, 189, 148, 106, 178, 120, 0, 122, 0, 0, + 159, 131, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 81, + 0, 0, 717, 0, 0, 718, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 108, 0, 0, - 0, 205, 0, 0, 0, 0, 144, 0, 160, 110, - 118, 84, 90, 0, 109, 136, 149, 153, 0, 0, - 0, 99, 0, 151, 140, 173, 0, 141, 150, 122, - 165, 145, 172, 206, 180, 162, 179, 186, 85, 161, - 171, 97, 154, 87, 169, 159, 128, 114, 115, 86, - 0, 148, 102, 106, 101, 137, 166, 167, 100, 189, - 91, 178, 89, 92, 177, 135, 164, 170, 129, 126, - 88, 168, 127, 125, 117, 104, 111, 142, 124, 143, - 112, 132, 131, 133, 0, 0, 0, 158, 175, 190, - 94, 0, 163, 181, 182, 183, 184, 185, 0, 0, - 95, 107, 134, 93, 113, 155, 116, 123, 147, 188, - 139, 152, 98, 174, 156, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 138, 0, 83, 0, 120, 187, 146, 105, 176, 103, - 0, 606, 0, 0, 0, 119, 0, 121, 0, 0, - 157, 130, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 81, - 0, 605, 0, 0, 0, 0, 0, 0, 96, 0, + 0, 0, 0, 109, 0, 0, 0, 207, 0, 0, + 0, 0, 146, 0, 162, 111, 119, 84, 90, 0, + 110, 137, 151, 155, 0, 0, 0, 99, 0, 153, + 141, 175, 0, 142, 152, 123, 167, 147, 174, 208, + 182, 164, 181, 188, 85, 163, 173, 97, 156, 87, + 171, 161, 129, 115, 116, 86, 0, 150, 102, 107, + 101, 138, 168, 169, 100, 191, 91, 180, 89, 92, + 179, 136, 166, 172, 130, 127, 88, 170, 128, 126, + 118, 105, 112, 144, 125, 145, 113, 133, 132, 134, + 0, 0, 0, 160, 177, 192, 94, 0, 165, 183, + 184, 185, 186, 187, 0, 0, 95, 108, 104, 143, + 135, 93, 114, 157, 117, 124, 149, 190, 140, 154, + 98, 176, 158, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 139, 0, + 83, 0, 121, 189, 148, 106, 178, 103, 0, 613, + 0, 0, 0, 120, 0, 122, 0, 0, 159, 131, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 81, 0, 612, + 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 108, 0, 0, 0, 205, 0, 0, - 0, 0, 144, 0, 160, 110, 118, 84, 90, 0, - 109, 136, 149, 153, 0, 0, 0, 99, 0, 151, - 140, 173, 0, 141, 150, 122, 165, 145, 172, 206, - 180, 162, 179, 186, 85, 161, 171, 97, 154, 87, - 169, 159, 128, 114, 115, 86, 0, 148, 102, 106, - 101, 137, 166, 167, 100, 189, 91, 178, 89, 92, - 177, 135, 164, 170, 129, 126, 88, 168, 127, 125, - 117, 104, 111, 142, 124, 143, 112, 132, 131, 133, - 0, 0, 0, 158, 175, 190, 94, 0, 163, 181, - 182, 183, 184, 185, 0, 0, 95, 107, 134, 93, - 113, 155, 116, 123, 147, 188, 139, 152, 98, 174, - 156, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 138, 0, 0, 0, 0, 0, 0, 0, 83, 103, - 120, 187, 146, 105, 176, 119, 0, 121, 0, 0, - 157, 130, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 54, 0, 0, 203, + 0, 109, 0, 0, 0, 207, 0, 0, 0, 0, + 146, 0, 162, 111, 119, 84, 90, 0, 110, 137, + 151, 155, 0, 0, 0, 99, 0, 153, 141, 175, + 0, 142, 152, 123, 167, 147, 174, 208, 182, 164, + 181, 188, 85, 163, 173, 97, 156, 87, 171, 161, + 129, 115, 116, 86, 0, 150, 102, 107, 101, 138, + 168, 169, 100, 191, 91, 180, 89, 92, 179, 136, + 166, 172, 130, 127, 88, 170, 128, 126, 118, 105, + 112, 144, 125, 145, 113, 133, 132, 134, 0, 0, + 0, 160, 177, 192, 94, 0, 165, 183, 184, 185, + 186, 187, 0, 0, 95, 108, 104, 143, 135, 93, + 114, 157, 117, 124, 149, 190, 140, 154, 98, 176, + 158, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 139, 0, 0, 0, 0, 0, 0, 0, 83, 103, + 121, 189, 148, 106, 178, 120, 0, 122, 0, 0, + 159, 131, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 54, 0, 0, 205, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 108, 0, 0, 0, 205, 0, 0, - 0, 0, 144, 0, 160, 110, 118, 84, 90, 0, - 109, 136, 149, 153, 0, 0, 0, 99, 0, 151, - 140, 173, 0, 141, 150, 122, 165, 145, 172, 206, - 180, 162, 179, 186, 85, 161, 171, 97, 154, 87, - 169, 159, 128, 114, 115, 86, 0, 148, 102, 106, - 101, 137, 166, 167, 100, 189, 91, 178, 89, 92, - 177, 135, 164, 170, 129, 126, 88, 168, 127, 125, - 117, 104, 111, 142, 124, 143, 112, 132, 131, 133, - 0, 0, 0, 158, 175, 190, 94, 0, 163, 181, - 182, 183, 184, 185, 0, 0, 95, 107, 134, 93, - 113, 155, 116, 123, 147, 188, 139, 152, 98, 174, - 156, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 138, 0, 0, 0, 0, 0, 0, 0, 83, 103, - 120, 187, 146, 105, 176, 119, 0, 121, 0, 0, - 157, 130, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 203, - 0, 809, 0, 0, 0, 0, 0, 0, 96, 0, + 0, 0, 0, 109, 0, 0, 0, 207, 0, 0, + 0, 0, 146, 0, 162, 111, 119, 84, 90, 0, + 110, 137, 151, 155, 0, 0, 0, 99, 0, 153, + 141, 175, 0, 142, 152, 123, 167, 147, 174, 208, + 182, 164, 181, 188, 85, 163, 173, 97, 156, 87, + 171, 161, 129, 115, 116, 86, 0, 150, 102, 107, + 101, 138, 168, 169, 100, 191, 91, 180, 89, 92, + 179, 136, 166, 172, 130, 127, 88, 170, 128, 126, + 118, 105, 112, 144, 125, 145, 113, 133, 132, 134, + 0, 0, 0, 160, 177, 192, 94, 0, 165, 183, + 184, 185, 186, 187, 0, 0, 95, 108, 104, 143, + 135, 93, 114, 157, 117, 124, 149, 190, 140, 154, + 98, 176, 158, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 139, 0, 0, 0, 0, 0, 0, 0, + 83, 103, 121, 189, 148, 106, 178, 120, 0, 122, + 0, 0, 159, 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 205, 0, 819, 0, 0, 0, 0, 0, 0, + 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 108, 0, 0, 0, 205, 0, 0, - 0, 0, 144, 0, 160, 110, 118, 84, 90, 0, - 109, 136, 149, 153, 0, 0, 0, 99, 0, 151, - 140, 173, 0, 141, 150, 122, 165, 145, 172, 206, - 180, 162, 179, 186, 85, 161, 171, 97, 154, 87, - 169, 159, 128, 114, 115, 86, 0, 148, 102, 106, - 101, 137, 166, 167, 100, 189, 91, 178, 89, 92, - 177, 135, 164, 170, 129, 126, 88, 168, 127, 125, - 117, 104, 111, 142, 124, 143, 112, 132, 131, 133, - 0, 0, 0, 158, 175, 190, 94, 0, 163, 181, - 182, 183, 184, 185, 0, 0, 95, 107, 134, 93, - 113, 155, 116, 123, 147, 188, 139, 152, 98, 174, - 156, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 138, 0, 0, 0, 0, 0, 0, 0, 83, 103, - 120, 187, 146, 105, 176, 119, 0, 121, 0, 0, - 157, 130, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 81, - 0, 506, 0, 0, 0, 0, 0, 0, 96, 0, + 0, 0, 0, 0, 0, 109, 0, 0, 0, 207, + 0, 0, 0, 0, 146, 0, 162, 111, 119, 84, + 90, 0, 110, 137, 151, 155, 0, 0, 0, 99, + 0, 153, 141, 175, 0, 142, 152, 123, 167, 147, + 174, 208, 182, 164, 181, 188, 85, 163, 173, 97, + 156, 87, 171, 161, 129, 115, 116, 86, 0, 150, + 102, 107, 101, 138, 168, 169, 100, 191, 91, 180, + 89, 92, 179, 136, 166, 172, 130, 127, 88, 170, + 128, 126, 118, 105, 112, 144, 125, 145, 113, 133, + 132, 134, 0, 0, 0, 160, 177, 192, 94, 0, + 165, 183, 184, 185, 186, 187, 0, 0, 95, 108, + 104, 143, 135, 93, 114, 157, 117, 124, 149, 190, + 140, 154, 98, 176, 158, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 139, 0, 0, 0, 0, 0, + 0, 0, 83, 103, 121, 189, 148, 106, 178, 120, + 0, 122, 0, 0, 159, 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 267, 0, 782, 0, 0, 0, 0, + 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 108, 0, 0, 0, 205, 0, 0, - 0, 0, 144, 0, 160, 110, 118, 84, 90, 0, - 109, 136, 149, 153, 0, 0, 0, 99, 0, 151, - 140, 173, 0, 141, 150, 122, 165, 145, 172, 206, - 180, 162, 179, 186, 85, 161, 171, 97, 154, 87, - 169, 159, 128, 114, 115, 86, 0, 148, 102, 106, - 101, 137, 166, 167, 100, 189, 91, 178, 89, 92, - 177, 135, 164, 170, 129, 126, 88, 168, 127, 125, - 117, 104, 111, 142, 124, 143, 112, 132, 131, 133, - 0, 0, 0, 158, 175, 190, 94, 0, 163, 181, - 182, 183, 184, 185, 0, 0, 95, 107, 134, 93, - 113, 155, 116, 123, 147, 188, 139, 152, 98, 174, - 156, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 138, 83, 0, - 120, 187, 146, 105, 176, 579, 103, 0, 0, 0, - 0, 0, 119, 0, 121, 0, 0, 157, 130, 0, + 0, 0, 0, 0, 0, 0, 0, 109, 0, 0, + 0, 207, 0, 0, 0, 0, 146, 0, 162, 111, + 119, 84, 90, 0, 110, 137, 151, 155, 0, 0, + 0, 99, 0, 153, 141, 175, 0, 142, 152, 123, + 167, 147, 174, 208, 182, 164, 181, 188, 85, 163, + 173, 97, 156, 87, 171, 161, 129, 115, 116, 86, + 0, 150, 102, 107, 101, 138, 168, 169, 100, 191, + 91, 180, 89, 92, 179, 136, 166, 172, 130, 127, + 88, 170, 128, 126, 118, 105, 112, 144, 125, 145, + 113, 133, 132, 134, 0, 0, 0, 160, 177, 192, + 94, 0, 165, 183, 184, 185, 186, 187, 0, 0, + 95, 108, 104, 143, 135, 93, 114, 157, 117, 124, + 149, 190, 140, 154, 98, 176, 158, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 139, 0, 0, 0, + 0, 0, 0, 0, 83, 103, 121, 189, 148, 106, + 178, 120, 0, 122, 0, 0, 159, 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 203, 0, 0, 0, - 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 267, 0, 778, 0, 0, + 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 109, + 0, 0, 0, 207, 0, 0, 0, 0, 146, 0, + 162, 111, 119, 84, 90, 0, 110, 137, 151, 155, + 0, 0, 0, 99, 0, 153, 141, 175, 0, 142, + 152, 123, 167, 147, 174, 208, 182, 164, 181, 188, + 85, 163, 173, 97, 156, 87, 171, 161, 129, 115, + 116, 86, 0, 150, 102, 107, 101, 138, 168, 169, + 100, 191, 91, 180, 89, 92, 179, 136, 166, 172, + 130, 127, 88, 170, 128, 126, 118, 105, 112, 144, + 125, 145, 113, 133, 132, 134, 0, 0, 0, 160, + 177, 192, 94, 0, 165, 183, 184, 185, 186, 187, + 0, 0, 95, 108, 104, 143, 135, 93, 114, 157, + 117, 124, 149, 190, 140, 154, 98, 176, 158, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 139, 0, + 0, 0, 0, 0, 0, 0, 83, 103, 121, 189, + 148, 106, 178, 120, 0, 122, 0, 0, 159, 131, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 81, 0, 513, + 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 108, 0, 0, 0, 205, 0, 0, 0, 0, 144, - 0, 160, 110, 118, 84, 90, 0, 109, 136, 149, - 153, 0, 0, 0, 99, 0, 151, 140, 173, 0, - 141, 150, 122, 165, 145, 172, 206, 180, 162, 179, - 186, 85, 161, 171, 97, 154, 87, 169, 159, 128, - 114, 115, 86, 0, 148, 102, 106, 101, 137, 166, - 167, 100, 189, 91, 178, 89, 92, 177, 135, 164, - 170, 129, 126, 88, 168, 127, 125, 117, 104, 111, - 142, 124, 143, 112, 132, 131, 133, 0, 0, 0, - 158, 175, 190, 94, 0, 163, 181, 182, 183, 184, - 185, 0, 0, 95, 107, 134, 93, 113, 155, 116, - 123, 147, 188, 139, 152, 98, 174, 156, 0, 0, - 319, 0, 0, 0, 0, 0, 0, 138, 0, 0, - 0, 0, 0, 0, 0, 83, 103, 120, 187, 146, - 105, 176, 119, 0, 121, 0, 0, 157, 130, 0, + 0, 109, 0, 0, 0, 207, 0, 0, 0, 0, + 146, 0, 162, 111, 119, 84, 90, 0, 110, 137, + 151, 155, 0, 0, 0, 99, 0, 153, 141, 175, + 0, 142, 152, 123, 167, 147, 174, 208, 182, 164, + 181, 188, 85, 163, 173, 97, 156, 87, 171, 161, + 129, 115, 116, 86, 0, 150, 102, 107, 101, 138, + 168, 169, 100, 191, 91, 180, 89, 92, 179, 136, + 166, 172, 130, 127, 88, 170, 128, 126, 118, 105, + 112, 144, 125, 145, 113, 133, 132, 134, 0, 0, + 0, 160, 177, 192, 94, 0, 165, 183, 184, 185, + 186, 187, 0, 0, 95, 108, 104, 143, 135, 93, + 114, 157, 117, 124, 149, 190, 140, 154, 98, 176, + 158, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 139, 83, 0, + 121, 189, 148, 106, 178, 586, 103, 0, 0, 0, + 0, 0, 120, 0, 122, 0, 0, 159, 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 203, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 205, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 108, 0, 0, 0, 205, 0, 0, 0, 0, 144, - 0, 160, 110, 118, 84, 90, 0, 109, 136, 149, - 153, 0, 0, 0, 99, 0, 151, 140, 173, 0, - 141, 150, 122, 165, 145, 172, 206, 180, 162, 179, - 186, 85, 161, 171, 97, 154, 87, 169, 159, 128, - 114, 115, 86, 0, 148, 102, 106, 101, 137, 166, - 167, 100, 189, 91, 178, 89, 92, 177, 135, 164, - 170, 129, 126, 88, 168, 127, 125, 117, 104, 111, - 142, 124, 143, 112, 132, 131, 133, 0, 0, 0, - 158, 175, 190, 94, 0, 163, 181, 182, 183, 184, - 185, 0, 0, 95, 107, 134, 93, 113, 155, 116, - 123, 147, 188, 139, 152, 98, 174, 156, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 138, 0, 0, - 0, 0, 0, 0, 0, 83, 103, 120, 187, 146, - 105, 176, 119, 0, 121, 0, 0, 157, 130, 0, + 109, 0, 0, 0, 207, 0, 0, 0, 0, 146, + 0, 162, 111, 119, 84, 90, 0, 110, 137, 151, + 155, 0, 0, 0, 99, 0, 153, 141, 175, 0, + 142, 152, 123, 167, 147, 174, 208, 182, 164, 181, + 188, 85, 163, 173, 97, 156, 87, 171, 161, 129, + 115, 116, 86, 0, 150, 102, 107, 101, 138, 168, + 169, 100, 191, 91, 180, 89, 92, 179, 136, 166, + 172, 130, 127, 88, 170, 128, 126, 118, 105, 112, + 144, 125, 145, 113, 133, 132, 134, 0, 0, 0, + 160, 177, 192, 94, 0, 165, 183, 184, 185, 186, + 187, 0, 0, 95, 108, 104, 143, 135, 93, 114, + 157, 117, 124, 149, 190, 140, 154, 98, 176, 158, + 0, 0, 324, 0, 0, 0, 0, 0, 0, 139, + 0, 0, 0, 0, 0, 0, 0, 83, 103, 121, + 189, 148, 106, 178, 120, 0, 122, 0, 0, 159, + 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 205, 0, + 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 203, 0, 0, 0, - 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 109, 0, 0, 0, 207, 0, 0, 0, + 0, 146, 0, 162, 111, 119, 84, 90, 0, 110, + 137, 151, 155, 0, 0, 0, 99, 0, 153, 141, + 175, 0, 142, 152, 123, 167, 147, 174, 208, 182, + 164, 181, 188, 85, 163, 173, 97, 156, 87, 171, + 161, 129, 115, 116, 86, 0, 150, 102, 107, 101, + 138, 168, 169, 100, 191, 91, 180, 89, 92, 179, + 136, 166, 172, 130, 127, 88, 170, 128, 126, 118, + 105, 112, 144, 125, 145, 113, 133, 132, 134, 0, + 0, 0, 160, 177, 192, 94, 0, 165, 183, 184, + 185, 186, 187, 0, 0, 95, 108, 104, 143, 135, + 93, 114, 157, 117, 124, 149, 190, 140, 154, 98, + 176, 158, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 139, 0, 0, 0, 0, 0, 0, 0, 83, + 103, 121, 189, 148, 106, 178, 120, 0, 122, 0, + 0, 159, 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 108, 0, 200, 0, 205, 0, 0, 0, 0, 144, - 0, 160, 110, 118, 84, 90, 0, 109, 136, 149, - 153, 0, 0, 0, 99, 0, 151, 140, 173, 0, - 141, 150, 122, 165, 145, 172, 206, 180, 162, 179, - 186, 85, 161, 171, 97, 154, 87, 169, 159, 128, - 114, 115, 86, 0, 148, 102, 106, 101, 137, 166, - 167, 100, 189, 91, 178, 89, 92, 177, 135, 164, - 170, 129, 126, 88, 168, 127, 125, 117, 104, 111, - 142, 124, 143, 112, 132, 131, 133, 0, 0, 0, - 158, 175, 190, 94, 0, 163, 181, 182, 183, 184, - 185, 0, 0, 95, 107, 134, 93, 113, 155, 116, - 123, 147, 188, 139, 152, 98, 174, 156, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 138, 0, 0, - 0, 0, 0, 0, 0, 83, 103, 120, 187, 146, - 105, 176, 119, 0, 121, 0, 0, 157, 130, 0, + 205, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 81, 0, 0, 0, - 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 109, 0, 202, 0, 207, 0, + 0, 0, 0, 146, 0, 162, 111, 119, 84, 90, + 0, 110, 137, 151, 155, 0, 0, 0, 99, 0, + 153, 141, 175, 0, 142, 152, 123, 167, 147, 174, + 208, 182, 164, 181, 188, 85, 163, 173, 97, 156, + 87, 171, 161, 129, 115, 116, 86, 0, 150, 102, + 107, 101, 138, 168, 169, 100, 191, 91, 180, 89, + 92, 179, 136, 166, 172, 130, 127, 88, 170, 128, + 126, 118, 105, 112, 144, 125, 145, 113, 133, 132, + 134, 0, 0, 0, 160, 177, 192, 94, 0, 165, + 183, 184, 185, 186, 187, 0, 0, 95, 108, 104, + 143, 135, 93, 114, 157, 117, 124, 149, 190, 140, + 154, 98, 176, 158, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 139, 0, 0, 0, 0, 0, 0, + 0, 83, 103, 121, 189, 148, 106, 178, 120, 0, + 122, 0, 0, 159, 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 108, 0, 0, 0, 205, 0, 0, 0, 0, 144, - 0, 160, 110, 118, 84, 90, 0, 109, 136, 149, - 153, 0, 0, 0, 99, 0, 151, 140, 173, 0, - 141, 150, 122, 165, 145, 172, 206, 180, 162, 179, - 186, 85, 161, 171, 97, 154, 87, 169, 159, 128, - 114, 115, 86, 0, 148, 102, 106, 101, 137, 166, - 167, 100, 189, 91, 178, 89, 92, 177, 135, 164, - 170, 129, 126, 88, 168, 127, 125, 117, 104, 111, - 142, 124, 143, 112, 132, 131, 133, 0, 0, 0, - 158, 175, 190, 94, 0, 163, 181, 182, 183, 184, - 185, 0, 0, 95, 107, 134, 93, 113, 155, 116, - 123, 147, 188, 139, 152, 98, 174, 156, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 138, 0, 0, - 0, 0, 0, 0, 0, 83, 103, 120, 187, 146, - 105, 176, 119, 0, 121, 0, 0, 157, 130, 0, + 0, 0, 81, 0, 0, 0, 0, 0, 0, 0, + 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 203, 0, 0, 0, - 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 109, 0, 0, 0, + 207, 0, 0, 0, 0, 146, 0, 162, 111, 119, + 84, 90, 0, 110, 137, 151, 155, 0, 0, 0, + 99, 0, 153, 141, 175, 0, 142, 152, 123, 167, + 147, 174, 208, 182, 164, 181, 188, 85, 163, 173, + 97, 156, 87, 171, 161, 129, 115, 116, 86, 0, + 150, 102, 107, 101, 138, 168, 169, 100, 191, 91, + 180, 89, 92, 179, 136, 166, 172, 130, 127, 88, + 170, 128, 126, 118, 105, 112, 144, 125, 145, 113, + 133, 132, 134, 0, 0, 0, 160, 177, 192, 94, + 0, 165, 183, 184, 185, 186, 187, 0, 0, 95, + 108, 104, 143, 135, 93, 114, 157, 117, 124, 149, + 190, 140, 154, 98, 176, 158, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 139, 0, 0, 0, 0, + 0, 0, 0, 83, 103, 121, 189, 148, 106, 178, + 120, 0, 122, 0, 0, 159, 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 205, 0, 0, 0, 0, 0, + 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 108, 0, 0, 0, 205, 0, 0, 0, 0, 144, - 0, 160, 110, 118, 84, 90, 0, 109, 136, 149, - 153, 0, 0, 0, 99, 0, 151, 140, 173, 0, - 141, 150, 122, 165, 145, 172, 206, 180, 162, 179, - 186, 85, 161, 171, 97, 154, 87, 169, 159, 128, - 114, 115, 86, 0, 148, 102, 106, 101, 137, 166, - 167, 100, 189, 91, 178, 89, 92, 177, 135, 164, - 170, 129, 126, 88, 168, 127, 125, 117, 104, 111, - 142, 124, 143, 112, 132, 131, 133, 0, 0, 0, - 158, 175, 190, 94, 0, 163, 181, 182, 183, 184, - 185, 0, 0, 95, 107, 134, 93, 113, 155, 116, - 123, 147, 188, 139, 152, 98, 174, 156, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 138, 0, 0, - 0, 0, 0, 0, 0, 83, 103, 120, 187, 146, - 105, 176, 119, 0, 121, 0, 0, 157, 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 262, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 109, 0, + 0, 0, 207, 0, 0, 0, 0, 146, 0, 162, + 111, 119, 84, 90, 0, 110, 137, 151, 155, 0, + 0, 0, 99, 0, 153, 141, 175, 0, 142, 152, + 123, 167, 147, 174, 208, 182, 164, 181, 188, 85, + 163, 173, 97, 156, 87, 171, 161, 129, 115, 116, + 86, 0, 150, 102, 107, 101, 138, 168, 169, 100, + 191, 91, 180, 89, 92, 179, 136, 166, 172, 130, + 127, 88, 170, 128, 126, 118, 105, 112, 144, 125, + 145, 113, 133, 132, 134, 0, 0, 0, 160, 177, + 192, 94, 0, 165, 183, 184, 185, 186, 187, 0, + 0, 95, 108, 104, 143, 135, 93, 114, 157, 117, + 124, 149, 190, 140, 154, 98, 176, 158, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 139, 0, 0, + 0, 0, 0, 0, 0, 83, 103, 121, 189, 148, + 106, 178, 120, 0, 122, 0, 0, 159, 131, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 267, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 108, 0, 0, 0, 205, 0, 0, 0, 0, 144, - 0, 160, 110, 118, 84, 90, 0, 109, 136, 149, - 153, 0, 0, 0, 99, 0, 151, 140, 173, 0, - 141, 150, 122, 165, 145, 172, 206, 180, 162, 179, - 186, 85, 161, 171, 97, 154, 87, 169, 159, 128, - 114, 115, 86, 0, 148, 102, 106, 101, 137, 166, - 167, 100, 189, 91, 178, 89, 92, 177, 135, 164, - 170, 129, 126, 88, 168, 127, 125, 117, 104, 111, - 142, 124, 143, 112, 132, 131, 133, 0, 0, 0, - 158, 175, 190, 94, 0, 163, 181, 182, 183, 184, - 185, 0, 0, 95, 107, 134, 93, 113, 155, 116, - 123, 147, 188, 139, 152, 98, 174, 156, 0, 0, + 109, 0, 0, 0, 207, 0, 0, 0, 0, 146, + 0, 162, 111, 119, 84, 90, 0, 110, 137, 151, + 155, 0, 0, 0, 99, 0, 153, 141, 175, 0, + 142, 152, 123, 167, 147, 174, 208, 182, 164, 181, + 188, 85, 163, 173, 97, 156, 87, 171, 161, 129, + 115, 116, 86, 0, 150, 102, 107, 101, 138, 168, + 169, 100, 191, 91, 180, 89, 92, 179, 136, 166, + 172, 130, 127, 88, 170, 128, 126, 118, 105, 112, + 144, 125, 145, 113, 133, 132, 134, 0, 0, 0, + 160, 177, 192, 94, 0, 165, 183, 184, 185, 186, + 187, 0, 0, 95, 108, 104, 143, 135, 93, 114, + 157, 117, 124, 149, 190, 140, 154, 98, 176, 158, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 83, 0, 120, 187, 146, - 105, 176, + 0, 0, 0, 0, 0, 0, 0, 83, 0, 121, + 189, 148, 106, 178, } var yyPact = [...]int{ - 117, -1000, -184, -1000, -1000, -1000, -1000, -1000, -1000, -1000, + 1773, -1000, -181, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, - -1000, -1000, 914, 944, -1000, -1000, -1000, -1000, -1000, -1000, - 209, 7564, 72, 87, 28, 10349, 86, 1505, 10809, -1000, - -4, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -75, -100, - -1000, 710, -1000, -1000, -1000, -1000, -1000, 908, 911, 757, - 900, 804, -1000, 6124, 60, 60, 10119, 5144, -1000, -1000, - 335, 10809, 82, 10809, -155, 10579, 57, 57, 57, -1000, + -1000, -1000, 864, 908, -1000, -1000, -1000, -1000, -1000, -1000, + 197, 7700, 66, 91, -9, 10973, 88, 1457, 11437, -1000, + -7, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -99, -106, + -1000, 685, -1000, -1000, -1000, -1000, -1000, 855, 861, 709, + 846, 766, -1000, 6248, 53, 53, 10741, 5260, -1000, -1000, + 219, 11437, 82, 11437, -152, 11205, 57, 57, 57, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, @@ -1856,21 +1923,23 @@ var yyPact = [...]int{ -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, - -1000, 84, 10809, -1000, 10809, 56, 556, 56, 56, 56, - 10809, -1000, 125, -1000, -1000, -1000, -1000, 10809, 548, 856, - 63, 3080, 3080, 3080, 3080, 5, 3080, -62, 754, 923, - -1000, -1000, -1000, -1000, -1000, -1000, 3080, -1000, -1000, -1000, + -1000, -1000, -1000, 86, 11437, -1000, 11437, 54, 498, 54, + 54, 54, 11437, -1000, 126, -1000, -1000, -1000, -1000, 11437, + 478, 796, 3180, 52, 3180, -1000, 3180, 3180, -1000, 3180, + -1, 3180, -71, 726, 882, -1000, -1000, -1000, -1000, -1000, + -1000, 3180, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, + -1000, -1000, -1000, -1000, -1000, 445, 804, 6745, 6745, 864, + -1000, 685, -1000, -1000, -1000, 792, -1000, -1000, 275, 887, + -1000, 7468, 125, -1000, 6745, 1866, 659, -1000, -1000, 659, + -1000, -1000, 110, -1000, -1000, 7221, 7221, 7221, 7221, 7221, + 7221, 7221, 7221, -1000, -1000, -1000, -1000, -1000, -1000, -1000, + -1000, -1000, -1000, -1000, -1000, -1000, -1000, 659, -1000, 6498, + 659, 659, 659, 659, 659, 659, 659, 659, 6745, 659, + 659, 659, 659, 659, 659, 659, 659, 659, 659, 659, + 659, 659, 10509, 9342, 11437, 601, -1000, 650, 5000, -88, + -1000, -1000, -1000, 200, 9110, -1000, -1000, -1000, 795, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, - 490, 864, 6617, 6617, 914, -1000, 710, -1000, -1000, -1000, - 828, -1000, -1000, 280, 933, -1000, 7334, 119, -1000, 6617, - 1686, 712, -1000, -1000, 712, -1000, -1000, 107, -1000, -1000, - 7089, 7089, 7089, 7089, 7089, 7089, 7089, 7089, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, - -1000, -1000, 712, -1000, 6372, 712, 712, 712, 712, 712, - 712, 712, 712, 6617, 712, 712, 712, 712, 712, 712, - 712, 712, 712, 712, 712, 712, 712, 9889, 9192, 10809, - 675, -1000, 679, 4886, -113, -1000, -1000, -1000, 215, 8962, - -1000, -1000, -1000, 855, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, @@ -1878,201 +1947,194 @@ var yyPact = [...]int{ -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, + -1000, -1000, -1000, 574, 11437, -1000, 1977, -1000, 471, 3180, + 71, 664, 465, 252, 453, 11437, 11437, 3180, 653, 64, + 11437, 829, 725, 11437, 440, 438, -1000, 4740, -1000, 3180, + 3180, -1000, -1000, -1000, 3180, 3180, 3180, 3180, 3180, 3180, + -1000, -1000, -1000, -1000, 3180, 3180, -1000, 884, 236, -1000, + -1000, -1000, -1000, 11437, 6745, -1000, -1000, -1000, -1000, -1000, + -1000, 902, 150, 418, 122, 651, -1000, 309, 855, 445, + 766, 8872, 736, -1000, -1000, 11437, -1000, 6745, 6745, 424, + -1000, 10270, -1000, -1000, 3700, 168, 7221, 293, 163, 7221, + 7221, 7221, 7221, 7221, 7221, 7221, 7221, 7221, 7221, 7221, + 7221, 7221, 7221, 7221, 352, -1000, -1000, -1000, -1000, -1000, + -1000, -1000, -1000, 432, -1000, 685, 590, 590, 139, 139, + 139, 139, 139, 139, 139, 2379, 5754, 445, 570, 361, + 6498, 6248, 6248, 6745, 6745, 10038, 9806, 6248, 835, 242, + 361, 11669, -1000, 445, -1000, -1000, -1000, -1000, -1000, -1000, + -1000, 6248, 6248, 6248, 6248, 20, 11437, -1000, 656, 712, + -1000, -1000, -1000, 833, 8408, 8640, 20, 585, 9342, 11437, + -1000, -1000, 4480, 650, -88, 641, -1000, -127, -94, 5507, + 137, -1000, -1000, -1000, -1000, 2920, 207, 534, 253, -74, + -1000, -1000, -1000, 686, -1000, 686, 686, 686, 686, -38, + -38, -38, -38, -1000, -1000, -1000, -1000, -1000, 716, 711, + -1000, 686, 686, 686, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, - -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, 591, 10809, - -1000, 1915, -1000, 540, 3080, 75, 650, 534, 251, 533, - 10809, 10809, 3080, 66, 10809, 893, 750, 10809, 509, 480, - -1000, 4628, -1000, 3080, 3080, 3080, 3080, 3080, 3080, 3080, - 3080, -1000, -1000, -1000, -1000, -1000, -1000, 3080, 3080, -1000, - 925, 293, -1000, -1000, -1000, -1000, 10809, 6617, -1000, -1000, - -1000, -1000, -1000, -1000, 939, 160, 495, 118, 704, -1000, - 425, 908, 490, 804, 8726, 764, -1000, -1000, 10809, -1000, - 6617, 6617, 466, -1000, 9652, -1000, -1000, 3596, 167, 7089, - 365, 346, 7089, 7089, 7089, 7089, 7089, 7089, 7089, 7089, - 7089, 7089, 7089, 7089, 7089, 7089, 7089, 449, -1000, -1000, - -1000, -1000, -1000, -1000, -1000, -1000, 471, -1000, 710, 1065, - 1065, 144, 144, 144, 144, 144, 144, 144, 2306, 5634, - 490, 589, 413, 6372, 6124, 6124, 6617, 6617, 11039, 11039, - 6124, 901, 229, 413, 11039, -1000, 490, -1000, -1000, -1000, - -1000, -1000, -1000, -1000, 6124, 6124, 6124, 6124, 27, 10809, - -1000, 665, 1107, -1000, -1000, -1000, 895, 8266, 8496, 27, - 632, 9192, 10809, -1000, -1000, 4370, 679, -113, 653, -1000, - -127, -131, 5389, 122, -1000, -1000, -1000, -1000, 2822, 294, - 619, 323, -67, -1000, -1000, -1000, 720, -1000, 720, 720, - 720, 720, -30, -30, -30, -30, -1000, -1000, -1000, -1000, - -1000, 734, 733, -1000, 720, 720, 720, -1000, -1000, -1000, - -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, - -1000, -1000, -1000, 730, 730, 730, 723, 723, 708, -1000, - 10809, -174, 469, 3080, 880, 3080, -1000, 1225, -1000, 10809, - -1000, -1000, 10809, 3080, -1000, -1000, -1000, -1000, -1000, -1000, - -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, 10809, - 199, 10809, 10809, -1000, 413, -1000, 793, 6617, 6617, 4112, - 6617, -1000, -1000, -1000, 864, -1000, 901, 912, -1000, 825, - 824, 6124, -1000, -1000, 167, 254, -1000, -1000, 424, -1000, - -1000, -1000, -1000, 116, 712, -1000, 1808, -1000, -1000, -1000, - -1000, 365, 7089, 7089, 7089, 146, 1808, 1793, 641, 1866, - 144, 363, 363, 165, 165, 165, 165, 165, 237, 237, - -1000, -1000, -1000, 490, -1000, -1000, -1000, 490, 6124, 666, - -1000, -1000, 6617, -1000, 490, 578, 578, 418, 411, 707, - -1000, 114, 694, 578, 6124, 272, -1000, 6617, 490, -1000, - 578, 490, 578, 578, 642, 712, -1000, 11039, 9192, 9192, - 9192, 9192, 9192, -1000, 780, 774, -1000, 783, 768, 827, - 10809, -1000, 580, 8266, 140, 712, -1000, 9422, -1000, -1000, - 922, 9192, 657, -1000, -1000, 653, -113, -120, -1000, -1000, - -1000, -1000, 413, -1000, 452, 652, 2564, -1000, -1000, -1000, - -1000, -1000, -1000, -1000, 725, 455, -1000, 872, 170, 197, - 453, 871, -1000, -1000, -1000, 862, -1000, 261, -69, -1000, - -1000, 391, -30, -30, -1000, -1000, 122, 847, 122, 122, - 122, 442, 442, -1000, -1000, -1000, -1000, 380, -1000, -1000, - -1000, 361, -1000, 748, 10579, 3080, -1000, 3854, -1000, -1000, - -1000, -1000, -1000, -1000, 1326, 781, 174, -1000, -1000, -1000, - -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, 26, -1000, - 3080, -1000, 293, -1000, 429, 6617, -1000, -1000, 787, 413, - 413, 112, -1000, -1000, 10809, -1000, -1000, -1000, -1000, 677, - -1000, -1000, -1000, 3338, 6124, -1000, 146, 1808, 1726, -1000, - 7089, 7089, -1000, -1000, 578, 6124, 413, -1000, -1000, -1000, - 535, 449, 535, 7089, 7089, 4112, 7089, 7089, -166, 676, - 224, -1000, 6617, 355, -1000, -1000, -1000, -1000, -1000, 747, - 11039, 712, -1000, 8030, 10579, 659, -1000, 205, 1107, 729, - 746, 1207, -1000, -1000, -1000, -1000, 772, -1000, 771, -1000, - -1000, -1000, -1000, -1000, 81, 80, 79, 10579, -1000, 914, - 6617, 657, -1000, -1000, -1000, -137, -136, -1000, -1000, -1000, - 2822, -1000, 2822, 10579, 41, -1000, 453, 453, -1000, -1000, - -1000, 724, 745, 38, -1000, -1000, -1000, 611, 122, 122, - -1000, 198, -1000, -1000, -1000, 576, -1000, 562, 651, 552, - 10809, -1000, -1000, 649, -1000, 188, -1000, -1000, 10579, -1000, - -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, - -1000, 10579, 10809, -1000, -1000, -1000, -1000, -1000, 10579, -1000, - 199, -1000, 413, -1000, 3854, -1000, 922, 9192, -1000, -1000, - 490, -1000, 7089, 1808, 1808, -1000, -1000, 490, 720, 720, - -1000, 720, 723, -1000, 720, -13, 720, -14, 490, 490, - 1614, 1073, -1000, 1411, 313, 712, -163, -1000, 413, 6617, - -1000, 875, 629, 643, -1000, -1000, 5879, 490, 547, 111, - 539, -1000, 914, 11039, 6617, -1000, -1000, 6617, 722, -1000, - 6617, -1000, -1000, -1000, 712, 712, 712, 539, 908, 413, - -1000, -1000, -1000, -1000, 2564, -1000, 530, -1000, 720, -1000, - -1000, -1000, 10579, -54, 937, -1000, -1000, -1000, -1000, 716, - -1000, -1000, -1000, -1000, -1000, -1000, -30, 428, -30, 333, - -1000, 319, 3080, 3854, 2822, -1000, 715, -1000, -1000, -1000, - -1000, 866, -1000, 920, 646, -1000, 1808, -1000, -1000, 99, - -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, 7089, - 7089, -1000, 7089, 7089, 7089, 490, 421, 413, 870, -1000, - 712, -1000, -1000, 648, 10579, 10579, -1000, 10579, 908, -1000, - 413, 413, 10579, 413, 10579, 10579, 10579, 7794, -1000, 128, - 10579, -1000, 523, -1000, 155, -1000, -145, 596, 122, -1000, - 122, 595, 566, -1000, -1000, -1000, 10579, 712, 916, 910, - -1000, -1000, 1538, 1538, 1538, 1538, 36, -1000, -1000, 936, - -1000, 712, -1000, 710, 104, -1000, -1000, -1000, 519, 517, - 517, 517, 140, 128, -1000, 415, 176, 416, -1000, 40, - 10579, 276, 868, -1000, 865, 714, -1000, -1000, -1000, -1000, - -1000, 489, 19, -1000, 6617, 6617, -1000, -1000, -1000, -1000, - 490, 39, -177, 11039, 643, 490, 10579, -1000, -1000, -1000, - -1000, -1000, -1000, -1000, 289, -1000, -1000, 10809, -1000, -1000, - 336, -1000, -1000, 531, 650, 487, -1000, 10579, 413, 639, - -1000, 785, -171, -180, 634, -1000, -1000, -1000, 713, -1000, - -1000, -174, -1000, 19, 803, -1000, 784, -1000, 10579, -1000, - -1000, 15, -175, 468, 12, -178, 744, 712, -181, 742, - -1000, 930, 6853, -1000, -1000, 932, 192, 192, 1538, 490, - -1000, -1000, -1000, 47, 410, -1000, -1000, -1000, -1000, -1000, - -1000, + 710, 710, 710, 692, 692, 684, -1000, 11437, -169, 429, + 3180, 809, 3180, -1000, 132, -1000, 11437, 11437, 647, -1000, + 11437, 3180, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, + -1000, -1000, -1000, -1000, -1000, -1000, -1000, 11437, 289, 11437, + 11437, -1000, 361, -1000, 774, 6745, 6745, 4220, 6745, -1000, + -1000, -1000, 804, -1000, 835, 857, -1000, 787, 785, 6248, + -1000, -1000, 168, 183, -1000, -1000, 394, -1000, -1000, -1000, + -1000, 118, 659, -1000, 2094, -1000, -1000, -1000, -1000, 293, + 7221, 7221, 7221, 1262, 2094, 2169, 468, 962, 139, 378, + 378, 136, 136, 136, 136, 136, 287, 287, -1000, -1000, + -1000, 445, -1000, -1000, -1000, 445, 6248, 642, -1000, -1000, + 6745, -1000, 445, 559, 559, 340, 461, 678, 663, -1000, + 116, 661, 655, 559, 6248, 233, -1000, 6745, 445, -1000, + 559, 445, 559, 559, 649, 659, -1000, 11669, 9342, 9342, + 9342, 9342, 9342, -1000, 751, 748, -1000, 744, 740, 752, + 11437, -1000, 564, 8408, 133, 659, -1000, 9574, -1000, -1000, + 877, 9342, 609, -1000, -1000, 641, -88, -100, -1000, -1000, + -1000, -1000, 361, -1000, 415, 640, 2660, -1000, -1000, -1000, + -1000, -1000, -1000, -1000, 705, 414, -1000, 808, 188, 184, + 400, 806, -1000, -1000, -1000, 800, -1000, 262, -78, -1000, + -1000, 366, -38, -38, -1000, -1000, 137, 794, 137, 137, + 137, 393, 393, -1000, -1000, -1000, -1000, 339, -1000, -1000, + -1000, 337, -1000, 724, 11205, 3180, -1000, 3960, -1000, -1000, + -1000, -1000, -1000, -1000, 1370, 286, 172, -1000, -1000, -1000, + -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, 19, 61, + -1000, 3180, -1000, 236, -1000, 390, 6745, -1000, -1000, 771, + 361, 361, 114, -1000, -1000, 11437, -1000, -1000, -1000, -1000, + 625, -1000, -1000, -1000, 3440, 6248, -1000, 1262, 2094, 2154, + -1000, 7221, 7221, -1000, -1000, 559, 6248, 361, -1000, -1000, + -1000, 47, 352, 47, 7221, 7221, 7221, 7221, 4220, 7221, + 7221, 7221, 7221, -164, 620, 218, -1000, 6745, 349, -1000, + -1000, -1000, -1000, -1000, 722, 11669, 659, -1000, 8170, 11205, + 615, -1000, 193, 712, 715, 721, 683, -1000, -1000, -1000, + -1000, 747, -1000, 741, -1000, -1000, -1000, -1000, -1000, 80, + 78, 75, 11205, -1000, 864, 6745, 609, -1000, -1000, -1000, + -132, -119, -1000, -1000, -1000, 2920, -1000, 2920, 11205, 38, + -1000, 400, 400, -1000, -1000, -1000, 695, 720, 44, -1000, + -1000, -1000, 527, 137, 137, -1000, 214, -1000, -1000, -1000, + 552, -1000, 549, 638, 546, 11437, -1000, -1000, 637, -1000, + 191, -1000, -1000, 11205, -1000, -1000, -1000, -1000, -1000, -1000, + -1000, -1000, -1000, -1000, -1000, -1000, 11205, 11437, -1000, -1000, + -1000, -1000, -1000, 11205, 11437, -1000, 289, -1000, 361, -1000, + 3960, -1000, 877, 9342, -1000, -1000, 445, -1000, 7221, 2094, + 2094, -1000, -1000, 445, 686, 686, -1000, 686, 692, -1000, + 686, -16, 686, -22, 445, 445, 1625, 2008, 1601, 1952, + -1000, 1460, 1936, 851, 1748, 659, -159, -1000, 361, 6745, + -1000, 815, 579, 633, -1000, -1000, 6001, 445, 537, 109, + 533, -1000, 864, 11669, 6745, -1000, -1000, 6745, 687, -1000, + 6745, -1000, -1000, -1000, 659, 659, 659, 533, 855, 361, + -1000, -1000, -1000, -1000, 2660, -1000, 518, -1000, 686, -1000, + -1000, -1000, 11205, -61, 901, -1000, -1000, -1000, -1000, 680, + -1000, -1000, -1000, -1000, -1000, -1000, -38, 386, -38, 320, + -1000, 318, 3180, 3960, 2920, -1000, 668, -1000, -1000, -1000, + -1000, 820, -1000, -1000, 874, 635, -1000, 2094, -1000, -1000, + 94, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, + 7221, 7221, -1000, 7221, 7221, -1000, 7221, 7221, -1000, 7221, + 7221, 7221, 445, 385, 361, 805, -1000, 659, -1000, -1000, + 631, 11205, 11205, -1000, 11205, 855, -1000, 361, 361, 11205, + 361, 11205, 11205, 11205, 7932, -1000, 135, 11205, -1000, 504, + -1000, 177, -1000, -142, 501, 137, -1000, 137, 489, 469, + -1000, -1000, -1000, 11205, 659, 867, 847, -1000, -1000, 1653, + 1653, 1653, 1653, 1653, 1653, 1653, 1653, 29, -1000, -1000, + 896, -1000, 659, -1000, 685, 107, -1000, -1000, -1000, 496, + 493, 493, 493, 133, 135, -1000, 396, 185, 380, -1000, + 32, 11205, 272, 803, -1000, 802, 666, -1000, -1000, -1000, + -1000, -1000, 488, 12, -1000, 6745, 6745, -1000, -1000, -1000, + -1000, -1000, -1000, -1000, -1000, 445, 46, -172, 11669, 633, + 445, 11205, -1000, -1000, -1000, -1000, -1000, -1000, -1000, 282, + -1000, -1000, 11437, -1000, -1000, 372, -1000, -1000, 449, 664, + 448, -1000, 11205, 361, 584, -1000, 770, -167, -176, 555, + -1000, -1000, -1000, 660, -1000, -1000, -169, -1000, 12, 781, + -1000, 759, -1000, 11205, -1000, -1000, 9, -170, 444, 6, + -174, 657, 659, -178, 652, -1000, 899, 6983, -1000, -1000, + 894, 156, 156, 1653, 445, -1000, -1000, -1000, 43, 391, + -1000, -1000, -1000, -1000, -1000, -1000, } var yyPgo = [...]int{ - 0, 1241, 44, 481, 1239, 1236, 1235, 1227, 1226, 1211, - 1210, 1209, 1207, 1203, 1201, 1200, 1199, 1197, 1195, 1194, - 1190, 1185, 1184, 1183, 1182, 146, 1181, 1162, 1161, 59, - 1160, 77, 1158, 1148, 41, 122, 86, 43, 894, 1146, - 27, 71, 89, 1145, 33, 1144, 1142, 78, 1141, 55, - 1136, 1122, 281, 1121, 1120, 10, 23, 1119, 1116, 1115, - 1113, 70, 38, 1112, 1110, 1109, 1108, 1106, 1094, 53, - 7, 9, 22, 18, 1091, 28, 17, 1089, 52, 1086, - 1085, 1084, 1083, 26, 1082, 56, 1081, 11, 51, 1079, - 30, 60, 31, 15, 6, 66, 65, 1078, 16, 69, - 49, 1077, 1076, 381, 1063, 1062, 40, 1061, 1058, 19, - 189, 453, 1054, 1051, 1050, 1049, 75, 0, 538, 479, - 64, 1047, 1042, 1041, 1366, 73, 48, 12, 1040, 63, - 169, 35, 1038, 1035, 37, 1034, 1032, 1030, 1028, 1027, - 1024, 1022, 20, 1020, 1018, 1017, 34, 47, 1014, 1013, - 54, 21, 1012, 1010, 1009, 46, 58, 1002, 1000, 50, - 29, 991, 990, 975, 974, 972, 25, 57, 966, 13, - 963, 8, 961, 24, 960, 3, 959, 14, 958, 4, - 957, 5, 42, 1, 956, 2, 955, 953, 61, 290, - 952, 950, 92, + 0, 1169, 25, 427, 1168, 1167, 1166, 1165, 1162, 1161, + 1160, 1159, 1158, 1156, 1152, 1151, 1149, 1148, 1147, 1145, + 1143, 1130, 1128, 1127, 1126, 1125, 139, 1124, 1123, 1122, + 64, 1119, 78, 1118, 1117, 44, 60, 41, 54, 332, + 1116, 47, 58, 101, 1115, 34, 1111, 1099, 75, 1098, + 51, 1095, 1094, 107, 1093, 1092, 7, 30, 1087, 1085, + 1083, 1080, 70, 858, 1079, 1077, 1076, 1073, 1072, 1071, + 55, 6, 12, 11, 18, 1070, 414, 10, 1052, 52, + 1044, 1043, 1040, 1034, 24, 1030, 57, 1028, 21, 56, + 1026, 23, 65, 33, 19, 5, 79, 62, 1024, 16, + 66, 46, 1023, 1020, 376, 1017, 1015, 42, 1014, 1012, + 29, 173, 377, 1010, 1008, 1007, 1002, 63, 0, 530, + 96, 73, 994, 987, 985, 1419, 69, 53, 14, 976, + 35, 71, 40, 975, 974, 38, 973, 972, 971, 969, + 968, 967, 966, 663, 965, 964, 963, 28, 13, 962, + 961, 61, 22, 959, 943, 941, 45, 59, 940, 938, + 50, 27, 937, 936, 931, 930, 928, 37, 17, 927, + 20, 925, 9, 924, 31, 923, 2, 922, 15, 921, + 3, 920, 4, 43, 1, 919, 8, 917, 916, 48, + 316, 915, 914, 89, } var yyR1 = [...]int{ - 0, 186, 187, 187, 1, 1, 1, 1, 1, 1, + 0, 187, 188, 188, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 6, 3, 4, - 4, 5, 5, 7, 7, 28, 28, 8, 9, 9, - 9, 190, 190, 47, 47, 91, 91, 10, 10, 10, - 10, 96, 96, 100, 100, 100, 101, 101, 101, 101, - 132, 132, 11, 11, 11, 11, 11, 11, 11, 11, - 181, 181, 180, 179, 179, 178, 178, 177, 17, 162, - 164, 164, 163, 163, 163, 163, 156, 135, 135, 135, - 135, 138, 138, 136, 136, 136, 136, 136, 136, 136, - 137, 137, 137, 137, 137, 139, 139, 139, 139, 139, - 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, - 140, 140, 140, 140, 140, 141, 141, 141, 141, 141, - 141, 141, 141, 155, 155, 142, 142, 150, 150, 151, - 151, 151, 148, 148, 149, 149, 152, 152, 152, 143, - 143, 143, 143, 143, 143, 143, 143, 145, 145, 145, - 153, 153, 146, 146, 146, 147, 147, 147, 154, 154, - 154, 154, 154, 144, 144, 157, 157, 172, 172, 171, - 171, 171, 161, 161, 168, 168, 168, 168, 168, 159, - 159, 160, 160, 170, 170, 169, 158, 158, 173, 173, - 173, 173, 184, 185, 183, 183, 183, 183, 183, 165, - 165, 165, 166, 166, 166, 167, 167, 167, 12, 12, - 12, 12, 12, 12, 12, 12, 12, 182, 182, 182, - 182, 182, 182, 182, 182, 182, 182, 182, 176, 174, - 174, 175, 175, 13, 14, 14, 14, 14, 14, 15, - 15, 18, 19, 19, 19, 19, 19, 19, 19, 19, - 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, - 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, - 107, 107, 105, 105, 108, 108, 106, 106, 106, 109, - 109, 109, 133, 133, 133, 20, 20, 22, 22, 23, - 24, 21, 21, 21, 21, 21, 21, 21, 16, 191, - 25, 26, 26, 27, 27, 27, 31, 31, 31, 29, - 29, 30, 30, 36, 36, 35, 35, 37, 37, 37, - 37, 121, 121, 121, 120, 120, 39, 39, 40, 40, - 41, 41, 42, 42, 42, 42, 54, 54, 90, 90, - 92, 92, 43, 43, 43, 43, 44, 44, 45, 45, - 46, 46, 128, 128, 127, 127, 127, 126, 126, 48, - 48, 48, 50, 49, 49, 49, 49, 51, 51, 53, - 53, 52, 52, 55, 55, 55, 55, 56, 56, 38, - 38, 38, 38, 38, 38, 38, 104, 104, 58, 58, - 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, - 68, 68, 68, 68, 68, 68, 59, 59, 59, 59, - 59, 59, 59, 34, 34, 69, 69, 69, 75, 70, - 70, 62, 62, 62, 62, 62, 62, 62, 62, 62, - 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, - 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, - 62, 62, 62, 66, 66, 66, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 65, 65, 65, 65, 65, 65, 65, 65, 192, - 192, 67, 67, 67, 67, 32, 32, 32, 32, 32, - 131, 131, 134, 134, 134, 134, 134, 134, 134, 134, - 134, 134, 134, 134, 134, 79, 79, 33, 33, 77, - 77, 78, 80, 80, 76, 76, 76, 61, 61, 61, - 61, 61, 61, 61, 61, 63, 63, 63, 81, 81, - 82, 82, 83, 83, 84, 84, 85, 86, 86, 86, - 87, 87, 87, 87, 88, 88, 88, 60, 60, 60, - 60, 60, 60, 89, 89, 89, 89, 93, 93, 71, - 71, 73, 73, 72, 74, 94, 94, 98, 95, 95, - 99, 99, 99, 99, 97, 97, 97, 123, 123, 123, - 102, 102, 110, 110, 111, 111, 103, 103, 112, 112, - 112, 112, 112, 112, 112, 112, 112, 112, 113, 113, - 113, 114, 114, 115, 115, 115, 122, 122, 118, 118, - 119, 119, 124, 124, 125, 125, 116, 116, 116, 116, - 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, - 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, - 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, - 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, - 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, - 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, - 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, - 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, - 116, 116, 116, 116, 116, 116, 116, 116, 116, 117, + 4, 5, 5, 7, 7, 29, 29, 8, 9, 9, + 9, 191, 191, 48, 48, 92, 92, 10, 10, 10, + 10, 97, 97, 101, 101, 101, 102, 102, 102, 102, + 133, 133, 11, 11, 11, 11, 11, 11, 11, 11, + 182, 182, 181, 180, 180, 179, 179, 178, 17, 163, + 165, 165, 164, 164, 164, 164, 157, 136, 136, 136, + 136, 139, 139, 137, 137, 137, 137, 137, 137, 137, + 138, 138, 138, 138, 138, 140, 140, 140, 140, 140, + 141, 141, 141, 141, 141, 141, 141, 141, 141, 141, + 141, 141, 141, 141, 141, 142, 142, 142, 142, 142, + 142, 142, 142, 156, 156, 143, 143, 151, 151, 152, + 152, 152, 149, 149, 150, 150, 153, 153, 153, 144, + 144, 144, 144, 144, 144, 144, 144, 146, 146, 146, + 154, 154, 147, 147, 147, 148, 148, 148, 155, 155, + 155, 155, 155, 145, 145, 158, 158, 173, 173, 172, + 172, 172, 162, 162, 169, 169, 169, 169, 169, 160, + 160, 161, 161, 171, 171, 170, 159, 159, 174, 174, + 174, 174, 185, 186, 184, 184, 184, 184, 184, 166, + 166, 166, 167, 167, 167, 168, 168, 168, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 183, 183, 183, + 183, 183, 183, 183, 183, 183, 183, 183, 177, 175, + 175, 176, 176, 13, 18, 18, 14, 14, 14, 14, + 14, 15, 15, 19, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 108, 108, 106, 106, 109, + 109, 107, 107, 107, 110, 110, 110, 134, 134, 134, + 21, 21, 23, 23, 24, 25, 22, 22, 22, 22, + 22, 22, 22, 16, 192, 26, 27, 27, 28, 28, + 28, 32, 32, 32, 30, 30, 31, 31, 37, 37, + 36, 36, 38, 38, 38, 38, 122, 122, 122, 121, + 121, 40, 40, 41, 41, 42, 42, 43, 43, 43, + 43, 55, 55, 91, 91, 93, 93, 44, 44, 44, + 44, 45, 45, 46, 46, 47, 47, 129, 129, 128, + 128, 128, 127, 127, 49, 49, 49, 51, 50, 50, + 50, 50, 52, 52, 54, 54, 53, 53, 56, 56, + 56, 56, 57, 57, 39, 39, 39, 39, 39, 39, + 39, 105, 105, 59, 59, 58, 58, 58, 58, 58, + 58, 58, 58, 58, 58, 69, 69, 69, 69, 69, + 69, 60, 60, 60, 60, 60, 60, 60, 35, 35, + 70, 70, 70, 76, 71, 71, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 67, 67, + 67, 65, 65, 65, 65, 65, 65, 65, 65, 65, + 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, + 65, 65, 66, 66, 66, 66, 66, 66, 66, 66, + 193, 193, 68, 68, 68, 68, 33, 33, 33, 33, + 33, 132, 132, 135, 135, 135, 135, 135, 135, 135, + 135, 135, 135, 135, 135, 135, 80, 80, 34, 34, + 78, 78, 79, 81, 81, 77, 77, 77, 62, 62, + 62, 62, 62, 62, 62, 62, 64, 64, 64, 82, + 82, 83, 83, 84, 84, 85, 85, 86, 87, 87, + 87, 88, 88, 88, 88, 89, 89, 89, 61, 61, + 61, 61, 61, 61, 90, 90, 90, 90, 94, 94, + 72, 72, 74, 74, 73, 75, 95, 95, 99, 96, + 96, 100, 100, 100, 100, 98, 98, 98, 124, 124, + 124, 103, 103, 111, 111, 112, 112, 104, 104, 113, + 113, 113, 113, 113, 113, 113, 113, 113, 113, 114, + 114, 114, 115, 115, 116, 116, 116, 123, 123, 119, + 119, 120, 120, 125, 125, 126, 126, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, @@ -2082,9 +2144,18 @@ var yyR1 = [...]int{ 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, - 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, - 117, 117, 117, 117, 117, 117, 117, 117, 117, 188, - 189, 129, 130, 130, 130, + 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, + 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, + 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, + 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, + 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, + 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, + 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, + 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, + 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, + 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, + 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, + 118, 118, 189, 190, 130, 131, 131, 131, } var yyR2 = [...]int{ @@ -2112,44 +2183,45 @@ var yyR2 = [...]int{ 1, 3, 1, 2, 3, 1, 1, 1, 6, 7, 7, 12, 7, 7, 7, 4, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, - 3, 8, 8, 5, 4, 6, 5, 4, 4, 3, - 2, 3, 4, 4, 4, 4, 4, 4, 4, 4, - 3, 3, 3, 3, 4, 3, 7, 5, 4, 2, - 2, 4, 4, 2, 2, 2, 2, 2, 2, 3, - 1, 1, 0, 1, 1, 1, 0, 2, 2, 0, - 2, 2, 0, 1, 1, 2, 1, 1, 2, 1, - 1, 2, 2, 2, 2, 2, 3, 3, 2, 0, - 2, 0, 2, 1, 2, 2, 0, 1, 1, 0, - 1, 0, 1, 0, 1, 1, 3, 1, 2, 3, - 5, 0, 1, 2, 1, 1, 0, 2, 1, 3, - 1, 1, 1, 3, 1, 3, 3, 7, 1, 3, - 1, 3, 4, 4, 4, 3, 2, 4, 0, 1, - 0, 2, 0, 1, 0, 1, 2, 1, 1, 1, - 2, 2, 1, 2, 3, 2, 3, 2, 2, 2, - 1, 1, 3, 0, 5, 5, 5, 0, 2, 1, - 3, 3, 2, 3, 1, 2, 0, 3, 1, 1, - 3, 3, 4, 4, 5, 3, 4, 5, 6, 2, - 1, 2, 1, 2, 1, 2, 1, 1, 1, 1, - 1, 1, 1, 0, 2, 1, 1, 1, 3, 1, - 3, 1, 1, 1, 1, 1, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 2, 2, 2, 2, 2, 2, 2, 3, 1, - 1, 1, 1, 4, 5, 6, 4, 4, 6, 6, - 6, 6, 8, 8, 6, 8, 8, 9, 7, 5, - 4, 2, 2, 2, 2, 2, 2, 2, 2, 0, - 2, 4, 4, 4, 4, 0, 3, 4, 7, 3, - 1, 1, 2, 3, 3, 1, 2, 2, 1, 2, - 1, 2, 2, 1, 2, 0, 1, 0, 2, 1, - 2, 4, 0, 2, 1, 3, 5, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 2, 2, 0, 3, - 0, 2, 0, 3, 1, 3, 2, 0, 1, 1, - 0, 2, 4, 4, 0, 2, 4, 2, 1, 3, - 5, 4, 6, 1, 3, 3, 5, 0, 5, 1, - 3, 1, 2, 3, 1, 1, 3, 3, 1, 3, - 3, 3, 3, 3, 1, 2, 1, 1, 1, 1, - 1, 1, 0, 2, 0, 3, 0, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, - 1, 1, 1, 0, 1, 1, 0, 2, 1, 1, + 3, 8, 8, 3, 3, 5, 4, 6, 5, 4, + 4, 3, 2, 3, 4, 4, 3, 4, 4, 4, + 4, 4, 4, 3, 2, 3, 3, 2, 3, 4, + 3, 7, 5, 4, 2, 2, 4, 4, 2, 2, + 2, 2, 2, 2, 3, 1, 1, 0, 1, 1, + 1, 0, 2, 2, 0, 2, 2, 0, 1, 1, + 2, 1, 1, 2, 1, 1, 2, 2, 2, 2, + 2, 3, 3, 2, 0, 2, 0, 2, 1, 2, + 2, 0, 1, 1, 0, 1, 0, 1, 0, 1, + 1, 3, 1, 2, 3, 5, 0, 1, 2, 1, + 1, 0, 2, 1, 3, 1, 1, 1, 3, 1, + 3, 3, 7, 1, 3, 1, 3, 4, 4, 4, + 3, 2, 4, 0, 1, 0, 2, 0, 1, 0, + 1, 2, 1, 1, 1, 2, 2, 1, 2, 3, + 2, 3, 2, 2, 2, 1, 1, 3, 0, 5, + 5, 5, 0, 2, 1, 3, 3, 2, 3, 1, + 2, 0, 3, 1, 1, 3, 3, 4, 4, 5, + 3, 4, 5, 6, 2, 1, 2, 1, 2, 1, + 2, 1, 1, 1, 1, 1, 1, 1, 0, 2, + 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, + 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, + 2, 2, 2, 3, 1, 1, 1, 1, 4, 5, + 6, 4, 4, 6, 6, 6, 6, 8, 8, 6, + 8, 8, 6, 8, 8, 6, 8, 8, 9, 7, + 5, 4, 2, 2, 2, 2, 2, 2, 2, 2, + 0, 2, 4, 4, 4, 4, 0, 3, 4, 7, + 3, 1, 1, 2, 3, 3, 1, 2, 2, 1, + 2, 1, 2, 2, 1, 2, 0, 1, 0, 2, + 1, 2, 4, 0, 2, 1, 3, 5, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 2, 2, 0, + 3, 0, 2, 0, 3, 1, 3, 2, 0, 1, + 1, 0, 2, 4, 4, 0, 2, 4, 2, 1, + 3, 5, 4, 6, 1, 3, 3, 5, 0, 5, + 1, 3, 1, 2, 3, 1, 1, 3, 3, 1, + 3, 3, 3, 3, 3, 1, 2, 1, 1, 1, + 1, 1, 1, 0, 2, 0, 3, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 0, 1, 1, 0, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -2171,289 +2243,295 @@ var yyR2 = [...]int{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 0, 0, 1, 1, + 1, 1, 1, 1, 0, 0, 1, 1, } var yyChk = [...]int{ - -1000, -186, -1, -2, -6, -7, -8, -9, -10, -11, - -12, -13, -14, -15, -18, -19, -20, -22, -23, -24, - -21, -16, -3, -4, 6, 7, -28, 9, 10, 30, + -1000, -187, -1, -2, -6, -7, -8, -9, -10, -11, + -12, -13, -14, -15, -19, -20, -21, -23, -24, -25, + -22, -16, -3, -4, 6, 7, -29, 9, 10, 30, -17, 115, 116, 118, 117, 149, 119, 142, 50, 162, 163, 165, 166, 25, 143, 144, 147, 148, 31, 32, - 121, -188, 8, 248, 54, -187, 263, -83, 15, -27, - 5, -25, -191, -25, -25, -25, -25, -25, -162, -164, - 54, 90, -115, 125, 72, 157, 240, 122, 123, 129, - -118, 57, -117, 256, 135, 162, 173, 167, 194, 186, - 136, 184, 187, 227, 214, 224, 66, 165, 236, 145, - 182, 178, 176, 27, 199, 261, 177, 225, 121, 138, - 133, 200, 204, 228, 171, 172, 230, 198, 134, 33, - 258, 35, 153, 231, 202, 197, 193, 196, 170, 192, - 39, 206, 205, 207, 226, 189, 139, 179, 18, 234, - 148, 151, 201, 203, 130, 155, 260, 232, 175, 140, - 152, 147, 235, 141, 166, 229, 238, 38, 211, 169, - 132, 163, 159, 216, 190, 154, 180, 181, 195, 168, - 191, 164, 156, 149, 237, 212, 262, 188, 185, 160, - 158, 217, 218, 219, 220, 221, 161, 259, 233, 183, - 213, -103, 125, 127, 123, 123, 124, 125, 240, 122, - 123, -52, -124, 57, -117, 125, 157, 123, 108, 187, - 115, 215, 124, 33, 155, -133, 123, -105, 158, 214, - 217, 218, 219, 221, 220, 161, 57, 229, 228, 222, - -124, 164, -129, -129, -129, -129, -129, 216, 216, -129, - -2, -87, 17, 16, -5, -3, -188, 6, 20, 21, - -31, 40, 41, -26, -37, 99, -38, -124, -57, 74, - -62, 29, 57, -117, 23, -61, -58, -76, -74, -75, - 108, 109, 110, 97, 98, 105, 75, 111, -66, -64, - -65, -67, 59, 58, 67, 60, 61, 62, 63, 68, - 69, 70, -118, -72, -188, 44, 45, 249, 250, 251, - 252, 255, 253, 77, 34, 239, 247, 246, 245, 243, - 244, 241, 242, 128, 240, 103, 248, -103, -103, 11, - -47, -52, -95, -132, 164, -99, 229, 228, -119, -97, - -118, -116, 227, 187, 226, 120, 73, 22, 24, 209, - 76, 108, 16, 77, 107, 249, 115, 48, 241, 242, - 239, 251, 252, 240, 215, 29, 10, 25, 143, 21, - 101, 117, 80, 81, 146, 23, 144, 70, 19, 51, - 11, 13, 14, 128, 127, 92, 124, 46, 8, 111, - 26, 89, 42, 28, 44, 90, 17, 243, 244, 31, - 255, 150, 103, 49, 36, 74, 68, 71, 52, 72, - 15, 47, 91, 118, 248, 45, 122, 6, 254, 30, - 142, 43, 123, 79, 126, 69, 5, 129, 32, 9, - 50, 53, 245, 246, 247, 34, 78, 12, -163, 90, - -156, 57, -52, 124, -52, 248, -118, -111, 128, -111, - -111, 123, -52, -52, -110, 128, 57, -110, -110, -110, - -52, 112, -52, 57, 30, 240, 57, 155, 123, 156, - 125, -130, -188, -119, -130, -130, -130, 159, 160, -130, - -108, -107, 224, 225, 216, 223, 52, 12, -130, -129, - -129, -189, 56, -88, 19, 31, -38, -124, -84, -85, - -38, -83, -2, -25, 36, -29, 21, 65, 11, -121, - 73, 72, 89, -120, 22, -118, 59, 112, -38, -59, - 92, 74, 90, 91, 76, 94, 93, 104, 97, 98, - 99, 100, 101, 102, 103, 95, 96, 107, 82, 83, - 84, 85, 86, 87, 88, -104, -188, -75, -188, 113, - 114, -62, -62, -62, -62, -62, -62, -62, -62, -188, - -2, -70, -38, -188, -188, -188, -188, -188, -188, -188, - -188, -188, -79, -38, -188, -192, -188, -192, -192, -192, - -192, -192, -192, -192, -188, -188, -188, -188, -53, 26, - -52, -40, -41, -42, -43, -54, -75, -188, -52, -52, - -47, -190, 55, 11, 53, 55, -95, 164, -96, -100, - 230, 232, 82, -123, -118, 59, 29, 30, 56, 55, - -52, -135, -138, -140, -139, -141, -136, -137, 184, 185, - 108, 188, 190, 191, 192, 193, 194, 195, 196, 197, - 198, 199, 30, 145, 180, 181, 182, 183, 200, 201, - 202, 203, 204, 205, 206, 207, 167, 168, 169, 170, - 171, 172, 173, 175, 176, 177, 178, 179, 57, -130, - 125, -181, 53, 57, 74, 57, -52, -52, -130, 126, - -52, 23, 52, -52, 57, 57, -125, -124, -116, -130, - -130, -130, -130, -130, -130, -130, -130, -130, -130, 11, - -106, 11, 92, -52, -38, 9, 92, 55, 18, 112, - 55, -86, 24, 25, -87, -189, -31, -63, -118, 60, - 63, -30, 43, -52, -38, -38, -68, 68, 74, 69, - 70, -120, 99, -125, -119, -116, -62, -69, -72, -75, - 64, 92, 90, 91, 76, -62, -62, -62, -62, -62, - -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, - -131, 57, 59, 57, -61, -61, -118, -36, 21, -35, - -37, -189, 55, -189, -2, -35, -35, -38, -38, -76, - -118, -124, -76, -35, -29, -77, -78, 78, -76, -189, - -35, -36, -35, -35, -91, 151, -52, 30, 55, -48, - -50, -49, -51, 42, 46, 48, 43, 44, 45, 49, - -128, 22, -40, -188, -127, 151, -126, 22, -124, 59, - -91, 53, -40, -52, -99, -96, 55, 231, 233, 234, - 52, 71, -38, -147, 107, -165, -166, -167, -119, 59, - 60, -156, -157, -158, -168, 137, -173, 130, 132, 129, - -159, 138, 124, 28, 56, -152, 68, 74, -148, 212, - -142, 54, -142, -142, -142, -142, -146, 187, -146, -146, - -146, 54, 54, -142, -142, -142, -150, 54, -150, -150, - -151, 54, -151, -122, 53, -52, -179, 259, -180, 57, - -130, 23, -130, -112, 120, 117, 118, -176, 116, 209, - 187, 66, 29, 15, 249, 151, 262, 57, 152, -52, - -52, -130, -52, -109, 90, 12, -124, -124, 38, -38, - -38, -125, -85, -88, -102, 19, 11, 34, 34, -35, - 68, 69, 70, 112, -188, -69, -62, -62, -62, -34, - 146, 73, -189, -189, -35, 55, -38, -189, -189, -189, - 55, 53, 22, 55, 11, 112, 55, 11, -189, -35, - -80, -78, 80, -38, -189, -189, -189, -189, -189, -60, - 30, 34, -2, -188, -188, -94, -98, -76, -41, -42, - -42, -41, -42, 42, 42, 42, 47, 42, 47, 42, - -49, -124, -189, -55, 50, 127, 51, -188, -126, -56, - 12, -40, -56, -100, -101, 235, 232, 238, 57, 59, - 55, -167, 82, 54, 57, 28, -159, -159, -160, 57, - -160, 28, -143, 29, 68, -149, 213, 60, -146, -146, - -147, 30, -147, -147, -147, -155, 59, -155, 60, 60, - 52, -118, -130, -178, -177, -119, -129, -182, 157, 131, - 137, 138, 133, 57, 124, 28, 130, 132, 151, 129, - -182, 157, -113, -114, 126, 22, 124, 28, 151, -130, - -106, 59, -38, 39, 112, -52, -39, 11, 99, -119, - -36, -34, 73, -62, -62, -189, -37, -134, 108, 184, - 145, 182, 178, 198, 189, 211, 180, 212, -131, -134, - -62, -62, -119, -62, -62, 256, -83, 81, -38, 79, - -93, 52, -94, -71, -73, -72, -188, -2, -89, -118, - -92, -118, -56, 55, 82, -45, -44, 52, 53, -46, - 52, -44, 42, 42, 124, 124, 124, -92, -83, -38, - -56, 232, 236, 237, -166, -167, -170, -169, -118, -173, - -160, -160, 54, -145, 52, 59, 60, 61, 68, 239, - 67, 56, -147, -147, 57, 108, 56, 55, 56, 55, - 56, 55, -52, 55, 82, -129, -118, -129, -118, -52, - -129, -118, -109, -56, -40, -189, -62, -189, -142, -142, - -142, -151, -142, 172, -142, 172, -189, -189, -189, 55, - 19, -189, 55, 19, -188, -33, 254, -38, 27, -93, - 55, -189, -189, -189, 55, 112, -189, 55, -83, -98, - -38, -38, 54, -38, -188, -188, -188, -189, -87, 56, - 55, -142, -90, -118, -153, 209, 9, 54, -146, 59, - -146, 60, 60, -130, -177, -167, 54, 26, -81, 13, - -146, 57, -62, -62, -62, -62, -62, -189, 59, 28, - -73, 34, -2, -188, -118, -118, -118, -87, -90, -90, - -90, -90, -127, -172, -171, 53, 134, 66, -169, 56, - 55, -154, 130, 28, 129, 239, 56, -147, -147, 56, - 56, -90, -188, -82, 14, 16, -189, -189, -189, -189, - -32, 92, 259, 9, -71, -2, 112, 56, -189, -189, - -189, -55, -171, 57, -161, 82, 59, 140, -118, -144, - 66, 28, 28, 54, 56, -174, -175, 151, -38, -70, - -189, 257, 49, 260, -94, -189, -118, 60, -52, 59, - 56, -181, -189, 55, -118, 39, 258, 261, 54, -179, - -175, 34, 39, -90, 153, 259, 56, 154, 260, -184, - -185, 52, -188, 261, -185, 52, 10, 9, -62, 150, - -183, 141, 136, 139, 30, -183, -189, -189, 135, 29, - 68, + 121, -189, 8, 250, 54, -188, 265, -84, 15, -28, + 5, -26, -192, -26, -26, -26, -26, -26, -163, -165, + 54, 90, -116, 125, 72, 157, 242, 122, 123, 129, + -119, 57, -118, 258, 135, 162, 173, 167, 194, 186, + 136, 184, 187, 229, 214, 224, 66, 165, 238, 145, + 182, 178, 176, 27, 226, 199, 263, 177, 225, 121, + 138, 133, 200, 204, 230, 171, 172, 232, 198, 134, + 33, 260, 35, 153, 233, 202, 197, 193, 196, 170, + 192, 39, 206, 205, 207, 228, 189, 139, 179, 18, + 236, 148, 151, 227, 201, 203, 130, 155, 262, 234, + 175, 140, 152, 147, 237, 141, 166, 231, 240, 38, + 211, 169, 132, 163, 159, 216, 190, 154, 180, 181, + 195, 168, 191, 164, 156, 149, 239, 212, 264, 188, + 185, 160, 158, 217, 218, 219, 220, 221, 161, 261, + 235, 183, 213, -104, 125, 127, 123, 123, 124, 125, + 242, 122, 123, -53, -125, 57, -118, 125, 157, 123, + 108, 187, 229, 115, 215, 226, 124, 33, 227, 155, + -134, 123, -106, 158, 214, 217, 218, 219, 221, 220, + 161, 57, 231, 230, 222, -125, 164, -130, -130, -130, + -130, -130, 216, 216, -130, -2, -88, 17, 16, -5, + -3, -189, 6, 20, 21, -32, 40, 41, -27, -38, + 99, -39, -125, -58, 74, -63, 29, 57, -118, 23, + -62, -59, -77, -75, -76, 108, 109, 110, 97, 98, + 105, 75, 111, -67, -65, -66, -68, 59, 58, 67, + 60, 61, 62, 63, 68, 69, 70, -119, -73, -189, + 44, 45, 251, 252, 253, 254, 257, 255, 77, 34, + 241, 249, 248, 247, 245, 246, 243, 244, 128, 242, + 103, 250, -104, -104, 11, -48, -53, -96, -133, 164, + -100, 231, 230, -120, -98, -119, -117, 229, 187, 228, + 120, 73, 22, 24, 209, 76, 108, 16, 77, 107, + 251, 115, 48, 243, 244, 241, 253, 254, 242, 215, + 29, 10, 25, 143, 21, 101, 117, 80, 81, 146, + 23, 144, 70, 19, 51, 11, 13, 14, 128, 127, + 92, 124, 46, 8, 111, 26, 89, 42, 28, 44, + 90, 17, 245, 246, 31, 257, 150, 103, 49, 36, + 74, 68, 71, 52, 72, 15, 47, 91, 118, 250, + 45, 122, 6, 256, 30, 142, 43, 123, 79, 126, + 69, 5, 129, 32, 9, 50, 53, 247, 248, 249, + 34, 78, 12, -164, 90, -157, 57, -53, 124, -53, + 250, -119, -112, 128, -112, -112, 123, -53, -18, -53, + -111, 128, 57, -111, -111, -111, -53, 112, -53, 57, + 30, -131, -189, -120, 242, 57, 155, 123, 156, 125, + -131, -131, -131, -131, 159, 160, -131, -109, -108, 224, + 225, 216, 223, 52, 12, -131, -130, -130, -190, 56, + -89, 19, 31, -39, -125, -85, -86, -39, -84, -2, + -26, 36, -30, 21, 65, 11, -122, 73, 72, 89, + -121, 22, -119, 59, 112, -39, -60, 92, 74, 90, + 91, 76, 94, 93, 104, 97, 98, 99, 100, 101, + 102, 103, 95, 96, 107, 82, 83, 84, 85, 86, + 87, 88, -105, -189, -76, -189, 113, 114, -63, -63, + -63, -63, -63, -63, -63, -63, -189, -2, -71, -39, + -189, -189, -189, -189, -189, -189, -189, -189, -189, -80, + -39, -189, -193, -189, -193, -193, -193, -193, -193, -193, + -193, -189, -189, -189, -189, -54, 26, -53, -41, -42, + -43, -44, -55, -76, -189, -53, -53, -48, -191, 55, + 11, 53, 55, -96, 164, -97, -101, 232, 234, 82, + -124, -119, 59, 29, 30, 56, 55, -53, -136, -139, + -141, -140, -142, -137, -138, 184, 185, 108, 188, 190, + 191, 192, 193, 194, 195, 196, 197, 198, 199, 30, + 145, 180, 181, 182, 183, 200, 201, 202, 203, 204, + 205, 206, 207, 167, 168, 169, 170, 171, 172, 173, + 175, 176, 177, 178, 179, 57, -131, 125, -182, 53, + 57, 74, 57, -53, -53, -131, 55, 126, -48, 23, + 52, -53, 57, 57, -126, -125, -117, -131, -131, -131, + -131, -131, -131, -131, -131, -131, -131, 11, -107, 11, + 92, -53, -39, 9, 92, 55, 18, 112, 55, -87, + 24, 25, -88, -190, -32, -64, -119, 60, 63, -31, + 43, -53, -39, -39, -69, 68, 74, 69, 70, -121, + 99, -126, -120, -117, -63, -70, -73, -76, 64, 92, + 90, 91, 76, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -132, 57, + 59, 57, -62, -62, -119, -37, 21, -36, -38, -190, + 55, -190, -2, -36, -36, -39, -39, -77, 59, -119, + -125, -77, 59, -36, -30, -78, -79, 78, -77, -190, + -36, -37, -36, -36, -92, 151, -53, 30, 55, -49, + -51, -50, -52, 42, 46, 48, 43, 44, 45, 49, + -129, 22, -41, -189, -128, 151, -127, 22, -125, 59, + -92, 53, -41, -53, -100, -97, 55, 233, 235, 236, + 52, 71, -39, -148, 107, -166, -167, -168, -120, 59, + 60, -157, -158, -159, -169, 137, -174, 130, 132, 129, + -160, 138, 124, 28, 56, -153, 68, 74, -149, 212, + -143, 54, -143, -143, -143, -143, -147, 187, -147, -147, + -147, 54, 54, -143, -143, -143, -151, 54, -151, -151, + -152, 54, -152, -123, 53, -53, -180, 261, -181, 57, + -131, 23, -131, -113, 120, 117, 118, -177, 116, 209, + 187, 66, 29, 15, 251, 151, 264, 57, 152, -53, + -53, -53, -131, -53, -110, 90, 12, -125, -125, 38, + -39, -39, -126, -86, -89, -103, 19, 11, 34, 34, + -36, 68, 69, 70, 112, -189, -70, -63, -63, -63, + -35, 146, 73, -190, -190, -36, 55, -39, -190, -190, + -190, 55, 53, 22, 55, 11, 55, 11, 112, 55, + 11, 55, 11, -190, -36, -81, -79, 80, -39, -190, + -190, -190, -190, -190, -61, 30, 34, -2, -189, -189, + -95, -99, -77, -42, -43, -43, -42, -43, 42, 42, + 42, 47, 42, 47, 42, -50, -125, -190, -56, 50, + 127, 51, -189, -127, -57, 12, -41, -57, -101, -102, + 237, 234, 240, 57, 59, 55, -168, 82, 54, 57, + 28, -160, -160, -161, 57, -161, 28, -144, 29, 68, + -150, 213, 60, -147, -147, -148, 30, -148, -148, -148, + -156, 59, -156, 60, 60, 52, -119, -131, -179, -178, + -120, -130, -183, 157, 131, 137, 138, 133, 57, 124, + 28, 130, 132, 151, 129, -183, 157, -114, -115, 126, + 22, 124, 28, 151, 126, -131, -107, 59, -39, 39, + 112, -53, -40, 11, 99, -120, -37, -35, 73, -63, + -63, -190, -38, -135, 108, 184, 145, 182, 178, 198, + 189, 211, 180, 212, -132, -135, -63, -63, -63, -63, + -120, -63, -63, -63, -63, 258, -84, 81, -39, 79, + -94, 52, -95, -72, -74, -73, -189, -2, -90, -119, + -93, -119, -57, 55, 82, -46, -45, 52, 53, -47, + 52, -45, 42, 42, 124, 124, 124, -93, -84, -39, + -57, 234, 238, 239, -167, -168, -171, -170, -119, -174, + -161, -161, 54, -146, 52, 59, 60, 61, 68, 241, + 67, 56, -148, -148, 57, 108, 56, 55, 56, 55, + 56, 55, -53, 55, 82, -130, -119, -130, -119, -53, + -130, -119, -53, -110, -57, -41, -190, -63, -190, -143, + -143, -143, -152, -143, 172, -143, 172, -190, -190, -190, + 55, 19, -190, 55, 19, -190, 55, 19, -190, 55, + 19, -189, -34, 256, -39, 27, -94, 55, -190, -190, + -190, 55, 112, -190, 55, -84, -99, -39, -39, 54, + -39, -189, -189, -189, -190, -88, 56, 55, -143, -91, + -119, -154, 209, 9, 54, -147, 59, -147, 60, 60, + -131, -178, -168, 54, 26, -82, 13, -147, 57, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -190, 59, + 28, -74, 34, -2, -189, -119, -119, -119, -88, -91, + -91, -91, -91, -128, -173, -172, 53, 134, 66, -170, + 56, 55, -155, 130, 28, 129, 241, 56, -148, -148, + 56, 56, -91, -189, -83, 14, 16, -190, -190, -190, + -190, -190, -190, -190, -190, -33, 92, 261, 9, -72, + -2, 112, 56, -190, -190, -190, -56, -172, 57, -162, + 82, 59, 140, -119, -145, 66, 28, 28, 54, 56, + -175, -176, 151, -39, -71, -190, 259, 49, 262, -95, + -190, -119, 60, -53, 59, 56, -182, -190, 55, -119, + 39, 260, 263, 54, -180, -176, 34, 39, -91, 153, + 261, 56, 154, 262, -185, -186, 52, -189, 263, -186, + 52, 10, 9, -63, 150, -184, 141, 136, 139, 30, + -184, -190, -190, 135, 29, 68, } var yyDef = [...]int{ 23, -2, 2, -2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - 21, 22, 542, 0, 309, 309, 309, 309, 309, 309, - 0, 613, 596, 0, 0, 0, 0, -2, 296, 297, - 0, 299, 300, 831, 831, 831, 831, 831, 0, 0, - 831, 0, 35, 36, 829, 1, 3, 550, 0, 0, - 313, 316, 311, 0, 596, 596, 0, 0, 62, 63, - 0, 0, 0, 816, 0, 817, 594, 594, 594, 614, - 615, 618, 619, 719, 720, 721, 722, 723, 724, 725, - 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, - 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, - 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, - 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, - 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, - 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, - 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, - 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, - 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, - 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, - 828, 0, 0, 597, 0, 592, 0, 592, 592, 592, - 0, 250, 381, 622, 623, 816, 817, 0, 0, 0, - 0, 832, 832, 832, 832, 0, 832, 0, 269, 270, - 273, 274, 275, 276, 277, 278, 832, 293, 294, 283, - 295, 298, 301, 302, 303, 304, 305, 831, 831, 308, - 29, 554, 0, 0, 542, 31, 0, 309, 314, 315, - 319, 317, 318, 310, 0, 327, 331, 0, 389, 0, - 394, 396, -2, -2, 0, 431, 432, 433, 434, 435, - 0, 0, 0, 0, 0, 0, 0, 0, 459, 460, - 461, 462, 527, 528, 529, 530, 531, 532, 533, 534, - 398, 399, 524, 574, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 515, 0, 489, 489, 489, 489, 489, - 489, 489, 489, 0, 0, 0, 0, 0, 0, 0, - 0, 43, 47, 0, 807, 578, -2, -2, 0, 0, - 620, 621, -2, 728, -2, 626, 627, 628, 629, 630, - 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, - 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, - 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, - 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, - 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, - 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, - 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, - 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, - 711, 712, 713, 714, 715, 716, 717, 718, 0, 0, - 82, 0, 80, 0, 832, 0, 70, 0, 0, 0, - 0, 0, 832, 0, 0, 0, 0, 0, 0, 0, - 249, 0, 251, 832, 832, 832, 832, 832, 832, 832, - 832, 260, 833, 834, 261, 262, 263, 832, 832, 265, - 0, 286, 284, 285, 280, 281, 0, 0, 279, 306, - 307, 30, 830, 24, 0, 0, 551, 0, 543, 544, - 547, 550, 29, 316, 0, 321, 320, 312, 0, 328, - 0, 0, 0, 332, 0, 334, 335, 0, 392, 0, + 21, 22, 553, 0, 314, 314, 314, 314, 314, 314, + 0, 624, 607, 0, 0, 0, 0, -2, 301, 302, + 0, 304, 305, 844, 844, 844, 844, 844, 0, 0, + 844, 0, 35, 36, 842, 1, 3, 561, 0, 0, + 318, 321, 316, 0, 607, 607, 0, 0, 62, 63, + 0, 0, 0, 829, 0, 830, 605, 605, 605, 625, + 626, 629, 630, 730, 731, 732, 733, 734, 735, 736, + 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, + 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, + 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, + 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, + 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, + 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, + 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, + 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, + 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, + 827, 828, 831, 832, 833, 834, 835, 836, 837, 838, + 839, 840, 841, 0, 0, 608, 0, 603, 0, 603, + 603, 603, 0, 252, 386, 633, 634, 829, 830, 0, + 0, 0, 845, 0, 845, 264, 845, 845, 267, 845, + 0, 845, 0, 274, 275, 278, 279, 280, 281, 282, + 283, 845, 298, 299, 288, 300, 303, 306, 307, 308, + 309, 310, 844, 844, 313, 29, 565, 0, 0, 553, + 31, 0, 314, 319, 320, 324, 322, 323, 315, 0, + 332, 336, 0, 394, 0, 399, 401, -2, -2, 0, + 436, 437, 438, 439, 440, 0, 0, 0, 0, 0, + 0, 0, 0, 464, 465, 466, 467, 538, 539, 540, + 541, 542, 543, 544, 545, 403, 404, 535, 585, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 526, 0, + 500, 500, 500, 500, 500, 500, 500, 500, 0, 0, + 0, 0, 0, 0, 0, 0, 43, 47, 0, 820, + 589, -2, -2, 0, 0, 631, 632, -2, 739, -2, + 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, + 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, + 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, + 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, + 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, + 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, + 697, 698, 699, 700, 701, 702, 703, 704, 705, 706, + 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, + 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, + 727, 728, 729, 0, 0, 82, 0, 80, 0, 845, + 0, 70, 0, 0, 0, 0, 0, 845, 243, 0, + 0, 0, 0, 0, 0, 0, 251, 0, 253, 845, + 845, 256, 846, 847, 845, 845, 845, 845, 845, 845, + 263, 265, 266, 268, 845, 845, 270, 0, 291, 289, + 290, 285, 286, 0, 0, 284, 311, 312, 30, 843, + 24, 0, 0, 562, 0, 554, 555, 558, 561, 29, + 321, 0, 326, 325, 317, 0, 333, 0, 0, 0, + 337, 0, 339, 340, 0, 397, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 416, 417, - 418, 419, 420, 421, 422, 395, 0, 409, 0, 0, - 0, 451, 452, 453, 454, 455, 456, 457, 0, 323, - 29, 0, 429, 0, 0, 0, 0, 0, 0, 0, - 0, 319, 0, 516, 0, 481, 0, 482, 483, 484, - 485, 486, 487, 488, 0, 323, 0, 0, 45, 0, - 380, 0, 338, 340, 341, 342, -2, 0, 364, -2, - 0, 0, 0, 41, 42, 0, 48, 807, 50, 51, - 0, 0, 0, 165, 587, 588, 589, 585, 209, 0, - 0, 146, 142, 88, 89, 90, 135, 92, 135, 135, - 135, 135, 162, 162, 162, 162, 118, 119, 120, 121, - 122, 0, 0, 105, 135, 135, 135, 109, 125, 126, - 127, 128, 129, 130, 131, 132, 93, 94, 95, 96, - 97, 98, 99, 137, 137, 137, 139, 139, 616, 65, - 0, 73, 0, 832, 0, 832, 78, 0, 225, 0, - 244, 593, 0, 832, 247, 248, 382, 624, 625, 252, - 253, 254, 255, 256, 257, 258, 259, 264, 268, 0, - 289, 0, 0, 272, 271, 555, 0, 0, 0, 0, - 0, 546, 548, 549, 554, 32, 319, 0, 535, 0, - 0, 0, 322, 27, 390, 391, 393, 410, 0, 412, - 414, 333, 329, 0, 525, -2, 400, 401, 425, 426, - 427, 0, 0, 0, 0, 423, 405, 0, 436, 437, - 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, - 450, 500, 501, 0, 448, 449, 458, 0, 0, 324, - 325, 428, 0, 573, 29, 0, 0, 0, 0, 0, - 524, 0, 0, 0, 0, 522, 519, 0, 0, 490, - 0, 0, 0, 0, 0, 0, 379, 0, 0, 0, - 0, 0, 0, 369, 0, 0, 372, 0, 0, 0, - 0, 363, 0, 0, 383, 777, 365, 0, 367, 368, - 387, 0, 387, 44, 579, 49, 0, 0, 54, 55, - 580, 581, 582, 583, 0, 79, 210, 212, 215, 216, + 0, 0, 0, 0, 0, 421, 422, 423, 424, 425, + 426, 427, 400, 0, 414, 0, 0, 0, 456, 457, + 458, 459, 460, 461, 462, 0, 328, 29, 0, 434, + 0, 0, 0, 0, 0, 0, 0, 0, 324, 0, + 527, 0, 492, 0, 493, 494, 495, 496, 497, 498, + 499, 0, 328, 0, 0, 45, 0, 385, 0, 343, + 345, 346, 347, -2, 0, 369, -2, 0, 0, 0, + 41, 42, 0, 48, 820, 50, 51, 0, 0, 0, + 165, 598, 599, 600, 596, 209, 0, 0, 146, 142, + 88, 89, 90, 135, 92, 135, 135, 135, 135, 162, + 162, 162, 162, 118, 119, 120, 121, 122, 0, 0, + 105, 135, 135, 135, 109, 125, 126, 127, 128, 129, + 130, 131, 132, 93, 94, 95, 96, 97, 98, 99, + 137, 137, 137, 139, 139, 627, 65, 0, 73, 0, + 845, 0, 845, 78, 0, 225, 0, 0, 246, 604, + 0, 845, 249, 250, 387, 635, 636, 254, 255, 257, + 258, 259, 260, 261, 262, 269, 273, 0, 294, 0, + 0, 277, 276, 566, 0, 0, 0, 0, 0, 557, + 559, 560, 565, 32, 324, 0, 546, 0, 0, 0, + 327, 27, 395, 396, 398, 415, 0, 417, 419, 338, + 334, 0, 536, -2, 405, 406, 430, 431, 432, 0, + 0, 0, 0, 428, 410, 0, 441, 442, 443, 444, + 445, 446, 447, 448, 449, 450, 451, 452, 455, 511, + 512, 0, 453, 454, 463, 0, 0, 329, 330, 433, + 0, 584, 29, 0, 0, 0, 0, 0, 0, 535, + 0, 0, 0, 0, 0, 533, 530, 0, 0, 501, + 0, 0, 0, 0, 0, 0, 384, 0, 0, 0, + 0, 0, 0, 374, 0, 0, 377, 0, 0, 0, + 0, 368, 0, 0, 388, 789, 370, 0, 372, 373, + 392, 0, 392, 44, 590, 49, 0, 0, 54, 55, + 591, 592, 593, 594, 0, 79, 210, 212, 215, 216, 217, 83, 84, 85, 0, 0, 197, 0, 0, 191, 191, 0, 189, 190, 81, 149, 147, 0, 144, 143, 91, 0, 162, 162, 112, 113, 165, 0, 165, 165, 165, 0, 0, 106, 107, 108, 100, 0, 101, 102, - 103, 0, 104, 0, 0, 832, 67, 0, 71, 72, - 68, 595, 69, 831, 0, 0, 608, 226, 598, 599, - 600, 601, 602, 603, 604, 605, 606, 607, 0, 243, - 832, 246, 286, 267, 0, 0, 287, 288, 0, 552, - 553, 0, 545, 25, 0, 590, 591, 536, 537, 336, - 411, 413, 415, 0, 323, 402, 423, 406, 0, 403, - 0, 0, 397, 463, 0, 0, 430, -2, 466, 467, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 542, - 0, 520, 0, 0, 480, 491, 492, 493, 494, 567, - 0, 0, -2, 0, 0, 387, 575, 0, 339, 358, - 360, 0, 355, 370, 371, 373, 0, 375, 0, 377, - 378, 343, 345, 346, 0, 0, 0, 0, 366, 542, - 0, 387, 40, 52, 53, 0, 0, 59, 166, 167, - 0, 213, 0, 0, 0, 184, 191, 191, 187, 192, - 188, 0, 157, 0, 148, 87, 145, 0, 165, 165, - 114, 0, 115, 116, 117, 0, 133, 0, 0, 0, - 0, 617, 66, 74, 75, 0, 218, 831, 0, 227, - 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, - 831, 0, 0, 831, 609, 610, 611, 612, 0, 245, - 289, 290, 291, 556, 0, 26, 387, 0, 330, 526, - 0, 404, 0, 424, 407, 464, 326, 0, 135, 135, - 505, 135, 139, 508, 135, 510, 135, 513, 0, 0, - 0, 0, 525, 0, 0, 0, 517, 479, 523, 0, - 33, 0, 567, 557, 569, 571, 0, 29, 0, 563, - 0, 350, 542, 0, 0, 352, 359, 0, 0, 353, - 0, 354, 374, 376, 0, 0, 0, 0, 550, 388, + 103, 0, 104, 0, 0, 845, 67, 0, 71, 72, + 68, 606, 69, 844, 0, 0, 619, 226, 609, 610, + 611, 612, 613, 614, 615, 616, 617, 618, 0, 0, + 244, 845, 248, 291, 272, 0, 0, 292, 293, 0, + 563, 564, 0, 556, 25, 0, 601, 602, 547, 548, + 341, 416, 418, 420, 0, 328, 407, 428, 411, 0, + 408, 0, 0, 402, 468, 0, 0, 435, -2, 471, + 472, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 553, 0, 531, 0, 0, 491, + 502, 503, 504, 505, 578, 0, 0, -2, 0, 0, + 392, 586, 0, 344, 363, 365, 0, 360, 375, 376, + 378, 0, 380, 0, 382, 383, 348, 350, 351, 0, + 0, 0, 0, 371, 553, 0, 392, 40, 52, 53, + 0, 0, 59, 166, 167, 0, 213, 0, 0, 0, + 184, 191, 191, 187, 192, 188, 0, 157, 0, 148, + 87, 145, 0, 165, 165, 114, 0, 115, 116, 117, + 0, 133, 0, 0, 0, 0, 628, 66, 74, 75, + 0, 218, 844, 0, 227, 228, 229, 230, 231, 232, + 233, 234, 235, 236, 237, 844, 0, 0, 844, 620, + 621, 622, 623, 0, 0, 247, 294, 295, 296, 567, + 0, 26, 392, 0, 335, 537, 0, 409, 0, 429, + 412, 469, 331, 0, 135, 135, 516, 135, 139, 519, + 135, 521, 135, 524, 0, 0, 0, 0, 0, 0, + 536, 0, 0, 0, 0, 0, 528, 490, 534, 0, + 33, 0, 578, 568, 580, 582, 0, 29, 0, 574, + 0, 355, 553, 0, 0, 357, 364, 0, 0, 358, + 0, 359, 379, 381, 0, 0, 0, 0, 561, 393, 39, 56, 57, 58, 211, 214, 0, 193, 135, 196, 185, 186, 0, 160, 0, 150, 151, 152, 153, 154, 156, 136, 110, 111, 163, 164, 162, 0, 162, 0, - 140, 0, 832, 0, 0, 219, 0, 220, 222, 223, - 224, 0, 266, 538, 337, 465, 408, 468, 502, 162, - 506, 507, 509, 511, 512, 514, 470, 469, 471, 0, - 0, 474, 0, 0, 0, 0, 0, 521, 0, 34, - 0, 572, -2, 0, 0, 0, 46, 0, 550, 576, - 577, 356, 0, 361, 0, 0, 0, 364, 38, 176, - 0, 195, 0, 348, 168, 161, 0, 0, 165, 134, - 165, 0, 0, 64, 76, 77, 0, 0, 540, 0, - 503, 504, 0, 0, 0, 0, 495, 478, 518, 0, - 570, 0, -2, 0, 565, 564, 351, 37, 0, 0, - 0, 0, 383, 175, 177, 0, 182, 0, 194, 0, - 0, 173, 0, 170, 172, 158, 155, 123, 124, 138, - 141, 0, 0, 28, 0, 0, 472, 473, 475, 476, - 0, 0, 0, 0, 560, 29, 0, 357, 384, 385, - 386, 347, 178, 179, 0, 183, 181, 0, 349, 86, - 0, 169, 171, 0, 70, 0, 239, 0, 541, 539, - 477, 0, 0, 0, 568, -2, 566, 180, 0, 174, - 159, 73, 238, 0, 0, 496, 0, 499, 0, 221, - 240, 0, 497, 0, 0, 0, 198, 0, 0, 199, - 200, 0, 0, 498, 201, 0, 0, 0, 0, 0, - 202, 204, 205, 0, 0, 203, 241, 242, 206, 207, - 208, + 140, 0, 845, 0, 0, 219, 0, 220, 222, 223, + 224, 0, 245, 271, 549, 342, 470, 413, 473, 513, + 162, 517, 518, 520, 522, 523, 525, 475, 474, 476, + 0, 0, 482, 0, 0, 479, 0, 0, 485, 0, + 0, 0, 0, 0, 532, 0, 34, 0, 583, -2, + 0, 0, 0, 46, 0, 561, 587, 588, 361, 0, + 366, 0, 0, 0, 369, 38, 176, 0, 195, 0, + 353, 168, 161, 0, 0, 165, 134, 165, 0, 0, + 64, 76, 77, 0, 0, 551, 0, 514, 515, 0, + 0, 0, 0, 0, 0, 0, 0, 506, 489, 529, + 0, 581, 0, -2, 0, 576, 575, 356, 37, 0, + 0, 0, 0, 388, 175, 177, 0, 182, 0, 194, + 0, 0, 173, 0, 170, 172, 158, 155, 123, 124, + 138, 141, 0, 0, 28, 0, 0, 477, 478, 483, + 484, 480, 481, 486, 487, 0, 0, 0, 0, 571, + 29, 0, 362, 389, 390, 391, 352, 178, 179, 0, + 183, 181, 0, 354, 86, 0, 169, 171, 0, 70, + 0, 239, 0, 552, 550, 488, 0, 0, 0, 579, + -2, 577, 180, 0, 174, 159, 73, 238, 0, 0, + 507, 0, 510, 0, 221, 240, 0, 508, 0, 0, + 0, 198, 0, 0, 199, 200, 0, 0, 509, 201, + 0, 0, 0, 0, 0, 202, 204, 205, 0, 0, + 203, 241, 242, 206, 207, 208, } var yyTok1 = [...]int{ @@ -2462,7 +2540,7 @@ var yyTok1 = [...]int{ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 75, 3, 3, 3, 102, 94, 3, 54, 56, 99, 97, 55, 98, 112, 100, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 263, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 265, 83, 82, 84, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, @@ -2497,7 +2575,7 @@ var yyTok2 = [...]int{ 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, - 259, 260, 261, 262, + 259, 260, 261, 262, 263, 264, } var yyTok3 = [...]int{ 0, @@ -3132,19 +3210,19 @@ yydefault: //line sql.y:561 { // Change this to an alter statement - yyVAL.statement = &DDL{Action: AlterStr, Table: yyDollar[7].tableName, NewName: yyDollar[7].tableName} + yyVAL.statement = &DDL{Action: AlterStr, Table: yyDollar[7].tableName} } case 65: yyDollar = yyS[yypt-4 : yypt+1] //line sql.y:566 { - yyVAL.statement = &DDL{Action: CreateStr, NewName: yyDollar[3].tableName.ToViewName()} + yyVAL.statement = &DDL{Action: CreateStr, Table: yyDollar[3].tableName.ToViewName()} } case 66: yyDollar = yyS[yypt-6 : yypt+1] //line sql.y:570 { - yyVAL.statement = &DDL{Action: CreateStr, NewName: yyDollar[5].tableName.ToViewName()} + yyVAL.statement = &DDL{Action: CreateStr, Table: yyDollar[5].tableName.ToViewName()} } case 67: yyDollar = yyS[yypt-5 : yypt+1] @@ -3222,7 +3300,7 @@ yydefault: yyDollar = yyS[yypt-4 : yypt+1] //line sql.y:634 { - yyVAL.ddl = &DDL{Action: CreateStr, NewName: yyDollar[4].tableName} + yyVAL.ddl = &DDL{Action: CreateStr, Table: yyDollar[4].tableName} setDDL(yylex, yyVAL.ddl) } case 79: @@ -4076,19 +4154,19 @@ yydefault: yyDollar = yyS[yypt-6 : yypt+1] //line sql.y:1292 { - yyVAL.statement = &DDL{Action: AlterStr, Table: yyDollar[4].tableName, NewName: yyDollar[4].tableName} + yyVAL.statement = &DDL{Action: AlterStr, Table: yyDollar[4].tableName} } case 219: yyDollar = yyS[yypt-7 : yypt+1] //line sql.y:1296 { - yyVAL.statement = &DDL{Action: AlterStr, Table: yyDollar[4].tableName, NewName: yyDollar[4].tableName} + yyVAL.statement = &DDL{Action: AlterStr, Table: yyDollar[4].tableName} } case 220: yyDollar = yyS[yypt-7 : yypt+1] //line sql.y:1300 { - yyVAL.statement = &DDL{Action: AlterStr, Table: yyDollar[4].tableName, NewName: yyDollar[4].tableName} + yyVAL.statement = &DDL{Action: AlterStr, Table: yyDollar[4].tableName} } case 221: yyDollar = yyS[yypt-12 : yypt+1] @@ -4122,20 +4200,20 @@ yydefault: //line sql.y:1327 { // Change this to a rename statement - yyVAL.statement = &DDL{Action: RenameStr, Table: yyDollar[4].tableName, NewName: yyDollar[7].tableName} + yyVAL.statement = &DDL{Action: RenameStr, FromTables: TableNames{yyDollar[4].tableName}, ToTables: TableNames{yyDollar[7].tableName}} } case 224: yyDollar = yyS[yypt-7 : yypt+1] //line sql.y:1332 { // Rename an index can just be an alter - yyVAL.statement = &DDL{Action: AlterStr, Table: yyDollar[4].tableName, NewName: yyDollar[4].tableName} + yyVAL.statement = &DDL{Action: AlterStr, Table: yyDollar[4].tableName} } case 225: yyDollar = yyS[yypt-4 : yypt+1] //line sql.y:1337 { - yyVAL.statement = &DDL{Action: AlterStr, Table: yyDollar[3].tableName.ToViewName(), NewName: yyDollar[3].tableName.ToViewName()} + yyVAL.statement = &DDL{Action: AlterStr, Table: yyDollar[3].tableName.ToViewName()} } case 226: yyDollar = yyS[yypt-5 : yypt+1] @@ -4174,162 +4252,194 @@ yydefault: yyVAL.partDef = &PartitionDefinition{Name: yyDollar[2].colIdent, Maxvalue: true} } case 243: - yyDollar = yyS[yypt-5 : yypt+1] + yyDollar = yyS[yypt-3 : yypt+1] //line sql.y:1386 { - yyVAL.statement = &DDL{Action: RenameStr, Table: yyDollar[3].tableName, NewName: yyDollar[5].tableName} + yyVAL.statement = yyDollar[3].ddl } case 244: - yyDollar = yyS[yypt-4 : yypt+1] + yyDollar = yyS[yypt-3 : yypt+1] //line sql.y:1392 + { + yyVAL.ddl = &DDL{Action: RenameStr, FromTables: TableNames{yyDollar[1].tableName}, ToTables: TableNames{yyDollar[3].tableName}} + } + case 245: + yyDollar = yyS[yypt-5 : yypt+1] +//line sql.y:1396 + { + yyVAL.ddl = yyDollar[1].ddl + yyVAL.ddl.FromTables = append(yyVAL.ddl.FromTables, yyDollar[3].tableName) + yyVAL.ddl.ToTables = append(yyVAL.ddl.ToTables, yyDollar[5].tableName) + } + case 246: + yyDollar = yyS[yypt-4 : yypt+1] +//line sql.y:1404 { var exists bool if yyDollar[3].byt != 0 { exists = true } - yyVAL.statement = &DDL{Action: DropStr, Table: yyDollar[4].tableName, IfExists: exists} + yyVAL.statement = &DDL{Action: DropStr, FromTables: yyDollar[4].tableNames, IfExists: exists} } - case 245: + case 247: yyDollar = yyS[yypt-6 : yypt+1] -//line sql.y:1400 +//line sql.y:1412 { // Change this to an alter statement - yyVAL.statement = &DDL{Action: AlterStr, Table: yyDollar[5].tableName, NewName: yyDollar[5].tableName} + yyVAL.statement = &DDL{Action: AlterStr, Table: yyDollar[5].tableName} } - case 246: + case 248: yyDollar = yyS[yypt-5 : yypt+1] -//line sql.y:1405 +//line sql.y:1417 { var exists bool if yyDollar[3].byt != 0 { exists = true } - yyVAL.statement = &DDL{Action: DropStr, Table: yyDollar[4].tableName.ToViewName(), IfExists: exists} + yyVAL.statement = &DDL{Action: DropStr, FromTables: TableNames{yyDollar[4].tableName.ToViewName()}, IfExists: exists} } - case 247: + case 249: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:1413 +//line sql.y:1425 { yyVAL.statement = &DBDDL{Action: DropStr, DBName: string(yyDollar[4].bytes)} } - case 248: + case 250: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:1417 +//line sql.y:1429 { yyVAL.statement = &DBDDL{Action: DropStr, DBName: string(yyDollar[4].bytes)} } - case 249: + case 251: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:1423 +//line sql.y:1435 { yyVAL.statement = &DDL{Action: TruncateStr, Table: yyDollar[3].tableName} } - case 250: + case 252: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1427 +//line sql.y:1439 { yyVAL.statement = &DDL{Action: TruncateStr, Table: yyDollar[2].tableName} } - case 251: + case 253: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:1432 +//line sql.y:1444 { - yyVAL.statement = &DDL{Action: AlterStr, Table: yyDollar[3].tableName, NewName: yyDollar[3].tableName} + yyVAL.statement = &DDL{Action: AlterStr, Table: yyDollar[3].tableName} } - case 252: + case 254: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:1438 +//line sql.y:1450 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes) + " " + string(yyDollar[3].bytes)} } - case 253: + case 255: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:1442 +//line sql.y:1455 { - yyVAL.statement = &Show{Type: string(yyDollar[2].bytes) + " " + string(yyDollar[3].bytes)} + yyVAL.statement = &Show{Type: CharsetStr} } - case 254: + case 256: + yyDollar = yyS[yypt-3 : yypt+1] +//line sql.y:1459 + { + yyVAL.statement = &Show{Type: string(yyDollar[2].bytes)} + } + case 257: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:1446 +//line sql.y:1463 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes) + " " + string(yyDollar[3].bytes)} } - case 255: + case 258: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:1451 +//line sql.y:1468 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes) + " " + string(yyDollar[3].bytes)} } - case 256: + case 259: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:1455 +//line sql.y:1472 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes) + " " + string(yyDollar[3].bytes)} } - case 257: + case 260: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:1459 +//line sql.y:1476 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes) + " " + string(yyDollar[3].bytes)} } - case 258: + case 261: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:1463 +//line sql.y:1480 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes) + " " + string(yyDollar[3].bytes)} } - case 259: + case 262: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:1467 +//line sql.y:1484 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes) + " " + string(yyDollar[3].bytes)} } - case 260: + case 263: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:1471 +//line sql.y:1488 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes)} } - case 261: + case 264: + yyDollar = yyS[yypt-2 : yypt+1] +//line sql.y:1492 + { + yyVAL.statement = &Show{Type: string(yyDollar[2].bytes)} + } + case 265: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:1475 +//line sql.y:1496 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes)} } - case 262: + case 266: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:1479 +//line sql.y:1500 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes)} } - case 263: + case 267: + yyDollar = yyS[yypt-2 : yypt+1] +//line sql.y:1504 + { + yyVAL.statement = &Show{Type: string(yyDollar[2].bytes)} + } + case 268: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:1483 +//line sql.y:1508 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes)} } - case 264: + case 269: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:1487 +//line sql.y:1512 { yyVAL.statement = &Show{Scope: yyDollar[2].str, Type: string(yyDollar[3].bytes)} } - case 265: + case 270: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:1491 +//line sql.y:1516 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes)} } - case 266: + case 271: yyDollar = yyS[yypt-7 : yypt+1] -//line sql.y:1495 +//line sql.y:1520 { showTablesOpt := &ShowTablesOpt{Full: yyDollar[2].str, DbName: yyDollar[6].str, Filter: yyDollar[7].showFilter} yyVAL.statement = &Show{Type: string(yyDollar[3].str), ShowTablesOpt: showTablesOpt, OnTable: yyDollar[5].tableName} } - case 267: + case 272: yyDollar = yyS[yypt-5 : yypt+1] -//line sql.y:1500 +//line sql.y:1525 { // this is ugly, but I couldn't find a better way for now if yyDollar[3].str == "processlist" { @@ -4339,650 +4449,650 @@ yydefault: yyVAL.statement = &Show{Type: yyDollar[3].str, ShowTablesOpt: showTablesOpt} } } - case 268: + case 273: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:1510 +//line sql.y:1535 { yyVAL.statement = &Show{Scope: yyDollar[2].str, Type: string(yyDollar[3].bytes)} } - case 269: + case 274: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1514 +//line sql.y:1539 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes)} } - case 270: + case 275: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1518 +//line sql.y:1543 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes)} } - case 271: + case 276: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:1522 +//line sql.y:1547 { // Cannot dereference $4 directly, or else the parser stackcannot be pooled. See yyParsePooled showCollationFilterOpt := yyDollar[4].expr yyVAL.statement = &Show{Type: string(yyDollar[2].bytes), ShowCollationFilterOpt: &showCollationFilterOpt} } - case 272: + case 277: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:1528 +//line sql.y:1553 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes), OnTable: yyDollar[4].tableName} } - case 273: + case 278: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1532 +//line sql.y:1557 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes)} } - case 274: + case 279: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1536 +//line sql.y:1561 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes)} } - case 275: + case 280: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1540 +//line sql.y:1565 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes)} } - case 276: + case 281: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1544 +//line sql.y:1569 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes)} } - case 277: + case 282: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1548 +//line sql.y:1573 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes)} } - case 278: + case 283: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1552 +//line sql.y:1577 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes)} } - case 279: + case 284: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:1562 +//line sql.y:1587 { yyVAL.statement = &Show{Type: string(yyDollar[2].bytes)} } - case 280: + case 285: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1568 +//line sql.y:1593 { yyVAL.str = string(yyDollar[1].bytes) } - case 281: + case 286: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1572 +//line sql.y:1597 { yyVAL.str = string(yyDollar[1].bytes) } - case 282: + case 287: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:1578 +//line sql.y:1603 { yyVAL.str = "" } - case 283: + case 288: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1582 +//line sql.y:1607 { yyVAL.str = "full " } - case 284: + case 289: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1588 +//line sql.y:1613 { yyVAL.str = string(yyDollar[1].bytes) } - case 285: + case 290: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1592 +//line sql.y:1617 { yyVAL.str = string(yyDollar[1].bytes) } - case 286: + case 291: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:1598 +//line sql.y:1623 { yyVAL.str = "" } - case 287: + case 292: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1602 +//line sql.y:1627 { yyVAL.str = yyDollar[2].tableIdent.v } - case 288: + case 293: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1606 +//line sql.y:1631 { yyVAL.str = yyDollar[2].tableIdent.v } - case 289: + case 294: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:1612 +//line sql.y:1637 { yyVAL.showFilter = nil } - case 290: + case 295: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1616 +//line sql.y:1641 { yyVAL.showFilter = &ShowFilter{Like: string(yyDollar[2].bytes)} } - case 291: + case 296: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1620 +//line sql.y:1645 { yyVAL.showFilter = &ShowFilter{Filter: yyDollar[2].expr} } - case 292: + case 297: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:1626 +//line sql.y:1651 { yyVAL.str = "" } - case 293: + case 298: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1630 +//line sql.y:1655 { yyVAL.str = SessionStr } - case 294: + case 299: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1634 +//line sql.y:1659 { yyVAL.str = GlobalStr } - case 295: + case 300: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1640 +//line sql.y:1665 { yyVAL.statement = &Use{DBName: yyDollar[2].tableIdent} } - case 296: + case 301: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1644 +//line sql.y:1669 { yyVAL.statement = &Use{DBName: TableIdent{v: ""}} } - case 297: + case 302: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1650 +//line sql.y:1675 { yyVAL.statement = &Begin{} } - case 298: + case 303: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1654 +//line sql.y:1679 { yyVAL.statement = &Begin{} } - case 299: + case 304: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1660 +//line sql.y:1685 { yyVAL.statement = &Commit{} } - case 300: + case 305: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1666 +//line sql.y:1691 { yyVAL.statement = &Rollback{} } - case 301: + case 306: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1672 +//line sql.y:1697 { yyVAL.statement = &OtherRead{} } - case 302: + case 307: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1676 +//line sql.y:1701 { yyVAL.statement = &OtherRead{} } - case 303: + case 308: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1680 +//line sql.y:1705 { yyVAL.statement = &OtherRead{} } - case 304: + case 309: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1684 +//line sql.y:1709 { yyVAL.statement = &OtherAdmin{} } - case 305: + case 310: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1688 +//line sql.y:1713 { yyVAL.statement = &OtherAdmin{} } - case 306: + case 311: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:1692 +//line sql.y:1717 { yyVAL.statement = &OtherAdmin{} } - case 307: + case 312: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:1696 +//line sql.y:1721 { yyVAL.statement = &OtherAdmin{} } - case 308: + case 313: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1702 +//line sql.y:1727 { yyVAL.statement = &DDL{Action: FlushStr} } - case 309: + case 314: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:1706 +//line sql.y:1731 { setAllowComments(yylex, true) } - case 310: + case 315: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1710 +//line sql.y:1735 { yyVAL.bytes2 = yyDollar[2].bytes2 setAllowComments(yylex, false) } - case 311: + case 316: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:1716 +//line sql.y:1741 { yyVAL.bytes2 = nil } - case 312: + case 317: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1720 +//line sql.y:1745 { yyVAL.bytes2 = append(yyDollar[1].bytes2, yyDollar[2].bytes) } - case 313: + case 318: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1726 +//line sql.y:1751 { yyVAL.str = UnionStr } - case 314: + case 319: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1730 +//line sql.y:1755 { yyVAL.str = UnionAllStr } - case 315: + case 320: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1734 +//line sql.y:1759 { yyVAL.str = UnionDistinctStr } - case 316: + case 321: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:1739 +//line sql.y:1764 { yyVAL.str = "" } - case 317: + case 322: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1743 +//line sql.y:1768 { yyVAL.str = SQLNoCacheStr } - case 318: + case 323: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1747 +//line sql.y:1772 { yyVAL.str = SQLCacheStr } - case 319: + case 324: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:1752 +//line sql.y:1777 { yyVAL.str = "" } - case 320: + case 325: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1756 +//line sql.y:1781 { yyVAL.str = DistinctStr } - case 321: + case 326: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:1761 +//line sql.y:1786 { yyVAL.str = "" } - case 322: + case 327: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1765 +//line sql.y:1790 { yyVAL.str = StraightJoinHint } - case 323: + case 328: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:1770 +//line sql.y:1795 { yyVAL.selectExprs = nil } - case 324: + case 329: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1774 +//line sql.y:1799 { yyVAL.selectExprs = yyDollar[1].selectExprs } - case 325: + case 330: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1780 +//line sql.y:1805 { yyVAL.selectExprs = SelectExprs{yyDollar[1].selectExpr} } - case 326: + case 331: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:1784 +//line sql.y:1809 { yyVAL.selectExprs = append(yyVAL.selectExprs, yyDollar[3].selectExpr) } - case 327: + case 332: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1790 +//line sql.y:1815 { yyVAL.selectExpr = &StarExpr{} } - case 328: + case 333: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1794 +//line sql.y:1819 { yyVAL.selectExpr = &AliasedExpr{Expr: yyDollar[1].expr, As: yyDollar[2].colIdent} } - case 329: + case 334: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:1798 +//line sql.y:1823 { yyVAL.selectExpr = &StarExpr{TableName: TableName{Name: yyDollar[1].tableIdent}} } - case 330: + case 335: yyDollar = yyS[yypt-5 : yypt+1] -//line sql.y:1802 +//line sql.y:1827 { yyVAL.selectExpr = &StarExpr{TableName: TableName{Qualifier: yyDollar[1].tableIdent, Name: yyDollar[3].tableIdent}} } - case 331: + case 336: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:1807 +//line sql.y:1832 { yyVAL.colIdent = ColIdent{} } - case 332: + case 337: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1811 +//line sql.y:1836 { yyVAL.colIdent = yyDollar[1].colIdent } - case 333: + case 338: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1815 +//line sql.y:1840 { yyVAL.colIdent = yyDollar[2].colIdent } - case 335: + case 340: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1822 +//line sql.y:1847 { yyVAL.colIdent = NewColIdent(string(yyDollar[1].bytes)) } - case 336: + case 341: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:1827 +//line sql.y:1852 { yyVAL.tableExprs = TableExprs{&AliasedTableExpr{Expr: TableName{Name: NewTableIdent("dual")}}} } - case 337: + case 342: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1831 +//line sql.y:1856 { yyVAL.tableExprs = yyDollar[2].tableExprs } - case 338: + case 343: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1837 +//line sql.y:1862 { yyVAL.tableExprs = TableExprs{yyDollar[1].tableExpr} } - case 339: + case 344: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:1841 +//line sql.y:1866 { yyVAL.tableExprs = append(yyVAL.tableExprs, yyDollar[3].tableExpr) } - case 342: + case 347: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1851 +//line sql.y:1876 { yyVAL.tableExpr = yyDollar[1].aliasedTableName } - case 343: + case 348: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:1855 +//line sql.y:1880 { yyVAL.tableExpr = &AliasedTableExpr{Expr: yyDollar[1].subquery, As: yyDollar[3].tableIdent} } - case 344: + case 349: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1859 +//line sql.y:1884 { // missed alias for subquery yylex.Error("Every derived table must have its own alias") return 1 } - case 345: + case 350: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:1865 +//line sql.y:1890 { yyVAL.tableExpr = &ParenTableExpr{Exprs: yyDollar[2].tableExprs} } - case 346: + case 351: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:1871 +//line sql.y:1896 { yyVAL.aliasedTableName = &AliasedTableExpr{Expr: yyDollar[1].tableName, As: yyDollar[2].tableIdent, Hints: yyDollar[3].indexHints} } - case 347: + case 352: yyDollar = yyS[yypt-7 : yypt+1] -//line sql.y:1875 +//line sql.y:1900 { yyVAL.aliasedTableName = &AliasedTableExpr{Expr: yyDollar[1].tableName, Partitions: yyDollar[4].partitions, As: yyDollar[6].tableIdent, Hints: yyDollar[7].indexHints} } - case 348: + case 353: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1881 +//line sql.y:1906 { yyVAL.columns = Columns{yyDollar[1].colIdent} } - case 349: + case 354: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:1885 +//line sql.y:1910 { yyVAL.columns = append(yyVAL.columns, yyDollar[3].colIdent) } - case 350: + case 355: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1891 +//line sql.y:1916 { yyVAL.partitions = Partitions{yyDollar[1].colIdent} } - case 351: + case 356: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:1895 +//line sql.y:1920 { yyVAL.partitions = append(yyVAL.partitions, yyDollar[3].colIdent) } - case 352: + case 357: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:1908 +//line sql.y:1933 { yyVAL.tableExpr = &JoinTableExpr{LeftExpr: yyDollar[1].tableExpr, Join: yyDollar[2].str, RightExpr: yyDollar[3].tableExpr, Condition: yyDollar[4].joinCondition} } - case 353: + case 358: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:1912 +//line sql.y:1937 { yyVAL.tableExpr = &JoinTableExpr{LeftExpr: yyDollar[1].tableExpr, Join: yyDollar[2].str, RightExpr: yyDollar[3].tableExpr, Condition: yyDollar[4].joinCondition} } - case 354: + case 359: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:1916 +//line sql.y:1941 { yyVAL.tableExpr = &JoinTableExpr{LeftExpr: yyDollar[1].tableExpr, Join: yyDollar[2].str, RightExpr: yyDollar[3].tableExpr, Condition: yyDollar[4].joinCondition} } - case 355: + case 360: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:1920 +//line sql.y:1945 { yyVAL.tableExpr = &JoinTableExpr{LeftExpr: yyDollar[1].tableExpr, Join: yyDollar[2].str, RightExpr: yyDollar[3].tableExpr} } - case 356: + case 361: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1926 +//line sql.y:1951 { yyVAL.joinCondition = JoinCondition{On: yyDollar[2].expr} } - case 357: + case 362: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:1928 +//line sql.y:1953 { yyVAL.joinCondition = JoinCondition{Using: yyDollar[3].columns} } - case 358: + case 363: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:1932 +//line sql.y:1957 { yyVAL.joinCondition = JoinCondition{} } - case 359: + case 364: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1934 +//line sql.y:1959 { yyVAL.joinCondition = yyDollar[1].joinCondition } - case 360: + case 365: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:1938 +//line sql.y:1963 { yyVAL.joinCondition = JoinCondition{} } - case 361: + case 366: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1940 +//line sql.y:1965 { yyVAL.joinCondition = JoinCondition{On: yyDollar[2].expr} } - case 362: + case 367: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:1943 +//line sql.y:1968 { yyVAL.empty = struct{}{} } - case 363: + case 368: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1945 +//line sql.y:1970 { yyVAL.empty = struct{}{} } - case 364: + case 369: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:1948 +//line sql.y:1973 { yyVAL.tableIdent = NewTableIdent("") } - case 365: + case 370: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1952 +//line sql.y:1977 { yyVAL.tableIdent = yyDollar[1].tableIdent } - case 366: + case 371: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1956 +//line sql.y:1981 { yyVAL.tableIdent = yyDollar[2].tableIdent } - case 368: + case 373: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1963 +//line sql.y:1988 { yyVAL.tableIdent = NewTableIdent(string(yyDollar[1].bytes)) } - case 369: + case 374: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1969 +//line sql.y:1994 { yyVAL.str = JoinStr } - case 370: + case 375: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1973 +//line sql.y:1998 { yyVAL.str = JoinStr } - case 371: + case 376: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1977 +//line sql.y:2002 { yyVAL.str = JoinStr } - case 372: + case 377: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:1983 +//line sql.y:2008 { yyVAL.str = StraightJoinStr } - case 373: + case 378: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1989 +//line sql.y:2014 { yyVAL.str = LeftJoinStr } - case 374: + case 379: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:1993 +//line sql.y:2018 { yyVAL.str = LeftJoinStr } - case 375: + case 380: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:1997 +//line sql.y:2022 { yyVAL.str = RightJoinStr } - case 376: + case 381: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2001 +//line sql.y:2026 { yyVAL.str = RightJoinStr } - case 377: + case 382: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2007 +//line sql.y:2032 { yyVAL.str = NaturalJoinStr } - case 378: + case 383: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2011 +//line sql.y:2036 { if yyDollar[2].str == LeftJoinStr { yyVAL.str = NaturalLeftJoinStr @@ -4990,459 +5100,459 @@ yydefault: yyVAL.str = NaturalRightJoinStr } } - case 379: + case 384: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2021 +//line sql.y:2046 { yyVAL.tableName = yyDollar[2].tableName } - case 380: + case 385: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2025 +//line sql.y:2050 { yyVAL.tableName = yyDollar[1].tableName } - case 381: + case 386: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2031 +//line sql.y:2056 { yyVAL.tableName = TableName{Name: yyDollar[1].tableIdent} } - case 382: + case 387: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2035 +//line sql.y:2060 { yyVAL.tableName = TableName{Qualifier: yyDollar[1].tableIdent, Name: yyDollar[3].tableIdent} } - case 383: + case 388: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:2040 +//line sql.y:2065 { yyVAL.indexHints = nil } - case 384: + case 389: yyDollar = yyS[yypt-5 : yypt+1] -//line sql.y:2044 +//line sql.y:2069 { yyVAL.indexHints = &IndexHints{Type: UseStr, Indexes: yyDollar[4].columns} } - case 385: + case 390: yyDollar = yyS[yypt-5 : yypt+1] -//line sql.y:2048 +//line sql.y:2073 { yyVAL.indexHints = &IndexHints{Type: IgnoreStr, Indexes: yyDollar[4].columns} } - case 386: + case 391: yyDollar = yyS[yypt-5 : yypt+1] -//line sql.y:2052 +//line sql.y:2077 { yyVAL.indexHints = &IndexHints{Type: ForceStr, Indexes: yyDollar[4].columns} } - case 387: + case 392: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:2057 +//line sql.y:2082 { yyVAL.expr = nil } - case 388: + case 393: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2061 +//line sql.y:2086 { yyVAL.expr = yyDollar[2].expr } - case 389: + case 394: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2067 +//line sql.y:2092 { yyVAL.expr = yyDollar[1].expr } - case 390: + case 395: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2071 +//line sql.y:2096 { yyVAL.expr = &AndExpr{Left: yyDollar[1].expr, Right: yyDollar[3].expr} } - case 391: + case 396: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2075 +//line sql.y:2100 { yyVAL.expr = &OrExpr{Left: yyDollar[1].expr, Right: yyDollar[3].expr} } - case 392: + case 397: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2079 +//line sql.y:2104 { yyVAL.expr = &NotExpr{Expr: yyDollar[2].expr} } - case 393: + case 398: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2083 +//line sql.y:2108 { yyVAL.expr = &IsExpr{Operator: yyDollar[3].str, Expr: yyDollar[1].expr} } - case 394: + case 399: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2087 +//line sql.y:2112 { yyVAL.expr = yyDollar[1].expr } - case 395: + case 400: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2091 +//line sql.y:2116 { yyVAL.expr = &Default{ColName: yyDollar[2].str} } - case 396: + case 401: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:2097 +//line sql.y:2122 { yyVAL.str = "" } - case 397: + case 402: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2101 +//line sql.y:2126 { yyVAL.str = string(yyDollar[2].bytes) } - case 398: + case 403: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2107 +//line sql.y:2132 { yyVAL.boolVal = BoolVal(true) } - case 399: + case 404: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2111 +//line sql.y:2136 { yyVAL.boolVal = BoolVal(false) } - case 400: + case 405: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2117 +//line sql.y:2142 { yyVAL.expr = &ComparisonExpr{Left: yyDollar[1].expr, Operator: yyDollar[2].str, Right: yyDollar[3].expr} } - case 401: + case 406: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2121 +//line sql.y:2146 { yyVAL.expr = &ComparisonExpr{Left: yyDollar[1].expr, Operator: InStr, Right: yyDollar[3].colTuple} } - case 402: + case 407: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:2125 +//line sql.y:2150 { yyVAL.expr = &ComparisonExpr{Left: yyDollar[1].expr, Operator: NotInStr, Right: yyDollar[4].colTuple} } - case 403: + case 408: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:2129 +//line sql.y:2154 { yyVAL.expr = &ComparisonExpr{Left: yyDollar[1].expr, Operator: LikeStr, Right: yyDollar[3].expr, Escape: yyDollar[4].expr} } - case 404: + case 409: yyDollar = yyS[yypt-5 : yypt+1] -//line sql.y:2133 +//line sql.y:2158 { yyVAL.expr = &ComparisonExpr{Left: yyDollar[1].expr, Operator: NotLikeStr, Right: yyDollar[4].expr, Escape: yyDollar[5].expr} } - case 405: + case 410: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2137 +//line sql.y:2162 { yyVAL.expr = &ComparisonExpr{Left: yyDollar[1].expr, Operator: RegexpStr, Right: yyDollar[3].expr} } - case 406: + case 411: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:2141 +//line sql.y:2166 { yyVAL.expr = &ComparisonExpr{Left: yyDollar[1].expr, Operator: NotRegexpStr, Right: yyDollar[4].expr} } - case 407: + case 412: yyDollar = yyS[yypt-5 : yypt+1] -//line sql.y:2145 +//line sql.y:2170 { yyVAL.expr = &RangeCond{Left: yyDollar[1].expr, Operator: BetweenStr, From: yyDollar[3].expr, To: yyDollar[5].expr} } - case 408: + case 413: yyDollar = yyS[yypt-6 : yypt+1] -//line sql.y:2149 +//line sql.y:2174 { yyVAL.expr = &RangeCond{Left: yyDollar[1].expr, Operator: NotBetweenStr, From: yyDollar[4].expr, To: yyDollar[6].expr} } - case 409: + case 414: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2153 +//line sql.y:2178 { yyVAL.expr = &ExistsExpr{Subquery: yyDollar[2].subquery} } - case 410: + case 415: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2159 +//line sql.y:2184 { yyVAL.str = IsNullStr } - case 411: + case 416: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2163 +//line sql.y:2188 { yyVAL.str = IsNotNullStr } - case 412: + case 417: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2167 +//line sql.y:2192 { yyVAL.str = IsTrueStr } - case 413: + case 418: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2171 +//line sql.y:2196 { yyVAL.str = IsNotTrueStr } - case 414: + case 419: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2175 +//line sql.y:2200 { yyVAL.str = IsFalseStr } - case 415: + case 420: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2179 +//line sql.y:2204 { yyVAL.str = IsNotFalseStr } - case 416: + case 421: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2185 +//line sql.y:2210 { yyVAL.str = EqualStr } - case 417: + case 422: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2189 +//line sql.y:2214 { yyVAL.str = LessThanStr } - case 418: + case 423: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2193 +//line sql.y:2218 { yyVAL.str = GreaterThanStr } - case 419: + case 424: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2197 +//line sql.y:2222 { yyVAL.str = LessEqualStr } - case 420: + case 425: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2201 +//line sql.y:2226 { yyVAL.str = GreaterEqualStr } - case 421: + case 426: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2205 +//line sql.y:2230 { yyVAL.str = NotEqualStr } - case 422: + case 427: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2209 +//line sql.y:2234 { yyVAL.str = NullSafeEqualStr } - case 423: + case 428: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:2214 +//line sql.y:2239 { yyVAL.expr = nil } - case 424: + case 429: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2218 +//line sql.y:2243 { yyVAL.expr = yyDollar[2].expr } - case 425: + case 430: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2224 +//line sql.y:2249 { yyVAL.colTuple = yyDollar[1].valTuple } - case 426: + case 431: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2228 +//line sql.y:2253 { yyVAL.colTuple = yyDollar[1].subquery } - case 427: + case 432: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2232 +//line sql.y:2257 { yyVAL.colTuple = ListArg(yyDollar[1].bytes) } - case 428: + case 433: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2238 +//line sql.y:2263 { yyVAL.subquery = &Subquery{yyDollar[2].selStmt} } - case 429: + case 434: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2244 +//line sql.y:2269 { yyVAL.exprs = Exprs{yyDollar[1].expr} } - case 430: + case 435: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2248 +//line sql.y:2273 { yyVAL.exprs = append(yyDollar[1].exprs, yyDollar[3].expr) } - case 431: + case 436: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2254 +//line sql.y:2279 { yyVAL.expr = yyDollar[1].expr } - case 432: + case 437: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2258 +//line sql.y:2283 { yyVAL.expr = yyDollar[1].boolVal } - case 433: + case 438: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2262 +//line sql.y:2287 { yyVAL.expr = yyDollar[1].colName } - case 434: + case 439: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2266 +//line sql.y:2291 { yyVAL.expr = yyDollar[1].expr } - case 435: + case 440: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2270 +//line sql.y:2295 { yyVAL.expr = yyDollar[1].subquery } - case 436: + case 441: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2274 +//line sql.y:2299 { yyVAL.expr = &BinaryExpr{Left: yyDollar[1].expr, Operator: BitAndStr, Right: yyDollar[3].expr} } - case 437: + case 442: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2278 +//line sql.y:2303 { yyVAL.expr = &BinaryExpr{Left: yyDollar[1].expr, Operator: BitOrStr, Right: yyDollar[3].expr} } - case 438: + case 443: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2282 +//line sql.y:2307 { yyVAL.expr = &BinaryExpr{Left: yyDollar[1].expr, Operator: BitXorStr, Right: yyDollar[3].expr} } - case 439: + case 444: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2286 +//line sql.y:2311 { yyVAL.expr = &BinaryExpr{Left: yyDollar[1].expr, Operator: PlusStr, Right: yyDollar[3].expr} } - case 440: + case 445: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2290 +//line sql.y:2315 { yyVAL.expr = &BinaryExpr{Left: yyDollar[1].expr, Operator: MinusStr, Right: yyDollar[3].expr} } - case 441: + case 446: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2294 +//line sql.y:2319 { yyVAL.expr = &BinaryExpr{Left: yyDollar[1].expr, Operator: MultStr, Right: yyDollar[3].expr} } - case 442: + case 447: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2298 +//line sql.y:2323 { yyVAL.expr = &BinaryExpr{Left: yyDollar[1].expr, Operator: DivStr, Right: yyDollar[3].expr} } - case 443: + case 448: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2302 +//line sql.y:2327 { yyVAL.expr = &BinaryExpr{Left: yyDollar[1].expr, Operator: IntDivStr, Right: yyDollar[3].expr} } - case 444: + case 449: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2306 +//line sql.y:2331 { yyVAL.expr = &BinaryExpr{Left: yyDollar[1].expr, Operator: ModStr, Right: yyDollar[3].expr} } - case 445: + case 450: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2310 +//line sql.y:2335 { yyVAL.expr = &BinaryExpr{Left: yyDollar[1].expr, Operator: ModStr, Right: yyDollar[3].expr} } - case 446: + case 451: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2314 +//line sql.y:2339 { yyVAL.expr = &BinaryExpr{Left: yyDollar[1].expr, Operator: ShiftLeftStr, Right: yyDollar[3].expr} } - case 447: + case 452: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2318 +//line sql.y:2343 { yyVAL.expr = &BinaryExpr{Left: yyDollar[1].expr, Operator: ShiftRightStr, Right: yyDollar[3].expr} } - case 448: + case 453: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2322 +//line sql.y:2347 { yyVAL.expr = &BinaryExpr{Left: yyDollar[1].colName, Operator: JSONExtractOp, Right: yyDollar[3].expr} } - case 449: + case 454: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2326 +//line sql.y:2351 { yyVAL.expr = &BinaryExpr{Left: yyDollar[1].colName, Operator: JSONUnquoteExtractOp, Right: yyDollar[3].expr} } - case 450: + case 455: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2330 +//line sql.y:2355 { yyVAL.expr = &CollateExpr{Expr: yyDollar[1].expr, Charset: yyDollar[3].str} } - case 451: + case 456: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2334 +//line sql.y:2359 { yyVAL.expr = &UnaryExpr{Operator: BinaryStr, Expr: yyDollar[2].expr} } - case 452: + case 457: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2338 +//line sql.y:2363 { yyVAL.expr = &UnaryExpr{Operator: UBinaryStr, Expr: yyDollar[2].expr} } - case 453: + case 458: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2342 +//line sql.y:2367 { yyVAL.expr = &UnaryExpr{Operator: Utf8mb4Str, Expr: yyDollar[2].expr} } - case 454: + case 459: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2346 +//line sql.y:2371 { if num, ok := yyDollar[2].expr.(*SQLVal); ok && num.Type == IntVal { yyVAL.expr = num @@ -5450,9 +5560,9 @@ yydefault: yyVAL.expr = &UnaryExpr{Operator: UPlusStr, Expr: yyDollar[2].expr} } } - case 455: + case 460: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2354 +//line sql.y:2379 { if num, ok := yyDollar[2].expr.(*SQLVal); ok && num.Type == IntVal { // Handle double negative @@ -5466,21 +5576,21 @@ yydefault: yyVAL.expr = &UnaryExpr{Operator: UMinusStr, Expr: yyDollar[2].expr} } } - case 456: + case 461: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2368 +//line sql.y:2393 { yyVAL.expr = &UnaryExpr{Operator: TildaStr, Expr: yyDollar[2].expr} } - case 457: + case 462: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2372 +//line sql.y:2397 { yyVAL.expr = &UnaryExpr{Operator: BangStr, Expr: yyDollar[2].expr} } - case 458: + case 463: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2376 +//line sql.y:2401 { // This rule prevents the usage of INTERVAL // as a function. If support is needed for that, @@ -5488,431 +5598,467 @@ yydefault: // will be non-trivial because of grammar conflicts. yyVAL.expr = &IntervalExpr{Expr: yyDollar[2].expr, Unit: yyDollar[3].colIdent.String()} } - case 463: + case 468: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:2394 +//line sql.y:2419 { yyVAL.expr = &FuncExpr{Name: yyDollar[1].colIdent, Exprs: yyDollar[3].selectExprs} } - case 464: + case 469: yyDollar = yyS[yypt-5 : yypt+1] -//line sql.y:2398 +//line sql.y:2423 { yyVAL.expr = &FuncExpr{Name: yyDollar[1].colIdent, Distinct: true, Exprs: yyDollar[4].selectExprs} } - case 465: + case 470: yyDollar = yyS[yypt-6 : yypt+1] -//line sql.y:2402 +//line sql.y:2427 { yyVAL.expr = &FuncExpr{Qualifier: yyDollar[1].tableIdent, Name: yyDollar[3].colIdent, Exprs: yyDollar[5].selectExprs} } - case 466: + case 471: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:2412 +//line sql.y:2437 { yyVAL.expr = &FuncExpr{Name: NewColIdent("left"), Exprs: yyDollar[3].selectExprs} } - case 467: + case 472: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:2416 +//line sql.y:2441 { yyVAL.expr = &FuncExpr{Name: NewColIdent("right"), Exprs: yyDollar[3].selectExprs} } - case 468: + case 473: yyDollar = yyS[yypt-6 : yypt+1] -//line sql.y:2420 +//line sql.y:2445 { yyVAL.expr = &ConvertExpr{Expr: yyDollar[3].expr, Type: yyDollar[5].convertType} } - case 469: + case 474: yyDollar = yyS[yypt-6 : yypt+1] -//line sql.y:2424 +//line sql.y:2449 { yyVAL.expr = &ConvertExpr{Expr: yyDollar[3].expr, Type: yyDollar[5].convertType} } - case 470: + case 475: yyDollar = yyS[yypt-6 : yypt+1] -//line sql.y:2428 +//line sql.y:2453 { yyVAL.expr = &ConvertUsingExpr{Expr: yyDollar[3].expr, Type: yyDollar[5].str} } - case 471: + case 476: yyDollar = yyS[yypt-6 : yypt+1] -//line sql.y:2432 +//line sql.y:2457 { yyVAL.expr = &SubstrExpr{Name: yyDollar[3].colName, From: yyDollar[5].expr, To: nil} } - case 472: + case 477: yyDollar = yyS[yypt-8 : yypt+1] -//line sql.y:2436 +//line sql.y:2461 { yyVAL.expr = &SubstrExpr{Name: yyDollar[3].colName, From: yyDollar[5].expr, To: yyDollar[7].expr} } - case 473: + case 478: yyDollar = yyS[yypt-8 : yypt+1] -//line sql.y:2440 +//line sql.y:2465 { yyVAL.expr = &SubstrExpr{Name: yyDollar[3].colName, From: yyDollar[5].expr, To: yyDollar[7].expr} } - case 474: + case 479: yyDollar = yyS[yypt-6 : yypt+1] -//line sql.y:2444 +//line sql.y:2469 { yyVAL.expr = &SubstrExpr{Name: yyDollar[3].colName, From: yyDollar[5].expr, To: nil} } - case 475: + case 480: yyDollar = yyS[yypt-8 : yypt+1] -//line sql.y:2448 +//line sql.y:2473 { yyVAL.expr = &SubstrExpr{Name: yyDollar[3].colName, From: yyDollar[5].expr, To: yyDollar[7].expr} } - case 476: + case 481: yyDollar = yyS[yypt-8 : yypt+1] -//line sql.y:2452 +//line sql.y:2477 { yyVAL.expr = &SubstrExpr{Name: yyDollar[3].colName, From: yyDollar[5].expr, To: yyDollar[7].expr} } - case 477: + case 482: + yyDollar = yyS[yypt-6 : yypt+1] +//line sql.y:2481 + { + yyVAL.expr = &SubstrExpr{StrVal: NewStrVal(yyDollar[3].bytes), From: yyDollar[5].expr, To: nil} + } + case 483: + yyDollar = yyS[yypt-8 : yypt+1] +//line sql.y:2485 + { + yyVAL.expr = &SubstrExpr{StrVal: NewStrVal(yyDollar[3].bytes), From: yyDollar[5].expr, To: yyDollar[7].expr} + } + case 484: + yyDollar = yyS[yypt-8 : yypt+1] +//line sql.y:2489 + { + yyVAL.expr = &SubstrExpr{StrVal: NewStrVal(yyDollar[3].bytes), From: yyDollar[5].expr, To: yyDollar[7].expr} + } + case 485: + yyDollar = yyS[yypt-6 : yypt+1] +//line sql.y:2493 + { + yyVAL.expr = &SubstrExpr{StrVal: NewStrVal(yyDollar[3].bytes), From: yyDollar[5].expr, To: nil} + } + case 486: + yyDollar = yyS[yypt-8 : yypt+1] +//line sql.y:2497 + { + yyVAL.expr = &SubstrExpr{StrVal: NewStrVal(yyDollar[3].bytes), From: yyDollar[5].expr, To: yyDollar[7].expr} + } + case 487: + yyDollar = yyS[yypt-8 : yypt+1] +//line sql.y:2501 + { + yyVAL.expr = &SubstrExpr{StrVal: NewStrVal(yyDollar[3].bytes), From: yyDollar[5].expr, To: yyDollar[7].expr} + } + case 488: yyDollar = yyS[yypt-9 : yypt+1] -//line sql.y:2456 +//line sql.y:2505 { yyVAL.expr = &MatchExpr{Columns: yyDollar[3].selectExprs, Expr: yyDollar[7].expr, Option: yyDollar[8].str} } - case 478: + case 489: yyDollar = yyS[yypt-7 : yypt+1] -//line sql.y:2460 +//line sql.y:2509 { yyVAL.expr = &GroupConcatExpr{Distinct: yyDollar[3].str, Exprs: yyDollar[4].selectExprs, OrderBy: yyDollar[5].orderBy, Separator: yyDollar[6].str} } - case 479: + case 490: yyDollar = yyS[yypt-5 : yypt+1] -//line sql.y:2464 +//line sql.y:2513 { yyVAL.expr = &CaseExpr{Expr: yyDollar[2].expr, Whens: yyDollar[3].whens, Else: yyDollar[4].expr} } - case 480: + case 491: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:2468 +//line sql.y:2517 { yyVAL.expr = &ValuesFuncExpr{Name: yyDollar[3].colName} } - case 481: + case 492: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2478 +//line sql.y:2527 { yyVAL.expr = &FuncExpr{Name: NewColIdent("current_timestamp")} } - case 482: + case 493: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2482 +//line sql.y:2531 { yyVAL.expr = &FuncExpr{Name: NewColIdent("utc_timestamp")} } - case 483: + case 494: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2486 +//line sql.y:2535 { yyVAL.expr = &FuncExpr{Name: NewColIdent("utc_time")} } - case 484: + case 495: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2490 +//line sql.y:2539 { yyVAL.expr = &FuncExpr{Name: NewColIdent("utc_date")} } - case 485: + case 496: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2495 +//line sql.y:2544 { yyVAL.expr = &FuncExpr{Name: NewColIdent("localtime")} } - case 486: + case 497: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2500 +//line sql.y:2549 { yyVAL.expr = &FuncExpr{Name: NewColIdent("localtimestamp")} } - case 487: + case 498: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2505 +//line sql.y:2554 { yyVAL.expr = &FuncExpr{Name: NewColIdent("current_date")} } - case 488: + case 499: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2510 +//line sql.y:2559 { yyVAL.expr = &FuncExpr{Name: NewColIdent("current_time")} } - case 491: + case 502: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:2524 +//line sql.y:2573 { yyVAL.expr = &FuncExpr{Name: NewColIdent("if"), Exprs: yyDollar[3].selectExprs} } - case 492: + case 503: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:2528 +//line sql.y:2577 { yyVAL.expr = &FuncExpr{Name: NewColIdent("database"), Exprs: yyDollar[3].selectExprs} } - case 493: + case 504: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:2532 +//line sql.y:2581 { yyVAL.expr = &FuncExpr{Name: NewColIdent("mod"), Exprs: yyDollar[3].selectExprs} } - case 494: + case 505: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:2536 +//line sql.y:2585 { yyVAL.expr = &FuncExpr{Name: NewColIdent("replace"), Exprs: yyDollar[3].selectExprs} } - case 495: + case 506: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:2542 +//line sql.y:2591 { yyVAL.str = "" } - case 496: + case 507: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2546 +//line sql.y:2595 { yyVAL.str = BooleanModeStr } - case 497: + case 508: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:2550 +//line sql.y:2599 { yyVAL.str = NaturalLanguageModeStr } - case 498: + case 509: yyDollar = yyS[yypt-7 : yypt+1] -//line sql.y:2554 +//line sql.y:2603 { yyVAL.str = NaturalLanguageModeWithQueryExpansionStr } - case 499: + case 510: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2558 +//line sql.y:2607 { yyVAL.str = QueryExpansionStr } - case 500: + case 511: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2564 +//line sql.y:2613 { yyVAL.str = string(yyDollar[1].bytes) } - case 501: + case 512: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2568 +//line sql.y:2617 { yyVAL.str = string(yyDollar[1].bytes) } - case 502: + case 513: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2574 +//line sql.y:2623 { yyVAL.convertType = &ConvertType{Type: string(yyDollar[1].bytes), Length: yyDollar[2].optVal} } - case 503: + case 514: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2578 +//line sql.y:2627 { yyVAL.convertType = &ConvertType{Type: string(yyDollar[1].bytes), Length: yyDollar[2].optVal, Charset: yyDollar[3].str, Operator: CharacterSetStr} } - case 504: + case 515: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2582 +//line sql.y:2631 { yyVAL.convertType = &ConvertType{Type: string(yyDollar[1].bytes), Length: yyDollar[2].optVal, Charset: string(yyDollar[3].bytes)} } - case 505: + case 516: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2586 +//line sql.y:2635 { yyVAL.convertType = &ConvertType{Type: string(yyDollar[1].bytes)} } - case 506: + case 517: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2590 +//line sql.y:2639 { yyVAL.convertType = &ConvertType{Type: string(yyDollar[1].bytes), Length: yyDollar[2].optVal} } - case 507: + case 518: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2594 +//line sql.y:2643 { yyVAL.convertType = &ConvertType{Type: string(yyDollar[1].bytes)} yyVAL.convertType.Length = yyDollar[2].LengthScaleOption.Length yyVAL.convertType.Scale = yyDollar[2].LengthScaleOption.Scale } - case 508: + case 519: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2600 +//line sql.y:2649 { yyVAL.convertType = &ConvertType{Type: string(yyDollar[1].bytes)} } - case 509: + case 520: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2604 +//line sql.y:2653 { yyVAL.convertType = &ConvertType{Type: string(yyDollar[1].bytes), Length: yyDollar[2].optVal} } - case 510: + case 521: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2608 +//line sql.y:2657 { yyVAL.convertType = &ConvertType{Type: string(yyDollar[1].bytes)} } - case 511: + case 522: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2612 +//line sql.y:2661 { yyVAL.convertType = &ConvertType{Type: string(yyDollar[1].bytes)} } - case 512: + case 523: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2616 +//line sql.y:2665 { yyVAL.convertType = &ConvertType{Type: string(yyDollar[1].bytes), Length: yyDollar[2].optVal} } - case 513: + case 524: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2620 +//line sql.y:2669 { yyVAL.convertType = &ConvertType{Type: string(yyDollar[1].bytes)} } - case 514: + case 525: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2624 +//line sql.y:2673 { yyVAL.convertType = &ConvertType{Type: string(yyDollar[1].bytes)} } - case 515: + case 526: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:2629 +//line sql.y:2678 { yyVAL.expr = nil } - case 516: + case 527: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2633 +//line sql.y:2682 { yyVAL.expr = yyDollar[1].expr } - case 517: + case 528: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:2638 +//line sql.y:2687 { yyVAL.str = string("") } - case 518: + case 529: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2642 +//line sql.y:2691 { yyVAL.str = " separator '" + string(yyDollar[2].bytes) + "'" } - case 519: + case 530: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2648 +//line sql.y:2697 { yyVAL.whens = []*When{yyDollar[1].when} } - case 520: + case 531: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2652 +//line sql.y:2701 { yyVAL.whens = append(yyDollar[1].whens, yyDollar[2].when) } - case 521: + case 532: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:2658 +//line sql.y:2707 { yyVAL.when = &When{Cond: yyDollar[2].expr, Val: yyDollar[4].expr} } - case 522: + case 533: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:2663 +//line sql.y:2712 { yyVAL.expr = nil } - case 523: + case 534: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2667 +//line sql.y:2716 { yyVAL.expr = yyDollar[2].expr } - case 524: + case 535: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2673 +//line sql.y:2722 { yyVAL.colName = &ColName{Name: yyDollar[1].colIdent} } - case 525: + case 536: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2677 +//line sql.y:2726 { yyVAL.colName = &ColName{Qualifier: TableName{Name: yyDollar[1].tableIdent}, Name: yyDollar[3].colIdent} } - case 526: + case 537: yyDollar = yyS[yypt-5 : yypt+1] -//line sql.y:2681 +//line sql.y:2730 { yyVAL.colName = &ColName{Qualifier: TableName{Qualifier: yyDollar[1].tableIdent, Name: yyDollar[3].tableIdent}, Name: yyDollar[5].colIdent} } - case 527: + case 538: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2687 +//line sql.y:2736 { yyVAL.expr = NewStrVal(yyDollar[1].bytes) } - case 528: + case 539: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2691 +//line sql.y:2740 { yyVAL.expr = NewHexVal(yyDollar[1].bytes) } - case 529: + case 540: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2695 +//line sql.y:2744 { yyVAL.expr = NewBitVal(yyDollar[1].bytes) } - case 530: + case 541: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2699 +//line sql.y:2748 { yyVAL.expr = NewIntVal(yyDollar[1].bytes) } - case 531: + case 542: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2703 +//line sql.y:2752 { yyVAL.expr = NewFloatVal(yyDollar[1].bytes) } - case 532: + case 543: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2707 +//line sql.y:2756 { yyVAL.expr = NewHexNum(yyDollar[1].bytes) } - case 533: + case 544: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2711 +//line sql.y:2760 { yyVAL.expr = NewValArg(yyDollar[1].bytes) } - case 534: + case 545: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2715 +//line sql.y:2764 { yyVAL.expr = &NullVal{} } - case 535: + case 546: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2721 +//line sql.y:2770 { // TODO(sougou): Deprecate this construct. if yyDollar[1].colIdent.Lowered() != "value" { @@ -5921,239 +6067,239 @@ yydefault: } yyVAL.expr = NewIntVal([]byte("1")) } - case 536: + case 547: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2730 +//line sql.y:2779 { yyVAL.expr = NewIntVal(yyDollar[1].bytes) } - case 537: + case 548: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2734 +//line sql.y:2783 { yyVAL.expr = NewValArg(yyDollar[1].bytes) } - case 538: + case 549: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:2739 +//line sql.y:2788 { yyVAL.exprs = nil } - case 539: + case 550: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2743 +//line sql.y:2792 { yyVAL.exprs = yyDollar[3].exprs } - case 540: + case 551: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:2748 +//line sql.y:2797 { yyVAL.expr = nil } - case 541: + case 552: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2752 +//line sql.y:2801 { yyVAL.expr = yyDollar[2].expr } - case 542: + case 553: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:2757 +//line sql.y:2806 { yyVAL.orderBy = nil } - case 543: + case 554: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2761 +//line sql.y:2810 { yyVAL.orderBy = yyDollar[3].orderBy } - case 544: + case 555: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2767 +//line sql.y:2816 { yyVAL.orderBy = OrderBy{yyDollar[1].order} } - case 545: + case 556: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2771 +//line sql.y:2820 { yyVAL.orderBy = append(yyDollar[1].orderBy, yyDollar[3].order) } - case 546: + case 557: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2777 +//line sql.y:2826 { yyVAL.order = &Order{Expr: yyDollar[1].expr, Direction: yyDollar[2].str} } - case 547: + case 558: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:2782 +//line sql.y:2831 { yyVAL.str = AscScr } - case 548: + case 559: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2786 +//line sql.y:2835 { yyVAL.str = AscScr } - case 549: + case 560: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2790 +//line sql.y:2839 { yyVAL.str = DescScr } - case 550: + case 561: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:2795 +//line sql.y:2844 { yyVAL.limit = nil } - case 551: + case 562: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2799 +//line sql.y:2848 { yyVAL.limit = &Limit{Rowcount: yyDollar[2].expr} } - case 552: + case 563: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:2803 +//line sql.y:2852 { yyVAL.limit = &Limit{Offset: yyDollar[2].expr, Rowcount: yyDollar[4].expr} } - case 553: + case 564: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:2807 +//line sql.y:2856 { yyVAL.limit = &Limit{Offset: yyDollar[4].expr, Rowcount: yyDollar[2].expr} } - case 554: + case 565: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:2812 +//line sql.y:2861 { yyVAL.str = "" } - case 555: + case 566: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2816 +//line sql.y:2865 { yyVAL.str = ForUpdateStr } - case 556: + case 567: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:2820 +//line sql.y:2869 { yyVAL.str = ShareModeStr } - case 557: + case 568: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2833 +//line sql.y:2882 { yyVAL.ins = &Insert{Rows: yyDollar[2].values} } - case 558: + case 569: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2837 +//line sql.y:2886 { yyVAL.ins = &Insert{Rows: yyDollar[1].selStmt} } - case 559: + case 570: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2841 +//line sql.y:2890 { // Drop the redundant parenthesis. yyVAL.ins = &Insert{Rows: yyDollar[2].selStmt} } - case 560: + case 571: yyDollar = yyS[yypt-5 : yypt+1] -//line sql.y:2846 +//line sql.y:2895 { yyVAL.ins = &Insert{Columns: yyDollar[2].columns, Rows: yyDollar[5].values} } - case 561: + case 572: yyDollar = yyS[yypt-4 : yypt+1] -//line sql.y:2850 +//line sql.y:2899 { yyVAL.ins = &Insert{Columns: yyDollar[2].columns, Rows: yyDollar[4].selStmt} } - case 562: + case 573: yyDollar = yyS[yypt-6 : yypt+1] -//line sql.y:2854 +//line sql.y:2903 { // Drop the redundant parenthesis. yyVAL.ins = &Insert{Columns: yyDollar[2].columns, Rows: yyDollar[5].selStmt} } - case 563: + case 574: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2861 +//line sql.y:2910 { yyVAL.columns = Columns{yyDollar[1].colIdent} } - case 564: + case 575: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2865 +//line sql.y:2914 { yyVAL.columns = Columns{yyDollar[3].colIdent} } - case 565: + case 576: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2869 +//line sql.y:2918 { yyVAL.columns = append(yyVAL.columns, yyDollar[3].colIdent) } - case 566: + case 577: yyDollar = yyS[yypt-5 : yypt+1] -//line sql.y:2873 +//line sql.y:2922 { yyVAL.columns = append(yyVAL.columns, yyDollar[5].colIdent) } - case 567: + case 578: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:2878 +//line sql.y:2927 { yyVAL.updateExprs = nil } - case 568: + case 579: yyDollar = yyS[yypt-5 : yypt+1] -//line sql.y:2882 +//line sql.y:2931 { yyVAL.updateExprs = yyDollar[5].updateExprs } - case 569: + case 580: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2888 +//line sql.y:2937 { yyVAL.values = Values{yyDollar[1].valTuple} } - case 570: + case 581: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2892 +//line sql.y:2941 { yyVAL.values = append(yyDollar[1].values, yyDollar[3].valTuple) } - case 571: + case 582: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2898 +//line sql.y:2947 { yyVAL.valTuple = yyDollar[1].valTuple } - case 572: + case 583: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2902 +//line sql.y:2951 { yyVAL.valTuple = ValTuple{} } - case 573: + case 584: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2908 +//line sql.y:2957 { yyVAL.valTuple = ValTuple(yyDollar[2].exprs) } - case 574: + case 585: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2914 +//line sql.y:2963 { if len(yyDollar[1].valTuple) == 1 { yyVAL.expr = &ParenExpr{yyDollar[1].valTuple[0]} @@ -6161,314 +6307,314 @@ yydefault: yyVAL.expr = yyDollar[1].valTuple } } - case 575: + case 586: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2924 +//line sql.y:2973 { yyVAL.updateExprs = UpdateExprs{yyDollar[1].updateExpr} } - case 576: + case 587: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2928 +//line sql.y:2977 { yyVAL.updateExprs = append(yyDollar[1].updateExprs, yyDollar[3].updateExpr) } - case 577: + case 588: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2934 +//line sql.y:2983 { yyVAL.updateExpr = &UpdateExpr{Name: yyDollar[1].colName, Expr: yyDollar[3].expr} } - case 578: + case 589: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2940 +//line sql.y:2989 { yyVAL.setExprs = SetExprs{yyDollar[1].setExpr} } - case 579: + case 590: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2944 +//line sql.y:2993 { yyVAL.setExprs = append(yyDollar[1].setExprs, yyDollar[3].setExpr) } - case 580: + case 591: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2950 +//line sql.y:2999 { yyVAL.setExpr = &SetExpr{Name: yyDollar[1].colIdent, Expr: NewStrVal([]byte("on"))} } - case 581: + case 592: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2954 +//line sql.y:3003 { yyVAL.setExpr = &SetExpr{Name: yyDollar[1].colIdent, Expr: NewStrVal([]byte("off"))} } - case 582: + case 593: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2958 +//line sql.y:3007 { yyVAL.setExpr = &SetExpr{Name: yyDollar[1].colIdent, Expr: yyDollar[3].expr} } - case 583: + case 594: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:2962 +//line sql.y:3011 { yyVAL.setExpr = &SetExpr{Name: NewColIdent(string(yyDollar[1].bytes)), Expr: yyDollar[2].expr} } - case 585: + case 596: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2969 +//line sql.y:3018 { yyVAL.bytes = []byte("charset") } - case 587: + case 598: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2976 +//line sql.y:3025 { yyVAL.expr = NewStrVal([]byte(yyDollar[1].colIdent.String())) } - case 588: + case 599: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2980 +//line sql.y:3029 { yyVAL.expr = NewStrVal(yyDollar[1].bytes) } - case 589: + case 600: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:2984 +//line sql.y:3033 { yyVAL.expr = &Default{} } - case 592: + case 603: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:2993 +//line sql.y:3042 { yyVAL.byt = 0 } - case 593: + case 604: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:2995 +//line sql.y:3044 { yyVAL.byt = 1 } - case 594: + case 605: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:2998 +//line sql.y:3047 { yyVAL.empty = struct{}{} } - case 595: + case 606: yyDollar = yyS[yypt-3 : yypt+1] -//line sql.y:3000 +//line sql.y:3049 { yyVAL.empty = struct{}{} } - case 596: + case 607: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:3003 +//line sql.y:3052 { yyVAL.str = "" } - case 597: + case 608: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3005 +//line sql.y:3054 { yyVAL.str = IgnoreStr } - case 598: + case 609: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3009 +//line sql.y:3058 { yyVAL.empty = struct{}{} } - case 599: + case 610: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3011 +//line sql.y:3060 { yyVAL.empty = struct{}{} } - case 600: + case 611: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3013 +//line sql.y:3062 { yyVAL.empty = struct{}{} } - case 601: + case 612: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3015 +//line sql.y:3064 { yyVAL.empty = struct{}{} } - case 602: + case 613: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3017 +//line sql.y:3066 { yyVAL.empty = struct{}{} } - case 603: + case 614: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3019 +//line sql.y:3068 { yyVAL.empty = struct{}{} } - case 604: + case 615: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3021 +//line sql.y:3070 { yyVAL.empty = struct{}{} } - case 605: + case 616: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3023 +//line sql.y:3072 { yyVAL.empty = struct{}{} } - case 606: + case 617: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3025 +//line sql.y:3074 { yyVAL.empty = struct{}{} } - case 607: + case 618: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3027 +//line sql.y:3076 { yyVAL.empty = struct{}{} } - case 608: + case 619: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:3030 +//line sql.y:3079 { yyVAL.empty = struct{}{} } - case 609: + case 620: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3032 +//line sql.y:3081 { yyVAL.empty = struct{}{} } - case 610: + case 621: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3034 +//line sql.y:3083 { yyVAL.empty = struct{}{} } - case 611: + case 622: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3038 +//line sql.y:3087 { yyVAL.empty = struct{}{} } - case 612: + case 623: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3040 +//line sql.y:3089 { yyVAL.empty = struct{}{} } - case 613: + case 624: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:3043 +//line sql.y:3092 { yyVAL.empty = struct{}{} } - case 614: + case 625: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3045 +//line sql.y:3094 { yyVAL.empty = struct{}{} } - case 615: + case 626: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3047 +//line sql.y:3096 { yyVAL.empty = struct{}{} } - case 616: + case 627: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:3050 +//line sql.y:3099 { yyVAL.colIdent = ColIdent{} } - case 617: + case 628: yyDollar = yyS[yypt-2 : yypt+1] -//line sql.y:3052 +//line sql.y:3101 { yyVAL.colIdent = yyDollar[2].colIdent } - case 618: + case 629: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3056 +//line sql.y:3105 { yyVAL.colIdent = NewColIdent(string(yyDollar[1].bytes)) } - case 619: + case 630: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3060 +//line sql.y:3109 { yyVAL.colIdent = NewColIdent(string(yyDollar[1].bytes)) } - case 621: + case 632: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3067 +//line sql.y:3116 { yyVAL.colIdent = NewColIdent(string(yyDollar[1].bytes)) } - case 622: + case 633: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3073 +//line sql.y:3122 { yyVAL.tableIdent = NewTableIdent(string(yyDollar[1].bytes)) } - case 623: + case 634: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3077 +//line sql.y:3126 { yyVAL.tableIdent = NewTableIdent(string(yyDollar[1].bytes)) } - case 625: + case 636: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3084 +//line sql.y:3133 { yyVAL.tableIdent = NewTableIdent(string(yyDollar[1].bytes)) } - case 829: + case 842: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3313 +//line sql.y:3364 { if incNesting(yylex) { yylex.Error("max nesting level reached") return 1 } } - case 830: + case 843: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3322 +//line sql.y:3373 { decNesting(yylex) } - case 831: + case 844: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:3327 +//line sql.y:3378 { - forceEOF(yylex) + skipToEnd(yylex) } - case 832: + case 845: yyDollar = yyS[yypt-0 : yypt+1] -//line sql.y:3332 +//line sql.y:3383 { - forceEOF(yylex) + skipToEnd(yylex) } - case 833: + case 846: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3336 +//line sql.y:3387 { - forceEOF(yylex) + skipToEnd(yylex) } - case 834: + case 847: yyDollar = yyS[yypt-1 : yypt+1] -//line sql.y:3340 +//line sql.y:3391 { - forceEOF(yylex) + skipToEnd(yylex) } } goto yystack /* stack new state and value */ diff --git a/go/vt/sqlparser/sql.y b/go/vt/sqlparser/sql.y index a228742861b..2a0dcdbff9f 100644 --- a/go/vt/sqlparser/sql.y +++ b/go/vt/sqlparser/sql.y @@ -41,11 +41,11 @@ func decNesting(yylex interface{}) { yylex.(*Tokenizer).nesting-- } -// forceEOF forces the lexer to end prematurely. Not all SQL statements -// are supported by the Parser, thus calling forceEOF will make the lexer +// skipToEnd forces the lexer to end prematurely. Not all SQL statements +// are supported by the Parser, thus calling skipToEnd will make the lexer // return EOF early. -func forceEOF(yylex interface{}) { - yylex.(*Tokenizer).ForceEOF = true +func skipToEnd(yylex interface{}) { + yylex.(*Tokenizer).SkipToEnd = true } %} @@ -180,7 +180,7 @@ func forceEOF(yylex interface{}) { %token NULLX AUTO_INCREMENT APPROXNUM SIGNED UNSIGNED ZEROFILL // Supported SHOW tokens -%token COLLATION DATABASES TABLES VITESS_KEYSPACES VITESS_SHARDS VITESS_TABLETS VSCHEMA_TABLES VITESS_TARGET FULL PROCESSLIST COLUMNS FIELDS +%token COLLATION DATABASES TABLES VITESS_KEYSPACES VITESS_SHARDS VITESS_TABLETS VSCHEMA_TABLES VITESS_TARGET FULL PROCESSLIST COLUMNS FIELDS ENGINES PLUGINS // SET tokens %token NAMES CHARSET GLOBAL SESSION ISOLATION LEVEL READ WRITE ONLY REPEATABLE COMMITTED UNCOMMITTED SERIALIZABLE @@ -204,7 +204,7 @@ func forceEOF(yylex interface{}) { %type select_statement base_select union_lhs union_rhs %type stream_statement insert_statement update_statement delete_statement set_statement %type create_statement alter_statement rename_statement drop_statement truncate_statement flush_statement -%type create_table_prefix +%type create_table_prefix rename_list %type analyze_statement show_statement use_statement other_statement %type begin_statement commit_statement rollback_statement %type comment_opt comment_list @@ -267,7 +267,7 @@ func forceEOF(yylex interface{}) { %type charset_value %type table_id reserved_table_id table_alias as_opt_id %type as_opt -%type force_eof ddl_force_eof +%type skip_to_end ddl_skip_to_end %type charset %type set_session_or_global show_session_or_global %type convert_type @@ -557,18 +557,18 @@ create_statement: $1.OptLike = $2 $$ = $1 } -| CREATE constraint_opt INDEX ID using_opt ON table_name ddl_force_eof +| CREATE constraint_opt INDEX ID using_opt ON table_name ddl_skip_to_end { // Change this to an alter statement - $$ = &DDL{Action: AlterStr, Table: $7, NewName:$7} + $$ = &DDL{Action: AlterStr, Table: $7} } -| CREATE VIEW table_name ddl_force_eof +| CREATE VIEW table_name ddl_skip_to_end { - $$ = &DDL{Action: CreateStr, NewName: $3.ToViewName()} + $$ = &DDL{Action: CreateStr, Table: $3.ToViewName()} } -| CREATE OR REPLACE VIEW table_name ddl_force_eof +| CREATE OR REPLACE VIEW table_name ddl_skip_to_end { - $$ = &DDL{Action: CreateStr, NewName: $5.ToViewName()} + $$ = &DDL{Action: CreateStr, Table: $5.ToViewName()} } | CREATE VINDEX sql_id vindex_type_opt vindex_params_opt { @@ -578,11 +578,11 @@ create_statement: Params: $5, }} } -| CREATE DATABASE not_exists_opt ID ddl_force_eof +| CREATE DATABASE not_exists_opt ID ddl_skip_to_end { $$ = &DBDDL{Action: CreateStr, DBName: string($4)} } -| CREATE SCHEMA not_exists_opt ID ddl_force_eof +| CREATE SCHEMA not_exists_opt ID ddl_skip_to_end { $$ = &DBDDL{Action: CreateStr, DBName: string($4)} } @@ -632,7 +632,7 @@ vindex_param: create_table_prefix: CREATE TABLE not_exists_opt table_name { - $$ = &DDL{Action: CreateStr, NewName: $4} + $$ = &DDL{Action: CreateStr, Table: $4} setDDL(yylex, $$) } @@ -1288,17 +1288,17 @@ table_opt_value: } alter_statement: - ALTER ignore_opt TABLE table_name non_add_drop_or_rename_operation force_eof + ALTER ignore_opt TABLE table_name non_add_drop_or_rename_operation skip_to_end { - $$ = &DDL{Action: AlterStr, Table: $4, NewName: $4} + $$ = &DDL{Action: AlterStr, Table: $4} } -| ALTER ignore_opt TABLE table_name ADD alter_object_type force_eof +| ALTER ignore_opt TABLE table_name ADD alter_object_type skip_to_end { - $$ = &DDL{Action: AlterStr, Table: $4, NewName: $4} + $$ = &DDL{Action: AlterStr, Table: $4} } -| ALTER ignore_opt TABLE table_name DROP alter_object_type force_eof +| ALTER ignore_opt TABLE table_name DROP alter_object_type skip_to_end { - $$ = &DDL{Action: AlterStr, Table: $4, NewName: $4} + $$ = &DDL{Action: AlterStr, Table: $4} } | ALTER ignore_opt TABLE table_name ADD VINDEX sql_id '(' column_list ')' vindex_type_opt vindex_params_opt { @@ -1326,16 +1326,16 @@ alter_statement: | ALTER ignore_opt TABLE table_name RENAME to_opt table_name { // Change this to a rename statement - $$ = &DDL{Action: RenameStr, Table: $4, NewName: $7} + $$ = &DDL{Action: RenameStr, FromTables: TableNames{$4}, ToTables: TableNames{$7}} } -| ALTER ignore_opt TABLE table_name RENAME index_opt force_eof +| ALTER ignore_opt TABLE table_name RENAME index_opt skip_to_end { // Rename an index can just be an alter - $$ = &DDL{Action: AlterStr, Table: $4, NewName: $4} + $$ = &DDL{Action: AlterStr, Table: $4} } -| ALTER VIEW table_name ddl_force_eof +| ALTER VIEW table_name ddl_skip_to_end { - $$ = &DDL{Action: AlterStr, Table: $3.ToViewName(), NewName: $3.ToViewName()} + $$ = &DDL{Action: AlterStr, Table: $3.ToViewName()} } | ALTER ignore_opt TABLE table_name partition_operation { @@ -1382,32 +1382,44 @@ partition_definition: } rename_statement: - RENAME TABLE table_name TO table_name + RENAME TABLE rename_list { - $$ = &DDL{Action: RenameStr, Table: $3, NewName: $5} + $$ = $3 + } + +rename_list: + table_name TO table_name + { + $$ = &DDL{Action: RenameStr, FromTables: TableNames{$1}, ToTables: TableNames{$3}} + } +| rename_list ',' table_name TO table_name + { + $$ = $1 + $$.FromTables = append($$.FromTables, $3) + $$.ToTables = append($$.ToTables, $5) } drop_statement: - DROP TABLE exists_opt table_name + DROP TABLE exists_opt table_name_list { var exists bool if $3 != 0 { exists = true } - $$ = &DDL{Action: DropStr, Table: $4, IfExists: exists} + $$ = &DDL{Action: DropStr, FromTables: $4, IfExists: exists} } -| DROP INDEX ID ON table_name ddl_force_eof +| DROP INDEX ID ON table_name ddl_skip_to_end { // Change this to an alter statement - $$ = &DDL{Action: AlterStr, Table: $5, NewName: $5} + $$ = &DDL{Action: AlterStr, Table: $5} } -| DROP VIEW exists_opt table_name ddl_force_eof +| DROP VIEW exists_opt table_name ddl_skip_to_end { var exists bool if $3 != 0 { exists = true } - $$ = &DDL{Action: DropStr, Table: $4.ToViewName(), IfExists: exists} + $$ = &DDL{Action: DropStr, FromTables: TableNames{$4.ToViewName()}, IfExists: exists} } | DROP DATABASE exists_opt ID { @@ -1430,64 +1442,77 @@ truncate_statement: analyze_statement: ANALYZE TABLE table_name { - $$ = &DDL{Action: AlterStr, Table: $3, NewName: $3} + $$ = &DDL{Action: AlterStr, Table: $3} } show_statement: - SHOW BINARY ID ddl_force_eof /* SHOW BINARY LOGS */ + SHOW BINARY ID ddl_skip_to_end /* SHOW BINARY LOGS */ { $$ = &Show{Type: string($2) + " " + string($3)} } -| SHOW CHARACTER SET ddl_force_eof +/* SHOW CHARACTER SET and SHOW CHARSET are equivalent */ +| SHOW CHARACTER SET ddl_skip_to_end { - $$ = &Show{Type: string($2) + " " + string($3)} + $$ = &Show{Type: CharsetStr} } -| SHOW CREATE DATABASE ddl_force_eof +| SHOW CHARSET ddl_skip_to_end + { + $$ = &Show{Type: string($2)} + } +| SHOW CREATE DATABASE ddl_skip_to_end { $$ = &Show{Type: string($2) + " " + string($3)} } /* Rule to handle SHOW CREATE EVENT, SHOW CREATE FUNCTION, etc. */ -| SHOW CREATE ID ddl_force_eof +| SHOW CREATE ID ddl_skip_to_end { $$ = &Show{Type: string($2) + " " + string($3)} } -| SHOW CREATE PROCEDURE ddl_force_eof +| SHOW CREATE PROCEDURE ddl_skip_to_end { $$ = &Show{Type: string($2) + " " + string($3)} } -| SHOW CREATE TABLE ddl_force_eof +| SHOW CREATE TABLE ddl_skip_to_end { $$ = &Show{Type: string($2) + " " + string($3)} } -| SHOW CREATE TRIGGER ddl_force_eof +| SHOW CREATE TRIGGER ddl_skip_to_end { $$ = &Show{Type: string($2) + " " + string($3)} } -| SHOW CREATE VIEW ddl_force_eof +| SHOW CREATE VIEW ddl_skip_to_end { $$ = &Show{Type: string($2) + " " + string($3)} } -| SHOW DATABASES ddl_force_eof +| SHOW DATABASES ddl_skip_to_end { $$ = &Show{Type: string($2)} } -| SHOW INDEX ddl_force_eof +| SHOW ENGINES { $$ = &Show{Type: string($2)} } -| SHOW KEYS ddl_force_eof +| SHOW INDEX ddl_skip_to_end { $$ = &Show{Type: string($2)} } -| SHOW PROCEDURE ddl_force_eof +| SHOW KEYS ddl_skip_to_end { $$ = &Show{Type: string($2)} } -| SHOW show_session_or_global STATUS ddl_force_eof +| SHOW PLUGINS + { + $$ = &Show{Type: string($2)} + } +| SHOW PROCEDURE ddl_skip_to_end + { + $$ = &Show{Type: string($2)} + } +| SHOW show_session_or_global STATUS ddl_skip_to_end { $$ = &Show{Scope: $2, Type: string($3)} } -| SHOW TABLE ddl_force_eof +| SHOW TABLE ddl_skip_to_end { $$ = &Show{Type: string($2)} } @@ -1506,7 +1531,7 @@ show_statement: $$ = &Show{Type: $3, ShowTablesOpt: showTablesOpt} } } -| SHOW show_session_or_global VARIABLES ddl_force_eof +| SHOW show_session_or_global VARIABLES ddl_skip_to_end { $$ = &Show{Scope: $2, Type: string($3)} } @@ -1558,7 +1583,7 @@ show_statement: * SHOW BINARY LOGS * SHOW INVALID */ -| SHOW ID ddl_force_eof +| SHOW ID ddl_skip_to_end { $$ = &Show{Type: string($2)} } @@ -1668,37 +1693,37 @@ rollback_statement: } other_statement: - DESC force_eof + DESC skip_to_end { $$ = &OtherRead{} } -| DESCRIBE force_eof +| DESCRIBE skip_to_end { $$ = &OtherRead{} } -| EXPLAIN force_eof +| EXPLAIN skip_to_end { $$ = &OtherRead{} } -| REPAIR force_eof +| REPAIR skip_to_end { $$ = &OtherAdmin{} } -| OPTIMIZE force_eof +| OPTIMIZE skip_to_end { $$ = &OtherAdmin{} } -| LOCK TABLES force_eof +| LOCK TABLES skip_to_end { $$ = &OtherAdmin{} } -| UNLOCK TABLES force_eof +| UNLOCK TABLES skip_to_end { $$ = &OtherAdmin{} } flush_statement: - FLUSH force_eof + FLUSH skip_to_end { $$ = &DDL{Action: FlushStr} } @@ -2452,6 +2477,30 @@ function_call_keyword: { $$ = &SubstrExpr{Name: $3, From: $5, To: $7} } +| SUBSTR openb STRING ',' value_expression closeb + { + $$ = &SubstrExpr{StrVal: NewStrVal($3), From: $5, To: nil} + } +| SUBSTR openb STRING ',' value_expression ',' value_expression closeb + { + $$ = &SubstrExpr{StrVal: NewStrVal($3), From: $5, To: $7} + } +| SUBSTR openb STRING FROM value_expression FOR value_expression closeb + { + $$ = &SubstrExpr{StrVal: NewStrVal($3), From: $5, To: $7} + } +| SUBSTRING openb STRING ',' value_expression closeb + { + $$ = &SubstrExpr{StrVal: NewStrVal($3), From: $5, To: nil} + } +| SUBSTRING openb STRING ',' value_expression ',' value_expression closeb + { + $$ = &SubstrExpr{StrVal: NewStrVal($3), From: $5, To: $7} + } +| SUBSTRING openb STRING FROM value_expression FOR value_expression closeb + { + $$ = &SubstrExpr{StrVal: NewStrVal($3), From: $5, To: $7} + } | MATCH openb select_expression_list closeb AGAINST openb value_expression match_option closeb { $$ = &MatchExpr{Columns: $3, Expr: $7, Option: $8} @@ -3218,6 +3267,7 @@ non_reserved_keyword: | DECIMAL | DOUBLE | DUPLICATE +| ENGINES | ENUM | EXPANSION | FLOAT_TYPE @@ -3256,6 +3306,7 @@ non_reserved_keyword: | ONLY | OPTIMIZE | PARTITION +| PLUGINS | POINT | POLYGON | PRIMARY @@ -3323,20 +3374,20 @@ closeb: decNesting(yylex) } -force_eof: +skip_to_end: { - forceEOF(yylex) + skipToEnd(yylex) } -ddl_force_eof: +ddl_skip_to_end: { - forceEOF(yylex) + skipToEnd(yylex) } | openb { - forceEOF(yylex) + skipToEnd(yylex) } | reserved_sql_id { - forceEOF(yylex) + skipToEnd(yylex) } diff --git a/go/vt/sqlparser/token.go b/go/vt/sqlparser/token.go index 2058dcac7ab..c380e964ad8 100644 --- a/go/vt/sqlparser/token.go +++ b/go/vt/sqlparser/token.go @@ -37,7 +37,7 @@ type Tokenizer struct { InStream io.Reader AllowComments bool SkipSpecialComments bool - ForceEOF bool + SkipToEnd bool lastChar uint16 Position int lastToken []byte @@ -166,6 +166,7 @@ var keywords = map[string]int{ "elseif": UNUSED, "enclosed": UNUSED, "end": END, + "engines": ENGINES, "enum": ENUM, "escape": ESCAPE, "escaped": UNUSED, @@ -286,6 +287,7 @@ var keywords = map[string]int{ "outer": OUTER, "outfile": UNUSED, "partition": PARTITION, + "plugins": PLUGINS, "point": POINT, "polygon": POLYGON, "precision": UNUSED, @@ -428,6 +430,10 @@ func KeywordString(id int) string { // Lex returns the next token form the Tokenizer. // This function is used by go yacc. func (tkn *Tokenizer) Lex(lval *yySymType) int { + if tkn.SkipToEnd { + return tkn.skipStatement() + } + typ, val := tkn.Scan() for typ == COMMENT { if tkn.AllowComments { @@ -435,6 +441,13 @@ func (tkn *Tokenizer) Lex(lval *yySymType) int { } typ, val = tkn.Scan() } + if typ == 0 || typ == ';' || typ == LEX_ERROR { + // If encounter end of statement or invalid token, + // we should not accept partially parsed DDLs. They + // should instead result in parser errors. See the + // Parse function to see how this is handled. + tkn.partialDDL = nil + } lval.bytes = val tkn.lastToken = val return typ @@ -451,9 +464,7 @@ func (tkn *Tokenizer) Error(err string) { tkn.LastError = errors.New(buf.String()) // Try and re-sync to the next statement - if tkn.lastChar != ';' { - tkn.skipStatement() - } + tkn.skipStatement() } // Scan scans the tokenizer for the next token and returns @@ -475,11 +486,6 @@ func (tkn *Tokenizer) Scan() (int, []byte) { tkn.next() } - if tkn.ForceEOF { - tkn.skipStatement() - return 0, nil - } - tkn.skipBlank() switch ch := tkn.lastChar; { case isLetter(ch): @@ -505,14 +511,21 @@ func (tkn *Tokenizer) Scan() (int, []byte) { return tkn.scanNumber(false) case ch == ':': return tkn.scanBindVar() - case ch == ';' && tkn.multi: + case ch == ';': + if tkn.multi { + // In multi mode, ';' is treated as EOF. So, we don't advance. + // Repeated calls to Scan will keep returning 0 until ParseNext + // forces the advance. + return 0, nil + } + tkn.next() + return ';', nil + case ch == eofChar: return 0, nil default: tkn.next() switch ch { - case eofChar: - return 0, nil - case '=', ',', ';', '(', ')', '+', '*', '%', '^', '~': + case '=', ',', '(', ')', '+', '*', '%', '^', '~': return int(ch), nil case '&': if tkn.lastChar == '&' { @@ -613,12 +626,14 @@ func (tkn *Tokenizer) Scan() (int, []byte) { } } -// skipStatement scans until the EOF, or end of statement is encountered. -func (tkn *Tokenizer) skipStatement() { - ch := tkn.lastChar - for ch != ';' && ch != eofChar { - tkn.next() - ch = tkn.lastChar +// skipStatement scans until end of statement. +func (tkn *Tokenizer) skipStatement() int { + tkn.SkipToEnd = false + for { + typ, _ := tkn.Scan() + if typ == 0 || typ == ';' || typ == LEX_ERROR { + return typ + } } } @@ -930,7 +945,7 @@ func (tkn *Tokenizer) reset() { tkn.specialComment = nil tkn.posVarIndex = 0 tkn.nesting = 0 - tkn.ForceEOF = false + tkn.SkipToEnd = false } func isLetter(ch uint16) bool { diff --git a/go/vt/topo/consultopo/server.go b/go/vt/topo/consultopo/server.go index 56e1026f34a..55bd575f8fc 100644 --- a/go/vt/topo/consultopo/server.go +++ b/go/vt/topo/consultopo/server.go @@ -110,11 +110,14 @@ func NewServer(cell, serverAddr, root string) (*Server, error) { } cfg := api.DefaultConfig() cfg.Address = serverAddr - if creds != nil && creds[cell] != nil { - cfg.Token = creds[cell].ACLToken - } else { - log.Warningf("Client auth not configured for cell: %v", cell) + if creds != nil { + if creds[cell] != nil { + cfg.Token = creds[cell].ACLToken + } else { + log.Warningf("Client auth not configured for cell: %v", cell) + } } + client, err := api.NewClient(cfg) if err != nil { return nil, err diff --git a/go/vt/topo/server.go b/go/vt/topo/server.go index 3f6fc48a605..6c4a07a2483 100644 --- a/go/vt/topo/server.go +++ b/go/vt/topo/server.go @@ -48,6 +48,7 @@ import ( "sync" "golang.org/x/net/context" + "vitess.io/vitess/go/vt/log" ) @@ -175,6 +176,7 @@ func NewWithFactory(factory Factory, serverAddress, root string) (*Server, error if err != nil { return nil, err } + conn = NewStatsConn(GlobalCell, conn) var connReadOnly Conn if factory.HasGlobalReadOnlyCell(serverAddress, root) { @@ -182,6 +184,7 @@ func NewWithFactory(factory Factory, serverAddress, root string) (*Server, error if err != nil { return nil, err } + connReadOnly = NewStatsConn(GlobalReadOnlyCell, connReadOnly) } else { connReadOnly = conn } @@ -257,6 +260,7 @@ func (ts *Server) ConnForCell(ctx context.Context, cell string) (Conn, error) { if err != nil { return nil, fmt.Errorf("failed to create topo connection to %v, %v: %v", ci.ServerAddress, ci.Root, err) } + conn = NewStatsConn(cell, conn) ts.cells[cell] = conn return conn, nil } diff --git a/go/vt/topo/stats_conn.go b/go/vt/topo/stats_conn.go new file mode 100644 index 00000000000..dadef0e1861 --- /dev/null +++ b/go/vt/topo/stats_conn.go @@ -0,0 +1,157 @@ +/* +Copyright 2018 The Vitess Authors + Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package topo + +import ( + "time" + + "golang.org/x/net/context" + + "vitess.io/vitess/go/stats" +) + +var _ Conn = (*StatsConn)(nil) + +var ( + topoStatsConnTimings = stats.NewMultiTimings( + "TopologyConnOperations", + "TopologyConnOperations timings", + []string{"Operation", "Cell"}) + + topoStatsConnErrors = stats.NewCountersWithMultiLabels( + "TopologyConnErrors", + "TopologyConnErrors errors per operation", + []string{"Operation", "Cell"}) +) + +// The StatsConn is a wrapper for a Conn that emits stats for every operation +type StatsConn struct { + cell string + conn Conn +} + +// NewStatsConn returns a StatsConn +func NewStatsConn(cell string, conn Conn) *StatsConn { + return &StatsConn{ + cell: cell, + conn: conn, + } +} + +// ListDir is part of the Conn interface +func (st *StatsConn) ListDir(ctx context.Context, dirPath string, full bool) ([]DirEntry, error) { + startTime := time.Now() + statsKey := []string{"ListDir", st.cell} + defer topoStatsConnTimings.Record(statsKey, startTime) + res, err := st.conn.ListDir(ctx, dirPath, full) + if err != nil { + topoStatsConnErrors.Add(statsKey, int64(1)) + return res, err + } + return res, err +} + +// Create is part of the Conn interface +func (st *StatsConn) Create(ctx context.Context, filePath string, contents []byte) (Version, error) { + startTime := time.Now() + statsKey := []string{"Create", st.cell} + defer topoStatsConnTimings.Record(statsKey, startTime) + res, err := st.conn.Create(ctx, filePath, contents) + if err != nil { + topoStatsConnErrors.Add(statsKey, int64(1)) + return res, err + } + return res, err +} + +// Update is part of the Conn interface +func (st *StatsConn) Update(ctx context.Context, filePath string, contents []byte, version Version) (Version, error) { + startTime := time.Now() + statsKey := []string{"Update", st.cell} + defer topoStatsConnTimings.Record(statsKey, startTime) + res, err := st.conn.Update(ctx, filePath, contents, version) + if err != nil { + topoStatsConnErrors.Add(statsKey, int64(1)) + return res, err + } + return res, err +} + +// Get is part of the Conn interface +func (st *StatsConn) Get(ctx context.Context, filePath string) ([]byte, Version, error) { + startTime := time.Now() + statsKey := []string{"Get", st.cell} + defer topoStatsConnTimings.Record(statsKey, startTime) + bytes, version, err := st.conn.Get(ctx, filePath) + if err != nil { + topoStatsConnErrors.Add(statsKey, int64(1)) + return bytes, version, err + } + return bytes, version, err +} + +// Delete is part of the Conn interface +func (st *StatsConn) Delete(ctx context.Context, filePath string, version Version) error { + startTime := time.Now() + statsKey := []string{"Delete", st.cell} + defer topoStatsConnTimings.Record(statsKey, startTime) + err := st.conn.Delete(ctx, filePath, version) + if err != nil { + topoStatsConnErrors.Add(statsKey, int64(1)) + return err + } + return err +} + +// Lock is part of the Conn interface +func (st *StatsConn) Lock(ctx context.Context, dirPath, contents string) (LockDescriptor, error) { + startTime := time.Now() + statsKey := []string{"Lock", st.cell} + defer topoStatsConnTimings.Record(statsKey, startTime) + res, err := st.conn.Lock(ctx, dirPath, contents) + if err != nil { + topoStatsConnErrors.Add(statsKey, int64(1)) + return res, err + } + return res, err +} + +// Watch is part of the Conn interface +func (st *StatsConn) Watch(ctx context.Context, filePath string) (current *WatchData, changes <-chan *WatchData, cancel CancelFunc) { + startTime := time.Now() + statsKey := []string{"Watch", st.cell} + defer topoStatsConnTimings.Record(statsKey, startTime) + return st.conn.Watch(ctx, filePath) +} + +// NewMasterParticipation is part of the Conn interface +func (st *StatsConn) NewMasterParticipation(name, id string) (MasterParticipation, error) { + startTime := time.Now() + statsKey := []string{"NewMasterParticipation", st.cell} + defer topoStatsConnTimings.Record(statsKey, startTime) + res, err := st.conn.NewMasterParticipation(name, id) + if err != nil { + topoStatsConnErrors.Add(statsKey, int64(1)) + return res, err + } + return res, err +} + +// Close is part of the Conn interface +func (st *StatsConn) Close() { + startTime := time.Now() + statsKey := []string{"Close", st.cell} + defer topoStatsConnTimings.Record(statsKey, startTime) + st.conn.Close() +} diff --git a/go/vt/topo/stats_conn_test.go b/go/vt/topo/stats_conn_test.go new file mode 100644 index 00000000000..6b7f57c5559 --- /dev/null +++ b/go/vt/topo/stats_conn_test.go @@ -0,0 +1,311 @@ +/* +Copyright 2018 The Vitess Authors + Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package topo + +import ( + "fmt" + "testing" + + "golang.org/x/net/context" +) + +// The fakeConn is a wrapper for a Conn that emits stats for every operation +type fakeConn struct { + v Version +} + +// ListDir is part of the Conn interface +func (st *fakeConn) ListDir(ctx context.Context, dirPath string, full bool) (res []DirEntry, err error) { + if dirPath == "error" { + return res, fmt.Errorf("Dummy error") + + } + return res, err +} + +// Create is part of the Conn interface +func (st *fakeConn) Create(ctx context.Context, filePath string, contents []byte) (ver Version, err error) { + if filePath == "error" { + return ver, fmt.Errorf("Dummy error") + + } + return ver, err +} + +// Update is part of the Conn interface +func (st *fakeConn) Update(ctx context.Context, filePath string, contents []byte, version Version) (ver Version, err error) { + if filePath == "error" { + return ver, fmt.Errorf("Dummy error") + + } + return ver, err +} + +// Get is part of the Conn interface +func (st *fakeConn) Get(ctx context.Context, filePath string) (bytes []byte, ver Version, err error) { + if filePath == "error" { + return bytes, ver, fmt.Errorf("Dummy error") + + } + return bytes, ver, err +} + +// Delete is part of the Conn interface +func (st *fakeConn) Delete(ctx context.Context, filePath string, version Version) (err error) { + if filePath == "error" { + return fmt.Errorf("Dummy error") + } + return err +} + +// Lock is part of the Conn interface +func (st *fakeConn) Lock(ctx context.Context, dirPath, contents string) (lock LockDescriptor, err error) { + if dirPath == "error" { + return lock, fmt.Errorf("Dummy error") + + } + return lock, err +} + +// Watch is part of the Conn interface +func (st *fakeConn) Watch(ctx context.Context, filePath string) (current *WatchData, changes <-chan *WatchData, cancel CancelFunc) { + return current, changes, cancel +} + +// NewMasterParticipation is part of the Conn interface +func (st *fakeConn) NewMasterParticipation(name, id string) (mp MasterParticipation, err error) { + if name == "error" { + return mp, fmt.Errorf("Dummy error") + + } + return mp, err +} + +// Close is part of the Conn interface +func (st *fakeConn) Close() { +} + +//TestStatsConnTopoListDir emits stats on ListDir +func TestStatsConnTopoListDir(t *testing.T) { + conn := &fakeConn{} + statsConn := NewStatsConn("global", conn) + ctx := context.Background() + + statsConn.ListDir(ctx, "", true) + timingCounts := topoStatsConnTimings.Counts()["ListDir.global"] + if got, want := timingCounts, int64(1); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } + + // error is zero before getting an error + errorCount := topoStatsConnErrors.Counts()["ListDir.global"] + if got, want := errorCount, int64(0); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } + + statsConn.ListDir(ctx, "error", true) + + // error stats gets emitted + errorCount = topoStatsConnErrors.Counts()["ListDir.global"] + if got, want := errorCount, int64(1); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } +} + +//TestStatsConnTopoCreate emits stats on Create +func TestStatsConnTopoCreate(t *testing.T) { + conn := &fakeConn{} + statsConn := NewStatsConn("global", conn) + ctx := context.Background() + + statsConn.Create(ctx, "", []byte{}) + timingCounts := topoStatsConnTimings.Counts()["Create.global"] + if got, want := timingCounts, int64(1); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } + + // error is zero before getting an error + errorCount := topoStatsConnErrors.Counts()["Create.global"] + if got, want := errorCount, int64(0); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } + + statsConn.Create(ctx, "error", []byte{}) + + // error stats gets emitted + errorCount = topoStatsConnErrors.Counts()["Create.global"] + if got, want := errorCount, int64(1); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } +} + +//TestStatsConnTopoUpdate emits stats on Update +func TestStatsConnTopoUpdate(t *testing.T) { + conn := &fakeConn{} + statsConn := NewStatsConn("global", conn) + ctx := context.Background() + + statsConn.Update(ctx, "", []byte{}, conn.v) + timingCounts := topoStatsConnTimings.Counts()["Update.global"] + if got, want := timingCounts, int64(1); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } + + // error is zero before getting an error + errorCount := topoStatsConnErrors.Counts()["Update.global"] + if got, want := errorCount, int64(0); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } + + statsConn.Update(ctx, "error", []byte{}, conn.v) + + // error stats gets emitted + errorCount = topoStatsConnErrors.Counts()["Update.global"] + if got, want := errorCount, int64(1); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } +} + +//TestStatsConnTopoGet emits stats on Get +func TestStatsConnTopoGet(t *testing.T) { + conn := &fakeConn{} + statsConn := NewStatsConn("global", conn) + ctx := context.Background() + + statsConn.Get(ctx, "") + timingCounts := topoStatsConnTimings.Counts()["Get.global"] + if got, want := timingCounts, int64(1); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } + + // error is zero before getting an error + errorCount := topoStatsConnErrors.Counts()["Get.global"] + if got, want := errorCount, int64(0); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } + + statsConn.Get(ctx, "error") + + // error stats gets emitted + errorCount = topoStatsConnErrors.Counts()["Get.global"] + if got, want := errorCount, int64(1); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } +} + +//TestStatsConnTopoDelete emits stats on Delete +func TestStatsConnTopoDelete(t *testing.T) { + conn := &fakeConn{} + statsConn := NewStatsConn("global", conn) + ctx := context.Background() + + statsConn.Delete(ctx, "", conn.v) + timingCounts := topoStatsConnTimings.Counts()["Delete.global"] + if got, want := timingCounts, int64(1); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } + + // error is zero before getting an error + errorCount := topoStatsConnErrors.Counts()["Delete.global"] + if got, want := errorCount, int64(0); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } + + statsConn.Delete(ctx, "error", conn.v) + + // error stats gets emitted + errorCount = topoStatsConnErrors.Counts()["Delete.global"] + if got, want := errorCount, int64(1); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } +} + +//TestStatsConnTopoLock emits stats on Lock +func TestStatsConnTopoLock(t *testing.T) { + conn := &fakeConn{} + statsConn := NewStatsConn("global", conn) + ctx := context.Background() + + statsConn.Lock(ctx, "", "") + timingCounts := topoStatsConnTimings.Counts()["Lock.global"] + if got, want := timingCounts, int64(1); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } + + // error is zero before getting an error + errorCount := topoStatsConnErrors.Counts()["Lock.global"] + if got, want := errorCount, int64(0); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } + + statsConn.Lock(ctx, "error", "") + + // error stats gets emitted + errorCount = topoStatsConnErrors.Counts()["Lock.global"] + if got, want := errorCount, int64(1); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } +} + +//TestStatsConnTopoWatch emits stats on Watch +func TestStatsConnTopoWatch(t *testing.T) { + conn := &fakeConn{} + statsConn := NewStatsConn("global", conn) + ctx := context.Background() + + statsConn.Watch(ctx, "") + timingCounts := topoStatsConnTimings.Counts()["Watch.global"] + if got, want := timingCounts, int64(1); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } + +} + +//TestStatsConnTopoNewMasterParticipation emits stats on NewMasterParticipation +func TestStatsConnTopoNewMasterParticipation(t *testing.T) { + conn := &fakeConn{} + statsConn := NewStatsConn("global", conn) + + statsConn.NewMasterParticipation("", "") + timingCounts := topoStatsConnTimings.Counts()["NewMasterParticipation.global"] + if got, want := timingCounts, int64(1); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } + + // error is zero before getting an error + errorCount := topoStatsConnErrors.Counts()["NewMasterParticipation.global"] + if got, want := errorCount, int64(0); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } + + statsConn.NewMasterParticipation("error", "") + + // error stats gets emitted + errorCount = topoStatsConnErrors.Counts()["NewMasterParticipation.global"] + if got, want := errorCount, int64(1); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } +} + +//TestStatsConnTopoClose emits stats on Close +func TestStatsConnTopoClose(t *testing.T) { + conn := &fakeConn{} + statsConn := NewStatsConn("global", conn) + + statsConn.Close() + timingCounts := topoStatsConnTimings.Counts()["Close.global"] + if got, want := timingCounts, int64(1); got != want { + t.Errorf("stats were not properly recorded: got = %d, want = %d", got, want) + } +} diff --git a/go/vt/vtctl/backup.go b/go/vt/vtctl/backup.go index aadbd2a6a59..0309f41b8e8 100644 --- a/go/vt/vtctl/backup.go +++ b/go/vt/vtctl/backup.go @@ -17,6 +17,7 @@ limitations under the License. package vtctl import ( + "errors" "flag" "fmt" "io" @@ -24,6 +25,7 @@ import ( "golang.org/x/net/context" "vitess.io/vitess/go/vt/logutil" "vitess.io/vitess/go/vt/mysqlctl/backupstorage" + topodatapb "vitess.io/vitess/go/vt/proto/topodata" "vitess.io/vitess/go/vt/topo/topoproto" "vitess.io/vitess/go/vt/wrangler" ) @@ -34,12 +36,22 @@ func init() { commandListBackups, "", "Lists all the backups for a shard."}) + addCommand("Shards", command{ + "BackupShard", + commandBackupShard, + "", + "Chooses a tablet and creates a backup for a shard."}) addCommand("Shards", command{ "RemoveBackup", commandRemoveBackup, " ", "Removes a backup for the BackupStorage."}) + addCommand("Tablets", command{ + "Backup", + commandBackup, + "[-concurrency=4] ", + "Stops mysqld and uses the BackupStorage service to store a new backup. This function also remembers if the tablet was replicating so that it can restore the same state after the backup completes."}) addCommand("Tablets", command{ "RestoreFromBackup", commandRestoreFromBackup, @@ -47,6 +59,95 @@ func init() { "Stops mysqld and restores the data from the latest backup."}) } +func commandBackup(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error { + concurrency := subFlags.Int("concurrency", 4, "Specifies the number of compression/checksum jobs to run simultaneously") + if err := subFlags.Parse(args); err != nil { + return err + } + if subFlags.NArg() != 1 { + return fmt.Errorf("the Backup command requires the argument") + } + + tabletAlias, err := topoproto.ParseTabletAlias(subFlags.Arg(0)) + if err != nil { + return err + } + tabletInfo, err := wr.TopoServer().GetTablet(ctx, tabletAlias) + if err != nil { + return err + } + + return execBackup(ctx, wr, tabletInfo.Tablet, *concurrency) +} + +func commandBackupShard(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error { + concurrency := subFlags.Int("concurrency", 4, "Specifies the number of compression/checksum jobs to run simultaneously") + if err := subFlags.Parse(args); err != nil { + return err + } + if subFlags.NArg() != 1 { + return fmt.Errorf("action BackupShard requires ") + } + + keyspace, shard, err := topoproto.ParseKeyspaceShard(subFlags.Arg(0)) + if err != nil { + return err + } + + tablets, stats, err := wr.ShardReplicationStatuses(ctx, keyspace, shard) + if tablets == nil { + return err + } + + var tabletForBackup *topodatapb.Tablet + var secondsBehind uint32 + + for i := range tablets { + // don't run a backup on a non-slave type + if !tablets[i].IsSlaveType() { + continue + } + + // choose the first tablet as the baseline + if tabletForBackup == nil { + tabletForBackup = tablets[i].Tablet + secondsBehind = stats[i].SecondsBehindMaster + continue + } + + // choose a new tablet if it is more up to date + if stats[i].SecondsBehindMaster < secondsBehind { + tabletForBackup = tablets[i].Tablet + secondsBehind = stats[i].SecondsBehindMaster + } + } + + if tabletForBackup == nil { + return errors.New("no tablet available for backup") + } + + return execBackup(ctx, wr, tabletForBackup, *concurrency) +} + +// execBackup is shared by Backup and BackupShard +func execBackup(ctx context.Context, wr *wrangler.Wrangler, tablet *topodatapb.Tablet, concurrency int) error { + stream, err := wr.TabletManagerClient().Backup(ctx, tablet, concurrency) + if err != nil { + return err + } + for { + e, err := stream.Recv() + switch err { + case nil: + logutil.LogEvent(wr.Logger(), e) + case io.EOF: + return nil + default: + return err + } + } +} + func commandListBackups(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error { if err := subFlags.Parse(args); err != nil { return err diff --git a/go/vt/vtctl/vtctl.go b/go/vt/vtctl/vtctl.go index 0251e14b010..16167296870 100644 --- a/go/vt/vtctl/vtctl.go +++ b/go/vt/vtctl/vtctl.go @@ -96,7 +96,6 @@ import ( "errors" "flag" "fmt" - "io" "io/ioutil" "sort" "strconv" @@ -205,13 +204,13 @@ var commands = []commandGroup{ {"Sleep", commandSleep, " ", "Blocks the action queue on the specified tablet for the specified amount of time. This is typically used for testing."}, - {"Backup", commandBackup, - "[-concurrency=4] ", - "Stops mysqld and uses the BackupStorage service to store a new backup. This function also remembers if the tablet was replicating so that it can restore the same state after the backup completes."}, {"ExecuteHook", commandExecuteHook, " [ ...]", "Runs the specified hook on the given tablet. A hook is a script that resides in the $VTROOT/vthook directory. You can put any script into that directory and use this command to run that script.\n" + "For this command, the param=value arguments are parameters that the command passes to the specified hook."}, + {"ExecuteFetchAsApp", commandExecuteFetchAsApp, + "[-max_rows=10000] [-json] [-use_pool] ", + "Runs the given SQL command as a App on the remote tablet."}, {"ExecuteFetchAsDba", commandExecuteFetchAsDba, "[-max_rows=10000] [-disable_binlogs] [-json] ", "Runs the given SQL command as a DBA on the remote tablet."}, @@ -1030,42 +1029,37 @@ func commandSleep(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.Fla return wr.TabletManagerClient().Sleep(ctx, ti.Tablet, duration) } -func commandBackup(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error { - concurrency := subFlags.Int("concurrency", 4, "Specifies the number of compression/checksum jobs to run simultaneously") +func commandExecuteFetchAsApp(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error { + maxRows := subFlags.Int("max_rows", 10000, "Specifies the maximum number of rows to allow in fetch") + usePool := subFlags.Bool("use_pool", false, "Use connection from pool") + json := subFlags.Bool("json", false, "Output JSON instead of human-readable table") + if err := subFlags.Parse(args); err != nil { return err } - if subFlags.NArg() != 1 { - return fmt.Errorf("the Backup command requires the argument") + if subFlags.NArg() != 2 { + return fmt.Errorf("the and arguments are required for the ExecuteFetchAsApp command") } - tabletAlias, err := topoproto.ParseTabletAlias(subFlags.Arg(0)) - if err != nil { - return err - } - tabletInfo, err := wr.TopoServer().GetTablet(ctx, tabletAlias) + alias, err := topoproto.ParseTabletAlias(subFlags.Arg(0)) if err != nil { return err } - stream, err := wr.TabletManagerClient().Backup(ctx, tabletInfo.Tablet, *concurrency) + query := subFlags.Arg(1) + qrproto, err := wr.ExecuteFetchAsApp(ctx, alias, *usePool, query, *maxRows) if err != nil { return err } - for { - e, err := stream.Recv() - switch err { - case nil: - logutil.LogEvent(wr.Logger(), e) - case io.EOF: - return nil - default: - return err - } + qr := sqltypes.Proto3ToResult(qrproto) + if *json { + return printJSON(wr.Logger(), qr) } + printQueryResult(loggerWriter{wr.Logger()}, qr) + return nil } func commandExecuteFetchAsDba(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error { - maxRows := subFlags.Int("max_rows", 10000, "Specifies the maximum number of rows to allow in reset") + maxRows := subFlags.Int("max_rows", 10000, "Specifies the maximum number of rows to allow in fetch") disableBinlogs := subFlags.Bool("disable_binlogs", false, "Disables writing to binlogs during the query") reloadSchema := subFlags.Bool("reload_schema", false, "Indicates whether the tablet schema will be reloaded after executing the SQL command. The default value is false, which indicates that the tablet schema will not be reloaded.") json := subFlags.Bool("json", false, "Output JSON instead of human-readable table") diff --git a/go/vt/vtexplain/vtexplain_vtgate.go b/go/vt/vtexplain/vtexplain_vtgate.go index 4e90acf00f2..4ea5b73abd8 100644 --- a/go/vt/vtexplain/vtexplain_vtgate.go +++ b/go/vt/vtexplain/vtexplain_vtgate.go @@ -20,11 +20,11 @@ limitations under the License. package vtexplain import ( - "encoding/json" "fmt" "golang.org/x/net/context" + "vitess.io/vitess/go/json2" "vitess.io/vitess/go/vt/discovery" "vitess.io/vitess/go/vt/key" "vitess.io/vitess/go/vt/log" @@ -89,11 +89,15 @@ func buildTopology(opts *Options, vschemaStr string, numShardsPerKeyspace int) e explainTopo.Lock.Lock() defer explainTopo.Lock.Unlock() - explainTopo.Keyspaces = make(map[string]*vschemapb.Keyspace) - err := json.Unmarshal([]byte(vschemaStr), &explainTopo.Keyspaces) + // We have to use proto's custom json loader so it can + // handle string->enum conversion correctly. + var srvVSchema vschemapb.SrvVSchema + wrappedStr := fmt.Sprintf(`{"keyspaces": %s}`, vschemaStr) + err := json2.Unmarshal([]byte(wrappedStr), &srvVSchema) if err != nil { return err } + explainTopo.Keyspaces = srvVSchema.Keyspaces explainTopo.TabletConns = make(map[string]*explainTablet) for ks, vschema := range explainTopo.Keyspaces { diff --git a/go/vt/vtexplain/vtexplain_vttablet.go b/go/vt/vtexplain/vtexplain_vttablet.go index 59d2b0696b2..64d9ed55470 100644 --- a/go/vt/vtexplain/vtexplain_vttablet.go +++ b/go/vt/vtexplain/vtexplain_vttablet.go @@ -378,7 +378,7 @@ func initTabletEnvironment(ddls []*sqlparser.DDL, opts *Options) error { showTableRows := make([][]sqltypes.Value, 0, 4) for _, ddl := range ddls { - table := ddl.NewName.Name.String() + table := ddl.Table.Name.String() showTableRows = append(showTableRows, mysql.BaseShowTablesRow(table, false, "")) } schemaQueries[mysql.BaseShowTables] = &sqltypes.Result{ @@ -388,7 +388,7 @@ func initTabletEnvironment(ddls []*sqlparser.DDL, opts *Options) error { } for i, ddl := range ddls { - table := ddl.NewName.Name.String() + table := ddl.Table.Name.String() schemaQueries[mysql.BaseShowTablesForTable(table)] = &sqltypes.Result{ Fields: mysql.BaseShowTablesFields, RowsAffected: 1, diff --git a/go/vt/vtgate/engine/pullout_subquery.go b/go/vt/vtgate/engine/pullout_subquery.go new file mode 100644 index 00000000000..1ed44bfa19a --- /dev/null +++ b/go/vt/vtgate/engine/pullout_subquery.go @@ -0,0 +1,168 @@ +/* +Copyright 2018 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package engine + +import ( + "fmt" + + "vitess.io/vitess/go/sqltypes" + "vitess.io/vitess/go/vt/vterrors" + + querypb "vitess.io/vitess/go/vt/proto/query" + vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" +) + +var _ Primitive = (*PulloutSubquery)(nil) + +// PulloutSubquery executes a "pulled out" subquery and stores +// the results in a bind variable. +type PulloutSubquery struct { + Opcode PulloutOpcode + SubqueryResult string + HasValues string + Subquery Primitive + Underlying Primitive +} + +// RouteType returns a description of the query routing type used by the primitive +func (ps *PulloutSubquery) RouteType() string { + return ps.Opcode.String() +} + +// Execute satisfies the Primitive interface. +func (ps *PulloutSubquery) Execute(vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool) (*sqltypes.Result, error) { + combinedVars, err := ps.execSubquery(vcursor, bindVars) + if err != nil { + return nil, err + } + return ps.Underlying.Execute(vcursor, combinedVars, wantfields) +} + +// StreamExecute performs a streaming exec. +func (ps *PulloutSubquery) StreamExecute(vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool, callback func(*sqltypes.Result) error) error { + combinedVars, err := ps.execSubquery(vcursor, bindVars) + if err != nil { + return err + } + return ps.Underlying.StreamExecute(vcursor, combinedVars, wantfields, callback) +} + +// GetFields fetches the field info. +func (ps *PulloutSubquery) GetFields(vcursor VCursor, bindVars map[string]*querypb.BindVariable) (*sqltypes.Result, error) { + combinedVars := make(map[string]*querypb.BindVariable, len(bindVars)+1) + for k, v := range bindVars { + combinedVars[k] = v + } + switch ps.Opcode { + case PulloutValue: + combinedVars[ps.SubqueryResult] = sqltypes.NullBindVariable + case PulloutIn, PulloutNotIn: + combinedVars[ps.HasValues] = sqltypes.Int64BindVariable(0) + combinedVars[ps.SubqueryResult] = &querypb.BindVariable{ + Type: querypb.Type_TUPLE, + Values: []*querypb.Value{sqltypes.ValueToProto(sqltypes.NewInt64(0))}, + } + case PulloutExists: + combinedVars[ps.HasValues] = sqltypes.Int64BindVariable(0) + } + return ps.Underlying.GetFields(vcursor, combinedVars) +} + +func (ps *PulloutSubquery) execSubquery(vcursor VCursor, bindVars map[string]*querypb.BindVariable) (map[string]*querypb.BindVariable, error) { + result, err := ps.Subquery.Execute(vcursor, bindVars, false) + if err != nil { + return nil, err + } + combinedVars := make(map[string]*querypb.BindVariable, len(bindVars)+1) + for k, v := range bindVars { + combinedVars[k] = v + } + switch ps.Opcode { + case PulloutValue: + switch len(result.Rows) { + case 0: + combinedVars[ps.SubqueryResult] = sqltypes.NullBindVariable + case 1: + if len(result.Rows[0]) != 1 { + return nil, vterrors.New(vtrpcpb.Code_INVALID_ARGUMENT, "subquery returned more than one column") + } + combinedVars[ps.SubqueryResult] = sqltypes.ValueBindVariable(result.Rows[0][0]) + default: + return nil, vterrors.New(vtrpcpb.Code_INVALID_ARGUMENT, "subquery returned more than one row") + } + case PulloutIn, PulloutNotIn: + switch len(result.Rows) { + case 0: + combinedVars[ps.HasValues] = sqltypes.Int64BindVariable(0) + // Add a bogus value. It will not be checked. + combinedVars[ps.SubqueryResult] = &querypb.BindVariable{ + Type: querypb.Type_TUPLE, + Values: []*querypb.Value{sqltypes.ValueToProto(sqltypes.NewInt64(0))}, + } + default: + if len(result.Rows[0]) != 1 { + return nil, vterrors.New(vtrpcpb.Code_INVALID_ARGUMENT, "subquery returned more than one column") + } + combinedVars[ps.HasValues] = sqltypes.Int64BindVariable(1) + values := &querypb.BindVariable{ + Type: querypb.Type_TUPLE, + Values: make([]*querypb.Value, len(result.Rows)), + } + for i, v := range result.Rows { + values.Values[i] = sqltypes.ValueToProto(v[0]) + } + combinedVars[ps.SubqueryResult] = values + } + case PulloutExists: + switch len(result.Rows) { + case 0: + combinedVars[ps.HasValues] = sqltypes.Int64BindVariable(0) + default: + combinedVars[ps.HasValues] = sqltypes.Int64BindVariable(1) + } + } + return combinedVars, nil +} + +// PulloutOpcode is a number representing the opcode +// for the PulloutSubquery primitive. +type PulloutOpcode int + +// This is the list of PulloutOpcode values. +const ( + PulloutValue = PulloutOpcode(iota) + PulloutIn + PulloutNotIn + PulloutExists +) + +var pulloutName = map[PulloutOpcode]string{ + PulloutValue: "PulloutValue", + PulloutIn: "PulloutIn", + PulloutNotIn: "PulloutNotIn", + PulloutExists: "PulloutExists", +} + +func (code PulloutOpcode) String() string { + return pulloutName[code] +} + +// MarshalJSON serializes the PulloutOpcode as a JSON string. +// It's used for testing and diagnostics. +func (code PulloutOpcode) MarshalJSON() ([]byte, error) { + return ([]byte)(fmt.Sprintf("\"%s\"", code.String())), nil +} diff --git a/go/vt/vtgate/engine/pullout_subquery_test.go b/go/vt/vtgate/engine/pullout_subquery_test.go new file mode 100644 index 00000000000..778101b93b9 --- /dev/null +++ b/go/vt/vtgate/engine/pullout_subquery_test.go @@ -0,0 +1,377 @@ +/* +Copyright 2018 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package engine + +import ( + "errors" + "testing" + + "vitess.io/vitess/go/sqltypes" + + querypb "vitess.io/vitess/go/vt/proto/query" +) + +func TestPulloutSubqueryValueGood(t *testing.T) { + // Test one case with actual bind vars. + bindVars := map[string]*querypb.BindVariable{ + "aa": sqltypes.Int64BindVariable(1), + } + + sqResult := sqltypes.MakeTestResult( + sqltypes.MakeTestFields( + "col1", + "int64", + ), + "1", + ) + sfp := &fakePrimitive{ + results: []*sqltypes.Result{sqResult}, + } + underlyingResult := sqltypes.MakeTestResult( + sqltypes.MakeTestFields( + "col", + "int64", + ), + "0", + ) + ufp := &fakePrimitive{ + results: []*sqltypes.Result{underlyingResult}, + } + ps := &PulloutSubquery{ + Opcode: PulloutValue, + SubqueryResult: "sq", + Subquery: sfp, + Underlying: ufp, + } + + result, err := ps.Execute(nil, bindVars, false) + if err != nil { + t.Error(err) + } + sfp.ExpectLog(t, []string{`Execute aa: type:INT64 value:"1" false`}) + ufp.ExpectLog(t, []string{`Execute aa: type:INT64 value:"1" sq: type:INT64 value:"1" false`}) + expectResult(t, "ps.Execute", result, underlyingResult) +} + +func TestPulloutSubqueryValueNone(t *testing.T) { + sqResult := sqltypes.MakeTestResult( + sqltypes.MakeTestFields( + "col1", + "int64", + ), + ) + sfp := &fakePrimitive{ + results: []*sqltypes.Result{sqResult}, + } + ufp := &fakePrimitive{} + ps := &PulloutSubquery{ + Opcode: PulloutValue, + SubqueryResult: "sq", + Subquery: sfp, + Underlying: ufp, + } + + if _, err := ps.Execute(nil, make(map[string]*querypb.BindVariable), false); err != nil { + t.Error(err) + } + sfp.ExpectLog(t, []string{`Execute false`}) + ufp.ExpectLog(t, []string{`Execute sq: false`}) +} + +func TestPulloutSubqueryValueBadColumns(t *testing.T) { + sqResult := sqltypes.MakeTestResult( + sqltypes.MakeTestFields( + "col1|col2", + "int64|int64", + ), + "1|1", + ) + sfp := &fakePrimitive{ + results: []*sqltypes.Result{sqResult}, + } + ps := &PulloutSubquery{ + Opcode: PulloutValue, + SubqueryResult: "sq", + Subquery: sfp, + } + + _, err := ps.Execute(nil, make(map[string]*querypb.BindVariable), false) + expectError(t, "ps.Execute", err, "subquery returned more than one column") +} + +func TestPulloutSubqueryValueBadRows(t *testing.T) { + sqResult := sqltypes.MakeTestResult( + sqltypes.MakeTestFields( + "col1", + "int64", + ), + "1", + "2", + ) + sfp := &fakePrimitive{ + results: []*sqltypes.Result{sqResult}, + } + ps := &PulloutSubquery{ + Opcode: PulloutValue, + SubqueryResult: "sq", + Subquery: sfp, + } + + _, err := ps.Execute(nil, make(map[string]*querypb.BindVariable), false) + expectError(t, "ps.Execute", err, "subquery returned more than one row") +} + +func TestPulloutSubqueryInNotinGood(t *testing.T) { + sqResult := sqltypes.MakeTestResult( + sqltypes.MakeTestFields( + "col1", + "int64", + ), + "1", + "2", + ) + sfp := &fakePrimitive{ + results: []*sqltypes.Result{sqResult}, + } + ufp := &fakePrimitive{} + ps := &PulloutSubquery{ + Opcode: PulloutIn, + SubqueryResult: "sq", + HasValues: "has_values", + Subquery: sfp, + Underlying: ufp, + } + + if _, err := ps.Execute(nil, make(map[string]*querypb.BindVariable), false); err != nil { + t.Error(err) + } + sfp.ExpectLog(t, []string{`Execute false`}) + ufp.ExpectLog(t, []string{`Execute has_values: type:INT64 value:"1" sq: type:TUPLE values: values: false`}) + + // Test the NOT IN case just once eventhough it's common code. + sfp.rewind() + ufp.rewind() + ps.Opcode = PulloutNotIn + if _, err := ps.Execute(nil, make(map[string]*querypb.BindVariable), false); err != nil { + t.Error(err) + } + sfp.ExpectLog(t, []string{`Execute false`}) + ufp.ExpectLog(t, []string{`Execute has_values: type:INT64 value:"1" sq: type:TUPLE values: values: false`}) +} + +func TestPulloutSubqueryInNone(t *testing.T) { + sqResult := sqltypes.MakeTestResult( + sqltypes.MakeTestFields( + "col1", + "int64", + ), + ) + sfp := &fakePrimitive{ + results: []*sqltypes.Result{sqResult}, + } + ufp := &fakePrimitive{} + ps := &PulloutSubquery{ + Opcode: PulloutIn, + SubqueryResult: "sq", + HasValues: "has_values", + Subquery: sfp, + Underlying: ufp, + } + + if _, err := ps.Execute(nil, make(map[string]*querypb.BindVariable), false); err != nil { + t.Error(err) + } + sfp.ExpectLog(t, []string{`Execute false`}) + ufp.ExpectLog(t, []string{`Execute has_values: type:INT64 value:"0" sq: type:TUPLE values: false`}) +} + +func TestPulloutSubqueryInBadColumns(t *testing.T) { + sqResult := sqltypes.MakeTestResult( + sqltypes.MakeTestFields( + "col1|col2", + "int64|int64", + ), + "1|1", + ) + sfp := &fakePrimitive{ + results: []*sqltypes.Result{sqResult}, + } + ps := &PulloutSubquery{ + Opcode: PulloutIn, + SubqueryResult: "sq", + Subquery: sfp, + } + + _, err := ps.Execute(nil, make(map[string]*querypb.BindVariable), false) + expectError(t, "ps.Execute", err, "subquery returned more than one column") +} + +func TestPulloutSubqueryExists(t *testing.T) { + sqResult := sqltypes.MakeTestResult( + sqltypes.MakeTestFields( + "col1", + "int64", + ), + "1", + ) + sfp := &fakePrimitive{ + results: []*sqltypes.Result{sqResult}, + } + ufp := &fakePrimitive{} + ps := &PulloutSubquery{ + Opcode: PulloutExists, + HasValues: "has_values", + Subquery: sfp, + Underlying: ufp, + } + + if _, err := ps.Execute(nil, make(map[string]*querypb.BindVariable), false); err != nil { + t.Error(err) + } + sfp.ExpectLog(t, []string{`Execute false`}) + ufp.ExpectLog(t, []string{`Execute has_values: type:INT64 value:"1" false`}) +} + +func TestPulloutSubqueryExistsNone(t *testing.T) { + sqResult := sqltypes.MakeTestResult( + sqltypes.MakeTestFields( + "col1", + "int64", + ), + ) + sfp := &fakePrimitive{ + results: []*sqltypes.Result{sqResult}, + } + ufp := &fakePrimitive{} + ps := &PulloutSubquery{ + Opcode: PulloutExists, + HasValues: "has_values", + Subquery: sfp, + Underlying: ufp, + } + + if _, err := ps.Execute(nil, make(map[string]*querypb.BindVariable), false); err != nil { + t.Error(err) + } + sfp.ExpectLog(t, []string{`Execute false`}) + ufp.ExpectLog(t, []string{`Execute has_values: type:INT64 value:"0" false`}) +} + +func TestPulloutSubqueryError(t *testing.T) { + sfp := &fakePrimitive{ + sendErr: errors.New("err"), + } + ps := &PulloutSubquery{ + Opcode: PulloutExists, + SubqueryResult: "sq", + Subquery: sfp, + } + + _, err := ps.Execute(nil, make(map[string]*querypb.BindVariable), false) + expectError(t, "ps.Execute", err, "err") +} + +func TestPulloutSubqueryStream(t *testing.T) { + bindVars := map[string]*querypb.BindVariable{ + "aa": sqltypes.Int64BindVariable(1), + } + sqResult := sqltypes.MakeTestResult( + sqltypes.MakeTestFields( + "col1", + "int64", + ), + "1", + ) + sfp := &fakePrimitive{ + results: []*sqltypes.Result{sqResult}, + } + underlyingResult := sqltypes.MakeTestResult( + sqltypes.MakeTestFields( + "col", + "int64", + ), + "0", + ) + ufp := &fakePrimitive{ + results: []*sqltypes.Result{underlyingResult}, + } + ps := &PulloutSubquery{ + Opcode: PulloutValue, + SubqueryResult: "sq", + Subquery: sfp, + Underlying: ufp, + } + + result, err := wrapStreamExecute(ps, nil, bindVars, false) + if err != nil { + t.Error(err) + } + sfp.ExpectLog(t, []string{`Execute aa: type:INT64 value:"1" false`}) + ufp.ExpectLog(t, []string{`StreamExecute aa: type:INT64 value:"1" sq: type:INT64 value:"1" false`}) + expectResult(t, "ps.StreamExecute", result, underlyingResult) +} + +func TestPulloutSubqueryGetFields(t *testing.T) { + bindVars := map[string]*querypb.BindVariable{ + "aa": sqltypes.Int64BindVariable(1), + } + ufp := &fakePrimitive{} + ps := &PulloutSubquery{ + Opcode: PulloutValue, + SubqueryResult: "sq", + HasValues: "has_values", + Underlying: ufp, + } + + if _, err := ps.GetFields(nil, bindVars); err != nil { + t.Error(err) + } + ufp.ExpectLog(t, []string{ + `GetFields aa: type:INT64 value:"1" sq: `, + `Execute aa: type:INT64 value:"1" sq: true`, + }) + + ufp.rewind() + ps.Opcode = PulloutIn + if _, err := ps.GetFields(nil, bindVars); err != nil { + t.Error(err) + } + ufp.ExpectLog(t, []string{ + `GetFields aa: type:INT64 value:"1" has_values: type:INT64 value:"0" sq: type:TUPLE values: `, + `Execute aa: type:INT64 value:"1" has_values: type:INT64 value:"0" sq: type:TUPLE values: true`, + }) + + ufp.rewind() + ps.Opcode = PulloutNotIn + if _, err := ps.GetFields(nil, bindVars); err != nil { + t.Error(err) + } + ufp.ExpectLog(t, []string{ + `GetFields aa: type:INT64 value:"1" has_values: type:INT64 value:"0" sq: type:TUPLE values: `, + `Execute aa: type:INT64 value:"1" has_values: type:INT64 value:"0" sq: type:TUPLE values: true`, + }) + + ufp.rewind() + ps.Opcode = PulloutExists + if _, err := ps.GetFields(nil, bindVars); err != nil { + t.Error(err) + } + ufp.ExpectLog(t, []string{ + `GetFields aa: type:INT64 value:"1" has_values: type:INT64 value:"0" `, + `Execute aa: type:INT64 value:"1" has_values: type:INT64 value:"0" true`, + }) +} diff --git a/go/vt/vtgate/executor.go b/go/vt/vtgate/executor.go index 12671d3f191..f12dbc7d122 100644 --- a/go/vt/vtgate/executor.go +++ b/go/vt/vtgate/executor.go @@ -32,6 +32,7 @@ import ( "vitess.io/vitess/go/acl" "vitess.io/vitess/go/cache" + "vitess.io/vitess/go/mysql" "vitess.io/vitess/go/sqltypes" "vitess.io/vitess/go/stats" "vitess.io/vitess/go/vt/callerid" @@ -791,6 +792,66 @@ func (e *Executor) handleShow(ctx context.Context, safeSession *SafeSession, sql } return e.handleOther(ctx, safeSession, sql, bindVars, dest, keyspaces[0], destTabletType, logStats) } + // for STATUS, return empty result set + case sqlparser.KeywordString(sqlparser.STATUS): + return &sqltypes.Result{ + Fields: buildVarCharFields("Variable_name", "Value"), + Rows: make([][]sqltypes.Value, 0, 2), + RowsAffected: 0, + }, nil + // for ENGINES, we want to return just InnoDB + case sqlparser.KeywordString(sqlparser.ENGINES): + rows := make([][]sqltypes.Value, 0, 6) + row := buildVarCharRow( + "InnoDB", + "DEFAULT", + "Supports transactions, row-level locking, and foreign keys", + "YES", + "YES", + "YES") + rows = append(rows, row) + return &sqltypes.Result{ + Fields: buildVarCharFields("Engine", "Support", "Comment", "Transactions", "XA", "Savepoints"), + Rows: rows, + RowsAffected: 1, + }, nil + // for PLUGINS, return InnoDb + mysql_native_password + case sqlparser.KeywordString(sqlparser.PLUGINS): + rows := make([][]sqltypes.Value, 0, 5) + row := buildVarCharRow( + "InnoDB", + "ACTIVE", + "STORAGE ENGINE", + "NULL", + "GPL") + rows = append(rows, row) + return &sqltypes.Result{ + Fields: buildVarCharFields("Name", "Status", "Type", "Library", "License"), + Rows: rows, + RowsAffected: 1, + }, nil + // CHARSET & CHARACTER SET return utf8mb4 & utf8 + case sqlparser.KeywordString(sqlparser.CHARSET): + fields := buildVarCharFields("Charset", "Description", "Default collation") + maxLenField := &querypb.Field{Name: "Maxlen", Type: sqltypes.Int32} + fields = append(fields, maxLenField) + rows := make([][]sqltypes.Value, 0, 4) + row0 := buildVarCharRow( + "utf8", + "UTF-8 Unicode", + "utf8_general_ci") + row0 = append(row0, sqltypes.NewInt32(3)) + row1 := buildVarCharRow( + "utf8mb4", + "UTF-8 Unicode", + "utf8mb4_general_ci") + row1 = append(row1, sqltypes.NewInt32(4)) + rows = append(rows, row0, row1) + return &sqltypes.Result{ + Fields: fields, + Rows: rows, + RowsAffected: 2, + }, nil case sqlparser.KeywordString(sqlparser.TABLES): if show.ShowTablesOpt != nil && show.ShowTablesOpt.DbName != "" { show.ShowTablesOpt.DbName = "" @@ -1419,7 +1480,12 @@ func (e *Executor) VSchemaStats() *VSchemaStats { func buildVarCharFields(names ...string) []*querypb.Field { fields := make([]*querypb.Field, len(names)) for i, v := range names { - fields[i] = &querypb.Field{Name: v, Type: sqltypes.VarChar} + fields[i] = &querypb.Field{ + Name: v, + Type: sqltypes.VarChar, + Charset: mysql.CharacterSetUtf8, + Flags: uint32(querypb.MySqlFlag_NOT_NULL_FLAG), + } } return fields } diff --git a/go/vt/vtgate/executor_test.go b/go/vt/vtgate/executor_test.go index b659f7e6ba6..93ca6ff1f36 100644 --- a/go/vt/vtgate/executor_test.go +++ b/go/vt/vtgate/executor_test.go @@ -603,11 +603,12 @@ func TestExecutorShow(t *testing.T) { buildVarCharRow(KsTestSharded), buildVarCharRow(KsTestUnsharded), buildVarCharRow("TestXBadSharding"), + buildVarCharRow(KsTestBadVSchema), }, - RowsAffected: 4, + RowsAffected: 5, } if !reflect.DeepEqual(qr, wantqr) { - t.Errorf("show databases:\n%+v, want\n%+v", qr, wantqr) + t.Errorf("%v:\n%+v, want\n%+v", query, qr, wantqr) } } _, err := executor.Execute(context.Background(), "TestExecute", session, "show variables", nil) @@ -622,19 +623,96 @@ func TestExecutorShow(t *testing.T) { if err != nil { t.Error(err) } - qr, err := executor.Execute(context.Background(), "TestExecute", session, "show vitess_shards", nil) + for _, query := range []string{"show charset", "show charset like '%foo'", "show character set", "show character set like '%foo'"} { + qr, err := executor.Execute(context.Background(), "TestExecute", session, query, nil) + if err != nil { + t.Error(err) + } + wantqr := &sqltypes.Result{ + Fields: append(buildVarCharFields("Charset", "Description", "Default collation"), &querypb.Field{Name: "Maxlen", Type: sqltypes.Int32}), + Rows: [][]sqltypes.Value{ + append(buildVarCharRow( + "utf8", + "UTF-8 Unicode", + "utf8_general_ci"), sqltypes.NewInt32(3)), + append(buildVarCharRow( + "utf8mb4", + "UTF-8 Unicode", + "utf8mb4_general_ci"), + sqltypes.NewInt32(4)), + }, + RowsAffected: 2, + } + if !reflect.DeepEqual(qr, wantqr) { + t.Errorf("%v:\n%+v, want\n%+v", query, qr, wantqr) + } + } + qr, err := executor.Execute(context.Background(), "TestExecute", session, "show engines", nil) + if err != nil { + t.Error(err) + } + wantqr := &sqltypes.Result{ + Fields: buildVarCharFields("Engine", "Support", "Comment", "Transactions", "XA", "Savepoints"), + Rows: [][]sqltypes.Value{ + buildVarCharRow( + "InnoDB", + "DEFAULT", + "Supports transactions, row-level locking, and foreign keys", + "YES", + "YES", + "YES"), + }, + RowsAffected: 1, + } + if !reflect.DeepEqual(qr, wantqr) { + t.Errorf("show engines:\n%+v, want\n%+v", qr, wantqr) + } + qr, err = executor.Execute(context.Background(), "TestExecute", session, "show plugins", nil) + if err != nil { + t.Error(err) + } + wantqr = &sqltypes.Result{ + Fields: buildVarCharFields("Name", "Status", "Type", "Library", "License"), + Rows: [][]sqltypes.Value{ + buildVarCharRow( + "InnoDB", + "ACTIVE", + "STORAGE ENGINE", + "NULL", + "GPL"), + }, + RowsAffected: 1, + } + if !reflect.DeepEqual(qr, wantqr) { + t.Errorf("show plugins:\n%+v, want\n%+v", qr, wantqr) + } + for _, query := range []string{"show session status", "show session status like 'Ssl_cipher'"} { + qr, err = executor.Execute(context.Background(), "TestExecute", session, "show session status", nil) + if err != nil { + t.Error(err) + } + wantqr = &sqltypes.Result{ + Fields: buildVarCharFields("Variable_name", "Value"), + Rows: make([][]sqltypes.Value, 0, 2), + RowsAffected: 0, + } + if !reflect.DeepEqual(qr, wantqr) { + t.Errorf("%v:\n%+v, want\n%+v", query, qr, wantqr) + } + } + qr, err = executor.Execute(context.Background(), "TestExecute", session, "show vitess_shards", nil) if err != nil { t.Error(err) } // Just test for first & last. qr.Rows = [][]sqltypes.Value{qr.Rows[0], qr.Rows[len(qr.Rows)-1]} - wantqr := &sqltypes.Result{ + wantqr = &sqltypes.Result{ Fields: buildVarCharFields("Shards"), Rows: [][]sqltypes.Value{ buildVarCharRow("TestExecutor/-20"), - buildVarCharRow("TestXBadSharding/e0-"), + buildVarCharRow("TestXBadVSchema/e0-"), }, - RowsAffected: 25, + RowsAffected: 33, } if !reflect.DeepEqual(qr, wantqr) { t.Errorf("show vitess_shards:\n%+v, want\n%+v", qr, wantqr) @@ -802,9 +880,9 @@ func TestExecutorShow(t *testing.T) { Fields: buildVarCharFields("Shards"), Rows: [][]sqltypes.Value{ buildVarCharRow("TestSharded/-20"), - buildVarCharRow("TestXBadSharding/e0-"), + buildVarCharRow("TestXBadVSchema/e0-"), }, - RowsAffected: 17, + RowsAffected: 25, } if !reflect.DeepEqual(qr, wantqr) { t.Errorf("show databases:\n%+v, want\n%+v", qr, wantqr) diff --git a/go/vt/vtgate/planbuilder/delete.go b/go/vt/vtgate/planbuilder/delete.go index 4706a7718f2..0e969084476 100644 --- a/go/vt/vtgate/planbuilder/delete.go +++ b/go/vt/vtgate/planbuilder/delete.go @@ -39,7 +39,7 @@ func buildDeletePlan(del *sqlparser.Delete, vschema ContextVSchema) (*engine.Del } rb, ok := pb.bldr.(*route) if !ok { - return nil, errors.New("unsupported: multi-table delete statement in sharded keyspace") + return nil, errors.New("unsupported: multi-table/vindex delete statement in sharded keyspace") } edel.Keyspace = rb.ERoute.Keyspace if !edel.Keyspace.Sharded { diff --git a/go/vt/vtgate/planbuilder/expr.go b/go/vt/vtgate/planbuilder/expr.go index 8ade06522d8..84ee7b9646a 100644 --- a/go/vt/vtgate/planbuilder/expr.go +++ b/go/vt/vtgate/planbuilder/expr.go @@ -22,6 +22,7 @@ import ( "fmt" "vitess.io/vitess/go/vt/sqlparser" + "vitess.io/vitess/go/vt/vtgate/engine" ) // splitAndExpression breaks up the Expr into AND-separated conditions @@ -54,6 +55,12 @@ func skipParenthesis(node sqlparser.Expr) sqlparser.Expr { return node } +type subqueryInfo struct { + ast *sqlparser.Subquery + bldr builder + origin builder +} + // findOrigin identifies the right-most origin referenced by expr. In situations where // the expression references columns from multiple origins, the expression will be // pushed to the right-most origin, and the executor will use the results of @@ -77,9 +84,19 @@ func skipParenthesis(node sqlparser.Expr) sqlparser.Expr { // // If an expression has no references to the current query, then the left-most // origin is chosen as the default. -func (pb *primitiveBuilder) findOrigin(expr sqlparser.Expr) (origin builder, pushExpr sqlparser.Expr, err error) { +func (pb *primitiveBuilder) findOrigin(expr sqlparser.Expr) (pullouts []*pulloutSubquery, origin builder, pushExpr sqlparser.Expr, err error) { + // highestOrigin tracks the highest origin referenced by the expression. + // Default is the First. highestOrigin := pb.bldr.First() - var subroutes []*route + + // subqueries tracks the list of subqueries encountered. + var subqueries []subqueryInfo + + // constructsMap tracks the sub-construct in which a subquery + // occurred. The construct type decides on how the query gets + // pulled out. + constructsMap := make(map[*sqlparser.Subquery]sqlparser.Expr) + err = sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { switch node := node.(type) { case *sqlparser.ColName: @@ -90,6 +107,14 @@ func (pb *primitiveBuilder) findOrigin(expr sqlparser.Expr) (origin builder, pus if isLocal && newOrigin.Order() > highestOrigin.Order() { highestOrigin = newOrigin } + case *sqlparser.ComparisonExpr: + if node.Operator == sqlparser.InStr || node.Operator == sqlparser.NotInStr { + if sq, ok := node.Right.(*sqlparser.Subquery); ok { + constructsMap[sq] = node + } + } + case *sqlparser.ExistsExpr: + constructsMap[node.Subquery] = node case *sqlparser.Subquery: spb := newPrimitiveBuilder(pb.vschema, pb.jt) switch stmt := node.Select.(type) { @@ -104,18 +129,26 @@ func (pb *primitiveBuilder) findOrigin(expr sqlparser.Expr) (origin builder, pus default: panic(fmt.Sprintf("BUG: unexpected SELECT type: %T", node)) } - subroute, isRoute := spb.bldr.(*route) - if !isRoute { - return false, errors.New("unsupported: cross-shard query in subqueries") + sqi := subqueryInfo{ + ast: node, + bldr: spb.bldr, } for _, extern := range spb.st.Externs { // No error expected. These are resolved externs. newOrigin, isLocal, _ := pb.st.Find(extern) - if isLocal && newOrigin.Order() > highestOrigin.Order() { + if !isLocal { + continue + } + if highestOrigin.Order() < newOrigin.Order() { highestOrigin = newOrigin } + if sqi.origin == nil { + sqi.origin = newOrigin + } else if sqi.origin.Order() < newOrigin.Order() { + sqi.origin = newOrigin + } } - subroutes = append(subroutes, subroute) + subqueries = append(subqueries, sqi) return false, nil case *sqlparser.FuncExpr: switch { @@ -130,19 +163,70 @@ func (pb *primitiveBuilder) findOrigin(expr sqlparser.Expr) (origin builder, pus return true, nil }, expr) if err != nil { - return nil, nil, err - } - highestRoute, isRoute := highestOrigin.(*route) - if !isRoute && len(subroutes) > 0 { - return nil, nil, errors.New("unsupported: subquery cannot be merged with cross-shard subquery") + return nil, nil, nil, err } - for _, subroute := range subroutes { - if err := highestRoute.SubqueryCanMerge(pb, subroute); err != nil { - return nil, nil, err + + highestRoute, _ := highestOrigin.(*route) + for _, sqi := range subqueries { + subroute, _ := sqi.bldr.(*route) + if highestRoute != nil && subroute != nil && highestRoute.SubqueryCanMerge(pb, subroute) { + subroute.Redirect = highestRoute + continue + } + if sqi.origin != nil { + return nil, nil, nil, errors.New("unsupported: cross-shard correlated subquery") + } + + sqName, hasValues := pb.jt.GenerateSubqueryVars() + construct, ok := constructsMap[sqi.ast] + if !ok { + // (subquery) -> :_sq + expr = sqlparser.ReplaceExpr(expr, sqi.ast, sqlparser.NewValArg([]byte(":"+sqName))) + pullouts = append(pullouts, newPulloutSubquery(engine.PulloutValue, sqName, hasValues, sqi.bldr)) + continue + } + switch construct := construct.(type) { + case *sqlparser.ComparisonExpr: + if construct.Operator == sqlparser.InStr { + // a in (subquery) -> (:__sq_has_values = 1 and (a in ::__sq)) + newExpr := &sqlparser.ParenExpr{ + Expr: &sqlparser.AndExpr{ + Left: &sqlparser.ComparisonExpr{ + Left: sqlparser.NewValArg([]byte(":" + hasValues)), + Operator: sqlparser.EqualStr, + Right: sqlparser.NewIntVal([]byte("1")), + }, + Right: &sqlparser.ParenExpr{ + Expr: sqlparser.ReplaceExpr(construct, sqi.ast, sqlparser.ListArg([]byte("::"+sqName))), + }, + }, + } + expr = sqlparser.ReplaceExpr(expr, construct, newExpr) + pullouts = append(pullouts, newPulloutSubquery(engine.PulloutIn, sqName, hasValues, sqi.bldr)) + } else { + // a not in (subquery) -> (:__sq_has_values = 0 or (a not in ::__sq)) + newExpr := &sqlparser.ParenExpr{ + Expr: &sqlparser.OrExpr{ + Left: &sqlparser.ComparisonExpr{ + Left: sqlparser.NewValArg([]byte(":" + hasValues)), + Operator: sqlparser.EqualStr, + Right: sqlparser.NewIntVal([]byte("0")), + }, + Right: &sqlparser.ParenExpr{ + Expr: sqlparser.ReplaceExpr(construct, sqi.ast, sqlparser.ListArg([]byte("::"+sqName))), + }, + }, + } + expr = sqlparser.ReplaceExpr(expr, construct, newExpr) + pullouts = append(pullouts, newPulloutSubquery(engine.PulloutNotIn, sqName, hasValues, sqi.bldr)) + } + case *sqlparser.ExistsExpr: + // exists (subquery) -> :__sq_has_values + expr = sqlparser.ReplaceExpr(expr, construct, sqlparser.NewValArg([]byte(":"+hasValues))) + pullouts = append(pullouts, newPulloutSubquery(engine.PulloutExists, sqName, hasValues, sqi.bldr)) } - subroute.Redirect = highestRoute } - return highestOrigin, expr, nil + return pullouts, highestOrigin, expr, nil } func hasSubquery(node sqlparser.SQLNode) bool { diff --git a/go/vt/vtgate/planbuilder/from.go b/go/vt/vtgate/planbuilder/from.go index 69bf468fee8..ed1c761dc68 100644 --- a/go/vt/vtgate/planbuilder/from.go +++ b/go/vt/vtgate/planbuilder/from.go @@ -17,7 +17,6 @@ limitations under the License. package planbuilder import ( - "errors" "fmt" "vitess.io/vitess/go/sqltypes" @@ -94,8 +93,9 @@ func (pb *primitiveBuilder) processAliasedTable(tableExpr *sqlparser.AliasedTabl subroute, ok := spb.bldr.(*route) if !ok { - pb.bldr, pb.st = newSubquery(tableExpr.As, spb.bldr) - return nil + var err error + pb.bldr, pb.st, err = newSubquery(tableExpr.As, spb.bldr) + return err } // Since a route is more versatile than a subquery, we @@ -235,20 +235,16 @@ func convertToLeftJoin(ajoin *sqlparser.JoinTableExpr) { } func (pb *primitiveBuilder) join(rpb *primitiveBuilder, ajoin *sqlparser.JoinTableExpr) error { - if ajoin != nil && ajoin.Condition.Using != nil { - return errors.New("unsupported: join with USING(column_list) clause") - } lRoute, leftIsRoute := pb.bldr.(*route) rRoute, rightIsRoute := rpb.bldr.(*route) if leftIsRoute && rightIsRoute { // If both are routes, they have an opportunity // to merge into one. - if lRoute.ERoute.Opcode == engine.SelectNext || rRoute.ERoute.Opcode == engine.SelectNext { - return errors.New("unsupported: sequence join with another table") - } if lRoute.ERoute.Keyspace.Name != rRoute.ERoute.Keyspace.Name { goto nomerge } + // We don't have to check on SelectNext because the syntax + // doesn't allow joins. switch lRoute.ERoute.Opcode { case engine.SelectUnsharded: if rRoute.ERoute.Opcode == engine.SelectUnsharded { @@ -313,11 +309,12 @@ func (pb *primitiveBuilder) mergeRoutes(rpb *primitiveBuilder, ajoin *sqlparser. if ajoin == nil { return nil } - _, expr, err := pb.findOrigin(ajoin.Condition.On) + pullouts, _, expr, err := pb.findOrigin(ajoin.Condition.On) if err != nil { return err } ajoin.Condition.On = expr + pb.addPullouts(pullouts) for _, filter := range splitAndExpression(nil, ajoin.Condition.On) { lRoute.UpdatePlan(pb, filter) } diff --git a/go/vt/vtgate/planbuilder/insert.go b/go/vt/vtgate/planbuilder/insert.go index ad530e710cd..4ba900513b0 100644 --- a/go/vt/vtgate/planbuilder/insert.go +++ b/go/vt/vtgate/planbuilder/insert.go @@ -34,8 +34,11 @@ func buildInsertPlan(ins *sqlparser.Insert, vschema ContextVSchema) (*engine.Ins if err := pb.processAliasedTable(aliased); err != nil { return nil, err } - // route is guaranteed because of simple table expr. - rb := pb.bldr.(*route) + rb, ok := pb.bldr.(*route) + if !ok { + // This can happen only for vindexes right now. + return nil, fmt.Errorf("inserting into a vindex not allowed: %s", sqlparser.String(ins.Table)) + } if rb.ERoute.TargetDestination != nil { return nil, errors.New("unsupported: INSERT with a target destination") } diff --git a/go/vt/vtgate/planbuilder/jointab.go b/go/vt/vtgate/planbuilder/jointab.go index 4e19fe382df..fea57a44add 100644 --- a/go/vt/vtgate/planbuilder/jointab.go +++ b/go/vt/vtgate/planbuilder/jointab.go @@ -25,8 +25,9 @@ import ( // jointab manages procurement and naming of join // variables across primitives. type jointab struct { - refs map[*column]string - vars map[string]struct{} + refs map[*column]string + vars map[string]struct{} + varIndex int } // newJointab creates a new jointab for the current plan @@ -67,6 +68,33 @@ func (jt *jointab) Procure(bldr builder, col *sqlparser.ColName, to int) string return joinVar } +// GenerateSubqueryVars generates substitution variable names for +// a subquery. It returns two names based on: __sq, __sq_has_values. +// The appropriate names can be used for substitution +// depending on the scenario. +func (jt *jointab) GenerateSubqueryVars() (sq, hasValues string) { + for { + jt.varIndex++ + suffix := strconv.Itoa(jt.varIndex) + var1 := "__sq" + suffix + var2 := "__sq_has_values" + suffix + if jt.containsAny(var1, var2) { + continue + } + return var1, var2 + } +} + +func (jt *jointab) containsAny(names ...string) bool { + for _, name := range names { + if _, ok := jt.vars[name]; ok { + return true + } + jt.vars[name] = struct{}{} + } + return false +} + // Lookup returns the order of the route that supplies the column and // the join var name if one has already been assigned for it. func (jt *jointab) Lookup(col *sqlparser.ColName) (order int, joinVar string) { diff --git a/go/vt/vtgate/planbuilder/jointab_test.go b/go/vt/vtgate/planbuilder/jointab_test.go new file mode 100644 index 00000000000..a255de5bd35 --- /dev/null +++ b/go/vt/vtgate/planbuilder/jointab_test.go @@ -0,0 +1,43 @@ +/* +Copyright 2018 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package planbuilder + +import ( + "reflect" + "testing" +) + +func TestGenerateSubqueryVars(t *testing.T) { + jt := newJointab(map[string]struct{}{ + "__sq1": {}, + "__sq_has_values3": {}, + }) + + v1, v2 := jt.GenerateSubqueryVars() + combined := []string{v1, v2} + want := []string{"__sq2", "__sq_has_values2"} + if !reflect.DeepEqual(combined, want) { + t.Errorf("jt.GenerateSubqueryVars: %v, want %v", combined, want) + } + + v1, v2 = jt.GenerateSubqueryVars() + combined = []string{v1, v2} + want = []string{"__sq4", "__sq_has_values4"} + if !reflect.DeepEqual(combined, want) { + t.Errorf("jt.GenerateSubqueryVars: %v, want %v", combined, want) + } +} diff --git a/go/vt/vtgate/planbuilder/pullout_subquery.go b/go/vt/vtgate/planbuilder/pullout_subquery.go new file mode 100644 index 00000000000..9c2a9384c5f --- /dev/null +++ b/go/vt/vtgate/planbuilder/pullout_subquery.go @@ -0,0 +1,137 @@ +/* +Copyright 2018 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package planbuilder + +import ( + "vitess.io/vitess/go/vt/sqlparser" + "vitess.io/vitess/go/vt/vtgate/engine" +) + +var _ builder = (*pulloutSubquery)(nil) + +// pulloutSubquery is the builder for engine.PulloutSubquery. +// This gets built if a subquery is not correlated and can +// therefore can be pulled out and executed upfront. +type pulloutSubquery struct { + order int + subquery builder + underlying builder + eSubquery *engine.PulloutSubquery +} + +// newPulloutSubquery builds a new pulloutSubquery. +func newPulloutSubquery(opcode engine.PulloutOpcode, sqName, hasValues string, subquery builder) *pulloutSubquery { + return &pulloutSubquery{ + subquery: subquery, + eSubquery: &engine.PulloutSubquery{ + Opcode: opcode, + SubqueryResult: sqName, + HasValues: hasValues, + }, + } +} + +// setUnderlying sets the underlying primitive. +func (ps *pulloutSubquery) setUnderlying(underlying builder) { + ps.underlying = underlying + ps.underlying.Reorder(ps.subquery.Order()) + ps.order = ps.underlying.Order() + 1 +} + +// Order satisfies the builder interface. +func (ps *pulloutSubquery) Order() int { + return ps.order +} + +// Reorder satisfies the builder interface. +func (ps *pulloutSubquery) Reorder(order int) { + ps.subquery.Reorder(order) + ps.underlying.Reorder(ps.subquery.Order()) + ps.order = ps.underlying.Order() + 1 +} + +// Primitive satisfies the builder interface. +func (ps *pulloutSubquery) Primitive() engine.Primitive { + ps.eSubquery.Subquery = ps.subquery.Primitive() + ps.eSubquery.Underlying = ps.underlying.Primitive() + return ps.eSubquery +} + +// First satisfies the builder interface. +func (ps *pulloutSubquery) First() builder { + return ps.underlying.First() +} + +// ResultColumns satisfies the builder interface. +func (ps *pulloutSubquery) ResultColumns() []*resultColumn { + return ps.underlying.ResultColumns() +} + +// PushFilter satisfies the builder interface. +func (ps *pulloutSubquery) PushFilter(pb *primitiveBuilder, filter sqlparser.Expr, whereType string, origin builder) error { + return ps.underlying.PushFilter(pb, filter, whereType, origin) +} + +// PushSelect satisfies the builder interface. +func (ps *pulloutSubquery) PushSelect(expr *sqlparser.AliasedExpr, origin builder) (rc *resultColumn, colnum int, err error) { + return ps.underlying.PushSelect(expr, origin) +} + +// PushOrderByNull satisfies the builder interface. +func (ps *pulloutSubquery) PushOrderByNull() { + ps.underlying.PushOrderByNull() +} + +// PushOrderByRand satisfies the builder interface. +func (ps *pulloutSubquery) PushOrderByRand() { + ps.underlying.PushOrderByRand() +} + +// SetUpperLimit satisfies the builder interface. +// This is a no-op because we actually call SetLimit for this primitive. +// In the future, we may have to honor this call for subqueries. +func (ps *pulloutSubquery) SetUpperLimit(count *sqlparser.SQLVal) { + ps.underlying.SetUpperLimit(count) +} + +// PushMisc satisfies the builder interface. +func (ps *pulloutSubquery) PushMisc(sel *sqlparser.Select) { + ps.subquery.PushMisc(sel) + ps.underlying.PushMisc(sel) +} + +// Wireup satisfies the builder interface. +func (ps *pulloutSubquery) Wireup(bldr builder, jt *jointab) error { + if err := ps.underlying.Wireup(bldr, jt); err != nil { + return err + } + return ps.subquery.Wireup(bldr, jt) +} + +// SupplyVar satisfies the builder interface. +func (ps *pulloutSubquery) SupplyVar(from, to int, col *sqlparser.ColName, varname string) { + if from <= ps.subquery.Order() { + ps.subquery.SupplyVar(from, to, col, varname) + return + } + ps.underlying.SupplyVar(from, to, col, varname) +} + +// SupplyCol satisfies the builder interface. +func (ps *pulloutSubquery) SupplyCol(col *sqlparser.ColName) (rc *resultColumn, colnum int) { + panic("BUG: unreachable") +} diff --git a/go/vt/vtgate/planbuilder/route.go b/go/vt/vtgate/planbuilder/route.go index 92ed6126188..74084b8d028 100644 --- a/go/vt/vtgate/planbuilder/route.go +++ b/go/vt/vtgate/planbuilder/route.go @@ -590,23 +590,23 @@ func (rb *route) IsSingle() bool { // SubqueryCanMerge returns nil if the supplied route that represents // a subquery can be merged with the outer route. If not, it // returns an appropriate error. -func (rb *route) SubqueryCanMerge(pb *primitiveBuilder, inner *route) error { +func (rb *route) SubqueryCanMerge(pb *primitiveBuilder, inner *route) bool { if rb.ERoute.Keyspace.Name != inner.ERoute.Keyspace.Name { - return errors.New("unsupported: subquery keyspace different from outer query") + return false } switch inner.ERoute.Opcode { case engine.SelectUnsharded: if rb.ERoute.Opcode == engine.SelectUnsharded { - return nil + return true } - return errIntermixingUnsupported + return false case engine.SelectDBA: if rb.ERoute.Opcode == engine.SelectDBA { - return nil + return true } - return errIntermixingUnsupported + return false case engine.SelectNext: - return errors.New("unsupported: use of sequence in subquery") + return false case engine.SelectEqualUnique: // This checks for the case where the subquery is dependent // on the vindex column of the outer query: @@ -616,13 +616,13 @@ func (rb *route) SubqueryCanMerge(pb *primitiveBuilder, inner *route) error { switch vals := inner.condition.(type) { case *sqlparser.ColName: if pb.st.Vindex(vals, rb) == inner.ERoute.Vindex { - return nil + return true } } default: - return errors.New("unsupported: scatter subquery") + return false } - return rb.isSameShardedRoute(inner) + return rb.isSameShardedRoute(inner) == nil } // UnionCanMerge returns nil if the supplied route that represents diff --git a/go/vt/vtgate/planbuilder/select.go b/go/vt/vtgate/planbuilder/select.go index 19a3401f931..8814f328b1b 100644 --- a/go/vt/vtgate/planbuilder/select.go +++ b/go/vt/vtgate/planbuilder/select.go @@ -125,13 +125,17 @@ func (pb *primitiveBuilder) pushFilter(boolExpr sqlparser.Expr, whereType string filters := splitAndExpression(nil, boolExpr) reorderBySubquery(filters) for _, filter := range filters { - origin, expr, err := pb.findOrigin(filter) + pullouts, origin, expr, err := pb.findOrigin(filter) if err != nil { return err } - if err := pb.bldr.PushFilter(pb, expr, whereType, origin); err != nil { - return err + // The returned expression may be complex. Resplit before pushing. + for _, subexpr := range splitAndExpression(nil, expr) { + if err := pb.bldr.PushFilter(pb, subexpr, whereType, origin); err != nil { + return err + } } + pb.addPullouts(pullouts) } return nil } @@ -156,6 +160,14 @@ func reorderBySubquery(filters []sqlparser.Expr) { } } +// addPullouts adds the pullout subqueries to the primitiveBuilder. +func (pb *primitiveBuilder) addPullouts(pullouts []*pulloutSubquery) { + for _, pullout := range pullouts { + pullout.setUnderlying(pb.bldr) + pb.bldr = pullout + } +} + // pushSelectExprs identifies the target route for the // select expressions and pushes them down. func (pb *primitiveBuilder) pushSelectExprs(sel *sqlparser.Select, grouper groupByHandler) error { @@ -170,20 +182,31 @@ func (pb *primitiveBuilder) pushSelectExprs(sel *sqlparser.Select, grouper group // pusheSelectRoutes is a convenience function that pushes all the select // expressions and returns the list of resultColumns generated for it. func (pb *primitiveBuilder) pushSelectRoutes(selectExprs sqlparser.SelectExprs) ([]*resultColumn, error) { - resultColumns := make([]*resultColumn, len(selectExprs)) - for i, node := range selectExprs { + resultColumns := make([]*resultColumn, 0, len(selectExprs)) + for _, node := range selectExprs { switch node := node.(type) { case *sqlparser.AliasedExpr: - origin, expr, err := pb.findOrigin(node.Expr) + pullouts, origin, expr, err := pb.findOrigin(node.Expr) if err != nil { return nil, err } node.Expr = expr - resultColumns[i], _, err = pb.bldr.PushSelect(node, origin) + rc, _, err := pb.bldr.PushSelect(node, origin) if err != nil { return nil, err } + resultColumns = append(resultColumns, rc) + pb.addPullouts(pullouts) case *sqlparser.StarExpr: + var expanded bool + var err error + resultColumns, expanded, err = pb.expandStar(resultColumns, node) + if err != nil { + return nil, err + } + if expanded { + continue + } // We'll allow select * for simple routes. rb, ok := pb.bldr.(*route) if !ok { @@ -197,7 +220,7 @@ func (pb *primitiveBuilder) pushSelectRoutes(selectExprs sqlparser.SelectExprs) } } } - resultColumns[i] = rb.PushAnonymous(node) + resultColumns = append(resultColumns, rb.PushAnonymous(node)) case sqlparser.Nextval: rb, ok := pb.bldr.(*route) if !ok { @@ -207,7 +230,7 @@ func (pb *primitiveBuilder) pushSelectRoutes(selectExprs sqlparser.SelectExprs) if err := rb.SetOpcode(engine.SelectNext); err != nil { return nil, err } - resultColumns[i] = rb.PushAnonymous(node) + resultColumns = append(resultColumns, rb.PushAnonymous(node)) default: panic(fmt.Sprintf("BUG: unexpceted select expression type: %T", node)) } @@ -215,6 +238,90 @@ func (pb *primitiveBuilder) pushSelectRoutes(selectExprs sqlparser.SelectExprs) return resultColumns, nil } +// expandStar expands a StarExpr and pushes the expanded +// expressions down if the tables have authoritative column lists. +// If not, it returns false. +// This function breaks the abstraction a bit: it directly sets the +// the Metadata for newly created expressions. In all other cases, +// the Metadata is set through a symtab Find. +func (pb *primitiveBuilder) expandStar(inrcs []*resultColumn, expr *sqlparser.StarExpr) (outrcs []*resultColumn, expanded bool, err error) { + tables := pb.st.AllTables() + if tables == nil { + // no table metadata available. + return inrcs, false, nil + } + if expr.TableName.IsEmpty() { + for _, t := range tables { + // All tables must have authoritative column lists. + if !t.isAuthoritative { + return inrcs, false, nil + } + } + singleTable := false + if len(tables) == 1 { + singleTable = true + } + for _, t := range tables { + for _, col := range t.columnNames { + var expr *sqlparser.AliasedExpr + if singleTable { + // If there's only one table, we use unqualifed column names. + expr = &sqlparser.AliasedExpr{ + Expr: &sqlparser.ColName{ + Metadata: t.columns[col.Lowered()], + Name: col, + }, + } + } else { + // If a and b have id as their column, then + // select * from a join b should result in + // select a.id as id, b.id as id from a join b. + expr = &sqlparser.AliasedExpr{ + Expr: &sqlparser.ColName{ + Metadata: t.columns[col.Lowered()], + Name: col, + Qualifier: t.alias, + }, + As: col, + } + } + rc, _, err := pb.bldr.PushSelect(expr, t.origin) + if err != nil { + // Unreachable because PushSelect won't fail on ColName. + return inrcs, false, err + } + inrcs = append(inrcs, rc) + } + } + return inrcs, true, nil + } + + // Expression qualified with table name. + t, err := pb.st.FindTable(expr.TableName) + if err != nil { + return inrcs, false, err + } + if !t.isAuthoritative { + return inrcs, false, nil + } + for _, col := range t.columnNames { + expr := &sqlparser.AliasedExpr{ + Expr: &sqlparser.ColName{ + Metadata: t.columns[col.Lowered()], + Name: col, + Qualifier: expr.TableName, + }, + } + rc, _, err := pb.bldr.PushSelect(expr, t.origin) + if err != nil { + // Unreachable because PushSelect won't fail on ColName. + return inrcs, false, err + } + inrcs = append(inrcs, rc) + } + return inrcs, true, nil +} + // queryTimeout returns DirectiveQueryTimeout value if set, otherwise returns 0. func queryTimeout(d sqlparser.CommentDirectives) int { if d == nil { diff --git a/go/vt/vtgate/planbuilder/subquery.go b/go/vt/vtgate/planbuilder/subquery.go index e69892dbea8..f9b4a968965 100644 --- a/go/vt/vtgate/planbuilder/subquery.go +++ b/go/vt/vtgate/planbuilder/subquery.go @@ -18,6 +18,7 @@ package planbuilder import ( "errors" + "fmt" "vitess.io/vitess/go/vt/sqlparser" "vitess.io/vitess/go/vt/vtgate/engine" @@ -42,7 +43,7 @@ type subquery struct { } // newSubquery builds a new subquery. -func newSubquery(alias sqlparser.TableIdent, bldr builder) (*subquery, *symtab) { +func newSubquery(alias sqlparser.TableIdent, bldr builder) (*subquery, *symtab, error) { sq := &subquery{ order: bldr.Order() + 1, input: bldr, @@ -56,20 +57,17 @@ func newSubquery(alias sqlparser.TableIdent, bldr builder) (*subquery, *symtab) } // Create column symbols based on the result column names. - cols := make(map[string]*column) - for i, rc := range bldr.ResultColumns() { - cols[rc.alias.Lowered()] = &column{ - origin: sq, - colnum: i, + for _, rc := range bldr.ResultColumns() { + if _, ok := t.columns[rc.alias.Lowered()]; ok { + return nil, nil, fmt.Errorf("duplicate column names in subquery: %s", sqlparser.String(rc.alias)) } + t.addColumn(rc.alias, &column{origin: sq}) } - - // Populate the table with those columns and add it to symtab. - t.columns = cols + t.isAuthoritative = true st := newSymtab() // AddTable will not fail because symtab is empty. _ = st.AddTable(t) - return sq, st + return sq, st, nil } // Order satisfies the builder interface. diff --git a/go/vt/vtgate/planbuilder/symtab.go b/go/vt/vtgate/planbuilder/symtab.go index 440782b7696..ac4caae56dd 100644 --- a/go/vt/vtgate/planbuilder/symtab.go +++ b/go/vt/vtgate/planbuilder/symtab.go @@ -28,6 +28,8 @@ import ( querypb "vitess.io/vitess/go/vt/proto/query" ) +var errNoTable = errors.New("no table info") + // symtab represents the symbol table for a SELECT statement // or a subquery. The symtab evolves over time. // As a query is analyzed, multiple independent @@ -48,7 +50,8 @@ import ( // which is later used to determine if the subquery can be // merged with an outer route. type symtab struct { - tables map[sqlparser.TableName]*table + tables map[sqlparser.TableName]*table + tableNames []sqlparser.TableName // uniqueColumns has the column name as key // and points at the columns that tables contains. @@ -91,18 +94,18 @@ func newSymtabWithRoute(rb *route) *symtab { // and adds it to symtab. func (st *symtab) AddVindexTable(alias sqlparser.TableName, vindexTable *vindexes.Table, rb *route) error { t := &table{ - alias: alias, - columns: make(map[string]*column), - origin: rb, - vindexTable: vindexTable, + alias: alias, + origin: rb, + vindexTable: vindexTable, + isAuthoritative: vindexTable.ColumnListAuthoritative, } for _, col := range vindexTable.Columns { - t.columns[col.Name.Lowered()] = &column{ + t.addColumn(col.Name, &column{ origin: rb, st: st, typ: col.Type, - } + }) } for _, cv := range vindexTable.ColumnVindexes { @@ -117,21 +120,21 @@ func (st *symtab) AddVindexTable(alias sqlparser.TableName, vindexTable *vindexe col.Vindex = vindex continue } - t.columns[lowered] = &column{ + t.addColumn(cvcol, &column{ origin: rb, st: st, Vindex: vindex, - } + }) } } if ai := vindexTable.AutoIncrement; ai != nil { lowered := ai.Column.Lowered() if _, ok := t.columns[lowered]; !ok { - t.columns[lowered] = &column{ + t.addColumn(ai.Column, &column{ origin: rb, st: st, - } + }) } } return st.AddTable(t) @@ -144,6 +147,11 @@ func (st *symtab) AddVindexTable(alias sqlparser.TableName, vindexTable *vindexe // At this point, only tables and uniqueColumns are set. // All other fields are ignored. func (st *symtab) Merge(newsyms *symtab) error { + if st.tableNames == nil || newsyms.tableNames == nil { + // If any side of symtab has anonymous tables, + // we treat the merged symtab as having anonymous tables. + return nil + } for _, t := range newsyms.tables { if err := st.AddTable(t); err != nil { return err @@ -161,6 +169,7 @@ func (st *symtab) AddTable(t *table) error { return fmt.Errorf("duplicate symbol: %s", sqlparser.String(t.alias)) } st.tables[t.alias] = t + st.tableNames = append(st.tableNames, t.alias) // update the uniqueColumns list, and eliminate // duplicate symbols if found. @@ -178,6 +187,37 @@ func (st *symtab) AddTable(t *table) error { return nil } +// AllTables returns an ordered list of all current tables. +func (st *symtab) AllTables() []*table { + if len(st.tableNames) == 0 { + return nil + } + tables := make([]*table, 0, len(st.tableNames)) + for _, tname := range st.tableNames { + tables = append(tables, st.tables[tname]) + } + return tables +} + +// FindTable finds a table in symtab. This function is specifically used +// for expanding 'select a.*' constructs. If you're in a subquery, +// you're most likely referring to a table in the local 'from' clause. +// For this reason, the search is only performed in the current scope. +// This may be a deviation from the formal definition of SQL, but there +// are currently no use cases that require the full support. +func (st *symtab) FindTable(tname sqlparser.TableName) (*table, error) { + if st.tableNames == nil { + // Unreachable because current code path checks for this condition + // before invoking this function. + return nil, errNoTable + } + t, ok := st.tables[tname] + if !ok { + return nil, fmt.Errorf("table %v not found", sqlparser.String(tname)) + } + return t, nil +} + // ClearVindexes removes the Column Vindexes from the aliases signifying // that they cannot be used to make routing improvements. This is // called if a primitive is in the RHS of a LEFT JOIN. @@ -330,14 +370,14 @@ func (st *symtab) searchTables(col *sqlparser.ColName) (*column, error) { c, ok := t.columns[col.Name.Lowered()] if !ok { // We know all the column names of a subquery. Might as well return an error if it's not found. - if _, ok := t.origin.(*subquery); ok { - return nil, fmt.Errorf("symbol %s is referencing a non-existent column of the subquery", sqlparser.String(col)) + if t.isAuthoritative { + return nil, fmt.Errorf("symbol %s not found in table or subquery", sqlparser.String(col)) } c = &column{ origin: t.origin, st: st, } - t.columns[col.Name.Lowered()] = c + t.addColumn(col.Name, c) } return c, nil } @@ -400,10 +440,25 @@ func (st *symtab) ResolveSymbols(node sqlparser.SQLNode) error { // It represents a table alias in a FROM clause. It points // to the builder that represents it. type table struct { - alias sqlparser.TableName - columns map[string]*column - origin builder - vindexTable *vindexes.Table + alias sqlparser.TableName + columns map[string]*column + columnNames []sqlparser.ColIdent + isAuthoritative bool + origin builder + vindexTable *vindexes.Table +} + +func (t *table) addColumn(alias sqlparser.ColIdent, c *column) { + if t.columns == nil { + t.columns = make(map[string]*column) + } + lowered := alias.Lowered() + // Dups are allowed, but first one wins if referenced. + if _, ok := t.columns[lowered]; !ok { + c.colnum = len(t.columnNames) + t.columns[lowered] = c + } + t.columnNames = append(t.columnNames, alias) } // column represents a unique symbol in the query that other diff --git a/go/vt/vtgate/planbuilder/update.go b/go/vt/vtgate/planbuilder/update.go index 33c4a366d4b..dc7f6eb94b8 100644 --- a/go/vt/vtgate/planbuilder/update.go +++ b/go/vt/vtgate/planbuilder/update.go @@ -40,7 +40,7 @@ func buildUpdatePlan(upd *sqlparser.Update, vschema ContextVSchema) (*engine.Upd } rb, ok := pb.bldr.(*route) if !ok { - return nil, errors.New("unsupported: multi-table update statement in sharded keyspace") + return nil, errors.New("unsupported: multi-table/vindex update statement in sharded keyspace") } if rb.ERoute.TargetDestination != nil { return nil, errors.New("unsupported: UPDATE with a target destination") diff --git a/go/vt/vtgate/planbuilder/vindex_func.go b/go/vt/vtgate/planbuilder/vindex_func.go index 88adcf3768d..91d0885539c 100644 --- a/go/vt/vtgate/planbuilder/vindex_func.go +++ b/go/vt/vtgate/planbuilder/vindex_func.go @@ -55,16 +55,11 @@ func newVindexFunc(alias sqlparser.TableName, vindex vindexes.Vindex) (*vindexFu } // Column names are hard-coded to id, keyspace_id - t.columns = map[string]*column{ - "id": { - origin: vf, - colnum: 0, - }, - "keyspace_id": { - origin: vf, - colnum: 1, - }, - } + t.addColumn(sqlparser.NewColIdent("id"), &column{origin: vf}) + t.addColumn(sqlparser.NewColIdent("keyspace_id"), &column{origin: vf}) + t.addColumn(sqlparser.NewColIdent("range_start"), &column{origin: vf}) + t.addColumn(sqlparser.NewColIdent("range_end"), &column{origin: vf}) + t.isAuthoritative = true st := newSymtab() // AddTable will not fail because symtab is empty. @@ -151,18 +146,7 @@ func (vf *vindexFunc) PushSelect(expr *sqlparser.AliasedExpr, _ builder) (rc *re Name: rc.alias.String(), Type: querypb.Type_VARBINARY, }) - switch { - case col.Name.EqualString("id"): - vf.eVindexFunc.Cols = append(vf.eVindexFunc.Cols, 0) - case col.Name.EqualString("keyspace_id"): - vf.eVindexFunc.Cols = append(vf.eVindexFunc.Cols, 1) - case col.Name.EqualString("range_start"): - vf.eVindexFunc.Cols = append(vf.eVindexFunc.Cols, 2) - case col.Name.EqualString("range_end"): - vf.eVindexFunc.Cols = append(vf.eVindexFunc.Cols, 3) - default: - return nil, 0, fmt.Errorf("unrecognized column %s for vindex: %s", col.Name, vf.eVindexFunc.Vindex) - } + vf.eVindexFunc.Cols = append(vf.eVindexFunc.Cols, col.Metadata.(*column).colnum) return rc, len(vf.resultColumns) - 1, nil } diff --git a/go/vt/vtgate/plugin_mysql_server.go b/go/vt/vtgate/plugin_mysql_server.go index e67e1026e3b..e1c0e3408f0 100644 --- a/go/vt/vtgate/plugin_mysql_server.go +++ b/go/vt/vtgate/plugin_mysql_server.go @@ -48,6 +48,8 @@ var ( mysqlAllowClearTextWithoutTLS = flag.Bool("mysql_allow_clear_text_without_tls", false, "If set, the server will allow the use of a clear text password over non-SSL connections.") mysqlServerVersion = flag.String("mysql_server_version", mysql.DefaultServerVersion, "MySQL server version to advertise.") + mysqlServerRequireSecureTransport = flag.Bool("mysql_server_require_secure_transport", false, "Reject insecure connections but only if mysql_server_ssl_cert and mysql_server_ssl_key are provided") + mysqlSslCert = flag.String("mysql_server_ssl_cert", "", "Path to the ssl cert for mysql server plugin SSL") mysqlSslKey = flag.String("mysql_server_ssl_key", "", "Path to ssl key for mysql server plugin SSL") mysqlSslCa = flag.String("mysql_server_ssl_ca", "", "Path to ssl CA for mysql server plugin SSL. If specified, server will require and validate client certs.") @@ -212,9 +214,9 @@ func initMySQLProtocol() { log.Exitf("grpcutils.TLSServerConfig failed: %v", err) return } + mysqlListener.RequireSecureTransport = *mysqlServerRequireSecureTransport } mysqlListener.AllowClearTextWithoutTLS = *mysqlAllowClearTextWithoutTLS - // Check for the connection threshold if *mysqlSlowConnectWarnThreshold != 0 { log.Infof("setting mysql slow connection threshold to %v", mysqlSlowConnectWarnThreshold) diff --git a/go/vt/vtgate/sandbox_test.go b/go/vt/vtgate/sandbox_test.go index 97c10015a93..484464de4f6 100644 --- a/go/vt/vtgate/sandbox_test.go +++ b/go/vt/vtgate/sandbox_test.go @@ -43,12 +43,14 @@ const ( KsTestSharded = "TestSharded" KsTestUnsharded = "TestUnsharded" KsTestUnshardedServedFrom = "TestUnshardedServedFrom" + KsTestBadVSchema = "TestXBadVSchema" ) func init() { ksToSandbox = make(map[string]*sandbox) createSandbox(KsTestSharded) createSandbox(KsTestUnsharded) + createSandbox(KsTestBadVSchema) tabletconn.RegisterDialer("sandbox", sandboxDialer) flag.Set("tablet_protocol", "sandbox") } diff --git a/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash.go b/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash.go new file mode 100644 index 00000000000..ec87042fae8 --- /dev/null +++ b/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash.go @@ -0,0 +1,404 @@ +/* +Copyright 2017 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vindexes + +import ( + "encoding/binary" + "encoding/json" + "fmt" + + "vitess.io/vitess/go/sqltypes" + "vitess.io/vitess/go/vt/key" + topodatapb "vitess.io/vitess/go/vt/proto/topodata" +) + +var ( + _ Vindex = (*LookupUnicodeLooseMD5Hash)(nil) + _ Lookup = (*LookupUnicodeLooseMD5Hash)(nil) + _ Vindex = (*LookupUnicodeLooseMD5HashUnique)(nil) + _ Lookup = (*LookupUnicodeLooseMD5HashUnique)(nil) +) + +func init() { + Register("lookup_unicodeloosemd5_hash", NewLookupUnicodeLooseMD5Hash) + Register("lookup_unicodeloosemd5_hash_unique", NewLookupUnicodeLooseMD5HashUnique) +} + +//==================================================================== + +// LookupUnicodeLooseMD5Hash defines a vindex that uses a lookup table. +// The table is expected to define the id column as unique. It's +// NonUnique and a Lookup and stores the from value in a hashed form. +// Warning: This Vindex is being depcreated in favor of Lookup +type LookupUnicodeLooseMD5Hash struct { + name string + writeOnly bool + lkp lookupInternal +} + +// NewLookupUnicodeLooseMD5Hash creates a LookupUnicodeLooseMD5Hash vindex. +// The supplied map has the following required fields: +// table: name of the backing table. It can be qualified by the keyspace. +// from: list of columns in the table that have the 'from' values of the lookup vindex. +// to: The 'to' column name of the table. +// +// The following fields are optional: +// autocommit: setting this to "true" will cause inserts to upsert and deletes to be ignored. +// write_only: in this mode, Map functions return the full keyrange causing a full scatter. +func NewLookupUnicodeLooseMD5Hash(name string, m map[string]string) (Vindex, error) { + lh := &LookupUnicodeLooseMD5Hash{name: name} + + autocommit, err := boolFromMap(m, "autocommit") + if err != nil { + return nil, err + } + lh.writeOnly, err = boolFromMap(m, "write_only") + if err != nil { + return nil, err + } + + // if autocommit is on for non-unique lookup, upsert should also be on. + if err := lh.lkp.Init(m, autocommit, autocommit /* upsert */); err != nil { + return nil, err + } + return lh, nil +} + +// String returns the name of the vindex. +func (lh *LookupUnicodeLooseMD5Hash) String() string { + return lh.name +} + +// Cost returns the cost of this vindex as 20. +func (lh *LookupUnicodeLooseMD5Hash) Cost() int { + return 20 +} + +// IsUnique returns false since the Vindex is not unique. +func (lh *LookupUnicodeLooseMD5Hash) IsUnique() bool { + return false +} + +// IsFunctional returns false since the Vindex is not functional. +func (lh *LookupUnicodeLooseMD5Hash) IsFunctional() bool { + return false +} + +// Map can map ids to key.Destination objects. +func (lh *LookupUnicodeLooseMD5Hash) Map(vcursor VCursor, ids []sqltypes.Value) ([]key.Destination, error) { + out := make([]key.Destination, 0, len(ids)) + if lh.writeOnly { + for range ids { + out = append(out, key.DestinationKeyRange{KeyRange: &topodatapb.KeyRange{}}) + } + return out, nil + } + + ids, err := convertIds(ids) + if err != nil { + return nil, err + } + results, err := lh.lkp.Lookup(vcursor, ids) + if err != nil { + return nil, err + } + for _, result := range results { + if len(result.Rows) == 0 { + out = append(out, key.DestinationNone{}) + continue + } + ksids := make([][]byte, 0, len(result.Rows)) + for _, row := range result.Rows { + num, err := sqltypes.ToUint64(row[0]) + if err != nil { + // A failure to convert is equivalent to not being + // able to map. + continue + } + ksids = append(ksids, vhash(num)) + } + out = append(out, key.DestinationKeyspaceIDs(ksids)) + } + return out, nil +} + +// Verify returns true if ids maps to ksids. +func (lh *LookupUnicodeLooseMD5Hash) Verify(vcursor VCursor, ids []sqltypes.Value, ksids [][]byte) ([]bool, error) { + if lh.writeOnly { + out := make([]bool, len(ids)) + for i := range ids { + out[i] = true + } + return out, nil + } + + values, err := unhashList(ksids) + if err != nil { + return nil, fmt.Errorf("lookup.Verify.vunhash: %v", err) + } + ids, err = convertIds(ids) + if err != nil { + return nil, fmt.Errorf("lookup.Verify.vunhash: %v", err) + } + return lh.lkp.Verify(vcursor, ids, values) +} + +// Create reserves the id by inserting it into the vindex table. +func (lh *LookupUnicodeLooseMD5Hash) Create(vcursor VCursor, rowsColValues [][]sqltypes.Value, ksids [][]byte, ignoreMode bool) error { + values, err := unhashList(ksids) + if err != nil { + return fmt.Errorf("lookup.Create.vunhash: %v", err) + } + rowsColValues, err = convertRows(rowsColValues) + if err != nil { + return fmt.Errorf("lookup.Create.convert: %v", err) + } + return lh.lkp.Create(vcursor, rowsColValues, values, ignoreMode) +} + +// Update updates the entry in the vindex table. +func (lh *LookupUnicodeLooseMD5Hash) Update(vcursor VCursor, oldValues []sqltypes.Value, ksid []byte, newValues []sqltypes.Value) error { + v, err := vunhash(ksid) + if err != nil { + return fmt.Errorf("lookup.Update.vunhash: %v", err) + } + newValues, err = convertIds(newValues) + if err != nil { + return fmt.Errorf("lookup.Update.convert: %v", err) + } + oldValues, err = convertIds(oldValues) + if err != nil { + return fmt.Errorf("lookup.Update.convert: %v", err) + } + return lh.lkp.Update(vcursor, oldValues, sqltypes.NewUint64(v), newValues) +} + +// Delete deletes the entry from the vindex table. +func (lh *LookupUnicodeLooseMD5Hash) Delete(vcursor VCursor, rowsColValues [][]sqltypes.Value, ksid []byte) error { + v, err := vunhash(ksid) + if err != nil { + return fmt.Errorf("lookup.Delete.vunhash: %v", err) + } + rowsColValues, err = convertRows(rowsColValues) + if err != nil { + return fmt.Errorf("lookup.Delete.convert: %v", err) + } + return lh.lkp.Delete(vcursor, rowsColValues, sqltypes.NewUint64(v)) +} + +// MarshalJSON returns a JSON representation of LookupHash. +func (lh *LookupUnicodeLooseMD5Hash) MarshalJSON() ([]byte, error) { + return json.Marshal(lh.lkp) +} + +//==================================================================== + +// LookupUnicodeLooseMD5HashUnique defines a vindex that uses a lookup table. +// The table is expected to define the id column as unique. It's +// Unique and a Lookup and will store the from value in a hashed format. +// Warning: This Vindex is being depcreated in favor of LookupUnique +type LookupUnicodeLooseMD5HashUnique struct { + name string + writeOnly bool + lkp lookupInternal +} + +// NewLookupUnicodeLooseMD5HashUnique creates a LookupUnicodeLooseMD5HashUnique vindex. +// The supplied map has the following required fields: +// table: name of the backing table. It can be qualified by the keyspace. +// from: list of columns in the table that have the 'from' values of the lookup vindex. +// to: The 'to' column name of the table. +// +// The following fields are optional: +// autocommit: setting this to "true" will cause deletes to be ignored. +// write_only: in this mode, Map functions return the full keyrange causing a full scatter. +func NewLookupUnicodeLooseMD5HashUnique(name string, m map[string]string) (Vindex, error) { + lhu := &LookupUnicodeLooseMD5HashUnique{name: name} + + autocommit, err := boolFromMap(m, "autocommit") + if err != nil { + return nil, err + } + lhu.writeOnly, err = boolFromMap(m, "write_only") + if err != nil { + return nil, err + } + + // Don't allow upserts for unique vindexes. + if err := lhu.lkp.Init(m, autocommit, false /* upsert */); err != nil { + return nil, err + } + return lhu, nil +} + +// String returns the name of the vindex. +func (lhu *LookupUnicodeLooseMD5HashUnique) String() string { + return lhu.name +} + +// Cost returns the cost of this vindex as 10. +func (lhu *LookupUnicodeLooseMD5HashUnique) Cost() int { + return 10 +} + +// IsUnique returns true since the Vindex is unique. +func (lhu *LookupUnicodeLooseMD5HashUnique) IsUnique() bool { + return true +} + +// IsFunctional returns false since the Vindex is not functional. +func (lhu *LookupUnicodeLooseMD5HashUnique) IsFunctional() bool { + return false +} + +// Map can map ids to key.Destination objects. +func (lhu *LookupUnicodeLooseMD5HashUnique) Map(vcursor VCursor, ids []sqltypes.Value) ([]key.Destination, error) { + out := make([]key.Destination, 0, len(ids)) + if lhu.writeOnly { + for range ids { + out = append(out, key.DestinationKeyRange{KeyRange: &topodatapb.KeyRange{}}) + } + return out, nil + } + + ids, err := convertIds(ids) + if err != nil { + return nil, err + } + results, err := lhu.lkp.Lookup(vcursor, ids) + if err != nil { + return nil, err + } + for i, result := range results { + switch len(result.Rows) { + case 0: + out = append(out, key.DestinationNone{}) + case 1: + num, err := sqltypes.ToUint64(result.Rows[0][0]) + if err != nil { + out = append(out, key.DestinationNone{}) + continue + } + out = append(out, key.DestinationKeyspaceID(vhash(num))) + default: + return nil, fmt.Errorf("LookupUnicodeLooseMD5HashUnique.Map: unexpected multiple results from vindex %s: %v", lhu.lkp.Table, ids[i]) + } + } + return out, nil +} + +// Verify returns true if ids maps to ksids. +func (lhu *LookupUnicodeLooseMD5HashUnique) Verify(vcursor VCursor, ids []sqltypes.Value, ksids [][]byte) ([]bool, error) { + if lhu.writeOnly { + out := make([]bool, len(ids)) + for i := range ids { + out[i] = true + } + return out, nil + } + + values, err := unhashList(ksids) + if err != nil { + return nil, fmt.Errorf("lookup.Verify.vunhash: %v", err) + } + ids, err = convertIds(ids) + if err != nil { + return nil, fmt.Errorf("lookup.Verify.vunhash: %v", err) + } + return lhu.lkp.Verify(vcursor, ids, values) +} + +// Create reserves the id by inserting it into the vindex table. +func (lhu *LookupUnicodeLooseMD5HashUnique) Create(vcursor VCursor, rowsColValues [][]sqltypes.Value, ksids [][]byte, ignoreMode bool) error { + values, err := unhashList(ksids) + if err != nil { + return fmt.Errorf("lookup.Create.vunhash: %v", err) + } + rowsColValues, err = convertRows(rowsColValues) + if err != nil { + return fmt.Errorf("lookup.Create.convert: %v", err) + } + return lhu.lkp.Create(vcursor, rowsColValues, values, ignoreMode) +} + +// Delete deletes the entry from the vindex table. +func (lhu *LookupUnicodeLooseMD5HashUnique) Delete(vcursor VCursor, rowsColValues [][]sqltypes.Value, ksid []byte) error { + v, err := vunhash(ksid) + if err != nil { + return fmt.Errorf("lookup.Delete.vunhash: %v", err) + } + rowsColValues, err = convertRows(rowsColValues) + if err != nil { + return fmt.Errorf("lookup.Delete.convert: %v", err) + } + return lhu.lkp.Delete(vcursor, rowsColValues, sqltypes.NewUint64(v)) +} + +// Update updates the entry in the vindex table. +func (lhu *LookupUnicodeLooseMD5HashUnique) Update(vcursor VCursor, oldValues []sqltypes.Value, ksid []byte, newValues []sqltypes.Value) error { + v, err := vunhash(ksid) + if err != nil { + return fmt.Errorf("lookup.Update.vunhash: %v", err) + } + newValues, err = convertIds(newValues) + if err != nil { + return fmt.Errorf("lookup.Update.convert: %v", err) + } + oldValues, err = convertIds(oldValues) + if err != nil { + return fmt.Errorf("lookup.Update.convert: %v", err) + } + return lhu.lkp.Update(vcursor, oldValues, sqltypes.NewUint64(v), newValues) +} + +// MarshalJSON returns a JSON representation of LookupHashUnique. +func (lhu *LookupUnicodeLooseMD5HashUnique) MarshalJSON() ([]byte, error) { + return json.Marshal(lhu.lkp) +} + +func unicodeHashValue(value sqltypes.Value) (sqltypes.Value, error) { + hash, err := unicodeHash(value) + if err != nil { + return sqltypes.NULL, err + } + + return sqltypes.NewUint64(binary.BigEndian.Uint64(hash[:8])), nil +} + +func convertIds(ids []sqltypes.Value) ([]sqltypes.Value, error) { + converted := make([]sqltypes.Value, 0, len(ids)) + for _, id := range ids { + idVal, err := unicodeHashValue(id) + if err != nil { + return nil, err + } + converted = append(converted, idVal) + } + return converted, nil +} + +func convertRows(rows [][]sqltypes.Value) ([][]sqltypes.Value, error) { + converted := make([][]sqltypes.Value, 0, len(rows)) + for _, row := range rows { + row, err := convertIds(row) + if err != nil { + return nil, err + } + converted = append(converted, row) + } + return converted, nil +} diff --git a/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash_test.go b/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash_test.go new file mode 100644 index 00000000000..54eeb5f047c --- /dev/null +++ b/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash_test.go @@ -0,0 +1,455 @@ +/* +Copyright 2017 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreedto in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vindexes + +import ( + "reflect" + "testing" + + "vitess.io/vitess/go/sqltypes" + + "vitess.io/vitess/go/vt/key" + querypb "vitess.io/vitess/go/vt/proto/query" + topodatapb "vitess.io/vitess/go/vt/proto/topodata" +) + +const hashed10 uint64 = 17563797831108199066 +const hashed20 uint64 = 8729390916138266389 +const hashed30 uint64 = 1472608112194674795 +const hashed40 uint64 = 16576388050845489136 + +func TestLookupUnicodeLooseMD5HashMap(t *testing.T) { + lookup := createLookup(t, "lookup_unicodeloosemd5_hash", false) + vc := &vcursor{numRows: 2} + + got, err := lookup.Map(vc, []sqltypes.Value{sqltypes.NewInt64(10), sqltypes.NewInt64(20)}) + if err != nil { + t.Error(err) + } + want := []key.Destination{ + key.DestinationKeyspaceIDs([][]byte{ + []byte("\x16k@\xb4J\xbaK\xd6"), + []byte("\x06\xe7\xea\"Î’p\x8f"), + }), + key.DestinationKeyspaceIDs([][]byte{ + []byte("\x16k@\xb4J\xbaK\xd6"), + []byte("\x06\xe7\xea\"Î’p\x8f"), + }), + } + if !reflect.DeepEqual(got, want) { + t.Errorf("Map(): %#v, want %+v", got, want) + } + + wantqueries := []*querypb.BoundQuery{{ + Sql: "select toc from t where fromc = :fromc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed10), + }, + }, { + Sql: "select toc from t where fromc = :fromc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed20), + }, + }} + if !reflect.DeepEqual(vc.queries, wantqueries) { + t.Errorf("lookup.Map queries:\n%v, want\n%v", vc.queries, wantqueries) + } + + // Test query fail. + vc.mustFail = true + _, err = lookup.Map(vc, []sqltypes.Value{sqltypes.NewInt64(1)}) + wantErr := "lookup.Map: execute failed" + if err == nil || err.Error() != wantErr { + t.Errorf("lookup(query fail) err: %v, want %s", err, wantErr) + } + vc.mustFail = false +} + +func TestLookupUnicodeLooseMD5HashMapAutocommit(t *testing.T) { + lookupNonUnique, err := CreateVindex("lookup_unicodeloosemd5_hash", "lookup", map[string]string{ + "table": "t", + "from": "fromc", + "to": "toc", + "hash_from": "true", + "autocommit": "true", + }) + if err != nil { + t.Fatal(err) + } + vc := &vcursor{numRows: 2} + + got, err := lookupNonUnique.Map(vc, []sqltypes.Value{sqltypes.NewInt64(10), sqltypes.NewInt64(20)}) + if err != nil { + t.Error(err) + } + want := []key.Destination{ + key.DestinationKeyspaceIDs([][]byte{ + []byte("\x16k@\xb4J\xbaK\xd6"), + []byte("\x06\xe7\xea\"Î’p\x8f"), + }), + key.DestinationKeyspaceIDs([][]byte{ + []byte("\x16k@\xb4J\xbaK\xd6"), + []byte("\x06\xe7\xea\"Î’p\x8f"), + }), + } + if !reflect.DeepEqual(got, want) { + t.Errorf("Map(): %#v, want %+v", got, want) + } + + wantqueries := []*querypb.BoundQuery{{ + Sql: "select toc from t where fromc = :fromc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed10), + }, + }, { + Sql: "select toc from t where fromc = :fromc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed20), + }, + }} + if !reflect.DeepEqual(vc.queries, wantqueries) { + t.Errorf("lookup.Map queries:\n%v, want\n%v", vc.queries, wantqueries) + } + + if got, want := vc.autocommits, 2; got != want { + t.Errorf("Create(autocommit) count: %d, want %d", got, want) + } +} + +func TestLookupUnicodeLooseMD5HashMapWriteOnly(t *testing.T) { + lookupNonUnique := createLookup(t, "lookup_unicodeloosemd5_hash", true) + vc := &vcursor{numRows: 0} + + got, err := lookupNonUnique.Map(vc, []sqltypes.Value{sqltypes.NewInt64(10), sqltypes.NewInt64(20)}) + if err != nil { + t.Error(err) + } + want := []key.Destination{ + key.DestinationKeyRange{ + KeyRange: &topodatapb.KeyRange{}, + }, + key.DestinationKeyRange{ + KeyRange: &topodatapb.KeyRange{}, + }, + } + if !reflect.DeepEqual(got, want) { + t.Errorf("Map(): %#v, want %+v", got, want) + } +} + +func TestLookupUnicodeLooseMD5HashMapAbsent(t *testing.T) { + lookupNonUnique := createLookup(t, "lookup_unicodeloosemd5_hash", false) + vc := &vcursor{numRows: 0} + + got, err := lookupNonUnique.Map(vc, []sqltypes.Value{sqltypes.NewInt64(10), sqltypes.NewInt64(20)}) + if err != nil { + t.Error(err) + } + want := []key.Destination{ + key.DestinationNone{}, + key.DestinationNone{}, + } + if !reflect.DeepEqual(got, want) { + t.Errorf("Map(): %#v, want %+v", got, want) + } +} + +func TestLookupUnicodeLooseMD5HashVerify(t *testing.T) { + lookupNonUnique := createLookup(t, "lookup_unicodeloosemd5_hash", false) + vc := &vcursor{numRows: 1} + + got, err := lookupNonUnique.Verify(vc, + []sqltypes.Value{sqltypes.NewInt64(10), sqltypes.NewInt64(20)}, + [][]byte{[]byte("\x16k@\xb4J\xbaK\xd6"), []byte("\x06\xe7\xea\"Î’p\x8f")}) + if err != nil { + t.Error(err) + } + wantResult := []bool{true, true} + if !reflect.DeepEqual(got, wantResult) { + t.Errorf("lookuphash.Verify(match): %v, want %v", got, wantResult) + } + + wantqueries := []*querypb.BoundQuery{{ + Sql: "select fromc from t where fromc = :fromc and toc = :toc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed10), + "toc": sqltypes.Uint64BindVariable(1), + }, + }, { + Sql: "select fromc from t where fromc = :fromc and toc = :toc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed20), + "toc": sqltypes.Uint64BindVariable(2), + }, + }} + if !reflect.DeepEqual(vc.queries, wantqueries) { + t.Errorf("lookup.Verify queries:\n%v, want\n%v", vc.queries, wantqueries) + } + + // Test query fail. + vc.mustFail = true + _, err = lookupNonUnique.Verify(vc, []sqltypes.Value{sqltypes.NewInt64(1)}, [][]byte{[]byte("\x16k@\xb4J\xbaK\xd6")}) + want := "lookup.Verify: execute failed" + if err == nil || err.Error() != want { + t.Errorf("lookupNonUnique(query fail) err: %v, want %s", err, want) + } + vc.mustFail = false + + // writeOnly true should always yield true. + lookupNonUnique = createLookup(t, "lookup_unicodeloosemd5_hash", true) + vc.queries = nil + + got, err = lookupNonUnique.Verify(vc, []sqltypes.Value{sqltypes.NewInt64(10), sqltypes.NewInt64(20)}, [][]byte{[]byte(""), []byte("")}) + if err != nil { + t.Error(err) + } + if vc.queries != nil { + t.Errorf("lookup.Verify(writeOnly), queries: %v, want nil", vc.queries) + } + wantBools := []bool{true, true} + if !reflect.DeepEqual(got, wantBools) { + t.Errorf("lookup.Verify(writeOnly): %v, want %v", got, wantBools) + } +} + +func TestLookupUnicodeLooseMD5HashVerifyAutocommit(t *testing.T) { + lookupNonUnique, err := CreateVindex("lookup_unicodeloosemd5_hash", "lookup", map[string]string{ + "table": "t", + "from": "fromc", + "to": "toc", + "autocommit": "true", + }) + if err != nil { + t.Fatal(err) + } + vc := &vcursor{numRows: 1} + + _, err = lookupNonUnique.Verify(vc, []sqltypes.Value{sqltypes.NewInt64(10), sqltypes.NewInt64(20)}, + [][]byte{[]byte("\x16k@\xb4J\xbaK\xd6"), []byte("\x06\xe7\xea\"Î’p\x8f")}) + if err != nil { + t.Error(err) + } + + wantqueries := []*querypb.BoundQuery{{ + Sql: "select fromc from t where fromc = :fromc and toc = :toc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed10), + "toc": sqltypes.Uint64BindVariable(1), + }, + }, { + Sql: "select fromc from t where fromc = :fromc and toc = :toc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed20), + "toc": sqltypes.Uint64BindVariable(2), + }, + }} + if !reflect.DeepEqual(vc.queries, wantqueries) { + t.Errorf("lookup.Verify queries:\n%v, want\n%v", vc.queries, wantqueries) + } + + if got, want := vc.autocommits, 2; got != want { + t.Errorf("Create(autocommit) count: %d, want %d", got, want) + } +} + +func TestLookupUnicodeLooseMD5HashCreate(t *testing.T) { + lookupNonUnique := createLookup(t, "lookup_unicodeloosemd5_hash", false) + vc := &vcursor{} + + err := lookupNonUnique.(Lookup).Create(vc, [][]sqltypes.Value{{sqltypes.NewInt64(10)}, {sqltypes.NewInt64(20)}}, + [][]byte{[]byte("\x16k@\xb4J\xbaK\xd6"), []byte("\x06\xe7\xea\"Î’p\x8f")}, false /* ignoreMode */) + if err != nil { + t.Error(err) + } + + wantqueries := []*querypb.BoundQuery{{ + Sql: "insert into t(fromc, toc) values(:fromc0, :toc0), (:fromc1, :toc1)", + BindVariables: map[string]*querypb.BindVariable{ + "fromc0": sqltypes.Uint64BindVariable(hashed10), + "toc0": sqltypes.Uint64BindVariable(1), + "fromc1": sqltypes.Uint64BindVariable(hashed20), + "toc1": sqltypes.Uint64BindVariable(2), + }, + }} + if !reflect.DeepEqual(vc.queries, wantqueries) { + t.Errorf("lookup.Create queries:\n%v, want\n%v", vc.queries, wantqueries) + } + + // With ignore. + vc.queries = nil + err = lookupNonUnique.(Lookup).Create(vc, [][]sqltypes.Value{{sqltypes.NewInt64(10)}, {sqltypes.NewInt64(20)}}, + [][]byte{[]byte("\x16k@\xb4J\xbaK\xd6"), []byte("\x06\xe7\xea\"Î’p\x8f")}, true /* ignoreMode */) + if err != nil { + t.Error(err) + } + + wantqueries[0].Sql = "insert ignore into t(fromc, toc) values(:fromc0, :toc0), (:fromc1, :toc1)" + if !reflect.DeepEqual(vc.queries, wantqueries) { + t.Errorf("lookup.Create queries:\n%v, want\n%v", vc.queries, wantqueries) + } + + // Test query fail. + vc.mustFail = true + err = lookupNonUnique.(Lookup).Create(vc, [][]sqltypes.Value{{sqltypes.NewInt64(10)}}, [][]byte{[]byte("\x16k@\xb4J\xbaK\xd6")}, false /* ignoreMode */) + want := "lookup.Create: execute failed" + if err == nil || err.Error() != want { + t.Errorf("lookupNonUnique(query fail) err: %v, want %s", err, want) + } + vc.mustFail = false + + // Test column mismatch. + err = lookupNonUnique.(Lookup).Create(vc, [][]sqltypes.Value{{sqltypes.NewInt64(10), sqltypes.NewInt64(20)}}, [][]byte{[]byte("\x16k@\xb4J\xbaK\xd6")}, false /* ignoreMode */) + want = "lookup.Create: column vindex count does not match the columns in the lookup: 2 vs [fromc]" + if err == nil || err.Error() != want { + t.Errorf("lookupNonUnique(query fail) err: %v, want %s", err, want) + } +} + +func TestLookupUnicodeLooseMD5HashCreateAutocommit(t *testing.T) { + lookupNonUnique, err := CreateVindex("lookup_unicodeloosemd5_hash", "lookup", map[string]string{ + "table": "t", + "from": "from1,from2", + "to": "toc", + "autocommit": "true", + }) + if err != nil { + t.Fatal(err) + } + vc := &vcursor{} + + err = lookupNonUnique.(Lookup).Create( + vc, + [][]sqltypes.Value{{ + sqltypes.NewInt64(10), sqltypes.NewInt64(20), + }, { + sqltypes.NewInt64(30), sqltypes.NewInt64(40), + }}, + [][]byte{[]byte("\x16k@\xb4J\xbaK\xd6"), []byte("\x06\xe7\xea\"Î’p\x8f")}, + false /* ignoreMode */) + if err != nil { + t.Error(err) + } + + wantqueries := []*querypb.BoundQuery{{ + Sql: "insert into t(from1, from2, toc) values(:from10, :from20, :toc0), (:from11, :from21, :toc1) on duplicate key update from1=values(from1), from2=values(from2), toc=values(toc)", + BindVariables: map[string]*querypb.BindVariable{ + "from10": sqltypes.Uint64BindVariable(hashed10), + "from20": sqltypes.Uint64BindVariable(hashed20), + "toc0": sqltypes.Uint64BindVariable(1), + "from11": sqltypes.Uint64BindVariable(hashed30), + "from21": sqltypes.Uint64BindVariable(hashed40), + "toc1": sqltypes.Uint64BindVariable(2), + }, + }} + if !reflect.DeepEqual(vc.queries, wantqueries) { + t.Errorf("lookup.Create queries:\n%v, want\n%v", vc.queries, wantqueries) + } + + if got, want := vc.autocommits, 1; got != want { + t.Errorf("Create(autocommit) count: %d, want %d", got, want) + } +} + +func TestLookupUnicodeLooseMD5HashDelete(t *testing.T) { + lookupNonUnique := createLookup(t, "lookup_unicodeloosemd5_hash", false) + vc := &vcursor{} + + err := lookupNonUnique.(Lookup).Delete(vc, [][]sqltypes.Value{{sqltypes.NewInt64(10)}, {sqltypes.NewInt64(20)}}, []byte("\x16k@\xb4J\xbaK\xd6")) + if err != nil { + t.Error(err) + } + + wantqueries := []*querypb.BoundQuery{{ + Sql: "delete from t where fromc = :fromc and toc = :toc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed10), + "toc": sqltypes.Uint64BindVariable(1), + }, + }, { + Sql: "delete from t where fromc = :fromc and toc = :toc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed20), + "toc": sqltypes.Uint64BindVariable(1), + }, + }} + if !reflect.DeepEqual(vc.queries, wantqueries) { + t.Errorf("lookup.Delete queries:\n%v, want\n%v", vc.queries, wantqueries) + } + + // Test query fail. + vc.mustFail = true + err = lookupNonUnique.(Lookup).Delete(vc, [][]sqltypes.Value{{sqltypes.NewInt64(1)}}, []byte("\x16k@\xb4J\xbaK\xd6")) + want := "lookup.Delete: execute failed" + if err == nil || err.Error() != want { + t.Errorf("lookupNonUnique(query fail) err: %v, want %s", err, want) + } + vc.mustFail = false + + // Test column count fail. + err = lookupNonUnique.(Lookup).Delete(vc, [][]sqltypes.Value{{sqltypes.NewInt64(1), sqltypes.NewInt64(2)}}, []byte("\x16k@\xb4J\xbaK\xd6")) + want = "lookup.Delete: column vindex count does not match the columns in the lookup: 2 vs [fromc]" + if err == nil || err.Error() != want { + t.Errorf("lookupNonUnique(query fail) err: %v, want %s", err, want) + } +} + +func TestLookupUnicodeLooseMD5HashDeleteAutocommit(t *testing.T) { + lookupNonUnique, err := CreateVindex("lookup_unicodeloosemd5_hash", "lookup", map[string]string{ + "table": "t", + "from": "fromc", + "to": "toc", + "autocommit": "true", + }) + vc := &vcursor{} + + err = lookupNonUnique.(Lookup).Delete(vc, [][]sqltypes.Value{{sqltypes.NewInt64(10)}, {sqltypes.NewInt64(20)}}, []byte("\x16k@\xb4J\xbaK\xd6")) + if err != nil { + t.Error(err) + } + + wantqueries := []*querypb.BoundQuery(nil) + if !reflect.DeepEqual(vc.queries, wantqueries) { + t.Errorf("lookup.Delete queries:\n%v, want\n%v", vc.queries, wantqueries) + } +} + +func TestLookupUnicodeLooseMD5HashUpdate(t *testing.T) { + lookupNonUnique := createLookup(t, "lookup_unicodeloosemd5_hash", false) + vc := &vcursor{} + + err := lookupNonUnique.(Lookup).Update(vc, []sqltypes.Value{sqltypes.NewInt64(10)}, []byte("\x16k@\xb4J\xbaK\xd6"), []sqltypes.Value{sqltypes.NewInt64(20)}) + if err != nil { + t.Error(err) + } + + wantqueries := []*querypb.BoundQuery{{ + Sql: "delete from t where fromc = :fromc and toc = :toc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed10), + "toc": sqltypes.Uint64BindVariable(1), + }, + }, { + Sql: "insert into t(fromc, toc) values(:fromc0, :toc0)", + BindVariables: map[string]*querypb.BindVariable{ + "fromc0": sqltypes.Uint64BindVariable(hashed20), + "toc0": sqltypes.Uint64BindVariable(1), + }, + }} + if !reflect.DeepEqual(vc.queries, wantqueries) { + t.Errorf("lookup.Update queries:\n%v, want\n%v", vc.queries, wantqueries) + } +} diff --git a/go/vt/vtgate/vindexes/vschema.go b/go/vt/vtgate/vindexes/vschema.go index 7619b77d405..96a2100fee7 100644 --- a/go/vt/vtgate/vindexes/vschema.go +++ b/go/vt/vtgate/vindexes/vschema.go @@ -41,15 +41,16 @@ type VSchema struct { // Table represents a table in VSchema. type Table struct { - IsSequence bool `json:"is_sequence,omitempty"` - Name sqlparser.TableIdent `json:"name"` - Keyspace *Keyspace `json:"-"` - ColumnVindexes []*ColumnVindex `json:"column_vindexes,omitempty"` - Ordered []*ColumnVindex `json:"ordered,omitempty"` - Owned []*ColumnVindex `json:"owned,omitempty"` - AutoIncrement *AutoIncrement `json:"auto_increment,omitempty"` - Columns []Column `json:"columns,omitempty"` - Pinned []byte `json:"pinned,omitempty"` + IsSequence bool `json:"is_sequence,omitempty"` + Name sqlparser.TableIdent `json:"name"` + Keyspace *Keyspace `json:"-"` + ColumnVindexes []*ColumnVindex `json:"column_vindexes,omitempty"` + Ordered []*ColumnVindex `json:"ordered,omitempty"` + Owned []*ColumnVindex `json:"owned,omitempty"` + AutoIncrement *AutoIncrement `json:"auto_increment,omitempty"` + Columns []Column `json:"columns,omitempty"` + Pinned []byte `json:"pinned,omitempty"` + ColumnListAuthoritative bool `json:"column_list_authoritative,omitempty"` } // Keyspace contains the keyspcae info for each Table. @@ -89,6 +90,7 @@ type KeyspaceSchema struct { Keyspace *Keyspace Tables map[string]*Table Vindexes map[string]Vindex + Error error } // MarshalJSON returns a JSON representation of KeyspaceSchema. @@ -97,10 +99,17 @@ func (ks *KeyspaceSchema) MarshalJSON() ([]byte, error) { Sharded bool `json:"sharded,omitempty"` Tables map[string]*Table `json:"tables,omitempty"` Vindexes map[string]Vindex `json:"vindexes,omitempty"` + Error string `json:"error,omitempty"` }{ Sharded: ks.Keyspace.Sharded, Tables: ks.Tables, Vindexes: ks.Vindexes, + Error: func(ks *KeyspaceSchema) string { + if ks.Error == nil { + return "" + } + return ks.Error.Error() + }(ks), }) } @@ -118,14 +127,8 @@ func BuildVSchema(source *vschemapb.SrvVSchema) (vschema *VSchema, err error) { Keyspaces: make(map[string]*KeyspaceSchema), } buildKeyspaces(source, vschema) - err = buildTables(source, vschema) - if err != nil { - return nil, err - } - err = resolveAutoIncrement(source, vschema) - if err != nil { - return nil, err - } + buildTables(source, vschema) + resolveAutoIncrement(source, vschema) addDual(vschema) return vschema, nil } @@ -148,11 +151,9 @@ func BuildKeyspaceSchema(input *vschemapb.Keyspace, keyspace string) (*KeyspaceS Keyspaces: make(map[string]*KeyspaceSchema), } buildKeyspaces(formal, vschema) - err := buildTables(formal, vschema) - if err != nil { - return nil, err - } - return vschema.Keyspaces[keyspace], nil + buildTables(formal, vschema) + err := vschema.Keyspaces[keyspace].Error + return vschema.Keyspaces[keyspace], err } // ValidateKeyspace ensures that the keyspace vschema is valid. @@ -175,13 +176,16 @@ func buildKeyspaces(source *vschemapb.SrvVSchema, vschema *VSchema) { } } -func buildTables(source *vschemapb.SrvVSchema, vschema *VSchema) error { +func buildTables(source *vschemapb.SrvVSchema, vschema *VSchema) { +outer: for ksname, ks := range source.Keyspaces { - keyspace := vschema.Keyspaces[ksname].Keyspace + ksvschema := vschema.Keyspaces[ksname] + keyspace := ksvschema.Keyspace for vname, vindexInfo := range ks.Vindexes { vindex, err := CreateVindex(vindexInfo.Type, vname, vindexInfo.Params) if err != nil { - return err + ksvschema.Error = err + continue outer } if _, ok := vschema.uniqueVindexes[vname]; ok { vschema.uniqueVindexes[vname] = nil @@ -192,8 +196,9 @@ func buildTables(source *vschemapb.SrvVSchema, vschema *VSchema) error { } for tname, table := range ks.Tables { t := &Table{ - Name: sqlparser.NewTableIdent(tname), - Keyspace: keyspace, + Name: sqlparser.NewTableIdent(tname), + Keyspace: keyspace, + ColumnListAuthoritative: table.ColumnListAuthoritative, } if _, ok := vschema.uniqueTables[tname]; ok { vschema.uniqueTables[tname] = nil @@ -207,11 +212,13 @@ func buildTables(source *vschemapb.SrvVSchema, vschema *VSchema) error { if table.Pinned != "" { decoded, err := hex.DecodeString(table.Pinned) if err != nil { - return fmt.Errorf("could not decode the keyspace id for pin: %v", err) + ksvschema.Error = fmt.Errorf("could not decode the keyspace id for pin: %v", err) + continue outer } t.Pinned = decoded } else if keyspace.Sharded && len(table.ColumnVindexes) == 0 { - return fmt.Errorf("missing primary col vindex for table: %s", tname) + ksvschema.Error = fmt.Errorf("missing primary col vindex for table: %s", tname) + continue outer } // Initialize Columns. @@ -219,7 +226,8 @@ func buildTables(source *vschemapb.SrvVSchema, vschema *VSchema) error { for _, col := range table.Columns { name := sqlparser.NewColIdent(col.Name) if colNames[name.Lowered()] { - return fmt.Errorf("duplicate column name '%v' for table: %s", name, tname) + ksvschema.Error = fmt.Errorf("duplicate column name '%v' for table: %s", name, tname) + continue outer } colNames[name.Lowered()] = true t.Columns = append(t.Columns, Column{Name: name, Type: col.Type}) @@ -229,7 +237,8 @@ func buildTables(source *vschemapb.SrvVSchema, vschema *VSchema) error { for i, ind := range table.ColumnVindexes { vindexInfo, ok := ks.Vindexes[ind.Name] if !ok { - return fmt.Errorf("vindex %s not found for table %s", ind.Name, tname) + ksvschema.Error = fmt.Errorf("vindex %s not found for table %s", ind.Name, tname) + continue outer } vindex := vschema.Keyspaces[ksname].Vindexes[ind.Name] owned := false @@ -239,12 +248,14 @@ func buildTables(source *vschemapb.SrvVSchema, vschema *VSchema) error { var columns []sqlparser.ColIdent if ind.Column != "" { if len(ind.Columns) > 0 { - return fmt.Errorf("can't use column and columns at the same time in vindex (%s) and table (%s)", ind.Name, tname) + ksvschema.Error = fmt.Errorf("can't use column and columns at the same time in vindex (%s) and table (%s)", ind.Name, tname) + continue outer } columns = []sqlparser.ColIdent{sqlparser.NewColIdent(ind.Column)} } else { if len(ind.Columns) == 0 { - return fmt.Errorf("must specify at least one column for vindex (%s) and table (%s)", ind.Name, tname) + ksvschema.Error = fmt.Errorf("must specify at least one column for vindex (%s) and table (%s)", ind.Name, tname) + continue outer } for _, indCol := range ind.Columns { columns = append(columns, sqlparser.NewColIdent(indCol)) @@ -260,10 +271,12 @@ func buildTables(source *vschemapb.SrvVSchema, vschema *VSchema) error { if i == 0 { // Perform Primary vindex check. if !columnVindex.Vindex.IsUnique() { - return fmt.Errorf("primary vindex %s is not Unique for table %s", ind.Name, tname) + ksvschema.Error = fmt.Errorf("primary vindex %s is not Unique for table %s", ind.Name, tname) + continue outer } if owned { - return fmt.Errorf("primary vindex %s cannot be owned for table %s", ind.Name, tname) + ksvschema.Error = fmt.Errorf("primary vindex %s cannot be owned for table %s", ind.Name, tname) + continue outer } } t.ColumnVindexes = append(t.ColumnVindexes, columnVindex) @@ -274,10 +287,9 @@ func buildTables(source *vschemapb.SrvVSchema, vschema *VSchema) error { t.Ordered = colVindexSorted(t.ColumnVindexes) } } - return nil } -func resolveAutoIncrement(source *vschemapb.SrvVSchema, vschema *VSchema) error { +func resolveAutoIncrement(source *vschemapb.SrvVSchema, vschema *VSchema) { for ksname, ks := range source.Keyspaces { ksvschema := vschema.Keyspaces[ksname] for tname, table := range ks.Tables { @@ -288,12 +300,12 @@ func resolveAutoIncrement(source *vschemapb.SrvVSchema, vschema *VSchema) error t.AutoIncrement = &AutoIncrement{Column: sqlparser.NewColIdent(table.AutoIncrement.Column)} seq, err := vschema.findQualified(table.AutoIncrement.Sequence) if err != nil { - return fmt.Errorf("cannot resolve sequence %s: %v", table.AutoIncrement.Sequence, err) + ksvschema.Error = fmt.Errorf("cannot resolve sequence %s: %v", table.AutoIncrement.Sequence, err) + break } t.AutoIncrement.Sequence = seq } } - return nil } // addDual adds dual as a valid table to all keyspaces. diff --git a/go/vt/vtgate/vindexes/vschema_test.go b/go/vt/vtgate/vindexes/vschema_test.go index 133922bd058..d1f04d1a600 100644 --- a/go/vt/vtgate/vindexes/vschema_test.go +++ b/go/vt/vtgate/vindexes/vschema_test.go @@ -114,7 +114,8 @@ func TestUnshardedVSchema(t *testing.T) { }, }, } - got, err := BuildVSchema(&good) + got, _ := BuildVSchema(&good) + err := got.Keyspaces["unsharded"].Error if err != nil { t.Error(err) } @@ -168,6 +169,71 @@ func TestVSchemaColumns(t *testing.T) { }, }, } + got, _ := BuildVSchema(&good) + err := got.Keyspaces["unsharded"].Error + if err != nil { + t.Error(err) + } + ks := &Keyspace{ + Name: "unsharded", + } + t1 := &Table{ + Name: sqlparser.NewTableIdent("t1"), + Keyspace: ks, + Columns: []Column{{ + Name: sqlparser.NewColIdent("c1"), + Type: sqltypes.Null, + }, { + Name: sqlparser.NewColIdent("c2"), + Type: sqltypes.VarChar, + }}, + } + dual := &Table{ + Name: sqlparser.NewTableIdent("dual"), + Keyspace: ks, + } + want := &VSchema{ + uniqueTables: map[string]*Table{ + "t1": t1, + "dual": dual, + }, + uniqueVindexes: map[string]Vindex{}, + Keyspaces: map[string]*KeyspaceSchema{ + "unsharded": { + Keyspace: ks, + Tables: map[string]*Table{ + "t1": t1, + "dual": dual, + }, + Vindexes: map[string]Vindex{}, + }, + }, + } + if !reflect.DeepEqual(got, want) { + gotb, _ := json.Marshal(got) + wantb, _ := json.Marshal(want) + t.Errorf("BuildVSchema:\n%s, want\n%s", gotb, wantb) + } +} + +func TestVSchemaColumnListAuthoritative(t *testing.T) { + good := vschemapb.SrvVSchema{ + Keyspaces: map[string]*vschemapb.Keyspace{ + "unsharded": { + Tables: map[string]*vschemapb.Table{ + "t1": { + Columns: []*vschemapb.Column{{ + Name: "c1", + }, { + Name: "c2", + Type: sqltypes.VarChar, + }}, + ColumnListAuthoritative: true, + }, + }, + }, + }, + } got, err := BuildVSchema(&good) if err != nil { t.Error(err) @@ -185,6 +251,7 @@ func TestVSchemaColumns(t *testing.T) { Name: sqlparser.NewColIdent("c2"), Type: sqltypes.VarChar, }}, + ColumnListAuthoritative: true, } dual := &Table{ Name: sqlparser.NewTableIdent("dual"), @@ -230,8 +297,9 @@ func TestVSchemaColumnsFail(t *testing.T) { }, }, } - _, err := BuildVSchema(&good) + got, _ := BuildVSchema(&good) want := "duplicate column name 'c1' for table: t1" + err := got.Keyspaces["unsharded"].Error if err == nil || err.Error() != want { t.Errorf("BuildVSchema(dup col): %v, want %v", err, want) } @@ -250,7 +318,8 @@ func TestVSchemaPinned(t *testing.T) { }, }, } - got, err := BuildVSchema(&good) + got, _ := BuildVSchema(&good) + err := got.Keyspaces["sharded"].Error if err != nil { t.Error(err) } @@ -326,7 +395,8 @@ func TestShardedVSchemaOwned(t *testing.T) { }, }, } - got, err := BuildVSchema(&good) + got, _ := BuildVSchema(&good) + err := got.Keyspaces["sharded"].Error if err != nil { t.Error(err) } @@ -427,7 +497,8 @@ func TestShardedVSchemaMultiColumnVindex(t *testing.T) { }, }, } - got, err := BuildVSchema(&good) + got, _ := BuildVSchema(&good) + err := got.Keyspaces["sharded"].Error if err != nil { t.Error(err) } @@ -520,7 +591,8 @@ func TestShardedVSchemaNotOwned(t *testing.T) { }, }, } - got, err := BuildVSchema(&good) + got, _ := BuildVSchema(&good) + err := got.Keyspaces["sharded"].Error if err != nil { t.Error(err) } @@ -612,7 +684,8 @@ func TestBuildVSchemaVindexNotFoundFail(t *testing.T) { }, }, } - _, err := BuildVSchema(&bad) + got, _ := BuildVSchema(&bad) + err := got.Keyspaces["sharded"].Error want := `vindexType "noexist" not found` if err == nil || err.Error() != want { t.Errorf("BuildVSchema: %v, want %v", err, want) @@ -635,7 +708,8 @@ func TestBuildVSchemaNoColumnVindexFail(t *testing.T) { }, }, } - _, err := BuildVSchema(&bad) + got, _ := BuildVSchema(&bad) + err := got.Keyspaces["sharded"].Error want := "missing primary col vindex for table: t1" if err == nil || err.Error() != want { t.Errorf("BuildVSchema: %v, want %v", err, want) @@ -831,10 +905,15 @@ func TestBuildVSchemaDupVindex(t *testing.T) { }, }, } - got, err := BuildVSchema(&good) + got, _ := BuildVSchema(&good) + err := got.Keyspaces["ksa"].Error + err1 := got.Keyspaces["ksb"].Error if err != nil { t.Error(err) } + if err1 != nil { + t.Error(err1) + } ksa := &Keyspace{ Name: "ksa", Sharded: true, @@ -947,7 +1026,8 @@ func TestBuildVSchemaNoindexFail(t *testing.T) { }, }, } - _, err := BuildVSchema(&bad) + got, _ := BuildVSchema(&bad) + err := got.Keyspaces["sharded"].Error want := "vindex notexist not found for table t1" if err == nil || err.Error() != want { t.Errorf("BuildVSchema: %v, want %v", err, want) @@ -978,7 +1058,8 @@ func TestBuildVSchemaColumnAndColumnsFail(t *testing.T) { }, }, } - _, err := BuildVSchema(&bad) + got, _ := BuildVSchema(&bad) + err := got.Keyspaces["sharded"].Error want := `can't use column and columns at the same time in vindex (stfu) and table (t1)` if err == nil || err.Error() != want { t.Errorf("BuildVSchema: %v, want %v", err, want) @@ -1007,7 +1088,8 @@ func TestBuildVSchemaNoColumnsFail(t *testing.T) { }, }, } - _, err := BuildVSchema(&bad) + got, _ := BuildVSchema(&bad) + err := got.Keyspaces["sharded"].Error want := `must specify at least one column for vindex (stfu) and table (t1)` if err == nil || err.Error() != want { t.Errorf("BuildVSchema: %v, want %v", err, want) @@ -1037,7 +1119,8 @@ func TestBuildVSchemaNotUniqueFail(t *testing.T) { }, }, } - _, err := BuildVSchema(&bad) + got, _ := BuildVSchema(&bad) + err := got.Keyspaces["sharded"].Error want := "primary vindex stln is not Unique for table t1" if err == nil || err.Error() != want { t.Errorf("BuildVSchema: %v, want %v", err, want) @@ -1068,7 +1151,8 @@ func TestBuildVSchemaPrimaryNonFunctionalFail(t *testing.T) { }, }, } - _, err := BuildVSchema(&bad) + got, _ := BuildVSchema(&bad) + err := got.Keyspaces["sharded"].Error want := "primary vindex stlu cannot be owned for table t1" if err == nil || err.Error() != want { t.Errorf("BuildVSchema: %v, want %v", err, want) @@ -1124,10 +1208,15 @@ func TestSequence(t *testing.T) { }, }, } - got, err := BuildVSchema(&good) + got, _ := BuildVSchema(&good) + err := got.Keyspaces["sharded"].Error if err != nil { t.Error(err) } + err1 := got.Keyspaces["unsharded"].Error + if err1 != nil { + t.Error(err1) + } ksu := &Keyspace{ Name: "unsharded", } @@ -1259,7 +1348,8 @@ func TestBadSequence(t *testing.T) { }, }, } - _, err := BuildVSchema(&bad) + got, _ := BuildVSchema(&bad) + err := got.Keyspaces["sharded"].Error want := "cannot resolve sequence seq: table seq not found" if err == nil || err.Error() != want { t.Errorf("BuildVSchema: %v, want %v", err, want) @@ -1293,7 +1383,8 @@ func TestBadSequenceName(t *testing.T) { }, }, } - _, err := BuildVSchema(&bad) + got, _ := BuildVSchema(&bad) + err := got.Keyspaces["sharded"].Error want := "cannot resolve sequence a.b.seq: table a.b.seq not found" if err == nil || err.Error() != want { t.Errorf("BuildVSchema: %v, want %v", err, want) @@ -1504,7 +1595,8 @@ func TestBuildKeyspaceSchema(t *testing.T) { "t2": {}, }, } - got, err := BuildKeyspaceSchema(good, "ks") + got, _ := BuildKeyspaceSchema(good, "ks") + err := got.Error if err != nil { t.Error(err) } diff --git a/go/vt/vtgate/vschema_manager.go b/go/vt/vtgate/vschema_manager.go index 82f8ad8ffad..5462a7fdcc4 100644 --- a/go/vt/vtgate/vschema_manager.go +++ b/go/vt/vtgate/vschema_manager.go @@ -85,7 +85,6 @@ func (vm *VSchemaManager) watchSrvVSchema(ctx context.Context, cell string) { vschema, err = vindexes.BuildVSchema(v) if err != nil { log.Warningf("Error creating VSchema for cell %v (will try again next update): %v", cell, err) - v = nil err = fmt.Errorf("Error creating VSchema for cell %v: %v", cell, err) if vschemaCounters != nil { vschemaCounters.Add("Parsing", 1) diff --git a/go/vt/vtgate/vschema_stats.go b/go/vt/vtgate/vschema_stats.go index d70ce523ca9..adc1fd897ec 100644 --- a/go/vt/vtgate/vschema_stats.go +++ b/go/vt/vtgate/vschema_stats.go @@ -37,6 +37,7 @@ type VSchemaKeyspaceStats struct { Sharded bool TableCount int VindexCount int + Error string } // NewVSchemaStats returns a new VSchemaStats from a VSchema. @@ -56,6 +57,9 @@ func NewVSchemaStats(vschema *vindexes.VSchema, errorMessage string) *VSchemaSta s.VindexCount += len(t.ColumnVindexes) + len(t.Ordered) + len(t.Owned) } } + if k.Error != nil { + s.Error = k.Error.Error() + } stats.Keyspaces = append(stats.Keyspaces, s) } sort.Slice(stats.Keyspaces, func(i, j int) bool { return stats.Keyspaces[i].Keyspace < stats.Keyspaces[j].Keyspace }) @@ -77,7 +81,7 @@ const ( - + {{if .Error}} @@ -93,12 +97,14 @@ const ( + {{range $i, $ks := .Keyspaces}} + {{end}}
VSchema Cache in JSONVSchema Cache in JSON
Sharded Table Count Vindex CountError
{{$ks.Keyspace}} {{if $ks.Sharded}}Yes{{else}}No{{end}} {{$ks.TableCount}} {{$ks.VindexCount}}{{$ks.Error}}
` diff --git a/go/vt/vtgate/vtgate_test.go b/go/vt/vtgate/vtgate_test.go index e77270bb42d..f0300c4d45d 100644 --- a/go/vt/vtgate/vtgate_test.go +++ b/go/vt/vtgate/vtgate_test.go @@ -62,6 +62,19 @@ func init() { } } ` + getSandbox(KsTestBadVSchema).VSchema = ` + { + "sharded": true, + "tables": { + "t2": { + "auto_increment": { + "column": "id", + "sequence": "id_seq" + } + } + } + } + ` hcVTGateTest = discovery.NewFakeHealthCheck() *transactionMode = "MULTI" // The topo.Server is used to start watching the cells described diff --git a/go/vt/vttablet/tabletserver/planbuilder/ddl.go b/go/vt/vttablet/tabletserver/planbuilder/ddl.go index 9d9eaad3468..c6c055f5595 100644 --- a/go/vt/vttablet/tabletserver/planbuilder/ddl.go +++ b/go/vt/vttablet/tabletserver/planbuilder/ddl.go @@ -23,9 +23,7 @@ import ( // DDLPlan provides a plan for DDLs. type DDLPlan struct { - Action string - TableName sqlparser.TableName - NewName sqlparser.TableName + Action string } // DDLParse parses a DDL and produces a DDLPlan. @@ -39,18 +37,14 @@ func DDLParse(sql string) (plan *DDLPlan) { return &DDLPlan{Action: ""} } return &DDLPlan{ - Action: stmt.Action, - TableName: stmt.Table, - NewName: stmt.NewName, + Action: stmt.Action, } } func analyzeDDL(ddl *sqlparser.DDL, tables map[string]*schema.Table) *Plan { // TODO(sougou): Add support for sequences. plan := &Plan{ - PlanID: PlanDDL, - Table: tables[ddl.Table.Name.String()], - NewName: ddl.NewName.Name, + PlanID: PlanDDL, } // this can become a whitelist of fully supported ddl actions as support grows if ddl.PartitionSpec != nil { diff --git a/go/vt/vttablet/tabletserver/planbuilder/permission.go b/go/vt/vttablet/tabletserver/planbuilder/permission.go index c7b388b5eb0..5e503a7e204 100644 --- a/go/vt/vttablet/tabletserver/planbuilder/permission.go +++ b/go/vt/vttablet/tabletserver/planbuilder/permission.go @@ -50,11 +50,8 @@ func BuildPermissions(stmt sqlparser.Statement) []Permission { case *sqlparser.Set, *sqlparser.Show, *sqlparser.OtherRead: // no-op case *sqlparser.DDL: - if !node.Table.IsEmpty() { - permissions = buildTableNamePermissions(node.Table, tableacl.ADMIN, permissions) - } - if !node.NewName.IsEmpty() { - permissions = buildTableNamePermissions(node.NewName, tableacl.ADMIN, permissions) + for _, t := range node.AffectedTables() { + permissions = buildTableNamePermissions(t, tableacl.ADMIN, permissions) } case *sqlparser.OtherAdmin: // no op diff --git a/go/vt/vttablet/tabletserver/planbuilder/plan_test.go b/go/vt/vttablet/tabletserver/planbuilder/plan_test.go index 577ba2bd52d..f6f2e671a05 100644 --- a/go/vt/vttablet/tabletserver/planbuilder/plan_test.go +++ b/go/vt/vttablet/tabletserver/planbuilder/plan_test.go @@ -183,8 +183,6 @@ func TestDDLPlan(t *testing.T) { t.Fatalf("Error marshalling %v", plan) } matchString(t, tcase.lineno, expected["Action"], plan.Action) - matchString(t, tcase.lineno, expected["TableName"], sqlparser.String(plan.TableName)) - matchString(t, tcase.lineno, expected["NewName"], sqlparser.String(plan.NewName)) } } diff --git a/go/vt/vttablet/tabletserver/query_engine.go b/go/vt/vttablet/tabletserver/query_engine.go index 735f95ab2fe..c8388d6abeb 100644 --- a/go/vt/vttablet/tabletserver/query_engine.go +++ b/go/vt/vttablet/tabletserver/query_engine.go @@ -235,6 +235,7 @@ func NewQueryEngine(checker connpool.MySQLChecker, se *schema.Engine, config tab qe.streamBufferSize = sync2.NewAtomicInt64(int64(config.StreamBufferSize)) qe.passthroughDMLs = sync2.NewAtomicBool(config.PassthroughDMLs) + qe.allowUnsafeDMLs = config.AllowUnsafeDMLs planbuilder.PassthroughDMLs = config.PassthroughDMLs qe.accessCheckerLogger = logutil.NewThrottledLogger("accessChecker", 1*time.Second) diff --git a/go/vt/vttablet/tabletserver/tabletenv/config.go b/go/vt/vttablet/tabletserver/tabletenv/config.go index ea81237e41d..cc6eac7df9d 100644 --- a/go/vt/vttablet/tabletserver/tabletenv/config.go +++ b/go/vt/vttablet/tabletserver/tabletenv/config.go @@ -56,6 +56,7 @@ func init() { flag.IntVar(&Config.WarnResultSize, "queryserver-config-warn-result-size", DefaultQsConfig.WarnResultSize, "query server result size warning threshold, warn if number of rows returned from vttablet for non-streaming queries exceeds this") flag.IntVar(&Config.MaxDMLRows, "queryserver-config-max-dml-rows", DefaultQsConfig.MaxDMLRows, "query server max dml rows per statement, maximum number of rows allowed to return at a time for an update or delete with either 1) an equality where clauses on primary keys, or 2) a subselect statement. For update and delete statements in above two categories, vttablet will split the original query into multiple small queries based on this configuration value. ") flag.BoolVar(&Config.PassthroughDMLs, "queryserver-config-passthrough-dmls", DefaultQsConfig.PassthroughDMLs, "query server pass through all dml statements without rewriting") + flag.BoolVar(&Config.AllowUnsafeDMLs, "queryserver-config-allowunsafe-dmls", DefaultQsConfig.AllowUnsafeDMLs, "query server allow unsafe dml statements") flag.IntVar(&Config.StreamBufferSize, "queryserver-config-stream-buffer-size", DefaultQsConfig.StreamBufferSize, "query server stream buffer size, the maximum number of bytes sent from vttablet for each stream call. It's recommended to keep this value in sync with vtgate's stream_buffer_size.") flag.IntVar(&Config.QueryPlanCacheSize, "queryserver-config-query-cache-size", DefaultQsConfig.QueryPlanCacheSize, "query server query cache size, maximum number of queries to be cached. vttablet analyzes every incoming query and generate a query plan, these plans are being cached in a lru cache. This config controls the capacity of the lru cache.") @@ -134,6 +135,7 @@ type TabletConfig struct { WarnResultSize int MaxDMLRows int PassthroughDMLs bool + AllowUnsafeDMLs bool StreamBufferSize int QueryPlanCacheSize int SchemaReloadTime float64 @@ -205,6 +207,7 @@ var DefaultQsConfig = TabletConfig{ WarnResultSize: 0, MaxDMLRows: 500, PassthroughDMLs: false, + AllowUnsafeDMLs: false, QueryPlanCacheSize: 5000, SchemaReloadTime: 30 * 60, QueryTimeout: 30, diff --git a/go/vt/worker/executor.go b/go/vt/worker/executor.go index bc9cedfce87..757cdaf00e9 100644 --- a/go/vt/worker/executor.go +++ b/go/vt/worker/executor.go @@ -220,7 +220,7 @@ func (e *executor) checkError(ctx context.Context, err error, isRetry bool, mast e.wr.Logger().Warningf("ExecuteFetch failed on %v; will reresolve and retry because it's due to a MySQL read-only error: %v", tabletString, err) statsRetryCount.Add(1) statsRetryCounters.Add(retryCategoryReadOnly, 1) - case errNo == "2002" || errNo == "2006" || errNo == "2013": + case errNo == "2002" || errNo == "2006" || errNo == "2013" || errNo == "1053": // Note: // "2006" happens if the connection is already dead. Retrying a query in // this case is safe. @@ -229,6 +229,7 @@ func (e *executor) checkError(ctx context.Context, err error, isRetry bool, mast // it was aborted. If we retry the query and get a duplicate entry error, we // assume that the previous execution was successful and ignore the error. // See below for the handling of duplicate entry error "1062". + // "1053" is mysql shutting down e.wr.Logger().Warningf("ExecuteFetch failed on %v; will reresolve and retry because it's due to a MySQL connection error: %v", tabletString, err) statsRetryCount.Add(1) statsRetryCounters.Add(retryCategoryConnectionError, 1) diff --git a/go/vt/worker/legacy_split_clone_test.go b/go/vt/worker/legacy_split_clone_test.go index 09bd1329c4a..f1bd3cad9f7 100644 --- a/go/vt/worker/legacy_split_clone_test.go +++ b/go/vt/worker/legacy_split_clone_test.go @@ -171,7 +171,7 @@ func (tc *legacySplitCloneTestCase) setUp(v3 bool) { }, } sourceRdonly.FakeMysqlDaemon.CurrentMasterPosition = mysql.Position{ - GTIDSet: mysql.MariadbGTID{Domain: 12, Server: 34, Sequence: 5678}, + GTIDSet: mysql.MariadbGTIDSet{mysql.MariadbGTID{Domain: 12, Server: 34, Sequence: 5678}}, } sourceRdonly.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ "STOP SLAVE", diff --git a/go/vt/worker/restartable_result_reader.go b/go/vt/worker/restartable_result_reader.go index 1a629e57236..d21110d418e 100644 --- a/go/vt/worker/restartable_result_reader.go +++ b/go/vt/worker/restartable_result_reader.go @@ -82,14 +82,30 @@ func NewRestartableResultReader(ctx context.Context, logger logutil.Logger, tp t allowMultipleRetries: allowMultipleRetries, } - // If the initial connection fails, we do not restart. - if _ /* retryable */, err := r.getTablet(); err != nil { - return nil, vterrors.Wrap(err, "tablet=unknown") - } - if _ /* retryable */, err := r.startStream(); err != nil { - return nil, vterrors.Wrapf(err, "tablet=%v", topoproto.TabletAliasString(r.tablet.Alias)) + // If the initial connection fails we retry once. + // Note: The first retry will be the second attempt. + attempt := 0 + for { + attempt++ + var err error + var retryable bool + if retryable, err = r.getTablet(); err != nil { + err = fmt.Errorf("tablet=unknown: %v", err) + goto retry + } + if retryable, err = r.startStream(); err != nil { + err = fmt.Errorf("tablet=%v: %v", topoproto.TabletAliasString(r.tablet.Alias), err) + goto retry + } + return r, nil + + retry: + if !retryable || attempt > 1 { + return nil, fmt.Errorf("failed to initialize tablet connection: retryable %v, %v", retryable, err) + } + statsRetryCount.Add(1) + logger.Infof("retrying after error: %v", err) } - return r, nil } // getTablet (re)sets the tablet which is used for the streaming query. @@ -172,7 +188,7 @@ func (r *RestartableResultReader) nextWithRetries() (*sqltypes.Result, error) { retryCtx, retryCancel := context.WithTimeout(r.ctx, *retryDuration) defer retryCancel() - // Note: The first retry will be the second attempt. + // The first retry is the second attempt because we already tried once in Next() attempt := 1 start := time.Now() for { diff --git a/go/vt/worker/split_clone.go b/go/vt/worker/split_clone.go index 6fe32e9a26d..ff18878cb74 100644 --- a/go/vt/worker/split_clone.go +++ b/go/vt/worker/split_clone.go @@ -433,29 +433,20 @@ func (scw *SplitCloneWorker) run(ctx context.Context) error { return err } - // Phase 2a: Find destination master tablets. + // Phase 2: Find destination master tablets. if err := scw.findDestinationMasters(ctx); err != nil { return vterrors.Wrap(err, "findDestinationMasters() failed") } if err := checkDone(ctx); err != nil { return err } - // Phase 2b: Wait for minimum number of destination tablets (required for the - // diff). Note that while we wait for the minimum number, we'll always use - // *all* available RDONLY tablets from each destination shard. - if err := scw.waitForTablets(ctx, scw.destinationShards, *waitForHealthyTabletsTimeout); err != nil { - return vterrors.Wrap(err, "waitForDestinationTablets(destinationShards) failed") - } - if err := checkDone(ctx); err != nil { - return err - } // Phase 3: (optional) online clone. if scw.online { scw.wr.Logger().Infof("Online clone will be run now.") // 3a: Wait for minimum number of source tablets (required for the diff). if err := scw.waitForTablets(ctx, scw.sourceShards, *waitForHealthyTabletsTimeout); err != nil { - return vterrors.Wrap(err, "waitForDestinationTablets(sourceShards) failed") + return vterrors.Wrap(err, "waitForTablets(sourceShards) failed") } // 3b: Clone the data. start := time.Now() @@ -784,7 +775,7 @@ func (scw *SplitCloneWorker) waitForTablets(ctx context.Context, shardInfos []*t var wg sync.WaitGroup rec := concurrency.AllErrorRecorder{} - if len(shardInfos) > 0 { + if scw.minHealthyRdonlyTablets > 0 && len(shardInfos) > 0 { scw.wr.Logger().Infof("Waiting %v for %d %s/%s RDONLY tablet(s)", timeout, scw.minHealthyRdonlyTablets, shardInfos[0].Keyspace(), shardInfos[0].ShardName()) } @@ -976,7 +967,7 @@ func (scw *SplitCloneWorker) clone(ctx context.Context, state StatusWorkerState) // longer stopped at the same point as we took it offline initially. allowMultipleRetries = false } else { - tp = newShardTabletProvider(scw.tsc, scw.tabletTracker, si.Keyspace(), si.ShardName()) + tp = newShardTabletProvider(scw.tsc, scw.tabletTracker, si.Keyspace(), si.ShardName(), topodatapb.TabletType_RDONLY) } sourceResultReader, err := NewRestartableResultReader(ctx, scw.wr.Logger(), tp, td, chunk, allowMultipleRetries) if err != nil { @@ -987,16 +978,8 @@ func (scw *SplitCloneWorker) clone(ctx context.Context, state StatusWorkerState) sourceReaders[shardIndex] = sourceResultReader } - // Wait for enough healthy tablets (they might have become unhealthy - // and their replication lag might have increased due to a previous - // chunk pipeline.) - if err := scw.waitForTablets(ctx, scw.destinationShards, *retryDuration); err != nil { - processError("%v: No healthy destination tablets found (gave up after %v): ", errPrefix, time.Since(start), err) - return - } - for shardIndex, si := range scw.destinationShards { - tp := newShardTabletProvider(scw.tsc, scw.tabletTracker, si.Keyspace(), si.ShardName()) + tp := newShardTabletProvider(scw.tsc, scw.tabletTracker, si.Keyspace(), si.ShardName(), topodatapb.TabletType_MASTER) destResultReader, err := NewRestartableResultReader(ctx, scw.wr.Logger(), tp, td, chunk, true /* allowMultipleRetries */) if err != nil { processError("%v: NewRestartableResultReader for destination: %v failed: %v", errPrefix, tp.description(), err) diff --git a/go/vt/worker/split_clone_test.go b/go/vt/worker/split_clone_test.go index 184e87d1dc1..25bb32b4dea 100644 --- a/go/vt/worker/split_clone_test.go +++ b/go/vt/worker/split_clone_test.go @@ -57,7 +57,7 @@ const ( ) var ( - errReadOnly = errors.New("The MariaDB server is running with the --read-only option so it cannot execute this statement (errno 1290) during query:") + errReadOnly = errors.New("the MariaDB server is running with the --read-only option so it cannot execute this statement (errno 1290) during query: ") errStreamingQueryTimeout = errors.New("vttablet: generic::unknown: error: the query was killed either because it timed out or was canceled: (errno 2013) (sqlstate HY000) during query: ") ) @@ -76,9 +76,9 @@ type splitCloneTestCase struct { // Destination tablets. leftMasterFakeDb *fakesqldb.DB - leftMasterQs *fakes.StreamHealthQueryService + leftMasterQs *testQueryService rightMasterFakeDb *fakesqldb.DB - rightMasterQs *fakes.StreamHealthQueryService + rightMasterQs *testQueryService // leftReplica is used by the reparent test. leftReplica *testlib.FakeTablet @@ -92,14 +92,23 @@ type splitCloneTestCase struct { // defaultWorkerArgs are the full default arguments to run SplitClone. defaultWorkerArgs []string + + // Used to restore the default values after the test run + defaultExecuteFetchRetryTime time.Duration + defaultRetryDuration time.Duration } func (tc *splitCloneTestCase) setUp(v3 bool) { - tc.setUpWithConcurreny(v3, 10, 2, splitCloneTestRowsCount) + tc.setUpWithConcurrency(v3, 10, 2, splitCloneTestRowsCount) } -func (tc *splitCloneTestCase) setUpWithConcurreny(v3 bool, concurrency, writeQueryMaxRows, rowsCount int) { +func (tc *splitCloneTestCase) setUpWithConcurrency(v3 bool, concurrency, writeQueryMaxRows, rowsCount int) { *useV3ReshardingMode = v3 + + // Reset some retry flags for the tests that change that + tc.defaultRetryDuration = *retryDuration + tc.defaultExecuteFetchRetryTime = *executeFetchRetryTime + tc.ts = memorytopo.NewServer("cell1", "cell2") ctx := context.Background() tc.wi = NewInstance(tc.ts, "cell1", time.Second) @@ -204,7 +213,7 @@ func (tc *splitCloneTestCase) setUpWithConcurreny(v3 bool, concurrency, writeQue }, } sourceRdonly.FakeMysqlDaemon.CurrentMasterPosition = mysql.Position{ - GTIDSet: mysql.MariadbGTID{Domain: 12, Server: 34, Sequence: 5678}, + GTIDSet: mysql.MariadbGTIDSet{mysql.MariadbGTID{Domain: 12, Server: 34, Sequence: 5678}}, } sourceRdonly.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ "STOP SLAVE", @@ -245,12 +254,13 @@ func (tc *splitCloneTestCase) setUpWithConcurreny(v3 bool, concurrency, writeQue } // Fake stream health reponses because vtworker needs them to find the master. - tc.leftMasterQs = fakes.NewStreamHealthQueryService(leftMaster.Target()) - tc.leftMasterQs.AddDefaultHealthResponse() + shqs := fakes.NewStreamHealthQueryService(leftMaster.Target()) + shqs.AddDefaultHealthResponse() + tc.leftMasterQs = newTestQueryService(tc.t, leftMaster.Target(), shqs, 0, 2, topoproto.TabletAliasString(leftMaster.Tablet.Alias), false /* omitKeyspaceID */) tc.leftReplicaQs = fakes.NewStreamHealthQueryService(leftReplica.Target()) - tc.leftReplicaQs.AddDefaultHealthResponse() - tc.rightMasterQs = fakes.NewStreamHealthQueryService(rightMaster.Target()) - tc.rightMasterQs.AddDefaultHealthResponse() + shqs = fakes.NewStreamHealthQueryService(rightMaster.Target()) + shqs.AddDefaultHealthResponse() + tc.rightMasterQs = newTestQueryService(tc.t, rightMaster.Target(), shqs, 1, 2, topoproto.TabletAliasString(rightMaster.Tablet.Alias), false /* omitKeyspaceID */) grpcqueryservice.Register(leftMaster.RPCServer, tc.leftMasterQs) grpcqueryservice.Register(leftReplica.RPCServer, tc.leftReplicaQs) grpcqueryservice.Register(rightMaster.RPCServer, tc.rightMasterQs) @@ -276,6 +286,9 @@ func (tc *splitCloneTestCase) setUpWithConcurreny(v3 bool, concurrency, writeQue } func (tc *splitCloneTestCase) tearDown() { + *retryDuration = tc.defaultRetryDuration + *executeFetchRetryTime = tc.defaultExecuteFetchRetryTime + for _, ft := range tc.tablets { ft.StopActionLoop(tc.t) } @@ -519,7 +532,7 @@ func TestSplitCloneV2_Offline(t *testing.T) { // get processed concurrently while the other pending ones are blocked. func TestSplitCloneV2_Offline_HighChunkCount(t *testing.T) { tc := &splitCloneTestCase{t: t} - tc.setUpWithConcurreny(false /* v3 */, 10, 5 /* writeQueryMaxRows */, 1000 /* rowsCount */) + tc.setUpWithConcurrency(false /* v3 */, 10, 5 /* writeQueryMaxRows */, 1000 /* rowsCount */) defer tc.tearDown() args := make([]string, len(tc.defaultWorkerArgs)) @@ -545,6 +558,9 @@ func TestSplitCloneV2_Offline_RestartStreamingQuery(t *testing.T) { tc.setUp(false /* v3 */) defer tc.tearDown() + // Only wait 1 ms between retries, so that the test passes faster. + *executeFetchRetryTime = 1 * time.Millisecond + // Ensure that this test uses only the first tablet. This makes it easier // to verify that the restart actually happened for that tablet. // SplitClone will ignore the second tablet because we set its replication lag @@ -580,9 +596,12 @@ func TestSplitCloneV2_Offline_RestartStreamingQuery(t *testing.T) { // of the streaming query does not succeed here and instead vtworker will fail. func TestSplitCloneV2_Offline_FailOverStreamingQuery_NotAllowed(t *testing.T) { tc := &splitCloneTestCase{t: t} - tc.setUpWithConcurreny(false /* v3 */, 1, 10, splitCloneTestRowsCount) + tc.setUpWithConcurrency(false /* v3 */, 1, 10, splitCloneTestRowsCount) defer tc.tearDown() + // Only wait 1 ms between retries, so that the test passes faster. + *executeFetchRetryTime = 1 * time.Millisecond + // Ensure that this test uses only the first tablet. tc.sourceRdonlyQs[1].AddHealthResponseWithSecondsBehindMaster(3600) @@ -617,7 +636,7 @@ func TestSplitCloneV2_Offline_FailOverStreamingQuery_NotAllowed(t *testing.T) { // reading the last row. func TestSplitCloneV2_Online_FailOverStreamingQuery(t *testing.T) { tc := &splitCloneTestCase{t: t} - tc.setUpWithConcurreny(false /* v3 */, 1, 10, splitCloneTestRowsCount) + tc.setUpWithConcurrency(false /* v3 */, 1, 10, splitCloneTestRowsCount) defer tc.tearDown() // In the online phase we won't enable filtered replication. Don't expect it. @@ -672,7 +691,7 @@ func TestSplitCloneV2_Online_FailOverStreamingQuery(t *testing.T) { // available. func TestSplitCloneV2_Online_TabletsUnavailableDuringRestart(t *testing.T) { tc := &splitCloneTestCase{t: t} - tc.setUpWithConcurreny(false /* v3 */, 1, 10, splitCloneTestRowsCount) + tc.setUpWithConcurrency(false /* v3 */, 1, 10, splitCloneTestRowsCount) defer tc.tearDown() // In the online phase we won't enable filtered replication. Don't expect it. @@ -692,15 +711,9 @@ func TestSplitCloneV2_Online_TabletsUnavailableDuringRestart(t *testing.T) { tc.sourceRdonlyQs[0].AddHealthResponseWithNotServing() }) - // Only wait 1 ms between retries, so that the test passes faster. - *executeFetchRetryTime = 1 * time.Millisecond // Let vtworker keep retrying and give up rather quickly because the test // will be blocked until it finally fails. - defaultRetryDuration := *retryDuration *retryDuration = 500 * time.Millisecond - defer func() { - *retryDuration = defaultRetryDuration - }() // Run the vtworker command. args := []string{"SplitClone", @@ -757,14 +770,10 @@ func TestSplitCloneV2_Online_Offline(t *testing.T) { // When the online clone inserted the last rows, modify the destination test // query service such that it will return them as well. tc.leftMasterFakeDb.GetEntry(29).AfterFunc = func() { - for i := range []int{0, 1} { - tc.leftRdonlyQs[i].addGeneratedRows(100, 200) - } + tc.leftMasterQs.addGeneratedRows(100, 200) } tc.rightMasterFakeDb.GetEntry(29).AfterFunc = func() { - for i := range []int{0, 1} { - tc.rightRdonlyQs[i].addGeneratedRows(100, 200) - } + tc.rightMasterQs.addGeneratedRows(100, 200) } // Run the vtworker command. @@ -789,7 +798,7 @@ func TestSplitCloneV2_Offline_Reconciliation(t *testing.T) { tc := &splitCloneTestCase{t: t} // We reduce the parallelism to 1 to test the order of expected // insert/update/delete statements on the destination master. - tc.setUpWithConcurreny(false /* v3 */, 1, 10, splitCloneTestRowsCount) + tc.setUpWithConcurrency(false /* v3 */, 1, 10, splitCloneTestRowsCount) defer tc.tearDown() // We assume that an Online Clone ran before which copied the rows 100-199 @@ -807,15 +816,13 @@ func TestSplitCloneV2_Offline_Reconciliation(t *testing.T) { qs.addGeneratedRows(100, 190) } - for i := range []int{0, 1} { - // The destination has rows 100-190 with the source in common. - // Rows 191-200 are extraenous on the destination. - tc.leftRdonlyQs[i].addGeneratedRows(100, 200) - tc.rightRdonlyQs[i].addGeneratedRows(100, 200) - // But some data is outdated data and must be updated. - tc.leftRdonlyQs[i].modifyFirstRows(2) - tc.rightRdonlyQs[i].modifyFirstRows(2) - } + // The destination has rows 100-190 with the source in common. + // Rows 191-200 are extraneous on the destination. + tc.leftMasterQs.addGeneratedRows(100, 200) + tc.rightMasterQs.addGeneratedRows(100, 200) + // But some data is outdated data and must be updated. + tc.leftMasterQs.modifyFirstRows(2) + tc.rightMasterQs.modifyFirstRows(2) // The destination tablets should see inserts, updates and deletes. // Clear the entries added by setUp() because the reconcilation will @@ -893,11 +900,12 @@ func TestSplitCloneV2_RetryDueToReadonly(t *testing.T) { tc.setUp(false /* v3 */) defer tc.tearDown() + // Only wait 1 ms between retries, so that the test passes faster. + *executeFetchRetryTime = 1 * time.Millisecond + // Provoke a retry to test the error handling. tc.leftMasterFakeDb.AddExpectedQueryAtIndex(0, "INSERT INTO `vt_ks`.`table1` (`id`, `msg`, `keyspace_id`) VALUES (*", errReadOnly) tc.rightMasterFakeDb.AddExpectedQueryAtIndex(0, "INSERT INTO `vt_ks`.`table1` (`id`, `msg`, `keyspace_id`) VALUES (*", errReadOnly) - // Only wait 1 ms between retries, so that the test passes faster. - *executeFetchRetryTime = 1 * time.Millisecond // Run the vtworker command. if err := runCommand(t, tc.wi, tc.wi.wr, tc.defaultWorkerArgs); err != nil { @@ -922,6 +930,9 @@ func TestSplitCloneV2_RetryDueToReparent(t *testing.T) { tc.setUp(false /* v3 */) defer tc.tearDown() + // Only wait 1 ms between retries, so that the test passes faster. + *executeFetchRetryTime = 1 * time.Millisecond + // Provoke a reparent just before the copy finishes. // leftReplica will take over for the last, 30th, insert and the vreplication checkpoint. tc.leftReplicaFakeDb.AddExpectedQuery("INSERT INTO `vt_ks`.`table1` (`id`, `msg`, `keyspace_id`) VALUES (*", nil) @@ -960,9 +971,6 @@ func TestSplitCloneV2_RetryDueToReparent(t *testing.T) { // => vtworker has no MASTER to go to and will keep retrying. } - // Only wait 1 ms between retries, so that the test passes faster. - *executeFetchRetryTime = 1 * time.Millisecond - // Run the vtworker command. if err := runCommand(t, tc.wi, tc.wi.wr, tc.defaultWorkerArgs); err != nil { t.Fatal(err) @@ -982,11 +990,15 @@ func TestSplitCloneV2_NoMasterAvailable(t *testing.T) { tc.setUp(false /* v3 */) defer tc.tearDown() + // Only wait 1 ms between retries, so that the test passes faster. + *executeFetchRetryTime = 1 * time.Millisecond + // leftReplica will take over for the last, 30th, insert and the vreplication checkpoint. tc.leftReplicaFakeDb.AddExpectedQuery("INSERT INTO `vt_ks`.`table1` (`id`, `msg`, `keyspace_id`) VALUES (*", nil) // During the 29th write, let the MASTER disappear. tc.leftMasterFakeDb.GetEntry(28).AfterFunc = func() { + t.Logf("setting MASTER tablet to REPLICA") tc.leftMasterQs.UpdateType(topodatapb.TabletType_REPLICA) tc.leftMasterQs.AddDefaultHealthResponse() } @@ -1013,13 +1025,15 @@ func TestSplitCloneV2_NoMasterAvailable(t *testing.T) { defer cancel() for { - if statsRetryCounters.Counts()[retryCategoryNoMasterAvailable] >= 1 { + retries := statsRetryCounters.Counts()[retryCategoryNoMasterAvailable] + if retries >= 1 { + t.Logf("retried on no MASTER %v times", retries) break } select { case <-ctx.Done(): - t.Fatalf("timed out waiting for vtworker to retry due to NoMasterAvailable: %v", ctx.Err()) + panic(fmt.Errorf("timed out waiting for vtworker to retry due to NoMasterAvailable: %v", ctx.Err())) case <-time.After(10 * time.Millisecond): // Poll constantly. } @@ -1027,13 +1041,11 @@ func TestSplitCloneV2_NoMasterAvailable(t *testing.T) { // Make leftReplica the new MASTER. tc.leftReplica.Agent.TabletExternallyReparented(ctx, "1") + t.Logf("resetting tablet back to MASTER") tc.leftReplicaQs.UpdateType(topodatapb.TabletType_MASTER) tc.leftReplicaQs.AddDefaultHealthResponse() }() - // Only wait 1 ms between retries, so that the test passes faster. - *executeFetchRetryTime = 1 * time.Millisecond - // Run the vtworker command. if err := runCommand(t, tc.wi, tc.wi.wr, tc.defaultWorkerArgs); err != nil { t.Fatal(err) diff --git a/go/vt/worker/split_diff.go b/go/vt/worker/split_diff.go index a4f07cdf60b..e6cc6a7ff7a 100644 --- a/go/vt/worker/split_diff.go +++ b/go/vt/worker/split_diff.go @@ -96,6 +96,8 @@ func (sdw *SplitDiffWorker) StatusAsHTML() template.HTML { switch state { case WorkerStateDiff: result += "Running...
\n" + case WorkerStateDiffWillFail: + result += "Running - have already found differences...
\n" case WorkerStateDone: result += "Success.
\n" } @@ -112,6 +114,8 @@ func (sdw *SplitDiffWorker) StatusAsText() string { switch state { case WorkerStateDiff: result += "Running...\n" + case WorkerStateDiffWillFail: + result += "Running - have already found differences...\n" case WorkerStateDone: result += "Success.\n" } @@ -400,7 +404,9 @@ func (sdw *SplitDiffWorker) diff(ctx context.Context) error { sdw.destinationSchemaDefinition, err = sdw.wr.GetSchema( shortCtx, sdw.destinationAlias, nil /* tables */, sdw.excludeTables, false /* includeViews */) cancel() - rec.RecordError(err) + if err != nil { + sdw.markAsWillFail(rec, err) + } sdw.wr.Logger().Infof("Got schema from destination %v", sdw.destinationAlias) wg.Done() }() @@ -411,7 +417,9 @@ func (sdw *SplitDiffWorker) diff(ctx context.Context) error { sdw.sourceSchemaDefinition, err = sdw.wr.GetSchema( shortCtx, sdw.sourceAlias, nil /* tables */, sdw.excludeTables, false /* includeViews */) cancel() - rec.RecordError(err) + if err != nil { + sdw.markAsWillFail(rec, err) + } sdw.wr.Logger().Infof("Got schema from source %v", sdw.sourceAlias) wg.Done() }() @@ -495,7 +503,7 @@ func (sdw *SplitDiffWorker) diff(ctx context.Context) error { } if err != nil { newErr := vterrors.Wrap(err, "TableScan(ByKeyRange?)(source) failed") - rec.RecordError(newErr) + sdw.markAsWillFail(rec, newErr) sdw.wr.Logger().Errorf("%v", newErr) return } @@ -511,7 +519,7 @@ func (sdw *SplitDiffWorker) diff(ctx context.Context) error { } if err != nil { newErr := vterrors.Wrap(err, "TableScan(ByKeyRange?)(destination) failed") - rec.RecordError(newErr) + sdw.markAsWillFail(rec, newErr) sdw.wr.Logger().Errorf("%v", newErr) return } @@ -521,7 +529,7 @@ func (sdw *SplitDiffWorker) diff(ctx context.Context) error { differ, err := NewRowDiffer(sourceQueryResultReader, destinationQueryResultReader, tableDefinition) if err != nil { newErr := vterrors.Wrap(err, "NewRowDiffer() failed") - rec.RecordError(newErr) + sdw.markAsWillFail(rec, newErr) sdw.wr.Logger().Errorf("%v", newErr) return } @@ -530,12 +538,12 @@ func (sdw *SplitDiffWorker) diff(ctx context.Context) error { report, err := differ.Go(sdw.wr.Logger()) if err != nil { newErr := fmt.Errorf("Differ.Go failed: %v", err.Error()) - rec.RecordError(newErr) + sdw.markAsWillFail(rec, newErr) sdw.wr.Logger().Errorf("%v", newErr) } else { if report.HasDifferences() { err := fmt.Errorf("Table %v has differences: %v", tableDefinition.Name, report.String()) - rec.RecordError(err) + sdw.markAsWillFail(rec, err) sdw.wr.Logger().Warningf(err.Error()) } else { sdw.wr.Logger().Infof("Table %v checks out (%v rows processed, %v qps)", tableDefinition.Name, report.processedRows, report.processingQPS) @@ -549,3 +557,9 @@ func (sdw *SplitDiffWorker) diff(ctx context.Context) error { return rec.Error() } + +// markAsWillFail records the error and changes the state of the worker to reflect this +func (sdw *SplitDiffWorker) markAsWillFail(er concurrency.ErrorRecorder, err error) { + er.RecordError(err) + sdw.SetState(WorkerStateDiffWillFail) +} diff --git a/go/vt/worker/status_worker.go b/go/vt/worker/status_worker.go index 73bb49c1727..81c7e61feec 100644 --- a/go/vt/worker/status_worker.go +++ b/go/vt/worker/status_worker.go @@ -50,6 +50,9 @@ const ( // WorkerStateDiff is set when the worker compares the data. WorkerStateDiff StatusWorkerState = "running the diff" + // WorkerStateDiffWillFail is set when the worker is still comparing the data, but we have already found discrepancies. + WorkerStateDiffWillFail StatusWorkerState = "running the diff, already found differences" + // WorkerStateDebugRunning is set when an internal command (e.g. Block or Ping) is currently running. WorkerStateDebugRunning StatusWorkerState = "running an internal debug command" diff --git a/go/vt/worker/tablet_provider.go b/go/vt/worker/tablet_provider.go index f03602af4c6..2d42bcb7fc6 100644 --- a/go/vt/worker/tablet_provider.go +++ b/go/vt/worker/tablet_provider.go @@ -75,21 +75,22 @@ func (p *singleTabletProvider) description() string { // shardTabletProvider returns a random healthy RDONLY tablet for a given // keyspace and shard. It uses the HealthCheck module to retrieve the tablets. type shardTabletProvider struct { - tsc *discovery.TabletStatsCache - tracker *TabletTracker - keyspace string - shard string + tsc *discovery.TabletStatsCache + tracker *TabletTracker + keyspace string + shard string + tabletType topodatapb.TabletType } -func newShardTabletProvider(tsc *discovery.TabletStatsCache, tracker *TabletTracker, keyspace, shard string) *shardTabletProvider { - return &shardTabletProvider{tsc, tracker, keyspace, shard} +func newShardTabletProvider(tsc *discovery.TabletStatsCache, tracker *TabletTracker, keyspace, shard string, tabletType topodatapb.TabletType) *shardTabletProvider { + return &shardTabletProvider{tsc, tracker, keyspace, shard, tabletType} } func (p *shardTabletProvider) getTablet() (*topodatapb.Tablet, error) { // Pick any healthy serving tablet. - tablets := p.tsc.GetHealthyTabletStats(p.keyspace, p.shard, topodatapb.TabletType_RDONLY) + tablets := p.tsc.GetHealthyTabletStats(p.keyspace, p.shard, p.tabletType) if len(tablets) == 0 { - return nil, fmt.Errorf("%v: no healthy RDONLY tablets available", p.description()) + return nil, fmt.Errorf("%v: no healthy %v tablets available", p.description(), p.tabletType) } return p.tracker.Track(tablets), nil } diff --git a/go/vt/worker/vertical_split_clone_test.go b/go/vt/worker/vertical_split_clone_test.go index 9f31da571da..ad351c3363b 100644 --- a/go/vt/worker/vertical_split_clone_test.go +++ b/go/vt/worker/vertical_split_clone_test.go @@ -132,7 +132,7 @@ func TestVerticalSplitClone(t *testing.T) { }, } sourceRdonly.FakeMysqlDaemon.CurrentMasterPosition = mysql.Position{ - GTIDSet: mysql.MariadbGTID{Domain: 12, Server: 34, Sequence: 5678}, + GTIDSet: mysql.MariadbGTIDSet{mysql.MariadbGTID{Domain: 12, Server: 34, Sequence: 5678}}, } sourceRdonly.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ "STOP SLAVE", @@ -144,24 +144,20 @@ func TestVerticalSplitClone(t *testing.T) { sourceRdonlyQs.addGeneratedRows(verticalSplitCloneTestMin, verticalSplitCloneTestMax) grpcqueryservice.Register(sourceRdonly.RPCServer, sourceRdonlyQs) - // Set up destination rdonly which will be used as input for the diff during the clone. - destRdonlyShqs := fakes.NewStreamHealthQueryService(destRdonly.Target()) - destRdonlyShqs.AddDefaultHealthResponse() - destRdonlyQs := newTestQueryService(t, destRdonly.Target(), destRdonlyShqs, 0, 1, topoproto.TabletAliasString(destRdonly.Tablet.Alias), true /* omitKeyspaceID */) + // Set up destination master which will be used as input for the diff during the clone. + destMasterShqs := fakes.NewStreamHealthQueryService(destMaster.Target()) + destMasterShqs.AddDefaultHealthResponse() + destMasterQs := newTestQueryService(t, destMaster.Target(), destMasterShqs, 0, 1, topoproto.TabletAliasString(destMaster.Tablet.Alias), true /* omitKeyspaceID */) // This tablet is empty and does not return any rows. - grpcqueryservice.Register(destRdonly.RPCServer, destRdonlyQs) + grpcqueryservice.Register(destMaster.RPCServer, destMasterQs) - // Fake stream health reponses because vtworker needs them to find the master. - qs := fakes.NewStreamHealthQueryService(destMaster.Target()) - qs.AddDefaultHealthResponse() - grpcqueryservice.Register(destMaster.RPCServer, qs) // Only wait 1 ms between retries, so that the test passes faster *executeFetchRetryTime = (1 * time.Millisecond) // When the online clone inserted the last rows, modify the destination test // query service such that it will return them as well. destMasterFakeDb.GetEntry(29).AfterFunc = func() { - destRdonlyQs.addGeneratedRows(verticalSplitCloneTestMin, verticalSplitCloneTestMax) + destMasterQs.addGeneratedRows(verticalSplitCloneTestMin, verticalSplitCloneTestMax) } // Start action loop after having registered all RPC services. diff --git a/go/vt/worker/vertical_split_diff.go b/go/vt/worker/vertical_split_diff.go index c79321312ab..2980db2fdbf 100644 --- a/go/vt/worker/vertical_split_diff.go +++ b/go/vt/worker/vertical_split_diff.go @@ -89,6 +89,8 @@ func (vsdw *VerticalSplitDiffWorker) StatusAsHTML() template.HTML { switch state { case WorkerStateDiff: result += "Running:
\n" + case WorkerStateDiffWillFail: + result += "Running - have already found differences...
\n" case WorkerStateDone: result += "Success:
\n" } @@ -105,6 +107,8 @@ func (vsdw *VerticalSplitDiffWorker) StatusAsText() string { switch state { case WorkerStateDiff: result += "Running...\n" + case WorkerStateDiffWillFail: + result += "Running - have already found differences...\n" case WorkerStateDone: result += "Success.\n" } @@ -365,7 +369,9 @@ func (vsdw *VerticalSplitDiffWorker) diff(ctx context.Context) error { vsdw.destinationSchemaDefinition, err = vsdw.wr.GetSchema( shortCtx, vsdw.destinationAlias, vsdw.shardInfo.SourceShards[0].Tables, nil /* excludeTables */, false /* includeViews */) cancel() - rec.RecordError(err) + if err != nil { + vsdw.markAsWillFail(rec, err) + } vsdw.wr.Logger().Infof("Got schema from destination %v", topoproto.TabletAliasString(vsdw.destinationAlias)) wg.Done() }() @@ -376,7 +382,9 @@ func (vsdw *VerticalSplitDiffWorker) diff(ctx context.Context) error { vsdw.sourceSchemaDefinition, err = vsdw.wr.GetSchema( shortCtx, vsdw.sourceAlias, vsdw.shardInfo.SourceShards[0].Tables, nil /* excludeTables */, false /* includeViews */) cancel() - rec.RecordError(err) + if err != nil { + vsdw.markAsWillFail(rec, err) + } vsdw.wr.Logger().Infof("Got schema from source %v", topoproto.TabletAliasString(vsdw.sourceAlias)) wg.Done() }() @@ -409,7 +417,7 @@ func (vsdw *VerticalSplitDiffWorker) diff(ctx context.Context) error { sourceQueryResultReader, err := TableScan(ctx, vsdw.wr.Logger(), vsdw.wr.TopoServer(), vsdw.sourceAlias, tableDefinition) if err != nil { newErr := vterrors.Wrap(err, "TableScan(source) failed") - rec.RecordError(newErr) + vsdw.markAsWillFail(rec, newErr) vsdw.wr.Logger().Errorf("%v", newErr) return } @@ -418,7 +426,7 @@ func (vsdw *VerticalSplitDiffWorker) diff(ctx context.Context) error { destinationQueryResultReader, err := TableScan(ctx, vsdw.wr.Logger(), vsdw.wr.TopoServer(), vsdw.destinationAlias, tableDefinition) if err != nil { newErr := vterrors.Wrap(err, "TableScan(destination) failed") - rec.RecordError(newErr) + vsdw.markAsWillFail(rec, newErr) vsdw.wr.Logger().Errorf("%v", newErr) return } @@ -427,7 +435,7 @@ func (vsdw *VerticalSplitDiffWorker) diff(ctx context.Context) error { differ, err := NewRowDiffer(sourceQueryResultReader, destinationQueryResultReader, tableDefinition) if err != nil { newErr := vterrors.Wrap(err, "NewRowDiffer() failed") - rec.RecordError(newErr) + vsdw.markAsWillFail(rec, newErr) vsdw.wr.Logger().Errorf("%v", newErr) return } @@ -438,7 +446,7 @@ func (vsdw *VerticalSplitDiffWorker) diff(ctx context.Context) error { } else { if report.HasDifferences() { err := fmt.Errorf("Table %v has differences: %v", tableDefinition.Name, report.String()) - rec.RecordError(err) + vsdw.markAsWillFail(rec, err) vsdw.wr.Logger().Errorf("%v", err) } else { vsdw.wr.Logger().Infof("Table %v checks out (%v rows processed, %v qps)", tableDefinition.Name, report.processedRows, report.processingQPS) @@ -450,3 +458,9 @@ func (vsdw *VerticalSplitDiffWorker) diff(ctx context.Context) error { return rec.Error() } + +// markAsWillFail records the error and changes the state of the worker to reflect this +func (vsdw *VerticalSplitDiffWorker) markAsWillFail(er concurrency.ErrorRecorder, err error) { + er.RecordError(err) + vsdw.SetState(WorkerStateDiffWillFail) +} diff --git a/go/vt/wrangler/keyspace.go b/go/vt/wrangler/keyspace.go index 5b49bd07fd9..d65e721ba3f 100644 --- a/go/vt/wrangler/keyspace.go +++ b/go/vt/wrangler/keyspace.go @@ -874,6 +874,7 @@ func (wr *Wrangler) showVerticalResharding(ctx context.Context, keyspace, shard } func (wr *Wrangler) cancelVerticalResharding(ctx context.Context, keyspace, shard string) error { + wr.Logger().Infof("Cancel vertical resharding in keyspace %v", keyspace) destinationShard, err := wr.ts.GetShard(ctx, keyspace, shard) if err != nil { return err @@ -895,11 +896,14 @@ func (wr *Wrangler) cancelVerticalResharding(ctx context.Context, keyspace, shar if _, err := wr.tmc.VReplicationExec(ctx, destinationMasterTabletInfo.Tablet, binlogplayer.DeleteVReplication(destinationShard.SourceShards[0].Uid)); err != nil { return err } - _, err = wr.ts.UpdateShardFields(ctx, destinationShard.Keyspace(), destinationShard.ShardName(), func(si *topo.ShardInfo) error { + if _, err = wr.ts.UpdateShardFields(ctx, destinationShard.Keyspace(), destinationShard.ShardName(), func(si *topo.ShardInfo) error { si.SourceShards = nil return nil - }) - return err + }); err != nil { + return err + } + // set destination master back to serving + return wr.refreshMasters(ctx, []*topo.ShardInfo{destinationShard}) } // MigrateServedFrom is used during vertical splits to migrate a diff --git a/go/vt/wrangler/reparent.go b/go/vt/wrangler/reparent.go index ccb56dd388d..b19e9e267fe 100644 --- a/go/vt/wrangler/reparent.go +++ b/go/vt/wrangler/reparent.go @@ -203,6 +203,11 @@ func (wr *Wrangler) initShardMasterLocked(ctx context.Context, ev *events.Repare // we stop. It is probably because it is unreachable, and may leave // an unstable database process in the mix, with a database daemon // at a wrong replication spot. + + // Create a context for the following RPCs that respects waitSlaveTimeout + resetCtx, resetCancel := context.WithTimeout(ctx, waitSlaveTimeout) + defer resetCancel() + event.DispatchUpdate(ev, "resetting replication on all tablets") wg := sync.WaitGroup{} rec := concurrency.AllErrorRecorder{} @@ -211,13 +216,14 @@ func (wr *Wrangler) initShardMasterLocked(ctx context.Context, ev *events.Repare go func(alias string, tabletInfo *topo.TabletInfo) { defer wg.Done() wr.logger.Infof("resetting replication on tablet %v", alias) - if err := wr.tmc.ResetReplication(ctx, tabletInfo.Tablet); err != nil { + if err := wr.tmc.ResetReplication(resetCtx, tabletInfo.Tablet); err != nil { rec.RecordError(fmt.Errorf("Tablet %v ResetReplication failed (either fix it, or Scrap it): %v", alias, err)) } }(alias, tabletInfo) } wg.Wait() if err := rec.Error(); err != nil { + // if any of the slaves failed return err } @@ -242,7 +248,7 @@ func (wr *Wrangler) initShardMasterLocked(ctx context.Context, ev *events.Repare // Create a cancelable context for the following RPCs. // If error conditions happen, we can cancel all outgoing RPCs. - replCtx, replCancel := context.WithCancel(ctx) + replCtx, replCancel := context.WithTimeout(ctx, waitSlaveTimeout) defer replCancel() // Now tell the new master to insert the reparent_journal row, @@ -430,7 +436,7 @@ func (wr *Wrangler) plannedReparentShardLocked(ctx context.Context, ev *events.R // Create a cancelable context for the following RPCs. // If error conditions happen, we can cancel all outgoing RPCs. - replCtx, replCancel := context.WithCancel(ctx) + replCtx, replCancel := context.WithTimeout(ctx, waitSlaveTimeout) defer replCancel() // Go through all the tablets: diff --git a/go/vt/wrangler/tablet.go b/go/vt/wrangler/tablet.go index 956403fea8c..36a57f48ee1 100644 --- a/go/vt/wrangler/tablet.go +++ b/go/vt/wrangler/tablet.go @@ -173,6 +173,15 @@ func (wr *Wrangler) RefreshTabletState(ctx context.Context, tabletAlias *topodat return wr.tmc.RefreshState(ctx, ti.Tablet) } +// ExecuteFetchAsApp executes a query remotely using the App pool +func (wr *Wrangler) ExecuteFetchAsApp(ctx context.Context, tabletAlias *topodatapb.TabletAlias, usePool bool, query string, maxRows int) (*querypb.QueryResult, error) { + ti, err := wr.ts.GetTablet(ctx, tabletAlias) + if err != nil { + return nil, err + } + return wr.tmc.ExecuteFetchAsApp(ctx, ti.Tablet, usePool, []byte(query), maxRows) +} + // ExecuteFetchAsDba executes a query remotely using the DBA pool func (wr *Wrangler) ExecuteFetchAsDba(ctx context.Context, tabletAlias *topodatapb.TabletAlias, query string, maxRows int, disableBinlogs bool, reloadSchema bool) (*querypb.QueryResult, error) { ti, err := wr.ts.GetTablet(ctx, tabletAlias) diff --git a/go/vt/wrangler/testlib/backup_test.go b/go/vt/wrangler/testlib/backup_test.go index e359fba6916..a9abb4497d8 100644 --- a/go/vt/wrangler/testlib/backup_test.go +++ b/go/vt/wrangler/testlib/backup_test.go @@ -98,10 +98,12 @@ func TestBackupRestore(t *testing.T) { sourceTablet.FakeMysqlDaemon.ReadOnly = true sourceTablet.FakeMysqlDaemon.Replicating = true sourceTablet.FakeMysqlDaemon.CurrentMasterPosition = mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 2, - Server: 123, - Sequence: 457, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 2, + Server: 123, + Sequence: 457, + }, }, } sourceTablet.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ @@ -138,10 +140,12 @@ func TestBackupRestore(t *testing.T) { destTablet.FakeMysqlDaemon.ReadOnly = true destTablet.FakeMysqlDaemon.Replicating = true destTablet.FakeMysqlDaemon.CurrentMasterPosition = mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 2, - Server: 123, - Sequence: 457, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 2, + Server: 123, + Sequence: 457, + }, }, } destTablet.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ diff --git a/go/vt/wrangler/testlib/emergency_reparent_shard_test.go b/go/vt/wrangler/testlib/emergency_reparent_shard_test.go index acb1f1599e6..0df00ad3d58 100644 --- a/go/vt/wrangler/testlib/emergency_reparent_shard_test.go +++ b/go/vt/wrangler/testlib/emergency_reparent_shard_test.go @@ -49,10 +49,12 @@ func TestEmergencyReparentShard(t *testing.T) { newMaster.FakeMysqlDaemon.ReadOnly = true newMaster.FakeMysqlDaemon.Replicating = true newMaster.FakeMysqlDaemon.CurrentMasterPosition = mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 2, - Server: 123, - Sequence: 456, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 2, + Server: 123, + Sequence: 456, + }, }, } newMaster.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ @@ -62,10 +64,12 @@ func TestEmergencyReparentShard(t *testing.T) { "SUBINSERT INTO _vt.reparent_journal (time_created_ns, action_name, master_alias, replication_position) VALUES", } newMaster.FakeMysqlDaemon.PromoteSlaveResult = mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 2, - Server: 123, - Sequence: 456, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 2, + Server: 123, + Sequence: 456, + }, }, } newMaster.StartActionLoop(t, wr) @@ -80,10 +84,12 @@ func TestEmergencyReparentShard(t *testing.T) { goodSlave1.FakeMysqlDaemon.ReadOnly = true goodSlave1.FakeMysqlDaemon.Replicating = true goodSlave1.FakeMysqlDaemon.CurrentMasterPosition = mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 2, - Server: 123, - Sequence: 455, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 2, + Server: 123, + Sequence: 455, + }, }, } goodSlave1.FakeMysqlDaemon.SetMasterInput = topoproto.MysqlAddr(newMaster.Tablet) @@ -99,10 +105,12 @@ func TestEmergencyReparentShard(t *testing.T) { goodSlave2.FakeMysqlDaemon.ReadOnly = true goodSlave2.FakeMysqlDaemon.Replicating = false goodSlave2.FakeMysqlDaemon.CurrentMasterPosition = mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 2, - Server: 123, - Sequence: 454, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 2, + Server: 123, + Sequence: 454, + }, }, } goodSlave2.FakeMysqlDaemon.SetMasterInput = topoproto.MysqlAddr(newMaster.Tablet) @@ -165,10 +173,12 @@ func TestEmergencyReparentShardMasterElectNotBest(t *testing.T) { // new master newMaster.FakeMysqlDaemon.Replicating = true newMaster.FakeMysqlDaemon.CurrentMasterPosition = mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 2, - Server: 123, - Sequence: 456, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 2, + Server: 123, + Sequence: 456, + }, }, } newMaster.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ @@ -184,10 +194,12 @@ func TestEmergencyReparentShardMasterElectNotBest(t *testing.T) { // more advanced slave moreAdvancedSlave.FakeMysqlDaemon.Replicating = true moreAdvancedSlave.FakeMysqlDaemon.CurrentMasterPosition = mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 2, - Server: 123, - Sequence: 457, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 2, + Server: 123, + Sequence: 457, + }, }, } moreAdvancedSlave.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ diff --git a/go/vt/wrangler/testlib/init_shard_master_test.go b/go/vt/wrangler/testlib/init_shard_master_test.go index b1ca1cfe488..faf6d9069c0 100644 --- a/go/vt/wrangler/testlib/init_shard_master_test.go +++ b/go/vt/wrangler/testlib/init_shard_master_test.go @@ -58,10 +58,12 @@ func TestInitMasterShard(t *testing.T) { // Master: set a plausible ReplicationPosition to return, // and expect to add entry in _vt.reparent_journal master.FakeMysqlDaemon.CurrentMasterPosition = mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 5, - Server: 456, - Sequence: 890, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 5, + Server: 456, + Sequence: 890, + }, }, } master.FakeMysqlDaemon.ReadOnly = true @@ -186,10 +188,12 @@ func TestInitMasterShardOneSlaveFails(t *testing.T) { // Master: set a plausible ReplicationPosition to return, // and expect to add entry in _vt.reparent_journal master.FakeMysqlDaemon.CurrentMasterPosition = mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 5, - Server: 456, - Sequence: 890, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 5, + Server: 456, + Sequence: 890, + }, }, } master.FakeMysqlDaemon.ReadOnly = true diff --git a/go/vt/wrangler/testlib/migrate_served_from_test.go b/go/vt/wrangler/testlib/migrate_served_from_test.go index bd51f1fdf0c..6194e2150ed 100644 --- a/go/vt/wrangler/testlib/migrate_served_from_test.go +++ b/go/vt/wrangler/testlib/migrate_served_from_test.go @@ -81,10 +81,12 @@ func TestMigrateServedFrom(t *testing.T) { // sourceMaster will see the refresh, and has to respond to it // also will be asked about its replication position. sourceMaster.FakeMysqlDaemon.CurrentMasterPosition = mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 5, - Server: 456, - Sequence: 892, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 5, + Server: 456, + Sequence: 892, + }, }, } sourceMaster.StartActionLoop(t, wr) diff --git a/go/vt/wrangler/testlib/migrate_served_types_test.go b/go/vt/wrangler/testlib/migrate_served_types_test.go index eb8a872eb68..28ae7789ffe 100644 --- a/go/vt/wrangler/testlib/migrate_served_types_test.go +++ b/go/vt/wrangler/testlib/migrate_served_types_test.go @@ -116,10 +116,12 @@ func TestMigrateServedTypes(t *testing.T) { // sourceMaster will see the refresh, and has to respond to it // also will be asked about its replication position. sourceMaster.FakeMysqlDaemon.CurrentMasterPosition = mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 5, - Server: 456, - Sequence: 892, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 5, + Server: 456, + Sequence: 892, + }, }, } sourceMaster.StartActionLoop(t, wr) diff --git a/go/vt/wrangler/testlib/planned_reparent_shard_test.go b/go/vt/wrangler/testlib/planned_reparent_shard_test.go index 3bf5a31cc03..24edf62800d 100644 --- a/go/vt/wrangler/testlib/planned_reparent_shard_test.go +++ b/go/vt/wrangler/testlib/planned_reparent_shard_test.go @@ -46,17 +46,21 @@ func TestPlannedReparentShardNoMasterProvided(t *testing.T) { newMaster.FakeMysqlDaemon.ReadOnly = true newMaster.FakeMysqlDaemon.Replicating = true newMaster.FakeMysqlDaemon.WaitMasterPosition = mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 7, - Server: 123, - Sequence: 990, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 7, + Server: 123, + Sequence: 990, + }, }, } newMaster.FakeMysqlDaemon.PromoteSlaveResult = mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 7, - Server: 456, - Sequence: 991, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 7, + Server: 456, + Sequence: 991, + }, }, } newMaster.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ @@ -148,17 +152,21 @@ func TestPlannedReparentShard(t *testing.T) { newMaster.FakeMysqlDaemon.ReadOnly = true newMaster.FakeMysqlDaemon.Replicating = true newMaster.FakeMysqlDaemon.WaitMasterPosition = mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 7, - Server: 123, - Sequence: 990, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 7, + Server: 123, + Sequence: 990, + }, }, } newMaster.FakeMysqlDaemon.PromoteSlaveResult = mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 7, - Server: 456, - Sequence: 991, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 7, + Server: 456, + Sequence: 991, + }, }, } newMaster.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ diff --git a/go/vt/wrangler/testlib/reparent_utils_test.go b/go/vt/wrangler/testlib/reparent_utils_test.go index 18e97a95b71..f78b8b3734f 100644 --- a/go/vt/wrangler/testlib/reparent_utils_test.go +++ b/go/vt/wrangler/testlib/reparent_utils_test.go @@ -54,10 +54,12 @@ func TestShardReplicationStatuses(t *testing.T) { // master action loop (to initialize host and port) master.FakeMysqlDaemon.CurrentMasterPosition = mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 5, - Server: 456, - Sequence: 892, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 5, + Server: 456, + Sequence: 892, + }, }, } master.StartActionLoop(t, wr) @@ -65,10 +67,12 @@ func TestShardReplicationStatuses(t *testing.T) { // slave loop slave.FakeMysqlDaemon.CurrentMasterPosition = mysql.Position{ - GTIDSet: mysql.MariadbGTID{ - Domain: 5, - Server: 456, - Sequence: 890, + GTIDSet: mysql.MariadbGTIDSet{ + mysql.MariadbGTID{ + Domain: 5, + Server: 456, + Sequence: 890, + }, }, } slave.FakeMysqlDaemon.CurrentMasterHost = topoproto.MysqlHostname(master.Tablet) diff --git a/helm/vitess/README.md b/helm/vitess/README.md index 52d5a6bab4b..9e58d6b7fed 100644 --- a/helm/vitess/README.md +++ b/helm/vitess/README.md @@ -174,14 +174,58 @@ topology: ### Enable backup/restore using Google Cloud Storage +Enabling backups creates a cron job per shard that defaults to executing once per day at midnight. +This can be overridden on a per shard level so you can stagger when backups occur. + ``` topology: cells: - ... + - name: "zone1" + ... + keyspaces: + - name: "unsharded_dbname" + shards: + - name: "0" + backup: + cron: + schedule: "0 1 * * *" + suspend: false + tablets: + - type: "replica" + vttablet: + replicas: 2 + - name: "sharded_db" + shards: + - name: "-80" + backup: + cron: + schedule: "0 2 * * *" + suspend: false + tablets: + - type: "replica" + vttablet: + replicas: 2 + - name: "80-" + backup: + cron: + schedule: "0 3 * * *" + suspend: false + tablets: + - type: "replica" + vttablet: + replicas: 2 config: backup: enabled: true + + cron: + # the default schedule runs daily at midnight unless overridden by the individual shard + schedule: "0 0 * * *" + + # if this is set to true, the cron jobs are created, but never execute + suspend: false + backup_storage_implementation: gcs # Google Cloud Storage bucket to use for backups @@ -283,3 +327,97 @@ topology: orchestrator: enabled: true ``` + +### Enable TLS encryption for vitess grpc communication + +Each component of vitess requires a certificate and private key to secure incoming requests and further configuration for every outgoing connection. In this example TLS certificates were generated and stored in several kubernetes secrets: +```yaml +vttablet: + extraFlags: + # configure which certificates to use for serving grpc requests + grpc_cert: /vt/usersecrets/vttablet-tls/vttablet.pem + grpc_key: /vt/usersecrets/vttablet-tls/vttablet-key.pem + tablet_grpc_ca: /vt/usersecrets/vttablet-tls/vitess-ca.pem + tablet_grpc_server_name: vttablet + secrets: + - vttablet-tls + +vtctld: + extraFlags: + grpc_cert: /vt/usersecrets/vtctld-tls/vtctld.pem + grpc_key: /vt/usersecrets/vtctld-tls/vtctld-key.pem + tablet_grpc_ca: /vt/usersecrets/vtctld-tls/vitess-ca.pem + tablet_grpc_server_name: vttablet + tablet_manager_grpc_ca: /vt/usersecrets/vtctld-tls/vitess-ca.pem + tablet_manager_grpc_server_name: vttablet + secrets: + - vtctld-tls + +vtctlclient: # configuration used by both InitShardMaster-jobs and orchestrator to be able to communicate with vtctld + extraFlags: + vtctld_grpc_ca: /vt/usersecrets/vitess-ca/vitess-ca.pem + vtctld_grpc_server_name: vtctld + secrets: + - vitess-ca + +vtgate: + extraFlags: + grpc_cert: /vt/usersecrets/vtgate-tls/vtgate.pem + grpc_key: /vt/usersecrets/vtgate-tls/vtgate-key.pem + tablet_grpc_ca: /vt/usersecrets/vtgate-tls/vitess-ca.pem + tablet_grpc_server_name: vttablet + secrets: + - vtgate-tls +``` + +### Slave replication traffic encryption + +To encrypt traffic between slaves and master additional flags can be provided. By default MySQL generates self-signed certificates on startup (otherwise specify `ssl_*` settings within you `extraMyCnf`), that can be used to encrypt the traffic: +``` +vttablet: + extraFlags: + db_flags: 2048 + db_repl_use_ssl: true + db-config-repl-flags: 2048 + +``` + +### Percona at rest encryption using the vault plugin + +To use the [percona at rest encryption](https://www.percona.com/doc/percona-server/LATEST/management/data_at_rest_encryption.html) several additional settings have to be provided via an `extraMyCnf`-file. This makes only sense if the traffic is encrypted as well (see above sections), since binlog replication is unencrypted by default. +``` +apiVersion: v1 +kind: ConfigMap +metadata: + name: vttablet-extra-config + namespace: vitess +data: + extra.cnf: |- + early-plugin-load=keyring_vault=keyring_vault.so + # this includes default rpl plugins, see https://github.com/vitessio/vitess/blob/master/config/mycnf/master_mysql56.cnf for details + plugin-load=rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisync_slave.so;keyring_udf=keyring_udf.so + keyring_vault_config=/vt/usersecrets/vttablet-vault/vault.conf # load keyring configuration from secret + innodb_encrypt_tables=ON # encrypt all tables by default + encrypt_binlog=ON # binlog encryption + master_verify_checksum=ON # necessary for binlog encryption + binlog_checksum=CRC32 # necessary for binlog encryption + encrypt-tmp-files=ON # use temporary AES keys to encrypt temporary files +``` + +An example vault configuration, which is provided by the `vttablet-vault`-Secret in the above example: +``` +vault_url = https://10.0.0.1:8200 +secret_mount_point = vitess +token = 11111111-1111-1111-1111111111 +vault_ca = /vt/usersecrets/vttablet-vault/vault-ca-bundle.pem +``` + +At last add the secret containing the vault configuration and the additional MySQL-configuration to your helm values: +``` +vttablet: + flavor: "percona" # only works with percona + mysqlImage: "percona:5.7.23" + extraMyCnf: vttablet-extra-config + secrets: + - vttablet-vault +``` diff --git a/helm/vitess/examples/minikube.yaml b/helm/vitess/examples/minikube.yaml new file mode 100644 index 00000000000..d37f7c2216e --- /dev/null +++ b/helm/vitess/examples/minikube.yaml @@ -0,0 +1,71 @@ +topology: + cells: + - name: "zone1" + etcd: + replicas: 1 + vtctld: + replicas: 1 + vtgate: + replicas: 1 + mysqlProtocol: + enabled: true + authType: "none" + keyspaces: + - name: "commerce" + shards: + - name: "0" + tablets: + - type: "replica" + vttablet: + replicas: 2 + schema: + phase1: |- + create table product( + sku varbinary(128), + description varbinary(128), + price bigint, + primary key(sku) + ); + create table customer( + user_id bigint not null auto_increment, + email varbinary(128), + primary key(user_id) + ); + create table corder( + order_id bigint not null auto_increment, + user_id bigint, + product_id bigint, + msrp bigint, + primary key(order_id) + ); + vschema: + phase1: |- + { + "tables": { + "product": {}, + "customer": {}, + "corder": {} + } + } + +etcd: + replicas: 1 + resources: + +vtctld: + serviceType: "NodePort" + resources: + +vtgate: + serviceType: "NodePort" + resources: + +vttablet: + resources: + mysqlResources: + +pmm: + enabled: false + +orchestrator: + enabled: false diff --git a/helm/vitess/templates/NOTES.txt b/helm/vitess/templates/NOTES.txt index 5d884bb6373..c2b52cf7538 100644 --- a/helm/vitess/templates/NOTES.txt +++ b/helm/vitess/templates/NOTES.txt @@ -1,5 +1,5 @@ {{- $cell := (index .Values.topology.cells 0).name -}} -{{- $proxyURL := printf "http://localhost:8001/api/v1/proxy/namespaces/%s" .Release.Namespace -}} +{{- $proxyURL := printf "http://localhost:8001/api/v1/namespaces/%s" .Release.Namespace -}} Release name: {{.Release.Name}} @@ -8,7 +8,7 @@ To access administrative web pages, start a proxy with: Then use the following URLs: - vtctld: {{$proxyURL}}/services/vtctld:web/app/ - vtgate: {{$proxyURL}}/services/vtgate-{{$cell}}:web/ + vtctld: {{$proxyURL}}/services/vtctld:web/proxy/app/ + vtgate: {{$proxyURL}}/services/vtgate-{{$cell}}:web/proxy/ {{ if $.Values.orchestrator.enabled }}orchestrator: {{$proxyURL}}/services/orchestrator:web/{{ end }} {{ if $.Values.pmm.enabled }} pmm: {{$proxyURL}}/services/pmm:web/{{ end }} diff --git a/helm/vitess/templates/_cron-jobs.tpl b/helm/vitess/templates/_cron-jobs.tpl new file mode 100644 index 00000000000..ebbe576c612 --- /dev/null +++ b/helm/vitess/templates/_cron-jobs.tpl @@ -0,0 +1,77 @@ +################################### +# backup cron +################################### +{{ define "vttablet-backup-cron" -}} +# set tuple values to more recognizable variables +{{- $cellClean := index . 0 -}} +{{- $keyspaceClean := index . 1 -}} +{{- $shardClean := index . 2 -}} +{{- $shardName := index . 3 -}} +{{- $keyspace := index . 4 -}} +{{- $shard := index . 5 -}} +{{- $vitessTag := index . 6 -}} +{{- $backup := index . 7 -}} +{{- $namespace := index . 8 -}} +{{- $defaultVtctlclient := index . 9 }} + +{{ if $backup.enabled }} +# create cron job for current shard +--- +apiVersion: batch/v1beta1 +kind: CronJob +metadata: + name: {{ $shardName }}-backup + labels: + app: vitess + component: vttablet + cell: {{ $cellClean | quote }} + keyspace: {{ $keyspaceClean | quote }} + shard: {{ $shardClean | quote }} + +spec: + schedule: {{ $shard.backup.cron.schedule | default $backup.cron.schedule | quote }} + concurrencyPolicy: Forbid + suspend: {{ $shard.backup.cron.suspend | default $backup.cron.suspend }} + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 20 + + jobTemplate: + spec: + template: + metadata: + labels: + app: vitess + component: vttablet + cell: {{ $cellClean | quote }} + keyspace: {{ $keyspaceClean | quote }} + shard: {{ $shardClean | quote }} + # pod spec + spec: + restartPolicy: Never +{{ include "pod-security" . | indent 10 }} + + containers: + - name: backup + image: "vitess/vtctlclient:{{$vitessTag}}" + volumeMounts: +{{ include "user-secret-volumeMounts" $defaultVtctlclient.secrets | indent 14 }} + + command: ["bash"] + args: + - "-c" + - | + set -ex + + VTCTLD_SVC=vtctld.{{ $namespace }}:15999 + VTCTL_EXTRA_FLAGS=({{ include "format-flags-inline" $defaultVtctlclient.extraFlags }}) + + vtctlclient ${VTCTL_EXTRA_FLAGS[@]} -server $VTCTLD_SVC BackupShard {{ $keyspace.name }}/{{ $shard.name }} + + resources: + requests: + cpu: 10m + memory: 20Mi + +{{ end }} + +{{- end -}} diff --git a/helm/vitess/templates/_etcd.tpl b/helm/vitess/templates/_etcd.tpl index 2145e9c41b9..b30ce7c09c9 100644 --- a/helm/vitess/templates/_etcd.tpl +++ b/helm/vitess/templates/_etcd.tpl @@ -1,12 +1,12 @@ ################################### # etcd cluster managed by pre-installed etcd operator ################################### -{{- define "etcd" -}} +{{ define "etcd" -}} # set tuple values to more recognizable variables {{- $name := index . 0 -}} {{- $replicas := index . 1 -}} {{- $version := index . 2 -}} -{{- $resources := index . 3 -}} +{{- $resources := index . 3 }} ################################### # EtcdCluster diff --git a/helm/vitess/templates/_helpers.tpl b/helm/vitess/templates/_helpers.tpl index e5aec6cfa32..3463c7916d7 100644 --- a/helm/vitess/templates/_helpers.tpl +++ b/helm/vitess/templates/_helpers.tpl @@ -11,6 +11,17 @@ {{end -}} {{- end -}} +############################ +# Format a flag map into a command line (inline), +# as expected by the golang 'flag' package. +# Boolean flags must be given a value, such as "true" or "false". +############################# +{{- define "format-flags-inline" -}} +{{- range $key, $value := . -}} +-{{$key}}={{$value | quote}}{{" "}} +{{- end -}} +{{- end -}} + ############################# # Repeat a string N times, where N is the total number # of replicas. Len must be used on the calling end to @@ -114,7 +125,7 @@ fi export EXTRA_MY_CNF="$FLAVOR_MYCNF:/vtdataroot/tabletdata/report-host.cnf:/vt/config/mycnf/rbr.cnf" {{ if . }} -for filename in /vt/userconfig/*; do +for filename in /vt/userconfig/*.cnf; do export EXTRA_MY_CNF="$EXTRA_MY_CNF:$filename" done {{ end }} @@ -152,6 +163,9 @@ done -s3_backup_storage_bucket=$VT_S3_BACKUP_STORAGE_BUCKET -s3_backup_storage_root=$VT_S3_BACKUP_STORAGE_ROOT -s3_backup_server_side_encryption=$VT_S3_BACKUP_SERVER_SIDE_ENCRYPTION + + {{ else if eq .backup_storage_implementation "ceph" }} +-ceph_backup_storage_config=$CEPH_CREDENTIALS_FILE {{ end }} {{ end }} @@ -238,6 +252,12 @@ done secretName: {{ .s3Secret }} {{ end }} + {{ else if eq .backup_storage_implementation "ceph" }} + +- name: backup-creds + secret: + secretName: {{required ".cephSecret necessary to use backup_storage_implementation: ceph!" .cephSecret }} + {{ end }} {{ end }} @@ -265,6 +285,11 @@ done mountPath: /etc/secrets/creds {{ end }} + {{ else if eq .backup_storage_implementation "ceph" }} + +- name: backup-creds + mountPath: /etc/secrets/creds + {{ end }} {{ end }} @@ -296,6 +321,12 @@ export AWS_SHARED_CREDENTIALS_FILE=$credsPath cat $AWS_SHARED_CREDENTIALS_FILE {{ end }} + {{ else if eq .backup_storage_implementation "ceph" }} + +credsPath=/etc/secrets/creds/$(ls /etc/secrets/creds/ | head -1) +export CEPH_CREDENTIALS_FILE=$credsPath +cat $CEPH_CREDENTIALS_FILE + {{ end }} {{ end }} @@ -329,4 +360,33 @@ cat $AWS_SHARED_CREDENTIALS_FILE {{ end }} -{{- end -}} \ No newline at end of file +{{- end -}} + +############################# +# user secret volumes - expects list of secret names +############################# +{{- define "user-secret-volumes" -}} + +{{ if . }} +{{- range . }} +- name: user-secret-{{ . }} + secret: + secretName: {{ . }} +{{- end }} +{{ end }} + +{{- end -}} + +############################# +# user secret volumeMounts - expects list of secret names +############################# +{{- define "user-secret-volumeMounts" -}} + +{{ if . }} +{{- range . }} +- name: user-secret-{{ . }} + mountPath: /vt/usersecrets/{{ . }} +{{- end }} +{{ end }} + +{{- end -}} diff --git a/helm/vitess/templates/_jobs.tpl b/helm/vitess/templates/_jobs.tpl new file mode 100644 index 00000000000..6567e1cf91b --- /dev/null +++ b/helm/vitess/templates/_jobs.tpl @@ -0,0 +1,94 @@ +################################### +# keyspace initializations +################################### + +{{- define "vtctlclient-job" -}} +{{- $job := index . 0 -}} +{{- $defaultVtctlclient := index . 1 -}} +{{- $namespace := index . 2 -}} + +{{- $vitessTag := $job.vitessTag | default $defaultVtctlclient.vitessTag -}} +{{- $secrets := $job.secrets | default $defaultVtctlclient.secrets }} +--- +################################### +# Vitess vtctlclient Job +################################### +apiVersion: batch/v1 +kind: Job +metadata: + name: vtctlclient-{{ $job.name }} +spec: + backoffLimit: 1 + template: + spec: + restartPolicy: OnFailure + containers: + - name: vtjob + image: "vitess/vtctlclient:{{$vitessTag}}" + volumeMounts: +{{ include "user-secret-volumeMounts" $defaultVtctlclient.secrets | indent 10 }} + resources: +{{ toYaml ($job.resources | default $defaultVtctlclient.resources) | indent 10 }} + + command: ["bash"] + args: + - "-c" + - | + set -ex + + VTCTLD_SVC=vtctld.{{ $namespace }}:15999 + VTCTL_EXTRA_FLAGS=({{ include "format-flags-inline" $defaultVtctlclient.extraFlags }}) + vtctlclient ${VTCTL_EXTRA_FLAGS[@]} -server $VTCTLD_SVC {{ $job.command }} + volumes: +{{ include "user-secret-volumes" $secrets | indent 8 }} + +{{- end -}} + +{{- define "vtworker-job" -}} +{{- $job := index . 0 -}} +{{- $defaultVtworker := index . 1 -}} +{{- $namespace := index . 2 -}} + +{{- $vitessTag := $job.vitessTag | default $defaultVtworker.vitessTag -}} +{{- $secrets := $job.secrets | default $defaultVtworker.secrets }} +--- +################################### +# Vitess vtworker Job +################################### +apiVersion: batch/v1 +kind: Job +metadata: + name: vtworker-{{ $job.name }} +spec: + backoffLimit: 1 + template: + spec: +{{ include "pod-security" . | indent 6 }} + restartPolicy: OnFailure + containers: + - name: vtjob + image: "vitess/vtworker:{{$vitessTag}}" + volumeMounts: +{{ include "user-secret-volumeMounts" $defaultVtworker.secrets | indent 10 }} + resources: +{{ toYaml ($job.resources | default $defaultVtworker.resources) | indent 10 }} + + command: ["bash"] + args: + - "-c" + - | + set -ex + + eval exec /vt/bin/vtworker $(cat < /dev/null 2>&1; do + if (( $SECONDS > $TIMEOUT_SECONDS )); then + echo "timed out waiting for vtctlclient to be ready" + exit 1 + fi + sleep 5 + done + + while true; do + if (( $SECONDS > $TIMEOUT_SECONDS )); then + echo "timed out waiting for master" + exit 1 + fi + + # wait for all shards to have a master + {{- range $shard := $keyspace.shards }} + master_alias=$(vtctlclient ${VTLCTL_EXTRA_FLAGS[@]} -server $VTCTLD_SVC GetShard {{ $keyspace.name }}/{{ $shard.name }} | jq '.master_alias.uid') + if [ "$master_alias" == "null" -o "$master_alias" == "" ]; then + echo "no master for '{{ $keyspace.name }}/{{ $shard.name }}' yet, continuing to wait" + sleep 5 + continue + fi + {{- end }} + + break + done + + vtctlclient ${VTCTL_EXTRA_FLAGS[@]} -server $VTCTLD_SVC ApplySchema -sql "$(cat < /dev/null 2>&1; do + if (( $SECONDS > $TIMEOUT_SECONDS )); then + echo "timed out waiting for keyspace {{ $keyspace.name }} to be ready" + exit 1 + fi + sleep 5 + done + + vtctlclient ${VTCTL_EXTRA_FLAGS[@]} -server $VTCTLD_SVC ApplyVSchema -vschema "$(cat <> /tmp/recovery.log", - "vtctlclient -server vtctld.{{ $namespace }}:15999 TabletExternallyReparented {successorAlias}" + "vtctlclient {{ include "format-flags-inline" $defaultVtctlclient.extraFlags | toJson | trimAll "\"" }} -server vtctld.{{ $namespace }}:15999 TabletExternallyReparented {successorAlias}" ], "PostponeSlaveRecoveryOnLagMinutes": 0, "PostUnsuccessfulFailoverProcesses": [ @@ -123,7 +124,7 @@ data: "ReplicationLagQuery": "SELECT unix_timestamp() - floor(ts/1000000000) FROM `_vt`.heartbeat ORDER BY ts DESC LIMIT 1;", {{ else }} "ReplicationLagQuery": "", -{{ end }} +{{ end }} "ServeAgentsHttp": false, "SkipBinlogEventsContaining": [ ], @@ -148,4 +149,4 @@ data: "UseSSL": false, "VerifyReplicationFilters": false } -{{ end }} \ No newline at end of file +{{ end }} diff --git a/helm/vitess/templates/_orchestrator.tpl b/helm/vitess/templates/_orchestrator.tpl index 953eafb0689..3a6fd658ce6 100644 --- a/helm/vitess/templates/_orchestrator.tpl +++ b/helm/vitess/templates/_orchestrator.tpl @@ -1,9 +1,10 @@ ################################### # Master Orchestrator Service ################################### -{{- define "orchestrator" -}} +{{ define "orchestrator" -}} # set tuple values to more recognizable variables {{- $orc := index . 0 -}} +{{- $defaultVtctlclient := index . 1 }} apiVersion: v1 kind: Service @@ -58,7 +59,7 @@ spec: serviceName: orchestrator-headless replicas: {{ $orc.replicas }} podManagementPolicy: Parallel - updateStrategy: + updateStrategy: type: RollingUpdate selector: matchLabels: @@ -116,23 +117,27 @@ spec: mountPath: /conf/ - name: tmplogs mountPath: /tmp - +{{ include "user-secret-volumeMounts" $defaultVtctlclient.secrets | indent 12 }} env: - name: VTCTLD_SERVER_PORT value: "15999" - name: recovery-log - image: busybox - command: ["/bin/sh"] - args: ["-c", "tail -n+1 -F /tmp/recovery.log"] + image: vitess/logtail:latest + imagePullPolicy: Always + env: + - name: TAIL_FILEPATH + value: /tmp/recovery.log volumeMounts: - name: tmplogs mountPath: /tmp - name: audit-log - image: busybox - command: ["/bin/sh"] - args: ["-c", "tail -n+1 -F /tmp/orchestrator-audit.log"] + image: vitess/logtail:latest + imagePullPolicy: Always + env: + - name: TAIL_FILEPATH + value: /tmp/orchestrator-audit.log volumeMounts: - name: tmplogs mountPath: /tmp @@ -145,16 +150,17 @@ spec: emptyDir: {} - name: tmplogs emptyDir: {} +{{ include "user-secret-volumes" $defaultVtctlclient.secrets | indent 8 }} {{- end -}} ################################### # Per StatefulSet Orchestrator Service ################################### -{{- define "orchestrator-statefulset-service" -}} +{{ define "orchestrator-statefulset-service" -}} # set tuple values to more recognizable variables {{- $orc := index . 0 -}} -{{- $i := index . 1 -}} +{{- $i := index . 1 }} apiVersion: v1 kind: Service @@ -185,8 +191,8 @@ spec: # init-container to copy and sed # Orchestrator config from ConfigMap ################################### -{{- define "init-orchestrator" -}} -{{- $orc := . -}} +{{ define "init-orchestrator" -}} +{{- $orc := . }} - name: init-orchestrator image: {{ $orc.image | quote }} diff --git a/helm/vitess/templates/_pmm.tpl b/helm/vitess/templates/_pmm.tpl index 810171cb99a..2d3c7131f00 100644 --- a/helm/vitess/templates/_pmm.tpl +++ b/helm/vitess/templates/_pmm.tpl @@ -1,10 +1,10 @@ ################################### # pmm Service + Deployment ################################### -{{- define "pmm" -}} +{{ define "pmm" -}} # set tuple values to more recognizable variables {{- $pmm := index . 0 -}} -{{- $namespace := index . 1 -}} +{{- $namespace := index . 1 }} ################################### # pmm Service @@ -131,13 +131,13 @@ spec: ################################### # sidecar container running pmm-client ################################### -{{- define "cont-pmm-client" -}} +{{ define "cont-pmm-client" -}} {{- $pmm := index . 0 -}} -{{- $namespace := index . 1 -}} +{{- $namespace := index . 1 }} - name: "pmm-client" image: "vitess/pmm-client:{{ $pmm.pmmTag }}" - imagePullPolicy: IfNotPresent + imagePullPolicy: Always volumeMounts: - name: vtdataroot mountPath: "/vtdataroot" @@ -169,18 +169,23 @@ spec: pmm-admin config --server pmm.{{ $namespace }} --force # creates a systemd service - # TODO: remove "|| true" after https://jira.percona.com/projects/PMM/issues/PMM-1985 is resolved - pmm-admin add mysql:metrics --user root --socket /vtdataroot/tabletdata/mysql.sock --force || true + until [ -e /vtdataroot/tabletdata/mysql.sock ]; do + echo "Waiting for mysql.sock" + sleep 1 + done + pmm-admin add mysql:metrics --user root --socket /vtdataroot/tabletdata/mysql.sock --force # keep the container alive but still responsive to stop requests trap : TERM INT; sleep infinity & wait - name: pmm-client-metrics-log - image: busybox - command: ["/bin/sh"] - args: ["-c", "tail -n+1 -F /vtdataroot/pmm/pmm-mysql-metrics-42002.log"] + image: vitess/logtail:latest + imagePullPolicy: Always + env: + - name: TAIL_FILEPATH + value: /vtdataroot/pmm/pmm-mysql-metrics-42002.log volumeMounts: - name: vtdataroot mountPath: /vtdataroot -{{- end -}} \ No newline at end of file +{{- end -}} diff --git a/helm/vitess/templates/_shard.tpl b/helm/vitess/templates/_shard.tpl new file mode 100644 index 00000000000..d8c6c3f5846 --- /dev/null +++ b/helm/vitess/templates/_shard.tpl @@ -0,0 +1,185 @@ +################################### +# shard initializations +################################### + +{{ define "shard" -}} +{{- $cell := index . 0 -}} +{{- $keyspace := index . 1 -}} +{{- $shard := index . 2 -}} +{{- $defaultVtctlclient := index . 3 -}} +{{- $namespace := index . 4 -}} +{{- $totalTabletCount := index . 5 -}} + +{{- $cellClean := include "clean-label" $cell.name -}} +{{- $keyspaceClean := include "clean-label" $keyspace.name -}} +{{- $shardClean := include "clean-label" $shard.name -}} +{{- $shardName := printf "%s-%s-%s" $cellClean $keyspaceClean $shardClean | lower -}} + +{{- with $cell.vtctld }} +# define image to use +{{- $vitessTag := .vitessTag | default $defaultVtctlclient.vitessTag }} +--- +################################### +# InitShardMaster Job +################################### +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ $shardName }}-init-shard-master +spec: + backoffLimit: 1 + template: + spec: + restartPolicy: OnFailure + containers: + - name: init-shard-master + image: "vitess/vtctlclient:{{$vitessTag}}" + volumeMounts: +{{ include "user-secret-volumeMounts" $defaultVtctlclient.secrets | indent 10 }} + + command: ["bash"] + args: + - "-c" + - | + set -ex + + VTCTLD_SVC=vtctld.{{ $namespace }}:15999 + SECONDS=0 + TIMEOUT_SECONDS=600 + VTCTL_EXTRA_FLAGS=({{ include "format-flags-inline" $defaultVtctlclient.extraFlags }}) + + # poll every 5 seconds to see if vtctld is ready + until vtctlclient ${VTCTL_EXTRA_FLAGS[@]} -server $VTCTLD_SVC ListAllTablets {{ $cell.name }} > /dev/null 2>&1; do + if (( $SECONDS > $TIMEOUT_SECONDS )); then + echo "timed out waiting for vtctlclient to be ready" + exit 1 + fi + sleep 5 + done + + until [ $TABLETS_READY ]; do + # get all the tablets in the current cell + cellTablets="$(vtctlclient ${VTCTL_EXTRA_FLAGS[@]} -server $VTCTLD_SVC ListAllTablets {{ $cell.name }})" + + # filter to only the tablets in our current shard + shardTablets=$( echo "$cellTablets" | awk 'substr( $5,1,{{ len $shardName }} ) == "{{ $shardName }}" {print $0}') + + # check for a master tablet from the ListAllTablets call + masterTablet=$( echo "$shardTablets" | awk '$4 == "master" {print $1}') + if [ $masterTablet ]; then + echo "'$masterTablet' is already the master tablet, exiting without running InitShardMaster" + exit + fi + + # check for a master tablet from the GetShard call + master_alias=$(vtctlclient ${VTLCTL_EXTRA_FLAGS[@]} -server $VTCTLD_SVC GetShard {{ $keyspace.name }}/{{ $shard.name }} | jq '.master_alias.uid') + if [ "$master_alias" != "null" -a "$master_alias" != "" ]; then + echo "'$master_alias' is already the master tablet, exiting without running InitShardMaster" + exit + fi + + # count the number of newlines for the given shard to get the tablet count + tabletCount=$( echo "$shardTablets" | wc | awk '{print $1}') + + # check to see if the tablet count equals the expected tablet count + if [ $tabletCount == {{ $totalTabletCount }} ]; then + TABLETS_READY=true + else + if (( $SECONDS > $TIMEOUT_SECONDS )); then + echo "timed out waiting for tablets to be ready" + exit 1 + fi + + # wait 5 seconds for vttablets to continue getting ready + sleep 5 + fi + + done + + # find the tablet id for the "-replica-0" stateful set for a given cell, keyspace and shard + tablet_id=$( echo "$shardTablets" | awk 'substr( $5,1,{{ add (len $shardName) 10 }} ) == "{{ $shardName }}-replica-0" {print $1}') + + # initialize the shard master + until vtctlclient ${VTCTL_EXTRA_FLAGS[@]} -server $VTCTLD_SVC InitShardMaster -force {{ $keyspace.name }}/{{ $shard.name }} $tablet_id; do + if (( $SECONDS > $TIMEOUT_SECONDS )); then + echo "timed out waiting for InitShardMaster to succeed" + exit 1 + fi + sleep 5 + done + volumes: +{{ include "user-secret-volumes" (.secrets | default $defaultVtctlclient.secrets) | indent 8 }} + +{{- $copySchema := ($keyspace.copySchema | default $shard.copySchema) -}} +{{- if $copySchema }} +--- +################################### +# CopySchemaShard Job +################################### +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ $keyspaceClean }}-copy-schema-{{ $shardClean }} +spec: + backoffLimit: 1 + template: + spec: + restartPolicy: OnFailure + containers: + - name: copy-schema + image: "vitess/vtctlclient:{{$vitessTag}}" + volumeMounts: +{{ include "user-secret-volumeMounts" $defaultVtctlclient.secrets | indent 10 }} + + command: ["bash"] + args: + - "-c" + - | + set -ex + + VTCTLD_SVC=vtctld.{{ $namespace }}:15999 + SECONDS=0 + TIMEOUT_SECONDS=600 + VTCTL_EXTRA_FLAGS=({{ include "format-flags-inline" $defaultVtctlclient.extraFlags }}) + + # poll every 5 seconds to see if vtctld is ready + until vtctlclient ${VTCTL_EXTRA_FLAGS[@]} -server $VTCTLD_SVC ListAllTablets {{ $cell.name }} > /dev/null 2>&1; do + if (( $SECONDS > $TIMEOUT_SECONDS )); then + echo "timed out waiting for vtctlclient to be ready" + exit 1 + fi + sleep 5 + done + + while true; do + if (( $SECONDS > $TIMEOUT_SECONDS )); then + echo "timed out waiting for master" + exit 1 + fi + + # wait for all shards to have a master + master_alias=$(vtctlclient ${VTLCTL_EXTRA_FLAGS[@]} -server $VTCTLD_SVC GetShard {{ $keyspace.name }}/{{ $shard.name }} | jq '.master_alias.uid') + if [ "$master_alias" == "null" -o "$master_alias" == "" ]; then + echo "no master for '{{ $keyspace.name }}/{{ $shard.name }}' yet, continuing to wait" + sleep 5 + continue + fi + + break + done + + vtctlclient ${VTCTL_EXTRA_FLAGS[@]} -server $VTCTLD_SVC CopySchemaShard {{ if $copySchema.tables -}} + -tables=' + {{- range $index, $table := $copySchema.tables -}} + {{- if $index -}},{{- end -}} + {{ $table }} + {{- end -}} + ' + {{- end }} {{ $copySchema.source }} {{ $keyspace.name }}/{{ $shard.name }} + volumes: +{{ include "user-secret-volumes" (.secrets | default $defaultVtctlclient.secrets) | indent 8 }} +{{ end }} + + +{{- end -}} +{{- end -}} diff --git a/helm/vitess/templates/_vtctld.tpl b/helm/vitess/templates/_vtctld.tpl index e75ea0dd48a..44d9c32553c 100644 --- a/helm/vitess/templates/_vtctld.tpl +++ b/helm/vitess/templates/_vtctld.tpl @@ -1,7 +1,7 @@ ################################### # vtctld Service + Deployment ################################### -{{- define "vtctld" -}} +{{ define "vtctld" -}} # set tuple values to more recognizable variables {{- $topology := index . 0 -}} {{- $cell := index . 1 -}} @@ -13,7 +13,7 @@ # define image to use {{- $vitessTag := .vitessTag | default $defaultVtctld.vitessTag -}} -{{- $cellClean := include "clean-label" $cell.name -}} +{{- $cellClean := include "clean-label" $cell.name }} ################################### # vtctld Service @@ -60,6 +60,7 @@ spec: containers: - name: vtctld image: vitess/vtctld:{{$vitessTag}} + imagePullPolicy: Always readinessProbe: httpGet: path: /debug/health @@ -76,7 +77,7 @@ spec: {{ include "backup-env" $config.backup | indent 12 }} volumeMounts: {{ include "backup-volumeMount" $config.backup | indent 12 }} - +{{ include "user-secret-volumeMounts" (.secrets | default $defaultVtctld.secrets) | indent 12 }} resources: {{ toYaml (.resources | default $defaultVtctld.resources) | indent 12 }} command: @@ -102,11 +103,13 @@ spec: -topo_global_server_address="etcd-global-client.{{ $namespace }}:2379" -topo_global_root=/vitess/global {{ include "backup-flags" (tuple $config.backup "vtctld") | indent 16 }} +{{ include "format-flags-all" (tuple $defaultVtctld.extraFlags .extraFlags) | indent 16 }} END_OF_COMMAND ) volumes: {{ include "backup-volume" $config.backup | indent 8 }} +{{ include "user-secret-volumes" (.secrets | default $defaultVtctld.secrets) | indent 8 }} {{- end -}} {{- end -}} @@ -114,7 +117,7 @@ spec: ################################### # vtctld-affinity sets node/pod affinities ################################### -{{- define "vtctld-affinity" -}} +{{ define "vtctld-affinity" -}} # set tuple values to more recognizable variables {{- $cellClean := index . 0 -}} {{- $region := index . 1 -}} @@ -125,4 +128,4 @@ affinity: {{ include "node-affinity" $region | indent 2 }} {{- end -}} -{{- end -}} \ No newline at end of file +{{- end -}} diff --git a/helm/vitess/templates/_vtgate.tpl b/helm/vitess/templates/_vtgate.tpl index f0acf0644ff..a0f861d6654 100644 --- a/helm/vitess/templates/_vtgate.tpl +++ b/helm/vitess/templates/_vtgate.tpl @@ -1,7 +1,7 @@ ################################### # vtgate Service + Deployment ################################### -{{- define "vtgate" -}} +{{ define "vtgate" -}} # set tuple values to more recognizable variables {{- $topology := index . 0 -}} {{- $cell := index . 1 -}} @@ -12,7 +12,7 @@ # define image to use {{- $vitessTag := .vitessTag | default $defaultVtgate.vitessTag -}} -{{- $cellClean := include "clean-label" $cell.name -}} +{{- $cellClean := include "clean-label" $cell.name }} ################################### # vtgate Service @@ -66,13 +66,16 @@ spec: {{ include "vtgate-affinity" (tuple $cellClean $cell.region) | indent 6 }} {{ if $cell.mysqlProtocol.enabled }} +{{ if eq $cell.mysqlProtocol.authType "secret" }} initContainers: {{ include "init-mysql-creds" (tuple $vitessTag $cell) | indent 8 }} +{{ end }} {{ end }} containers: - name: vtgate image: vitess/vtgate:{{$vitessTag}} + imagePullPolicy: Always readinessProbe: httpGet: path: /debug/health @@ -88,7 +91,7 @@ spec: volumeMounts: - name: creds mountPath: "/mysqlcreds" - +{{ include "user-secret-volumeMounts" (.secrets | default $defaultVtgate.secrets) | indent 12 }} resources: {{ toYaml (.resources | default $defaultVtgate.resources) | indent 12 }} @@ -108,7 +111,12 @@ spec: -grpc_port=15991 {{ if $cell.mysqlProtocol.enabled }} -mysql_server_port=3306 +{{ if eq $cell.mysqlProtocol.authType "secret" }} + -mysql_auth_server_impl="static" -mysql_auth_server_static_file="/mysqlcreds/creds.json" +{{ else if eq $cell.mysqlProtocol.authType "none" }} + -mysql_auth_server_impl="none" +{{ end }} {{ end }} -service_map="grpc-vtgateservice" -cells_to_watch={{$cell.name | quote}} @@ -121,7 +129,7 @@ spec: volumes: - name: creds emptyDir: {} - +{{ include "user-secret-volumes" (.secrets | default $defaultVtgate.secrets) | indent 8 }} --- ################################### # vtgate PodDisruptionBudget @@ -168,10 +176,10 @@ spec: ################################### # vtgate-affinity sets node/pod affinities ################################### -{{- define "vtgate-affinity" -}} +{{ define "vtgate-affinity" -}} # set tuple values to more recognizable variables {{- $cellClean := index . 0 -}} -{{- $region := index . 1 -}} +{{- $region := index . 1 }} # affinity pod spec affinity: @@ -205,17 +213,18 @@ affinity: ################################### # init-container to set mysql credentials file -# it loops through the users and pulls out their +# it loops through the users and pulls out their # respective passwords from mounted secrets ################################### -{{- define "init-mysql-creds" -}} +{{ define "init-mysql-creds" -}} {{- $vitessTag := index . 0 -}} {{- $cell := index . 1 -}} -{{- with $cell.mysqlProtocol -}} +{{- with $cell.mysqlProtocol }} - name: init-mysql-creds image: "vitess/vtgate:{{$vitessTag}}" + imagePullPolicy: Always volumeMounts: - name: creds mountPath: "/mysqlcreds" @@ -246,4 +255,4 @@ affinity: echo $creds > /mysqlcreds/creds.json {{- end -}} -{{- end -}} \ No newline at end of file +{{- end -}} diff --git a/helm/vitess/templates/_vttablet.tpl b/helm/vitess/templates/_vttablet.tpl index 357845198ea..eba76e57e02 100644 --- a/helm/vitess/templates/_vttablet.tpl +++ b/helm/vitess/templates/_vttablet.tpl @@ -1,9 +1,9 @@ ################################### # vttablet Service ################################### -{{- define "vttablet-service" -}} +{{ define "vttablet-service" -}} # set tuple values to more recognizable variables -{{- $pmm := index . 0 -}} +{{- $pmm := index . 0 }} apiVersion: v1 kind: Service metadata: @@ -33,9 +33,9 @@ spec: {{- end -}} ################################### -# vttablet +# vttablet ################################### -{{- define "vttablet" -}} +{{ define "vttablet" -}} # set tuple values to more recognizable variables {{- $topology := index . 0 -}} {{- $cell := index . 1 -}} @@ -43,13 +43,13 @@ spec: {{- $shard := index . 3 -}} {{- $tablet := index . 4 -}} {{- $defaultVttablet := index . 5 -}} -{{- $namespace := index . 6 -}} -{{- $config := index . 7 -}} -{{- $pmm := index . 8 -}} -{{- $orc := index . 9 -}} -{{- $totalTabletCount := index . 10 -}} +{{- $defaultVtctlclient := index . 6 -}} +{{- $namespace := index . 7 -}} +{{- $config := index . 8 -}} +{{- $pmm := index . 9 -}} +{{- $orc := index . 10 -}} -# sanitize inputs to create tablet name +# sanitize inputs for labels {{- $cellClean := include "clean-label" $cell.name -}} {{- $keyspaceClean := include "clean-label" $keyspace.name -}} {{- $shardClean := include "clean-label" $shard.name -}} @@ -63,7 +63,8 @@ spec: {{- $vitessTag := .vitessTag | default $defaultVttablet.vitessTag -}} {{- $image := .image | default $defaultVttablet.image -}} {{- $mysqlImage := .mysqlImage | default $defaultVttablet.mysqlImage -}} - +{{- $mysqlImage := .mysqlImage | default $defaultVttablet.mysqlImage }} +--- ################################### # vttablet StatefulSet ################################### @@ -75,7 +76,7 @@ spec: serviceName: vttablet replicas: {{ .replicas | default $defaultVttablet.replicas }} podManagementPolicy: Parallel - updateStrategy: + updateStrategy: type: RollingUpdate selector: matchLabels: @@ -105,7 +106,9 @@ spec: containers: {{ include "cont-mysql" (tuple $topology $cell $keyspace $shard $tablet $defaultVttablet $uid) | indent 8 }} -{{ include "cont-vttablet" (tuple $topology $cell $keyspace $shard $tablet $defaultVttablet $vitessTag $uid $namespace $config $orc $totalTabletCount) | indent 8 }} +{{ include "cont-vttablet" (tuple $topology $cell $keyspace $shard $tablet $defaultVttablet $defaultVtctlclient $vitessTag $uid $namespace $config $orc) | indent 8 }} +{{ include "cont-logrotate" . | indent 8 }} +{{ include "cont-mysql-generallog" . | indent 8 }} {{ include "cont-mysql-errorlog" . | indent 8 }} {{ include "cont-mysql-slowlog" . | indent 8 }} {{ if $pmm.enabled }}{{ include "cont-pmm-client" (tuple $pmm $namespace) | indent 8 }}{{ end }} @@ -114,7 +117,8 @@ spec: - name: vt emptyDir: {} {{ include "backup-volume" $config.backup | indent 8 }} -{{ include "user-config-volume" $defaultVttablet.extraMyCnf | indent 8 }} +{{ include "user-config-volume" (.extraMyCnf | default $defaultVttablet.extraMyCnf) | indent 8 }} +{{ include "user-secret-volumes" (.secrets | default $defaultVttablet.secrets) | indent 8 }} volumeClaimTemplates: - metadata: @@ -143,95 +147,8 @@ spec: shard: {{ $shardClean | quote }} type: {{ $tablet.type | quote }} -{{ if eq $tablet.type "replica" }} ---- -################################### -# vttablet InitShardMaster Job -################################### -apiVersion: batch/v1 -kind: Job -metadata: - name: {{ $shardName }}-init-shard-master -spec: - backoffLimit: 1 - template: - spec: - restartPolicy: OnFailure - containers: - - name: init-shard-master - image: "vitess/vtctlclient:{{$vitessTag}}" - - command: ["bash"] - args: - - "-c" - - | - set -ex - - VTCTLD_SVC=vtctld.{{ $namespace }}:15999 - SECONDS=0 - TIMEOUT_SECONDS=600 - - # poll every 5 seconds to see if vtctld is ready - until vtctlclient -server $VTCTLD_SVC ListAllTablets {{ $cellClean }} > /dev/null 2>&1; do - if (( $SECONDS > $TIMEOUT_SECONDS )); then - echo "timed out waiting for vtctlclient to be ready" - exit 1 - fi - sleep 5 - done - - until [ $TABLETS_READY ]; do - # get all the tablets in the current cell - cellTablets="$(vtctlclient -server $VTCTLD_SVC ListAllTablets {{ $cellClean }})" - - # filter to only the tablets in our current shard - shardTablets=$( echo "$cellTablets" | awk 'substr( $5,1,{{ len $shardName }} ) == "{{ $shardName }}" {print $0}') - - # check for a master tablet from the ListAllTablets call - masterTablet=$( echo "$shardTablets" | awk '$4 == "master" {print $1}') - if [ $masterTablet ]; then - echo "'$masterTablet' is already the master tablet, exiting without running InitShardMaster" - exit - fi - - # check for a master tablet from the GetShard call - master_alias=$(vtctlclient -server $VTCTLD_SVC GetShard {{ $keyspace.name }}/{{ $shard.name }} | jq '.master_alias.uid') - if [ $master_alias != "null" ]; then - echo "'$master_alias' is already the master tablet, exiting without running InitShardMaster" - exit - fi - - # count the number of newlines for the given shard to get the tablet count - tabletCount=$( echo "$shardTablets" | wc | awk '{print $1}') - - # check to see if the tablet count equals the expected tablet count - if [ $tabletCount == {{ $totalTabletCount }} ]; then - TABLETS_READY=true - else - if (( $SECONDS > $TIMEOUT_SECONDS )); then - echo "timed out waiting for tablets to be ready" - exit 1 - fi - - # wait 5 seconds for vttablets to continue getting ready - sleep 5 - fi - - done - - # find the tablet id for the "-replica-0" stateful set for a given cell, keyspace and shard - tablet_id=$( echo "$shardTablets" | awk 'substr( $5,1,{{ add (len $shardName) 10 }} ) == "{{ $shardName }}-replica-0" {print $1}') - - # initialize the shard master - until vtctlclient -server $VTCTLD_SVC InitShardMaster -force {{ $keyspace.name }}/{{ $shard.name }} $tablet_id; do - if (( $SECONDS > $TIMEOUT_SECONDS )); then - echo "timed out waiting for InitShardMaster to succeed" - exit 1 - fi - sleep 5 - done - -{{- end -}} +# conditionally add cron job +{{ include "vttablet-backup-cron" (tuple $cellClean $keyspaceClean $shardClean $shardName $keyspace $shard $vitessTag $config.backup $namespace $defaultVtctlclient) }} {{- end -}} {{- end -}} @@ -239,13 +156,13 @@ spec: ################################### # init-container to copy binaries for mysql ################################### -{{- define "init-mysql" -}} +{{ define "init-mysql" -}} {{- $vitessTag := index . 0 -}} -{{- $cellClean := index . 1 -}} +{{- $cellClean := index . 1 }} - name: "init-mysql" image: "vitess/mysqlctld:{{$vitessTag}}" - imagePullPolicy: IfNotPresent + imagePullPolicy: Always volumeMounts: - name: vtdataroot mountPath: "/vtdataroot" @@ -263,8 +180,14 @@ spec: # copy necessary assets to the volumeMounts cp /vt/bin/mysqlctld /vttmp/bin/ + cp /bin/busybox /vttmp/bin/ cp -R /vt/config /vttmp/ + # make sure the log files exist + touch /vtdataroot/tabletdata/error.log + touch /vtdataroot/tabletdata/slow-query.log + touch /vtdataroot/tabletdata/general.log + {{- end -}} ################################### @@ -272,14 +195,15 @@ spec: # This converts the unique identity assigned by StatefulSet (pod name) # into a 31-bit unsigned integer for use as a Vitess tablet UID. ################################### -{{- define "init-vttablet" -}} +{{ define "init-vttablet" -}} {{- $vitessTag := index . 0 -}} {{- $cell := index . 1 -}} {{- $cellClean := index . 2 -}} -{{- $namespace := index . 3 -}} +{{- $namespace := index . 3 }} - name: init-vttablet image: "vitess/vtctl:{{$vitessTag}}" + imagePullPolicy: Always volumeMounts: - name: vtdataroot mountPath: "/vtdataroot" @@ -324,7 +248,7 @@ spec: ########################## # main vttablet container ########################## -{{- define "cont-vttablet" -}} +{{ define "cont-vttablet" -}} {{- $topology := index . 0 -}} {{- $cell := index . 1 -}} @@ -332,18 +256,19 @@ spec: {{- $shard := index . 3 -}} {{- $tablet := index . 4 -}} {{- $defaultVttablet := index . 5 -}} -{{- $vitessTag := index . 6 -}} -{{- $uid := index . 7 -}} -{{- $namespace := index . 8 -}} -{{- $config := index . 9 -}} -{{- $orc := index . 10 -}} -{{- $totalTabletCount := index . 11 -}} +{{- $defaultVtctlclient := index . 6 -}} +{{- $vitessTag := index . 7 -}} +{{- $uid := index . 8 -}} +{{- $namespace := index . 9 -}} +{{- $config := index . 10 -}} +{{- $orc := index . 11 -}} {{- $cellClean := include "clean-label" $cell.name -}} -{{- with $tablet.vttablet -}} +{{- with $tablet.vttablet }} - name: vttablet image: "vitess/vttablet:{{$vitessTag}}" + imagePullPolicy: Always readinessProbe: httpGet: path: /debug/health @@ -360,7 +285,8 @@ spec: - name: vtdataroot mountPath: "/vtdataroot" {{ include "backup-volumeMount" $config.backup | indent 4 }} -{{ include "user-config-volumeMount" $defaultVttablet.extraMyCnf | indent 4 }} +{{ include "user-config-volumeMount" (.extraMyCnf | default $defaultVttablet.extraMyCnf) | indent 4 }} +{{ include "user-secret-volumeMounts" (.secrets | default $defaultVttablet.secrets) | indent 4 }} resources: {{ toYaml (.resources | default $defaultVttablet.resources) | indent 6 }} @@ -379,15 +305,74 @@ spec: name: vitess-cm key: db.flavor + lifecycle: + preStop: + exec: + command: + - "bash" + - "-c" + - | + set -x + + VTCTLD_SVC=vtctld.{{ $namespace }}:15999 + VTCTL_EXTRA_FLAGS=({{ include "format-flags-inline" $defaultVtctlclient.extraFlags }}) + + master_alias_json=$(/vt/bin/vtctlclient ${VTCTL_EXTRA_FLAGS[@]} -server $VTCTLD_SVC GetShard {{ $keyspace.name }}/{{ $shard.name }}) + master_cell=$(jq -r '.master_alias.cell' <<< "$master_alias_json") + master_uid=$(jq -r '.master_alias.uid' <<< "$master_alias_json") + master_alias=$master_cell-$master_uid + + current_uid=$(cat /vtdataroot/tabletdata/tablet-uid) + current_alias={{ $cell.name }}-$current_uid + + if [ $master_alias != $current_alias ]; then + # since this isn't the master, there's no reason to reparent + exit + fi + + # TODO: add more robust health checks to make sure that we don't initiate a reparent + # if there isn't a healthy enough replica to take over + # - seconds behind master + # - use GTID_SUBTRACT + + RETRY_COUNT=0 + MAX_RETRY_COUNT=5 + + # retry reparenting + until [ $DONE_REPARENTING ]; do + + # reparent before shutting down + /vt/bin/vtctlclient ${VTCTL_EXTRA_FLAGS[@]} -server $VTCTLD_SVC PlannedReparentShard -keyspace_shard={{ $keyspace.name }}/{{ $shard.name }} -avoid_master=$current_alias + + # if PlannedReparentShard succeeded, then don't retry + if [ $? -eq 0 ]; then + DONE_REPARENTING=true + + # if we've reached the max retry count, exit unsuccessfully + elif [ $RETRY_COUNT -eq $MAX_RETRY_COUNT ]; then + exit 1 + + # otherwise, increment the retry count and sleep for 10 seconds + else + let RETRY_COUNT=RETRY_COUNT+1 + sleep 10 + fi + + done + + # delete the current tablet from topology. Not strictly necessary, but helps to prevent + # edge cases where there are two masters + /vt/bin/vtctlclient ${VTCTL_EXTRA_FLAGS[@]} -server $VTCTLD_SVC DeleteTablet $current_alias + command: ["bash"] args: - "-c" - | set -ex -{{ include "mycnf-exec" $defaultVttablet.extraMyCnf | indent 6 }} +{{ include "mycnf-exec" (.extraMyCnf | default $defaultVttablet.extraMyCnf) | indent 6 }} {{ include "backup-exec" $config.backup | indent 6 }} - + eval exec /vt/bin/vttablet $(cat <= 10.3) @@ -140,11 +182,25 @@ vttablet: # will block forever. "rdonly" tablets do not ACK. enableSemisync: false + # This sets the vttablet flag "-init_db_name_override" to be the keyspace name, rather + # than "vt_keyspace". This works better with many MySQL client applications + useKeyspaceNameAsDbName: true + # The name of a config map with N files inside of it. Each file will be added # to $EXTRA_MY_CNF, overriding any default my.cnf settings extraMyCnf: "" # extraMyCnf: extra-my-cnf + # mysqlSize can be "test" or "prod". Default is "prod". + # If the value is "test", then mysql is instanitated with a smaller footprint. + mysqlSize: "prod" + + # Additional flags that will be appended to the vttablet command + extraFlags: {} + + # User secrets that will be mounted under /vt/usersecrets/{secretname}/ + secrets: [] + resources: # common production values 2-4CPU/4-8Gi RAM limits: @@ -203,8 +259,8 @@ pmm: metricsResolution: 1s # METRICS_RETENTION (Option) - # This option determines how long metrics are stored at PMM Server. - # The value is passed as a combination of hours, minutes, and seconds, such as 720h0m0s. + # This option determines how long metrics are stored at PMM Server. + # The value is passed as a combination of hours, minutes, and seconds, such as 720h0m0s. # The minutes (a number followed by m) and seconds (a number followed by s) are optional. metricsRetention: 720h @@ -216,10 +272,10 @@ pmm: # NOTE: The value must be passed in kilobytes # NOTE: Make sure to quote this value so it isn't converted into scientific notation - # By default, Prometheus in PMM Server uses up to 768 MB of memory for storing the most recently used data chunks. + # By default, Prometheus in PMM Server uses up to 768 MB of memory for storing the most recently used data chunks. # Depending on the amount of data coming into Prometheus, you may require a higher limit to avoid throttling data ingestion, # or allow less memory consumption if it is needed for other processes. - # The limit affects only memory reserved for data chunks. Actual RAM usage by Prometheus is higher. + # The limit affects only memory reserved for data chunks. Actual RAM usage by Prometheus is higher. # It is recommended to set this limit to roughly 2/3 of the total memory that you are planning to allow for Prometheus. metricsMemory: "600000" diff --git a/java/client/pom.xml b/java/client/pom.xml index 773ee366f13..e11038f4f3d 100644 --- a/java/client/pom.xml +++ b/java/client/pom.xml @@ -84,7 +84,7 @@ protobuf-maven-plugin 0.5.0 - com.google.protobuf:protoc:${protobuf.java.version}:exe:${os.detected.classifier} + com.google.protobuf:protoc:${protobuf.protoc.version}:exe:${os.detected.classifier} ../../proto query.proto diff --git a/java/grpc-client/pom.xml b/java/grpc-client/pom.xml index c8bb78dcbf3..14faee962c3 100644 --- a/java/grpc-client/pom.xml +++ b/java/grpc-client/pom.xml @@ -100,7 +100,7 @@ protobuf-maven-plugin 0.5.0 - com.google.protobuf:protoc:${protobuf.java.version}:exe:${os.detected.classifier} + com.google.protobuf:protoc:${protobuf.protoc.version}:exe:${os.detected.classifier} grpc-java io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} ../../proto diff --git a/java/hadoop/pom.xml b/java/hadoop/pom.xml index 3d21d204afd..5be7992b642 100644 --- a/java/hadoop/pom.xml +++ b/java/hadoop/pom.xml @@ -81,7 +81,6 @@ maven-surefire-plugin 2.22.1 - false true @@ -89,7 +88,7 @@ org.apache.maven.plugins maven-failsafe-plugin - 2.19.1 + 2.22.1 @@ -99,6 +98,8 @@ + false + true io.vitess.client.TestEnv grpc_port diff --git a/java/pom.xml b/java/pom.xml index d111a8324de..f253f5b773c 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -17,7 +17,7 @@ client example grpc-client - + hadoop jdbc @@ -62,6 +62,7 @@ 1.15.0 3.5.1 + 3.5.1-1 @@ -222,10 +223,10 @@ org.apache.maven.plugins maven-compiler-plugin - 3.3 + 3.8.0 - 1.7 - 1.7 + 1.8 + 1.8 diff --git a/proto/vschema.proto b/proto/vschema.proto index 81276a767b5..bdf7e9834e9 100644 --- a/proto/vschema.proto +++ b/proto/vschema.proto @@ -64,7 +64,11 @@ message Table { // shard, as dictated by the keyspace id. // The keyspace id is represened in hex form // like in keyranges. - string pinned =5; + string pinned = 5; + // column_list_authoritative is set to true if columns is + // an authoritative list for the table. This allows + // us to expand 'select *' expressions. + bool column_list_authoritative = 6; } // ColumnVindex is used to associate a column to a vindex. diff --git a/py/vtproto/vschema_pb2.py b/py/vtproto/vschema_pb2.py index 81c5b4064e8..878d1d08dc9 100644 --- a/py/vtproto/vschema_pb2.py +++ b/py/vtproto/vschema_pb2.py @@ -20,7 +20,7 @@ name='vschema.proto', package='vschema', syntax='proto3', - serialized_pb=_b('\n\rvschema.proto\x12\x07vschema\x1a\x0bquery.proto\"\xfe\x01\n\x08Keyspace\x12\x0f\n\x07sharded\x18\x01 \x01(\x08\x12\x31\n\x08vindexes\x18\x02 \x03(\x0b\x32\x1f.vschema.Keyspace.VindexesEntry\x12-\n\x06tables\x18\x03 \x03(\x0b\x32\x1d.vschema.Keyspace.TablesEntry\x1a@\n\rVindexesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1e\n\x05value\x18\x02 \x01(\x0b\x32\x0f.vschema.Vindex:\x02\x38\x01\x1a=\n\x0bTablesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1d\n\x05value\x18\x02 \x01(\x0b\x32\x0e.vschema.Table:\x02\x38\x01\"\x81\x01\n\x06Vindex\x12\x0c\n\x04type\x18\x01 \x01(\t\x12+\n\x06params\x18\x02 \x03(\x0b\x32\x1b.vschema.Vindex.ParamsEntry\x12\r\n\x05owner\x18\x03 \x01(\t\x1a-\n\x0bParamsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xa7\x01\n\x05Table\x12\x0c\n\x04type\x18\x01 \x01(\t\x12.\n\x0f\x63olumn_vindexes\x18\x02 \x03(\x0b\x32\x15.vschema.ColumnVindex\x12.\n\x0e\x61uto_increment\x18\x03 \x01(\x0b\x32\x16.vschema.AutoIncrement\x12 \n\x07\x63olumns\x18\x04 \x03(\x0b\x32\x0f.vschema.Column\x12\x0e\n\x06pinned\x18\x05 \x01(\t\"=\n\x0c\x43olumnVindex\x12\x0e\n\x06\x63olumn\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0f\n\x07\x63olumns\x18\x03 \x03(\t\"1\n\rAutoIncrement\x12\x0e\n\x06\x63olumn\x18\x01 \x01(\t\x12\x10\n\x08sequence\x18\x02 \x01(\t\"1\n\x06\x43olumn\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x19\n\x04type\x18\x02 \x01(\x0e\x32\x0b.query.Type\"\x88\x01\n\nSrvVSchema\x12\x35\n\tkeyspaces\x18\x01 \x03(\x0b\x32\".vschema.SrvVSchema.KeyspacesEntry\x1a\x43\n\x0eKeyspacesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12 \n\x05value\x18\x02 \x01(\x0b\x32\x11.vschema.Keyspace:\x02\x38\x01\x42&Z$vitess.io/vitess/go/vt/proto/vschemab\x06proto3') + serialized_pb=_b('\n\rvschema.proto\x12\x07vschema\x1a\x0bquery.proto\"\xfe\x01\n\x08Keyspace\x12\x0f\n\x07sharded\x18\x01 \x01(\x08\x12\x31\n\x08vindexes\x18\x02 \x03(\x0b\x32\x1f.vschema.Keyspace.VindexesEntry\x12-\n\x06tables\x18\x03 \x03(\x0b\x32\x1d.vschema.Keyspace.TablesEntry\x1a@\n\rVindexesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1e\n\x05value\x18\x02 \x01(\x0b\x32\x0f.vschema.Vindex:\x02\x38\x01\x1a=\n\x0bTablesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1d\n\x05value\x18\x02 \x01(\x0b\x32\x0e.vschema.Table:\x02\x38\x01\"\x81\x01\n\x06Vindex\x12\x0c\n\x04type\x18\x01 \x01(\t\x12+\n\x06params\x18\x02 \x03(\x0b\x32\x1b.vschema.Vindex.ParamsEntry\x12\r\n\x05owner\x18\x03 \x01(\t\x1a-\n\x0bParamsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xca\x01\n\x05Table\x12\x0c\n\x04type\x18\x01 \x01(\t\x12.\n\x0f\x63olumn_vindexes\x18\x02 \x03(\x0b\x32\x15.vschema.ColumnVindex\x12.\n\x0e\x61uto_increment\x18\x03 \x01(\x0b\x32\x16.vschema.AutoIncrement\x12 \n\x07\x63olumns\x18\x04 \x03(\x0b\x32\x0f.vschema.Column\x12\x0e\n\x06pinned\x18\x05 \x01(\t\x12!\n\x19\x63olumn_list_authoritative\x18\x06 \x01(\x08\"=\n\x0c\x43olumnVindex\x12\x0e\n\x06\x63olumn\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0f\n\x07\x63olumns\x18\x03 \x03(\t\"1\n\rAutoIncrement\x12\x0e\n\x06\x63olumn\x18\x01 \x01(\t\x12\x10\n\x08sequence\x18\x02 \x01(\t\"1\n\x06\x43olumn\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x19\n\x04type\x18\x02 \x01(\x0e\x32\x0b.query.Type\"\x88\x01\n\nSrvVSchema\x12\x35\n\tkeyspaces\x18\x01 \x03(\x0b\x32\".vschema.SrvVSchema.KeyspacesEntry\x1a\x43\n\x0eKeyspacesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12 \n\x05value\x18\x02 \x01(\x0b\x32\x11.vschema.Keyspace:\x02\x38\x01\x42&Z$vitess.io/vitess/go/vt/proto/vschemab\x06proto3') , dependencies=[query__pb2.DESCRIPTOR,]) @@ -270,6 +270,13 @@ message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='column_list_authoritative', full_name='vschema.Table.column_list_authoritative', index=5, + number=6, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -283,7 +290,7 @@ oneofs=[ ], serialized_start=429, - serialized_end=596, + serialized_end=631, ) @@ -327,8 +334,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=598, - serialized_end=659, + serialized_start=633, + serialized_end=694, ) @@ -365,8 +372,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=661, - serialized_end=710, + serialized_start=696, + serialized_end=745, ) @@ -403,8 +410,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=712, - serialized_end=761, + serialized_start=747, + serialized_end=796, ) @@ -441,8 +448,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=833, - serialized_end=900, + serialized_start=868, + serialized_end=935, ) _SRVVSCHEMA = _descriptor.Descriptor( @@ -471,8 +478,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=764, - serialized_end=900, + serialized_start=799, + serialized_end=935, ) _KEYSPACE_VINDEXESENTRY.fields_by_name['value'].message_type = _VINDEX diff --git a/test/initial_sharding.py b/test/initial_sharding.py index 645ed257159..0a101192ae0 100755 --- a/test/initial_sharding.py +++ b/test/initial_sharding.py @@ -240,16 +240,27 @@ def test_resharding(self): for t in [shard_master, shard_replica, shard_rdonly1]: t.create_db('vt_test_keyspace') + # replica is not started, InitShardMaster should timeout shard_master.start_vttablet(wait_for_state=None, binlog_use_v3_resharding_mode=False) - shard_replica.start_vttablet(wait_for_state=None, - binlog_use_v3_resharding_mode=False) shard_rdonly1.start_vttablet(wait_for_state=None, binlog_use_v3_resharding_mode=False) - for t in [shard_master, shard_replica, shard_rdonly1]: + for t in [shard_master, shard_rdonly1]: t.wait_for_vttablet_state('NOT_SERVING') + # reparent to make the tablets work - expect fail + # because replica tablet is not up + _, stderr = utils.run_vtctl(['InitShardMaster', '-force', 'test_keyspace/0', + shard_master.tablet_alias], auto_log=True, expect_fail=True) + + self.assertIn('Tablet test_nj-0000062345 ResetReplication failed', stderr) + # start replica + shard_replica.start_vttablet(wait_for_state=None, + binlog_use_v3_resharding_mode=False) + + shard_replica.wait_for_vttablet_state('NOT_SERVING') + # reparent to make the tablets work utils.run_vtctl(['InitShardMaster', '-force', 'test_keyspace/0', shard_master.tablet_alias], auto_log=True) diff --git a/test/reparent.py b/test/reparent.py index c4957465e81..5f1f07a30b5 100755 --- a/test/reparent.py +++ b/test/reparent.py @@ -339,8 +339,8 @@ def _test_reparent_graceful(self, shard_id): tablet_62044.kill_vttablet() - # This is a manual test to check error formatting. - def _test_reparent_slave_offline(self, shard_id='0'): + # Reparenting should return error if replica vttablet is down + def test_reparent_slave_offline(self, shard_id='0'): utils.run_vtctl(['CreateKeyspace', 'test_keyspace']) # create the database so vttablets start, as they are serving @@ -377,9 +377,11 @@ def _test_reparent_slave_offline(self, shard_id='0'): tablet_31981.kill_vttablet() # Perform a graceful reparent operation. - utils.run_vtctl(['PlannedReparentShard', + _, stderr = utils.run_vtctl(['PlannedReparentShard', '-keyspace_shard', 'test_keyspace/' + shard_id, - '-new_master', tablet_62044.tablet_alias]) + '-new_master', tablet_62044.tablet_alias], expect_fail=True) + self.assertIn('Tablet test_ny-0000031981 SetMaster failed', stderr) + self._check_master_tablet(tablet_62044) tablet.kill_tablets([tablet_62344, tablet_62044, tablet_41983]) diff --git a/test/vertical_split.py b/test/vertical_split.py index 7b33c7923fb..4f9d64b64e0 100755 --- a/test/vertical_split.py +++ b/test/vertical_split.py @@ -427,6 +427,8 @@ def test_vertical_split(self): # test Cancel first utils.run_vtctl(['CancelResharding', 'destination_keyspace/0'], auto_log=True) self.check_no_binlog_player(destination_master) + # master should be in serving state after cancel + utils.check_tablet_query_service(self, destination_master, True, False) # redo VerticalSplitClone utils.run_vtworker(['--cell', 'test_nj', diff --git a/test/vtctld_test.py b/test/vtctld_test.py index ec0ebf7c9bb..9bdfbe03a12 100755 --- a/test/vtctld_test.py +++ b/test/vtctld_test.py @@ -36,6 +36,14 @@ # all tablets tablets = [shard_0_master, shard_0_replica, shard_1_master, shard_1_replica] +select_one_table_output = """ ++---+ +| a | ++---+ +| 1 | ++---+ +""".lstrip() + def setUpModule(): try: @@ -191,13 +199,15 @@ def test_execute_fetch_as_dba(self): # All we care is that it's the human-readable table, not JSON or protobuf. out, _ = utils.run_vtctl(['ExecuteFetchAsDba', shard_0_replica.tablet_alias, 'SELECT 1 AS a'], trap_output=True) - want = """+---+ -| a | -+---+ -| 1 | -+---+ -""" - self.assertEqual(want, out) + self.assertEqual(select_one_table_output, out) + + def test_execute_fetch_as_dba(self): + """Make sure ExecuteFetchAsApp prints a human-readable table by default.""" + # Use a simple example so we're not sensitive to alignment settings, etc. + # All we care is that it's the human-readable table, not JSON or protobuf. + out, _ = utils.run_vtctl(['ExecuteFetchAsApp', shard_0_replica.tablet_alias, + 'SELECT 1 AS a'], trap_output=True) + self.assertEqual(select_one_table_output, out) if __name__ == '__main__': utils.main() diff --git a/test/vtgatev3_test.py b/test/vtgatev3_test.py index 2de9e869777..557832dfd5b 100755 --- a/test/vtgatev3_test.py +++ b/test/vtgatev3_test.py @@ -1680,7 +1680,7 @@ def test_upsert(self): [(1, 1, 1, 1), (3, 3, 3, 0), (4, 4, 4, 0), (5, 5, 5, 5), (6, 6, 6, 6), (7, 7, 7, 7)]) - def test_joins(self): + def test_joins_subqueries(self): vtgate_conn = get_connection() vtgate_conn.begin() self.execute_on_master( @@ -1788,6 +1788,20 @@ def test_joins(self): [('id', self.int_type), ('name', self.string_type), ('info', self.string_type)])) + + # test a cross-shard subquery + result = self.execute_on_master( + vtgate_conn, + 'select id, name from join_user ' + 'where id in (select user_id from join_user_extra)', + {}) + self.assertEqual( + result, + ([(1L, 'name1')], + 1, + 0, + [('id', self.int_type), + ('name', self.string_type)])) vtgate_conn.begin() self.execute_on_master( vtgate_conn, diff --git a/test/vthook-make_mycnf b/test/vthook-make_mycnf new file mode 100755 index 00000000000..9755abc56ae --- /dev/null +++ b/test/vthook-make_mycnf @@ -0,0 +1,27 @@ +#!/bin/bash + +# Copyright 2018 The Vitess Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# this script creates a dummy my.cnf which won't actually work +echo "KEYSPACE=""$KEYSPACE" +echo "SHARD=""$SHARD" +echo "TABLET_TYPE=""$TABLET_TYPE" +echo "TABLET_ID=""$TABLET_ID" +echo "TABLET_DIR=""$TABLET_DIR" +echo "MYSQL_PORT=""$MYSQL_PORT" + +echo "server-id = 22222" +echo "datadir = /vt/vtdataroot/vt_0000011111/data" +echo "port = 6802" diff --git a/test/worker.py b/test/worker.py index d3f373ba6d9..b84567efb8b 100755 --- a/test/worker.py +++ b/test/worker.py @@ -448,12 +448,6 @@ def verify_successful_worker_copy_with_reparent(self, mysql_down=False): Raises: AssertionError if things didn't go as expected. """ - if mysql_down: - logging.debug('Shutting down mysqld on destination masters.') - utils.wait_procs( - [shard_0_master.shutdown_mysql(), - shard_1_master.shutdown_mysql()]) - worker_proc, worker_port, worker_rpc_port = utils.run_vtworker_bg( ['--cell', 'test_nj', '--use_v3_resharding_mode=false'], auto_log=True) @@ -471,14 +465,13 @@ def verify_successful_worker_copy_with_reparent(self, mysql_down=False): '--destination_writer_count', '1', '--min_healthy_rdonly_tablets', '1', '--max_tps', '9999'] - if not mysql_down: - # Make the clone as slow as necessary such that there is enough time to - # run PlannedReparent in the meantime. - # TODO(mberlin): Once insert_values is fixed to uniformly distribute the - # rows across shards when sorted by primary key, remove - # --chunk_count 2, --min_rows_per_chunk 1 and set - # --source_reader_count back to 1. - args.extend(['--source_reader_count', '2', + # Make the clone as slow as necessary such that there is enough time to + # run PlannedReparent in the meantime. + # TODO(mberlin): Once insert_values is fixed to uniformly distribute the + # rows across shards when sorted by primary key, remove + # --chunk_count 2, --min_rows_per_chunk 1 and set + # --source_reader_count back to 1. + args.extend(['--source_reader_count', '2', '--chunk_count', '2', '--min_rows_per_chunk', '1', '--write_query_max_rows', '1']) @@ -486,6 +479,23 @@ def verify_successful_worker_copy_with_reparent(self, mysql_down=False): workerclient_proc = utils.run_vtworker_client_bg(args, worker_rpc_port) if mysql_down: + # vtworker is blocked at this point. This is a good time to test that its + # throttler server is reacting to RPCs. + self.check_throttler_service('localhost:%d' % worker_rpc_port, + ['test_keyspace/-80', 'test_keyspace/80-'], + 9999) + + utils.poll_for_vars( + 'vtworker', worker_port, + 'WorkerState == cloning the data (online)', + condition_fn=lambda v: v.get('WorkerState') == 'cloning the' + ' data (online)') + + logging.debug('Worker is in copy state, Shutting down mysqld on destination masters.') + utils.wait_procs( + [shard_0_master.shutdown_mysql(), + shard_1_master.shutdown_mysql()]) + # If MySQL is down, we wait until vtworker retried at least once to make # sure it reached the point where a write failed due to MySQL being down. # There should be two retries at least, one for each destination shard. @@ -493,13 +503,7 @@ def verify_successful_worker_copy_with_reparent(self, mysql_down=False): 'vtworker', worker_port, 'WorkerRetryCount >= 2', condition_fn=lambda v: v.get('WorkerRetryCount') >= 2) - logging.debug('Worker has retried at least twice, starting reparent now') - - # vtworker is blocked at this point. This is a good time to test that its - # throttler server is reacting to RPCs. - self.check_throttler_service('localhost:%d' % worker_rpc_port, - ['test_keyspace/-80', 'test_keyspace/80-'], - 9999) + logging.debug('Worker has retried at least once per shard, starting reparent now') # Bring back masters. Since we test with semi-sync now, we need at least # one replica for the new master. This test is already quite expensive, diff --git a/vendor/vendor.json b/vendor/vendor.json index f7611722c64..0874880cda7 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -958,6 +958,12 @@ "revision": "04e1573abc896e70388bd387a69753c378d46466", "revisionTime": "2016-07-30T22:43:56Z" }, + { + "checksumSHA1": "Tr5yx/ucT6KllmNwUIRQUweZhWI=", + "path": "golang.org/x/sys/unix", + "revision": "ec83556a53fe16b65c452a104ea9d1e86a671852", + "revisionTime": "2018-11-19T19:44:06Z" + }, { "checksumSHA1": "Gwx30TD3lMSgskBCmXTDXbICPRQ=", "path": "golang.org/x/text/collate", @@ -1115,100 +1121,100 @@ "revisionTime": "2017-05-31T20:35:52Z" }, { - "checksumSHA1": "Ts0J7j6y5Js06AhCzZj9XNJoB+g=", + "checksumSHA1": "qniZg+TMtxqdBOxLukGnd5fBsmc=", "path": "google.golang.org/grpc", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { - "checksumSHA1": "xBhmO0Vn4kzbmySioX+2gBImrkk=", + "checksumSHA1": "B+kZFVP8zRiQMpoEb39Mp2oSmqg=", "path": "google.golang.org/grpc/balancer", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { - "checksumSHA1": "CPWX/IgaQSR3+78j4sPrvHNkW+U=", + "checksumSHA1": "lw+L836hLeH8+//le+C+ycddCCU=", "path": "google.golang.org/grpc/balancer/base", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { "checksumSHA1": "DJ1AtOk4Pu7bqtUMob95Hw8HPNw=", "path": "google.golang.org/grpc/balancer/roundrobin", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { - "checksumSHA1": "j8Qs+yfgwYYOtodB/1bSlbzV5rs=", + "checksumSHA1": "R3tuACGAPyK4lr+oSNt1saUzC0M=", "path": "google.golang.org/grpc/codes", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { "checksumSHA1": "XH2WYcDNwVO47zYShREJjcYXm0Y=", "path": "google.golang.org/grpc/connectivity", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { - "checksumSHA1": "KthiDKNPHMeIu967enqtE4NaZzI=", + "checksumSHA1": "wA6y5rkH1v4bWBe5M1r/Hdtgma4=", "path": "google.golang.org/grpc/credentials", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { "checksumSHA1": "QbufP1o0bXrtd5XecqdRCK/Vl0M=", "path": "google.golang.org/grpc/credentials/oauth", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { - "checksumSHA1": "mJTBJC0n9J2CV+tHX+dJosYOZmg=", + "checksumSHA1": "cfLb+pzWB+Glwp82rgfcEST1mv8=", "path": "google.golang.org/grpc/encoding", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { "checksumSHA1": "LKKkn7EYA+Do9Qwb2/SUKLFNxoo=", "path": "google.golang.org/grpc/encoding/proto", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { "checksumSHA1": "H7SuPUqbPcdbNqgl+k3ohuwMAwE=", "path": "google.golang.org/grpc/grpclb/grpc_lb_v1/messages", "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { - "checksumSHA1": "ntHev01vgZgeIh5VFRmbLx/BSTo=", + "checksumSHA1": "ZPPSFisPDz2ANO4FBZIft+fRxyk=", "path": "google.golang.org/grpc/grpclog", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { "checksumSHA1": "QyasSHZlgle+PHSIQ2/p+fr+ihY=", @@ -1219,100 +1225,140 @@ "versionExact": "v1.11.2" }, { - "checksumSHA1": "Qvf3zdmRCSsiM/VoBv0qB/naHtU=", + "checksumSHA1": "8uLpHZuwD6Ug/QlvN94QyHaOack=", "path": "google.golang.org/grpc/internal", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" + }, + { + "checksumSHA1": "uDJA7QK2iGnEwbd9TPqkLaM+xuU=", + "path": "google.golang.org/grpc/internal/backoff", + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" + }, + { + "checksumSHA1": "8dcRbrJWAcFQIXVuchR6z4ItIzg=", + "path": "google.golang.org/grpc/internal/channelz", + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" + }, + { + "checksumSHA1": "5dFUCEaPjKwza9kwKqgljp8ckU4=", + "path": "google.golang.org/grpc/internal/envconfig", + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" + }, + { + "checksumSHA1": "70gndc/uHwyAl3D45zqp7vyHWlo=", + "path": "google.golang.org/grpc/internal/grpcrand", + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" + }, + { + "checksumSHA1": "3/+ZIaJhkKSSqt6Z3VDS0Q8zMXA=", + "path": "google.golang.org/grpc/internal/transport", + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { "checksumSHA1": "hcuHgKp8W0wIzoCnNfKI8NUss5o=", "path": "google.golang.org/grpc/keepalive", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { - "checksumSHA1": "RUgjR0iUFLCgdLAnNqiH+8jTzuk=", + "checksumSHA1": "OjIAi5AzqlQ7kLtdAyjvdgMf6hc=", "path": "google.golang.org/grpc/metadata", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { - "checksumSHA1": "5dwF592DPvhF2Wcex3m7iV6aGRQ=", + "checksumSHA1": "VvGBoawND0urmYDy11FT+U1IHtU=", "path": "google.golang.org/grpc/naming", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { "checksumSHA1": "n5EgDdBqFMa2KQFhtl+FF/4gIFo=", "path": "google.golang.org/grpc/peer", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { - "checksumSHA1": "qbA3XLvX0RTvaqQefvFDtE9GaJs=", + "checksumSHA1": "GEq6wwE1qWLmkaM02SjxBmmnHDo=", "path": "google.golang.org/grpc/resolver", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { - "checksumSHA1": "WpWF+bDzObsHf+bjoGpb/abeFxo=", + "checksumSHA1": "90rwIeFK1zLW8M57MfzmejpCwP4=", "path": "google.golang.org/grpc/resolver/dns", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { "checksumSHA1": "zs9M4xE8Lyg4wvuYvR00XoBxmuw=", "path": "google.golang.org/grpc/resolver/passthrough", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { "checksumSHA1": "YclPgme2gT3S0hTkHVdE1zAxJdo=", "path": "google.golang.org/grpc/stats", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { - "checksumSHA1": "FXiovlBmrYdS4QT0Z4nV+x+v5HI=", + "checksumSHA1": "t/NhHuykWsxY0gEBd2WIv5RVBK8=", "path": "google.golang.org/grpc/status", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { "checksumSHA1": "qvArRhlrww5WvRmbyMF2mUfbJew=", "path": "google.golang.org/grpc/tap", - "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", - "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "revision": "8dea3dc473e90c8179e519d91302d0597c0ca1d1", + "revisionTime": "2018-09-11T17:48:51Z", + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { "checksumSHA1": "sg7RY87LaWXaZMj0cuLQQaJJQYo=", "path": "google.golang.org/grpc/transport", "revision": "d89cded64628466c4ab532d1f0ba5c220459ebe8", "revisionTime": "2018-04-04T21:41:50Z", - "version": "v1.11.2", - "versionExact": "v1.11.2" + "version": "v1.15.0", + "versionExact": "v1.15.0" }, { "checksumSHA1": "wSu8owMAP7GixsYoSZ4CmKUVhnU=",