blob: 949f22f00026a67e01aa8380e045bc4fb5ad9df8 [file] [log] [blame]
create or replace language plpython3u;
-- Helper function, to call either __gp_aoseg, or gp_aocsseg, depending
-- on whether the table is row- or column-oriented. This allows us to
-- run the same test queries on both.
--
-- The Python utility that runs this doesn't know about dollar-quoting,
-- and thinks that a ';' at end of line ends the command. The /* in func */
-- comments at the end of each line thwarts that.
CREATE OR REPLACE FUNCTION gp_ao_or_aocs_seg(rel regclass,
segment_id OUT integer,
segno OUT integer,
tupcount OUT bigint,
modcount OUT bigint,
formatversion OUT smallint,
state OUT smallint)
RETURNS SETOF record as $$
declare
amname_var text; /* in func */
begin /* in func */
select amname into amname_var from pg_class c, pg_am am where c.relam = am.oid and c.oid = rel; /* in func */
if amname_var = 'ao_column' then /* in func */
for segment_id, segno, tupcount, modcount, formatversion, state in SELECT DISTINCT x.segment_id, x.segno, x.tupcount, x.modcount, x.formatversion, x.state FROM gp_toolkit.__gp_aocsseg(rel) x loop /* in func */
return next; /* in func */
end loop; /* in func */
elsif amname_var = 'ao_row' then /* in func */
for segment_id, segno, tupcount, modcount, formatversion, state in SELECT x.segment_id, x.segno, x.tupcount, x.modcount, x.formatversion, x.state FROM gp_toolkit.__gp_aoseg(rel) x loop /* in func */
return next; /* in func */
end loop; /* in func */
else /* in func */
raise '% is not an AO_ROW or AO_COLUMN table', rel::text; /* in func */
end if; /* in func */
end; /* in func */
$$ LANGUAGE plpgsql;
-- Show locks in master and in segments. Because the number of segments
-- in the cluster depends on configuration, we print only summary information
-- of the locks in segments. If a relation is locked only on one segment,
-- we print that as a special case, but otherwise we just print "n segments",
-- meaning the relation is locked on more than one segment.
create or replace view locktest_master as
select coalesce(
case when relname like 'pg_toast%index' then 'toast index'
when relname like 'pg_toast%' then 'toast table'
when relname like 'pg_aoseg%' then 'aoseg table'
when relname like 'pg_aovisimap%index' then 'aovisimap index'
when relname like 'pg_aovisimap%' then 'aovisimap table'
else relname end, 'dropped table'),
mode,
locktype,
'master'::text as node
from pg_locks l
left outer join pg_class c on ((l.locktype = 'append-only segment file' and l.relation = c.relfilenode) or (l.locktype != 'append-only segment file' and l.relation = c.oid)),
pg_database d
where relation is not null
and l.database = d.oid
and (relname <> 'gp_fault_strategy' and relname != 'locktest_master' or relname is NULL)
and d.datname = current_database()
and l.gp_segment_id = -1
group by l.gp_segment_id, relation, relname, locktype, mode
order by 1, 3, 2;
create or replace view locktest_segments_dist as
select relname,
mode,
locktype,
l.gp_segment_id as node,
relation
from pg_locks l
left outer join pg_class c on ((l.locktype = 'append-only segment file' and l.relation = c.relfilenode) or (l.locktype != 'append-only segment file' and l.relation = c.oid)),
pg_database d
where relation is not null
and l.database = d.oid
and (relname <> 'gp_fault_strategy' and relname != 'locktest_segments_dist' or relname is NULL)
and d.datname = current_database()
and l.gp_segment_id > -1
group by l.gp_segment_id, relation, relname, locktype, mode;
create or replace view locktest_segments as
SELECT coalesce(
case when relname like 'pg_toast%index' then 'toast index'
when relname like 'pg_toast%' then 'toast table'
when relname like 'pg_aoseg%' then 'aoseg table'
when relname like 'pg_aovisimap%index' then 'aovisimap index'
when relname like 'pg_aovisimap%' then 'aovisimap table'
else relname end, 'dropped table'),
mode,
locktype,
case when count(*) = 1 then '1 segment'
else 'n segments' end as node
FROM gp_dist_random('locktest_segments_dist')
group by relname, relation, mode, locktype;
-- Helper function
CREATE or REPLACE FUNCTION wait_until_waiting_for_required_lock (rel_name text, lmode text, segment_id integer) /*in func*/
RETURNS bool AS
$$
declare
retries int; /* in func */
begin /* in func */
retries := 1200; /* in func */
loop /* in func */
if (select not granted from pg_locks l where granted='f' and l.relation::regclass = rel_name::regclass and l.mode=lmode and l.gp_segment_id=segment_id) then /* in func */
return true; /* in func */
end if; /* in func */
if retries <= 0 then /* in func */
return false; /* in func */
end if; /* in func */
perform pg_sleep(0.1); /* in func */
retries := retries - 1; /* in func */
end loop; /* in func */
end; /* in func */
$$ language plpgsql;
--
-- pg_ctl:
-- datadir: data directory of process to target with `pg_ctl`
-- command: commands valid for `pg_ctl`
-- command_mode: modes valid for `pg_ctl -m`
--
create or replace function pg_ctl(datadir text, command text, command_mode text default 'immediate')
returns text as $$
class PgCtlError(Exception):
def __init__(self, errmsg):
self.errmsg = errmsg
def __str__(self):
return repr(self.errmsg)
import subprocess
if command == 'promote':
cmd = 'pg_ctl promote -D %s' % datadir
elif command in ('stop', 'restart'):
cmd = 'pg_ctl -l postmaster.log -D %s ' % datadir
cmd = cmd + '-w -t 600 -m %s %s' % (command_mode, command)
else:
return 'Invalid command input'
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
shell=True)
stdout, stderr = proc.communicate()
if proc.returncode == 0:
return 'OK'
else:
raise PgCtlError(stdout.decode()+'|'+stderr.decode())
$$ language plpython3u;
--
-- pg_ctl_start:
--
-- Start a specific greenplum segment
--
-- intentionally separate from pg_ctl() because it needs more information
--
-- datadir: data directory of process to target with `pg_ctl`
-- port: which port the server should start on
--
create or replace function pg_ctl_start(datadir text, port bigint)
returns text as $$
import subprocess
cmd = 'pg_ctl -l postmaster.log -D %s ' % datadir
opts = '-p %d' % (port)
opts = opts + ' -c gp_role=execute'
cmd = cmd + '-o "%s" start' % opts
return subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True).decode().replace('.', '')
$$ language plpython3u;
--
-- restart_primary_segments_containing_data_for(table_name text):
-- table_name: the table containing data whose segment that needs a restart
--
-- Note: this does an immediate restart, which forces recovery
--
create or replace function restart_primary_segments_containing_data_for(table_name text) returns setof integer as $$
declare
segment_id integer; /* in func */
begin
for segment_id in select * from primary_segments_containing_data_for(table_name)
loop
perform pg_ctl(
(select get_data_directory_for(segment_id)),
'restart',
'immediate'
); /* in func */
end loop; /* in func */
end; /* in func */
$$ language plpgsql;
--
-- clean_restart_primary_segments_containing_data_for(table_name text):
-- table_name: the table containing data whose segment that needs a restart
--
-- Note: this does a fast restart, which does not require recovery
--
create or replace function clean_restart_primary_segments_containing_data_for(table_name text) returns setof integer as $$
declare
segment_id integer; /* in func */
begin
for segment_id in select * from primary_segments_containing_data_for(table_name)
loop
perform pg_ctl(
(select get_data_directory_for(segment_id)),
'restart',
'fast'
); /* in func */
end loop; /* in func */
end; /* in func */
$$ language plpgsql;
create or replace function primary_segments_containing_data_for(table_name text) returns setof integer as $$
begin
return query execute 'select distinct gp_segment_id from ' || table_name; /* in func */
end; /* in func */
$$ language plpgsql;
create or replace function get_data_directory_for(segment_number int, segment_role text default 'p') returns text as $$
BEGIN
return (
select datadir
from gp_segment_configuration
where role=segment_role and
content=segment_number
); /* in func */
END; /* in func */
$$ language plpgsql;
create or replace function master() returns setof gp_segment_configuration as $$
select * from gp_segment_configuration where role='p' and content=-1; /* in func */
$$ language sql;
create or replace function wait_until_segment_synchronized(segment_number int) returns text as $$
begin
for i in 1..6000 loop
if (select count(*) = 0 from gp_segment_configuration where content = segment_number and mode != 's') then
return 'OK'; /* in func */
end if; /* in func */
perform pg_sleep(0.1); /* in func */
perform gp_request_fts_probe_scan(); /* in func */
end loop; /* in func */
return 'Fail'; /* in func */
end; /* in func */
$$ language plpgsql;
create or replace function wait_until_all_segments_synchronized() returns text as $$
begin
for i in 1..6000 loop
if (select count(*) = 0 from gp_segment_configuration where content != -1 and mode != 's') then
return 'OK'; /* in func */
end if; /* in func */
perform pg_sleep(0.1); /* in func */
perform gp_request_fts_probe_scan(); /* in func */
end loop; /* in func */
return 'Fail'; /* in func */
end; /* in func */
$$ language plpgsql;
CREATE OR REPLACE FUNCTION is_query_waiting_for_syncrep(iterations int, check_query text) RETURNS bool AS $$
for i in range(iterations):
results = plpy.execute("SELECT gp_execution_segment() AS content, query, wait_event\
FROM gp_dist_random('pg_stat_activity')\
WHERE gp_execution_segment() = 1 AND\
query = '%s' AND\
wait_event = 'SyncRep'" % check_query )
if results:
return True
return False
$$ LANGUAGE plpython3u VOLATILE;
create or replace function wait_for_replication_replay (segid int, retries int) returns bool as
$$
declare
i int; /* in func */
result bool; /* in func */
begin
i := 0; /* in func */
-- Wait until the mirror/standby has replayed up to flush location
loop
SELECT flush_lsn = replay_lsn INTO result from gp_stat_replication where gp_segment_id = segid; /* in func */
if result then
return true; /* in func */
end if; /* in func */
if i >= retries then
return false; /* in func */
end if; /* in func */
perform pg_sleep(0.1); /* in func */
perform pg_stat_clear_snapshot(); /* in func */
i := i + 1; /* in func */
end loop; /* in func */
end; /* in func */
$$ language plpgsql;
create or replace function wait_until_standby_in_state(targetstate text)
returns text as $$
declare
replstate text; /* in func */
i int; /* in func */
begin
i := 0; /* in func */
while i < 1200 loop
select state into replstate from pg_stat_replication; /* in func */
if replstate = targetstate then
return replstate; /* in func */
end if; /* in func */
perform pg_sleep(0.1); /* in func */
perform pg_stat_clear_snapshot(); /* in func */
i := i + 1; /* in func */
end loop; /* in func */
return replstate; /* in func */
end; /* in func */
$$ language plpgsql;
--
-- pg_basebackup:
-- host: host of the gpdb segment to back up
-- port: port of the gpdb segment to back up
-- slotname: desired slot name to create and associate with backup
-- datadir: destination data directory of the backup
-- forceoverwrite: overwrite the destination directory if it exists already
-- xlog_method: (stream/fetch) how to obtain XLOG segment files from source
--
-- usage: `select pg_basebackup('somehost', 12345, 'some_slot_name', '/some/destination/data/directory')`
--
create or replace function pg_basebackup(host text, dbid int, port bigint, create_slot boolean, slotname text, datadir text, force_overwrite boolean, xlog_method text) returns text as $$
import subprocess
import os
cmd = 'pg_basebackup --no-sync --checkpoint=fast -h %s -p %d -R -D %s --target-gp-dbid %d' % (host, port, datadir, dbid)
if create_slot:
cmd += ' --create-slot'
if slotname is not None:
cmd += ' --slot %s' % (slotname)
if force_overwrite:
cmd += ' --force-overwrite'
if xlog_method == 'stream':
cmd += ' --wal-method stream'
elif xlog_method == 'fetch':
cmd += ' --wal-method fetch'
else:
plpy.error('invalid xlog method')
cmd += ' --no-verify-checksums'
try:
# Unset PGAPPNAME so that the pg_stat_replication.application_name is not affected
if os.getenv('PGAPPNAME') is not None:
os.environ.pop('PGAPPNAME')
results = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True).replace(b'.', b'').decode()
except subprocess.CalledProcessError as e:
results = str(e) + "\ncommand output: " + (e.output.decode())
return results
$$ language plpython3u;
create or replace function count_of_items_in_directory(user_path text) returns text as $$
import subprocess
cmd = 'ls {user_path}'.format(user_path=user_path)
results = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True).replace(b'.', b'').decode()
return len([result for result in results.splitlines() if result != ''])
$$ language plpython3u;
create or replace function count_of_items_in_database_directory(user_path text, database_oid oid) returns int as $$
import subprocess
import os
directory = os.path.join(user_path, str(database_oid))
cmd = 'ls ' + directory
results = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True).replace(b'.', b'').decode()
return len([result for result in results.splitlines() if result != ''])
$$ language plpython3u;
create or replace function validate_tablespace_symlink(datadir text, tablespacedir text, dbid int, tablespace_oid oid) returns boolean as $$
import os
return os.readlink('%s/pg_tblspc/%d' % (datadir, tablespace_oid)) == ('%s/%d' % (tablespacedir, dbid))
$$ language plpython3u;
-- This function is used to loop until master shutsdown, to make sure
-- next command executed is only after restart and doesn't go through
-- while PANIC is still being processed by master, as master continues
-- to accept connections for a while despite undergoing PANIC.
CREATE OR REPLACE FUNCTION wait_till_master_shutsdown()
RETURNS void AS
$$
DECLARE
i int; /* in func */
BEGIN
i := 0; /* in func */
while i < 120 loop
i := i + 1; /* in func */
PERFORM pg_sleep(.5); /* in func */
end loop; /* in func */
END; /* in func */
$$ LANGUAGE plpgsql;
-- start_ignore
set statement_timeout='180s';
-- end_ignore
-- Helper function that ensures stats collector receives stat from the latest operation.
create or replace function wait_until_dead_tup_change_to(relid oid, stat_val_expected bigint)
returns text as $$
declare
stat_val int; /* in func */
i int; /* in func */
begin
i := 0; /* in func */
while i < 1200 loop
select pg_stat_get_dead_tuples(relid) into stat_val; /* in func */
if stat_val = stat_val_expected then /* in func */
return 'OK'; /* in func */
end if; /* in func */
perform pg_sleep(0.1); /* in func */
perform pg_stat_clear_snapshot(); /* in func */
i := i + 1; /* in func */
end loop; /* in func */
return 'Fail'; /* in func */
end; /* in func */
$$ language plpgsql;
-- Helper function that ensures mirror of the specified contentid is down.
create or replace function wait_for_mirror_down(contentid smallint, timeout_sec integer) returns bool as
$$
declare i int; /* in func */
begin /* in func */
i := 0; /* in func */
loop /* in func */
perform gp_request_fts_probe_scan(); /* in func */
if (select count(1) from gp_segment_configuration where role='m' and content=$1 and status='d') = 1 then /* in func */
return true; /* in func */
end if; /* in func */
if i >= 2 * $2 then /* in func */
return false; /* in func */
end if; /* in func */
perform pg_sleep(0.5); /* in func */
i = i + 1; /* in func */
end loop; /* in func */
end; /* in func */
$$ language plpgsql;
-- Helper function that ensures stats collector receives stat from the latest operation.
create or replace function wait_until_vacuum_count_change_to(relid oid, stat_val_expected bigint)
returns text as $$
declare
stat_val int; /* in func */
i int; /* in func */
begin
i := 0; /* in func */
while i < 1200 loop
select pg_stat_get_vacuum_count(relid) into stat_val; /* in func */
if stat_val = stat_val_expected then /* in func */
return 'OK'; /* in func */
end if; /* in func */
perform pg_sleep(0.1); /* in func */
perform pg_stat_clear_snapshot(); /* in func */
i := i + 1; /* in func */
end loop; /* in func */
return 'Fail'; /* in func */
end; /* in func */
$$ language plpgsql;
-- Helper function to get the number of blocks in a relation.
CREATE OR REPLACE FUNCTION nblocks(rel regclass) RETURNS int AS $$ /* in func */
BEGIN /* in func */
RETURN pg_relation_size(rel) / current_setting('block_size')::int; /* in func */
END; $$ /* in func */
LANGUAGE PLPGSQL;
-- Helper function to populate logical heap pages in a certain block sequence.
-- Can be used for both heap and AO/CO tables. The target block sequence into
-- which we insert the pages depends on the session which is inserting the data.
-- This is currently meant to be used with a single column integer table.
--
-- Sample usage: SELECT populate_pages('foo', 1, tid '(33554435,0)')
-- This will insert tuples with value=1 into a single QE such that logical
-- heap blocks [33554432, 33554434] will be full and 33554435 will have only
-- 1 tuple.
--
-- Note: while using this with AO/CO tables, please account for how the block
-- sequences start/end based on the concurrency level (see AOSegmentGet_startHeapBlock())
CREATE OR REPLACE FUNCTION populate_pages(relname text, value int, upto tid) RETURNS VOID AS $$ /* in func */
DECLARE curtid tid; /* in func */
BEGIN /* in func */
LOOP /* in func */
EXECUTE format('INSERT INTO %I VALUES($1) RETURNING ctid', relname) INTO curtid USING value; /* in func */
EXIT WHEN curtid > upto; /* in func */
END LOOP; /* in func */
END; $$ /* in func */
LANGUAGE PLPGSQL;