diff --git a/test/distributed/cases/git4data/branch/edge/branch_edge_cases.result b/test/distributed/cases/git4data/branch/edge/branch_edge_cases.result new file mode 100644 index 0000000000000..67428bcdb1e9b --- /dev/null +++ b/test/distributed/cases/git4data/branch/edge/branch_edge_cases.result @@ -0,0 +1,158 @@ +drop database if exists br_index; +create database br_index; +use br_index; +create table base( +id int primary key, +u int, +k int, +v varchar(20), +unique key uk_u(u), +key idx_k(k) +); +insert into base values +(1, 101, 10, 'one'), +(2, 102, 20, 'two'), +(3, 103, 30, 'three'); +data branch create table dst from base; +data branch create table src from base; +insert into src values (4, 104, 20, 'four'); +update src set k = 40, v = 'two-src' where id = 2; +data branch pick src into dst keys(2, 4) when conflict accept; +select id, u, k, v from dst where k = 20 order by id; +id u k v +4 104 20 four +select id, u, k, v from dst where k = 40 order by id; +id u k v +2 102 40 two-src +insert into dst values (5, 104, 50, 'dup-u'); +-- @regex("Duplicate entry '104' for key '(u|__mo_index_idx_col)'(\[[0-9,]+\])?", true) +Duplicate entry '104' for key 'u' +data branch create table merge_dst from base; +data branch create table merge_src from base; +insert into merge_src values (4, 104, 40, 'four'); +data branch merge merge_src into merge_dst; +select id, u, k, v from merge_dst where u = 104; +id u k v +4 104 40 four +data branch create table conflict_dst from base; +data branch create table conflict_src from base; +insert into conflict_dst values (4, 104, 40, 'dst-four'); +insert into conflict_src values (5, 104, 50, 'src-five'); +data branch merge conflict_src into conflict_dst when conflict accept; +-- @regex("Duplicate entry '104' for key '(u|__mo_index_idx_col)'(\[[0-9,]+\])?", true) +Duplicate entry '104' for key 'u' +select id, u, k, v from conflict_dst order by id; +id u k v +1 101 10 one +2 102 20 two +3 103 30 three +4 104 40 dst-four +drop database br_index; +drop database if exists br_schema_drift; +create database br_schema_drift; +use br_schema_drift; +create table base(a int primary key, b int); +insert into base values (1, 10), (2, 20), (3, 30); +data branch create table left_add from base; +data branch create table right_add from base; +alter table left_add add column c varchar(20) default 'left-only'; +update left_add set b = 11, c = 'changed' where a = 1; +insert into right_add values (4, 40); +data branch diff left_add against right_add columns (a, b) output summary; +internal error: the target table schema is not equivalent to the base table. +data branch create table both_add_left from base; +data branch create table both_add_right from base; +alter table both_add_left add column c varchar(20) default 'same'; +alter table both_add_right add column c varchar(20) default 'same'; +update both_add_left set c = 'left' where a = 2; +update both_add_right set c = 'right' where a = 3; +data branch diff both_add_left against both_add_right columns (a, c); +diff both_add_left against both_add_right flag a c +both_add_left INSERT 2 left +both_add_right INSERT 2 same +both_add_left INSERT 3 same +both_add_right INSERT 3 right +data branch create table drop_left from base; +data branch create table drop_right from base; +alter table drop_left drop column b; +data branch diff drop_left against drop_right; +internal error: the target table schema is not equivalent to the base table. +data branch create table rename_left from base; +data branch create table rename_right from base; +alter table rename_left rename column b to bb; +data branch diff rename_left against rename_right; +diff rename_left against rename_right flag a bb +drop database br_schema_drift; +drop database if exists br_txn_src; +drop database if exists br_txn_dst; +create database br_txn_src; +use br_txn_src; +create table base(a int primary key, b varchar(20)); +insert into base values (1, 'one'), (2, 'two'); +begin; +data branch create table br_commit from base; +commit; +select count(*) as br_commit_rows from br_commit; +br_commit_rows +2 +begin; +data branch create table br_rollback from base; +rollback; +select count(*) as br_rollback_tables +from mo_catalog.mo_tables +where reldatabase = 'br_txn_src' and relname = 'br_rollback'; +br_rollback_tables +0 +begin; +data branch delete table br_txn_src.br_commit; +rollback; +select count(*) as br_commit_after_rollback +from mo_catalog.mo_tables +where reldatabase = 'br_txn_src' and relname = 'br_commit'; +br_commit_after_rollback +1 +begin; +data branch delete table br_txn_src.br_commit; +commit; +select count(*) as br_commit_after_commit +from mo_catalog.mo_tables +where reldatabase = 'br_txn_src' and relname = 'br_commit'; +br_commit_after_commit +0 +data branch create table pick_dst from base; +data branch create table pick_src from base; +insert into pick_src values (3, 'three'); +begin; +data branch pick pick_src into pick_dst keys(3) when conflict accept; +-- @regex("(DATA BRANCH PICK is not supported in explicit transactions|Write conflicts detected|txn need retry|deadlock detected)", true) +internal error: DATA BRANCH PICK is not supported in explicit transactions +rollback; +select count(*) as pick_dst_rows from pick_dst; +pick_dst_rows +2 +data branch create database br_txn_dst from br_txn_src; +select count(*) as dst_tables +from mo_catalog.mo_tables +where reldatabase = 'br_txn_dst' +and relname in ('base', 'pick_dst', 'pick_src'); +dst_tables +3 +begin; +data branch delete database br_txn_dst; +rollback; +select count(*) as dst_tables_after_rollback +from mo_catalog.mo_tables +where reldatabase = 'br_txn_dst' +and relname in ('base', 'pick_dst', 'pick_src'); +dst_tables_after_rollback +3 +begin; +data branch delete database br_txn_dst; +commit; +select count(*) as dst_tables_after_commit +from mo_catalog.mo_tables +where reldatabase = 'br_txn_dst'; +dst_tables_after_commit +0 +drop database if exists br_txn_dst; +drop database br_txn_src; diff --git a/test/distributed/cases/git4data/branch/edge/branch_edge_cases.sql b/test/distributed/cases/git4data/branch/edge/branch_edge_cases.sql new file mode 100644 index 0000000000000..a103f02062eb9 --- /dev/null +++ b/test/distributed/cases/git4data/branch/edge/branch_edge_cases.sql @@ -0,0 +1,156 @@ +-- Data branch edge cases that are not covered by basic diff/merge/pick flows. + +-- Case 1: secondary/unique indexes remain valid after PICK/MERGE. +drop database if exists br_index; +create database br_index; +use br_index; + +create table base( + id int primary key, + u int, + k int, + v varchar(20), + unique key uk_u(u), + key idx_k(k) +); +insert into base values + (1, 101, 10, 'one'), + (2, 102, 20, 'two'), + (3, 103, 30, 'three'); + +data branch create table dst from base; +data branch create table src from base; + +insert into src values (4, 104, 20, 'four'); +update src set k = 40, v = 'two-src' where id = 2; +data branch pick src into dst keys(2, 4) when conflict accept; + +select id, u, k, v from dst where k = 20 order by id; +select id, u, k, v from dst where k = 40 order by id; + +-- Ordinary unique-index enforcement still rejects duplicates after PICK. +-- @regex("Duplicate entry '104' for key '(u|__mo_index_idx_col)'(\[[0-9,]+\])?",true) +insert into dst values (5, 104, 50, 'dup-u'); + +data branch create table merge_dst from base; +data branch create table merge_src from base; +insert into merge_src values (4, 104, 40, 'four'); +data branch merge merge_src into merge_dst; +select id, u, k, v from merge_dst where u = 104; + +data branch create table conflict_dst from base; +data branch create table conflict_src from base; +insert into conflict_dst values (4, 104, 40, 'dst-four'); +insert into conflict_src values (5, 104, 50, 'src-five'); +-- MERGE must not bypass unique-index validation. +-- @regex("Duplicate entry '104' for key '(u|__mo_index_idx_col)'(\[[0-9,]+\])?",true) +data branch merge conflict_src into conflict_dst when conflict accept; +select id, u, k, v from conflict_dst order by id; + +drop database br_index; + +-- Case 2: schema drift after branching. +drop database if exists br_schema_drift; +create database br_schema_drift; +use br_schema_drift; + +create table base(a int primary key, b int); +insert into base values (1, 10), (2, 20), (3, 30); + +data branch create table left_add from base; +data branch create table right_add from base; +alter table left_add add column c varchar(20) default 'left-only'; +update left_add set b = 11, c = 'changed' where a = 1; +insert into right_add values (4, 40); +data branch diff left_add against right_add columns (a, b) output summary; + +data branch create table both_add_left from base; +data branch create table both_add_right from base; +alter table both_add_left add column c varchar(20) default 'same'; +alter table both_add_right add column c varchar(20) default 'same'; +update both_add_left set c = 'left' where a = 2; +update both_add_right set c = 'right' where a = 3; +data branch diff both_add_left against both_add_right columns (a, c); + +data branch create table drop_left from base; +data branch create table drop_right from base; +alter table drop_left drop column b; +-- Divergent schemas are rejected with a schema-equivalence error. +data branch diff drop_left against drop_right; + +data branch create table rename_left from base; +data branch create table rename_right from base; +alter table rename_left rename column b to bb; +-- A rename on one branch keeps the renamed table queryable for diff. +data branch diff rename_left against rename_right; + +drop database br_schema_drift; + +-- Case 3: transaction behavior. +drop database if exists br_txn_src; +drop database if exists br_txn_dst; +create database br_txn_src; +use br_txn_src; + +create table base(a int primary key, b varchar(20)); +insert into base values (1, 'one'), (2, 'two'); + +begin; +data branch create table br_commit from base; +commit; +select count(*) as br_commit_rows from br_commit; + +begin; +data branch create table br_rollback from base; +rollback; +select count(*) as br_rollback_tables + from mo_catalog.mo_tables + where reldatabase = 'br_txn_src' and relname = 'br_rollback'; + +begin; +data branch delete table br_txn_src.br_commit; +rollback; +select count(*) as br_commit_after_rollback + from mo_catalog.mo_tables + where reldatabase = 'br_txn_src' and relname = 'br_commit'; + +begin; +data branch delete table br_txn_src.br_commit; +commit; +select count(*) as br_commit_after_commit + from mo_catalog.mo_tables + where reldatabase = 'br_txn_src' and relname = 'br_commit'; + +data branch create table pick_dst from base; +data branch create table pick_src from base; +insert into pick_src values (3, 'three'); + +begin; +-- @regex("(DATA BRANCH PICK is not supported in explicit transactions|Write conflicts detected|txn need retry|deadlock detected)",true) +data branch pick pick_src into pick_dst keys(3) when conflict accept; +rollback; +select count(*) as pick_dst_rows from pick_dst; + +data branch create database br_txn_dst from br_txn_src; +select count(*) as dst_tables + from mo_catalog.mo_tables + where reldatabase = 'br_txn_dst' + and relname in ('base', 'pick_dst', 'pick_src'); + +begin; +data branch delete database br_txn_dst; +rollback; +select count(*) as dst_tables_after_rollback + from mo_catalog.mo_tables + where reldatabase = 'br_txn_dst' + and relname in ('base', 'pick_dst', 'pick_src'); + +begin; +data branch delete database br_txn_dst; +commit; +select count(*) as dst_tables_after_commit + from mo_catalog.mo_tables + where reldatabase = 'br_txn_dst'; + +drop database if exists br_txn_dst; +drop database br_txn_src; diff --git a/test/distributed/cases/git4data/branch/edge/branch_privilege.result b/test/distributed/cases/git4data/branch/edge/branch_privilege.result new file mode 100644 index 0000000000000..e529f11d70aa2 --- /dev/null +++ b/test/distributed/cases/git4data/branch/edge/branch_privilege.result @@ -0,0 +1,87 @@ +set global enable_privilege_cache = off; +drop account if exists br_priv_bypass_acc; +create account br_priv_bypass_acc admin_name "admin" identified by "111"; +drop database if exists br_priv_src; +create database br_priv_src; +use br_priv_src; +create table base_tbl(a int primary key, b varchar(20)); +insert into base_tbl values (1, 'one'), (2, 'two'); +create snapshot br_priv_tbl_sp for table br_priv_src base_tbl; +create snapshot br_priv_db_sp for database br_priv_src; +create role r_tbl_select_only; +create role r_tbl_create_only; +create role r_tbl_both; +grant select on table br_priv_src.base_tbl to r_tbl_select_only; +grant create table on database br_priv_src to r_tbl_create_only; +grant select on table br_priv_src.base_tbl to r_tbl_both; +grant create table on database br_priv_src to r_tbl_both; +create user u_tbl_select_only identified by '111' default role r_tbl_select_only; +create user u_tbl_create_only identified by '111' default role r_tbl_create_only; +create user u_tbl_both identified by '111' default role r_tbl_both; +create role r_db_select_only; +create role r_db_create_only; +create role r_db_both; +grant select on table br_priv_src.base_tbl to r_db_select_only; +grant create database on account * to r_db_create_only; +grant select on table br_priv_src.base_tbl to r_db_both; +grant create database on account * to r_db_both; +create user u_db_select_only identified by '111' default role r_db_select_only; +create user u_db_create_only identified by '111' default role r_db_create_only; +create user u_db_both identified by '111' default role r_db_both; +data branch create table br_priv_src.br_tbl_select_only +from br_priv_src.base_tbl{snapshot='br_priv_tbl_sp'}; + +select count(*) as br_tbl_select_only_exists +from mo_catalog.mo_tables +where reldatabase = 'br_priv_src' +and relname = 'br_tbl_select_only'; +br_tbl_select_only_exists +1 +data branch create table br_priv_src.br_tbl_create_only +from br_priv_src.base_tbl{snapshot='br_priv_tbl_sp'}; + +select count(*) as br_tbl_create_only_exists +from mo_catalog.mo_tables +where reldatabase = 'br_priv_src' +and relname = 'br_tbl_create_only'; +br_tbl_create_only_exists +1 +data branch create table br_priv_src.br_tbl_both +from br_priv_src.base_tbl{snapshot='br_priv_tbl_sp'}; +select count(*) as br_tbl_both_exists +from mo_catalog.mo_tables +where reldatabase = 'br_priv_src' +and relname = 'br_tbl_both'; +br_tbl_both_exists +1 +data branch create database br_db_select_only +from br_priv_src{snapshot='br_priv_db_sp'}; + +select count(*) as br_db_select_only_exists +from mo_catalog.mo_database +where datname = 'br_db_select_only'; +br_db_select_only_exists +1 +data branch create database br_db_create_only +from br_priv_src{snapshot='br_priv_db_sp'}; + +select count(*) as br_db_create_only_exists +from mo_catalog.mo_database +where datname = 'br_db_create_only'; +br_db_create_only_exists +1 +data branch create database br_db_both +from br_priv_src{snapshot='br_priv_db_sp'}; +select count(*) as br_db_both_exists +from mo_catalog.mo_database +where datname = 'br_db_both'; +br_db_both_exists +1 +drop snapshot br_priv_tbl_sp; +drop snapshot br_priv_db_sp; +drop database if exists br_priv_src; +drop database if exists br_db_select_only; +drop database if exists br_db_create_only; +drop database if exists br_db_both; +drop account if exists br_priv_bypass_acc; +set global enable_privilege_cache = on; diff --git a/test/distributed/cases/git4data/branch/edge/branch_privilege.sql b/test/distributed/cases/git4data/branch/edge/branch_privilege.sql new file mode 100644 index 0000000000000..88df1a3492b5e --- /dev/null +++ b/test/distributed/cases/git4data/branch/edge/branch_privilege.sql @@ -0,0 +1,156 @@ +-- DATA BRANCH CREATE privilege bypass repro. +-- +-- Official contract: +-- 1. The user needs read privileges on the source table/database. +-- 2. The user needs create privileges on the destination table/database. +-- +-- Current bug: +-- DATA BRANCH CREATE TABLE succeeds when either source SELECT or target +-- CREATE TABLE privilege is missing. +-- DATA BRANCH CREATE DATABASE succeeds when either source read privilege or +-- CREATE DATABASE privilege is missing. + +set global enable_privilege_cache = off; + +drop account if exists br_priv_bypass_acc; +create account br_priv_bypass_acc admin_name "admin" identified by "111"; + +-- @session:id=1&user=br_priv_bypass_acc:admin&password=111 +drop database if exists br_priv_src; +create database br_priv_src; +use br_priv_src; + +create table base_tbl(a int primary key, b varchar(20)); +insert into base_tbl values (1, 'one'), (2, 'two'); +create snapshot br_priv_tbl_sp for table br_priv_src base_tbl; +create snapshot br_priv_db_sp for database br_priv_src; + +-- Roles for table-branch privilege matrix. +create role r_tbl_select_only; +create role r_tbl_create_only; +create role r_tbl_both; + +grant select on table br_priv_src.base_tbl to r_tbl_select_only; +grant create table on database br_priv_src to r_tbl_create_only; +grant select on table br_priv_src.base_tbl to r_tbl_both; +grant create table on database br_priv_src to r_tbl_both; + +create user u_tbl_select_only identified by '111' default role r_tbl_select_only; +create user u_tbl_create_only identified by '111' default role r_tbl_create_only; +create user u_tbl_both identified by '111' default role r_tbl_both; + +-- Roles for database-branch privilege matrix. +create role r_db_select_only; +create role r_db_create_only; +create role r_db_both; + +grant select on table br_priv_src.base_tbl to r_db_select_only; +grant create database on account * to r_db_create_only; +grant select on table br_priv_src.base_tbl to r_db_both; +grant create database on account * to r_db_both; + +create user u_db_select_only identified by '111' default role r_db_select_only; +create user u_db_create_only identified by '111' default role r_db_create_only; +create user u_db_both identified by '111' default role r_db_both; + +-- Case 1: TABLE branch with only source SELECT privilege. +-- Expected after fix: +-- should fail because the user does not have CREATE TABLE on br_priv_src. +-- Current actual: +-- succeeds and creates br_tbl_select_only. +-- @session:id=2&user=br_priv_bypass_acc:u_tbl_select_only:r_tbl_select_only&password=111 +-- @bvt:issue#24840 +data branch create table br_priv_src.br_tbl_select_only + from br_priv_src.base_tbl{snapshot='br_priv_tbl_sp'}; + +-- @session:id=1&user=br_priv_bypass_acc:admin&password=111 +select count(*) as br_tbl_select_only_exists + from mo_catalog.mo_tables + where reldatabase = 'br_priv_src' + and relname = 'br_tbl_select_only'; +-- @bvt:issue + +-- Case 2: TABLE branch with only target CREATE TABLE privilege. +-- Expected after fix: +-- should fail because the user does not have SELECT on br_priv_src.base_tbl. +-- Current actual: +-- succeeds and creates br_tbl_create_only. +-- @session:id=3&user=br_priv_bypass_acc:u_tbl_create_only:r_tbl_create_only&password=111 +-- @bvt:issue#24840 +data branch create table br_priv_src.br_tbl_create_only + from br_priv_src.base_tbl{snapshot='br_priv_tbl_sp'}; + +-- @session:id=1&user=br_priv_bypass_acc:admin&password=111 +select count(*) as br_tbl_create_only_exists + from mo_catalog.mo_tables + where reldatabase = 'br_priv_src' + and relname = 'br_tbl_create_only'; +-- @bvt:issue + +-- Case 3: TABLE branch with both privileges. +-- Expected: +-- succeeds. +-- @session:id=4&user=br_priv_bypass_acc:u_tbl_both:r_tbl_both&password=111 +data branch create table br_priv_src.br_tbl_both + from br_priv_src.base_tbl{snapshot='br_priv_tbl_sp'}; + +-- @session:id=1&user=br_priv_bypass_acc:admin&password=111 +select count(*) as br_tbl_both_exists + from mo_catalog.mo_tables + where reldatabase = 'br_priv_src' + and relname = 'br_tbl_both'; + +-- Case 4: DATABASE branch with only source-table SELECT privilege. +-- Expected after fix: +-- should fail because the user does not have CREATE DATABASE. +-- Current actual: +-- succeeds and creates br_db_select_only. +-- @session:id=5&user=br_priv_bypass_acc:u_db_select_only:r_db_select_only&password=111 +-- @bvt:issue#24840 +data branch create database br_db_select_only + from br_priv_src{snapshot='br_priv_db_sp'}; + +-- @session:id=1&user=br_priv_bypass_acc:admin&password=111 +select count(*) as br_db_select_only_exists + from mo_catalog.mo_database + where datname = 'br_db_select_only'; +-- @bvt:issue + +-- Case 5: DATABASE branch with only CREATE DATABASE privilege. +-- Expected after fix: +-- should fail because the user does not have read privilege on source data. +-- Current actual: +-- succeeds and creates br_db_create_only. +-- @session:id=6&user=br_priv_bypass_acc:u_db_create_only:r_db_create_only&password=111 +-- @bvt:issue#24840 +data branch create database br_db_create_only + from br_priv_src{snapshot='br_priv_db_sp'}; + +-- @session:id=1&user=br_priv_bypass_acc:admin&password=111 +select count(*) as br_db_create_only_exists + from mo_catalog.mo_database + where datname = 'br_db_create_only'; +-- @bvt:issue + +-- Case 6: DATABASE branch with both privileges. +-- Expected: +-- succeeds. +-- @session:id=7&user=br_priv_bypass_acc:u_db_both:r_db_both&password=111 +data branch create database br_db_both + from br_priv_src{snapshot='br_priv_db_sp'}; + +-- @session:id=1&user=br_priv_bypass_acc:admin&password=111 +select count(*) as br_db_both_exists + from mo_catalog.mo_database + where datname = 'br_db_both'; + +drop snapshot br_priv_tbl_sp; +drop snapshot br_priv_db_sp; +drop database if exists br_priv_src; +drop database if exists br_db_select_only; +drop database if exists br_db_create_only; +drop database if exists br_db_both; + +-- @session +drop account if exists br_priv_bypass_acc; +set global enable_privilege_cache = on;