-- Export distributed snapshot tests

-- start_matchsubs
-- m/[0-9a-fA-F]+-[0-9a-fA-F]+-\d/
-- s/[0-9a-fA-F]+-[0-9a-fA-F]+-\d/#######-########-#/

-- m/^DETAIL:  The source process with PID \d+ is not running anymore./
-- s/^DETAIL:  The source process with PID \d+ is not running anymore./DETAIL:  The source process with PID is not running anymore./
-- end_matchsubs

-- start_ignore
DROP FUNCTION IF EXISTS corrupt_snapshot_file(text, text);
DROP FUNCTION IF EXISTS snapshot_file_ds_fields_exist(text);
DROP LANGUAGE IF EXISTS plpython3u;
DROP TABLE IF EXISTS export_distributed_snapshot_test1;

CREATE LANGUAGE plpython3u;
-- end_ignore

-- Corrupt field entry for given snapshot file
CREATE OR REPLACE FUNCTION  corrupt_snapshot_file(token text, field text) RETURNS integer as
$$
  import os
  content = bytearray()
  query = "select (select datadir from gp_segment_configuration where role='p' and content=-1) || '/pg_snapshots/' as path"
  rv = plpy.execute(query)
  abs_path = rv[0]['path']
  snapshot_file = abs_path + token
  if not os.path.isfile(snapshot_file):
    plpy.info('skipping non-existent file %s' % (snapshot_file))
  else:
    plpy.info('corrupting file %s for field %s' % (snapshot_file, field))
    with open(snapshot_file , "rb+") as f:
      for line in f:
        l = line.decode()
        id = l.split(":")[0]
        if field == id:
          corrupt = l[:-2] + '*' + l[len(l)-1:]
          content.extend(corrupt.encode())
        else:
          content.extend(line)
      f.seek(0)
      f.truncate
      f.write(content)
      f.close()
  return 0
$$ LANGUAGE plpython3u;

-- Determine if field exists for given snapshot file
CREATE OR REPLACE FUNCTION  snapshot_file_ds_fields_exist(token text) RETURNS boolean as
$$
  import os
  content = bytearray()
  query = "select (select datadir from gp_segment_configuration where role='p' and content=-1) || '/pg_snapshots/' as path"
  rv = plpy.execute(query)
  abs_path = rv[0]['path']
  snapshot_file = abs_path + token
  if not os.path.isfile(snapshot_file):
    plpy.info('snapshot file %s does not exist' % (snapshot_file))
    return -1
  else:
    plpy.info('checking file %s for ds fields' % (snapshot_file))
    with open(snapshot_file , "rb+") as f:
      for line in f:
        l = line.decode()
        if "ds" in l:
          return True
  return False
$$ LANGUAGE plpython3u;

-- INSERT test
CREATE TABLE export_distributed_snapshot_test1 (a int);
INSERT INTO export_distributed_snapshot_test1 values(1);

1: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
1: @post_run ' TOKEN=`echo "${RAW_STR}" | awk \'NR==3\' | awk \'{print $1}\'` && echo "${RAW_STR}"': SELECT pg_export_snapshot();

INSERT INTO export_distributed_snapshot_test1 values(2);
SELECT * FROM  export_distributed_snapshot_test1;

-- Transaction 2 should return 2 rows
2: SELECT * FROM  export_distributed_snapshot_test1;
2: COMMIT;

2: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
2: @pre_run 'echo "${RAW_STR}" | sed "s#@TOKEN#${TOKEN}#"': SET TRANSACTION SNAPSHOT '@TOKEN';
-- Transaction 2 should now return 1 row
2: SELECT * FROM  export_distributed_snapshot_test1;
2: COMMIT;

1: COMMIT;

-- DELETE test
CREATE TABLE export_distributed_snapshot_test2 (a int);
INSERT INTO export_distributed_snapshot_test2 SELECT a FROM generate_series(1,3) a;

1: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
1: @post_run ' TOKEN=`echo "${RAW_STR}" | awk \'NR==3\' | awk \'{print $1}\'` && echo "${RAW_STR}"': SELECT pg_export_snapshot();

DELETE FROM export_distributed_snapshot_test2 WHERE a=1;

-- Should return 3 rows
1: SELECT * FROM export_distributed_snapshot_test2 ;

-- Should return 2 rows
2: SELECT * FROM  export_distributed_snapshot_test2;

2: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
2: @pre_run 'echo "${RAW_STR}" | sed "s#@TOKEN#${TOKEN}#"': SET TRANSACTION SNAPSHOT '@TOKEN';

-- Should return 3 rows
2: SELECT * FROM  export_distributed_snapshot_test2;
2: COMMIT;

1: COMMIT;

-- UPDATE test
CREATE TABLE export_distributed_snapshot_test3 (a int);
INSERT INTO export_distributed_snapshot_test3 SELECT a FROM generate_series(1,5) a;

1: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
1: @post_run ' TOKEN=`echo "${RAW_STR}" | awk \'NR==3\' | awk \'{print $1}\'` && echo "${RAW_STR}"': SELECT pg_export_snapshot();

UPDATE export_distributed_snapshot_test3 SET a=99 WHERE a=1;

-- Should return 0 rows
1: SELECT * FROM export_distributed_snapshot_test3 WHERE a=99;

-- Should return 1 row
2: SELECT * FROM export_distributed_snapshot_test3 WHERE a=99;

2: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
2: @pre_run 'echo "${RAW_STR}" | sed "s#@TOKEN#${TOKEN}#"': SET TRANSACTION SNAPSHOT '@TOKEN';

-- Should return 0 rows
2: SELECT * FROM export_distributed_snapshot_test3 WHERE a=99;
2: COMMIT;

-- Should return 1 row
2: SELECT * FROM export_distributed_snapshot_test3 WHERE a=99;

1: COMMIT;

-- DROP test
CREATE TABLE export_distributed_snapshot_test4 (a int);

1: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
2: BEGIN;

1: @post_run ' TOKEN=`echo "${RAW_STR}" | awk \'NR==3\' | awk \'{print $1}\'` && echo "${RAW_STR}"': SELECT pg_export_snapshot();
1: SELECT gp_segment_id, relname from gp_dist_random('pg_class') where relname = 'export_distributed_snapshot_test4';

-- Drop table in transaction
2: DROP TABLE export_distributed_snapshot_test4;
2: COMMIT;

3: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
3: @pre_run 'echo "${RAW_STR}" | sed "s#@TOKEN#${TOKEN}#"': SET TRANSACTION SNAPSHOT '@TOKEN';
-- The table should still be visible to transaction 3 using transaction 1's snapshot.
3: SELECT gp_segment_id, relname from gp_dist_random('pg_class') where relname = 'export_distributed_snapshot_test4';
3: COMMIT;
-- The table should no longer be visible to transaction 3.
3: SELECT gp_segment_id, relname from gp_dist_random('pg_class') where relname = 'export_distributed_snapshot_test4';

1: COMMIT;

-- Test corrupt fields in snapshot file

-- xmin
1: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;

1: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
1: @post_run ' TOKEN=`echo "${RAW_STR}" | awk \'NR==3\' | awk \'{print $1}\'` && echo "${RAW_STR}"': SELECT pg_export_snapshot();

2: @pre_run 'echo "${RAW_STR}" | sed "s#@TOKEN#${TOKEN}#"': select corrupt_snapshot_file('@TOKEN', 'xmin');

2: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
2: @pre_run 'echo "${RAW_STR}" | sed "s#@TOKEN#${TOKEN}#"': SET TRANSACTION SNAPSHOT '@TOKEN';

1: END;
2: END;

-- dsxminall

1: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;

1: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
1: @post_run ' TOKEN=`echo "${RAW_STR}" | awk \'NR==3\' | awk \'{print $1}\'` && echo "${RAW_STR}"': SELECT pg_export_snapshot();

2: @pre_run 'echo "${RAW_STR}" | sed "s#@TOKEN#${TOKEN}#"': select corrupt_snapshot_file('@TOKEN', 'dsxminall');

2: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
2: @pre_run 'echo "${RAW_STR}" | sed "s#@TOKEN#${TOKEN}#"': SET TRANSACTION SNAPSHOT '@TOKEN';

1: END;
2: END;

-- dsxmin
1: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;

1: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
1: @post_run ' TOKEN=`echo "${RAW_STR}" | awk \'NR==3\' | awk \'{print $1}\'` && echo "${RAW_STR}"': SELECT pg_export_snapshot();

2: @pre_run 'echo "${RAW_STR}" | sed "s#@TOKEN#${TOKEN}#"': select corrupt_snapshot_file('@TOKEN', 'dsxmin');

2: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
2: @pre_run 'echo "${RAW_STR}" | sed "s#@TOKEN#${TOKEN}#"': SET TRANSACTION SNAPSHOT '@TOKEN';

1: END;
2: END;

-- Test export snapshot in utility mode does not export distributed snapshot fields

-1U: BEGIN;
-1U: BEGIN;
-1U: @post_run ' TOKEN=`echo "${RAW_STR}" | awk \'NR==3\' | awk \'{print $1}\'` && echo "${RAW_STR}"': SELECT pg_export_snapshot();

-- Should return false
1: @pre_run 'echo "${RAW_STR}" | sed "s#@TOKEN#${TOKEN}#"': select snapshot_file_ds_fields_exist('@TOKEN');

-1Uq:
1: END;

-- Test import snapshot in utility mode fails if distributed snapshot fields exist
1: BEGIN;
1: BEGIN;
1: @post_run ' TOKEN=`echo "${RAW_STR}" | awk \'NR==3\' | awk \'{print $1}\'` && echo "${RAW_STR}"': SELECT pg_export_snapshot();

-- Open utility mode connection on coordinator and set snapshot
-1U: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-1U: @pre_run 'echo "${RAW_STR}" | sed "s#@TOKEN#${TOKEN}#"': SET TRANSACTION SNAPSHOT '@TOKEN';

-1Uq:
1: END;

-- Test export snapshot in utility mode and import snapshot in utility mode succeeds
-1U: @db_name postgres: BEGIN;
-1U: BEGIN;
-1U: @post_run ' TOKEN=`echo "${RAW_STR}" | awk \'NR==3\' | awk \'{print $1}\'` && echo "${RAW_STR}"': SELECT pg_export_snapshot();

-- Should return false
1: @pre_run 'echo "${RAW_STR}" | sed "s#@TOKEN#${TOKEN}#"': select snapshot_file_ds_fields_exist('@TOKEN');

-- Open another utility mode connection and set the snapshot
! TOKEN=$(find ${COORDINATOR_DATA_DIRECTORY}/pg_snapshots/ -name "0*" -exec basename {} \;) \
&& echo ${TOKEN} \
&& PGOPTIONS='-c gp_role=utility' psql postgres -Atc "BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; SET TRANSACTION SNAPSHOT '${TOKEN}';";

-1Uq:
