I'm trying to copy database schema using a function from Copy schema and create new schema with different name in the same data base
-- Function: clone_schema(text, text)
-- DROP FUNCTION clone_schema(text, text);
CREATE OR REPLACE FUNCTION clone_schema(
source_schema text,
dest_schema text,
include_recs boolean)
RETURNS void AS
$BODY$
-- This function will clone all sequences, tables, data, views & functions from any existing schema to a new one
-- SAMPLE CALL:
-- SELECT clone_schema('public', 'new_schema', TRUE);
DECLARE
src_oid oid;
tbl_oid oid;
func_oid oid;
table_rec record;
seq_rec record;
object text;
sequence_ text;
table_ text;
buffer text;
seq_buffer text;
table_buffer text;
srctbl text;
default_ text;
column_ text;
qry text;
dest_qry text;
v_def text;
seqval bigint;
sq_last_value bigint;
sq_max_value bigint;
sq_start_value bigint;
sq_increment_by bigint;
sq_min_value bigint;
sq_cache_value bigint;
sq_log_cnt bigint;
sq_is_called boolean;
sq_is_cycled boolean;
sq_cycled char(10);
BEGIN
-- Check that source_schema exists
SELECT oid INTO src_oid
FROM pg_namespace
WHERE nspname = source_schema;
IF NOT FOUND
THEN
RAISE EXCEPTION 'source schema % does not exist!', source_schema;
RETURN ;
END IF;
-- Check that dest_schema does not yet exist
PERFORM nspname
FROM pg_namespace
WHERE nspname = dest_schema;
IF FOUND
THEN
RAISE EXCEPTION 'dest schema % already exists!', dest_schema;
RETURN ;
END IF;
EXECUTE 'CREATE SCHEMA "' || dest_schema || '"';
-- Create tables
FOR object IN
SELECT TABLE_NAME::text
FROM information_schema.tables
WHERE table_schema = source_schema
AND table_type = 'BASE TABLE'
LOOP
buffer := '"' || dest_schema || '".' || quote_ident(object);
EXECUTE 'CREATE TABLE ' || buffer || ' (LIKE "' || source_schema || '".' || quote_ident(object)
|| ' INCLUDING ALL);';
IF include_recs
THEN
-- Insert records from source table
EXECUTE 'INSERT INTO ' || buffer || ' SELECT * FROM "' || source_schema || '".' || quote_ident(object) || ';';
END IF;
END LOOP;
-- add FK constraint
FOR qry IN
SELECT 'ALTER TABLE "' || dest_schema || '".' || quote_ident(rn.relname)
|| ' ADD CONSTRAINT ' || quote_ident(ct.conname) || ' ' || pg_get_constraintdef(ct.oid) || ';'
FROM pg_constraint ct
JOIN pg_class rn ON rn.oid = ct.conrelid
WHERE connamespace = src_oid
AND rn.relkind = 'r'
AND ct.contype = 'f'
LOOP
EXECUTE qry;
END LOOP;
-- Create sequences
FOR seq_rec IN
SELECT
s.sequence_name::text,
table_name,
column_name
FROM information_schema.sequences s
JOIN (
SELECT
substring(column_default from E'^nextval\\(''(?:[^"'']?.*["'']?\\.)?([^'']*)''(?:::text|::regclass)?\\)')::text as seq_name,
table_name,
column_name
FROM information_schema.columns
WHERE column_default LIKE 'nextval%'
AND table_schema = source_schema
) c ON c.seq_name = s.sequence_name
WHERE sequence_schema = source_schema
LOOP
seq_buffer := quote_ident(dest_schema) || '.' || quote_ident(seq_rec.sequence_name);
EXECUTE 'CREATE SEQUENCE ' || seq_buffer || ';';
qry := 'SELECT last_value, max_value, start_value, increment_by, min_value, cache_value, log_cnt, is_cycled, is_called
FROM "' || source_schema || '".' || quote_ident(seq_rec.sequence_name) || ';';
EXECUTE qry INTO sq_last_value, sq_max_value, sq_start_value, sq_increment_by, sq_min_value, sq_cache_value, sq_log_cnt, sq_is_cycled, sq_is_called ;
IF sq_is_cycled
THEN
sq_cycled := 'CYCLE';
ELSE
sq_cycled := 'NO CYCLE';
END IF;
EXECUTE 'ALTER SEQUENCE ' || seq_buffer
|| ' INCREMENT BY ' || sq_increment_by
|| ' MINVALUE ' || sq_min_value
|| ' MAXVALUE ' || sq_max_value
|| ' START WITH ' || sq_start_value
|| ' RESTART ' || sq_min_value
|| ' CACHE ' || sq_cache_value
|| ' OWNED BY ' || quote_ident(dest_schema ) || '.'
|| quote_ident(seq_rec.table_name) || '.'
|| quote_ident(seq_rec.column_name) || ' '
|| sq_cycled || ' ;' ;
IF include_recs
THEN
EXECUTE 'SELECT setval( ''' || seq_buffer || ''', ' || sq_last_value || ', ' || sq_is_called || ');' ;
ELSE
EXECUTE 'SELECT setval( ''' || seq_buffer || ''', ' || sq_start_value || ', ' || sq_is_called || ');' ;
END IF;
table_buffer := quote_ident(dest_schema) || '.' || quote_ident(seq_rec.table_name);
FOR table_rec IN
SELECT column_name::text AS column_,
REPLACE(column_default::text, source_schema, quote_ident(dest_schema)) AS default_
FROM information_schema.COLUMNS
WHERE table_schema = dest_schema
AND TABLE_NAME = seq_rec.table_name
AND column_default LIKE 'nextval(%' || seq_rec.sequence_name || '%::regclass)'
LOOP
EXECUTE 'ALTER TABLE ' || table_buffer || ' ALTER COLUMN ' || table_rec.column_ || ' SET DEFAULT nextval(' || quote_literal(seq_buffer) || '::regclass);';
END LOOP;
END LOOP;
-- Create views
FOR object IN
SELECT table_name::text,
view_definition
FROM information_schema.views
WHERE table_schema = source_schema
LOOP
buffer := '"' || dest_schema || '".' || quote_ident(object);
SELECT view_definition INTO v_def
FROM information_schema.views
WHERE table_schema = source_schema
AND table_name = quote_ident(object);
EXECUTE 'CREATE OR REPLACE VIEW ' || buffer || ' AS ' || v_def || ';' ;
END LOOP;
-- Create functions
FOR func_oid IN
SELECT oid
FROM pg_proc
WHERE pronamespace = src_oid
LOOP
SELECT pg_get_functiondef(func_oid) INTO qry;
SELECT replace(qry, source_schema, dest_schema) INTO dest_qry;
EXECUTE dest_qry;
END LOOP;
RETURN;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
But when I'm trying to execute this I'm getting an error:
ERROR: column "max_value" does not exist
Perhaps you meant to reference the column "dict_test_value_seq.last_value"
When I'm trying to get max_value of that sequence I get it properly. How can be this function improved to get sequences values?
You are using a version of PostgreSQL that is 10 or greater, meaning that you can no longer get the max_value by selecting from the sequence. Instead, you need to access the pg_sequence catalog, but you may want to simplify queries by just using the information_schema.sequences view:
SELECT start_value, minimum_value, maximum_value, increment, cycle_option
FROM information_schema.sequences
WHERE (sequence_schema, sequence_name) = ('schemaname','mysequence');
How can I query the access right privileges for a function?
I want to generate a GRANT EXECUTE script for all the functions in the database.
I am using PostgreSQL 8.3.
You can query the system catalogs:
SELECT f.proname AS name,
f.proargtypes AS signature,
f.proacl AS permissions
FROM pg_catalog.pg_proc AS f
JOIN pg_catalog.pg_namespace AS s
ON f.pronamespace = s.oid
WHERE f.proname = 'myfunction'
AND s.nspname = 'myschema';
You know that PUBLIC has the EXECUTE privilege on functions by default, right?
I created a function (function chain exactly) to generate the scripts of function (functrion stub, drop script, create script, owner script, grant rights script). We are using dbeaver (I am not sure about other tools) and from the grid results we can copy paste the function definition. The resulting function can also be used to backup function definitions to a table etc. Let me know if you run into issues to it (we are running postgre 8.3 and this works for our functions).
CREATE AGGREGATE public.textcat_all(
basetype = text,
sfunc = textcat,
stype = text,
initcond = ''
);
CREATE OR REPLACE FUNCTION public.getfunctionddl(functionOid oid)
RETURNS text AS
$BODY$
DECLARE
funcschema text;
funcname text = NULL;
paranames text;
paramodes text;
paratypes text;
paraintypes text;
function_body text = NULL;
paranames_array text[];
paramodes_array text[];
paratypes_array text[];
params_sql text = '';
type_name text = '';
return_type text;
params_sql_no_name text ='';
grants text;
proc_owner text;
proacl_txt text;
lanname_txt text;
function_sql text;
upper_array int;
in_param_cnt int = 0;
out_param_cnt int = 0;
prosecdef_b bool;
BEGIN
SELECT proargtypes, proallargtypes, proargnames, proargmodes, prosrc, ns.nspname, p.proname, prorettype, proacl, lanname, prosecdef, rolname
INTO paraintypes, paratypes, paranames, paramodes, function_body, funcschema, funcname, return_type, proacl_txt, lanname_txt, prosecdef_b, proc_owner
FROM pg_proc p
INNER JOIN pg_namespace ns ON ns.oid = p.pronamespace
INNER JOIN pg_language pl ON pl.oid = prolang
INNER JOIN pg_roles rl ON rl.oid = proowner
WHERE p.oid = functionOid
AND lanname <> 'internal';
IF COALESCE(funcname, '') = '' THEN
RETURN NULL;
END IF;
paratypes := REPLACE(COALESCE(paratypes, paraintypes), ',', ' ');
return_type := format_type(return_type::oid,NULL);
return_type := CASE WHEN return_type = 'character varying' THEN 'varchar' ELSE return_type END;
if paranames IS NULL OR paranames = '' THEN
params_sql := '()';
params_sql_no_name := '()';
ELSE
paratypes := REPLACE(REPLACE(paratypes, '{', ''), '}', '');
paranames := REPLACE(REPLACE(paranames, '{', ''), '}', '');
paramodes := REPLACE(REPLACE(paramodes, '{', ''), '}', '');
paratypes_array:=string_to_array(paratypes,' ');
paranames_array:=string_to_array(paranames,',');
paramodes_array:=string_to_array(paramodes,',');
upper_array := array_upper(paratypes_array,1);
params_sql := '(' || CASE WHEN upper_array > 5 THEN '
' ELSE '' END;
params_sql_no_name := '(';
FOR i IN array_lower(paratypes_array,1) .. array_upper(paratypes_array,1)
LOOP
type_name := format_type(paratypes_array[i]::oid, NULL);
type_name := CASE WHEN type_name = 'character varying' THEN 'varchar' ELSE type_name END;
params_sql := params_sql || CASE WHEN paramodes IS NULL OR paramodes = '' THEN '' WHEN paramodes_array[i] = 'o' THEN 'OUT ' ELSE '' END || paranames_array[i] || ' ' || type_name || CASE WHEN i = upper_array THEN ')' WHEN upper_array <= 5 THEN ', ' ELSE ',
' END;
params_sql_no_name := params_sql_no_name || CASE WHEN paramodes IS NULL OR paramodes = '' THEN '' WHEN paramodes_array[i] = 'o' THEN 'OUT ' ELSE '' END || type_name || CASE WHEN i = upper_array THEN ')' ELSE ',' END;
in_param_cnt := in_param_cnt + CASE WHEN paramodes IS NULL OR paramodes = '' THEN 1 WHEN paramodes_array[i] = 'o' THEN 0 ELSE 1 END;
out_param_cnt := out_param_cnt + CASE WHEN paramodes IS NULL OR paramodes = '' THEN 0 WHEN paramodes_array[i] = 'o' THEN 1 ELSE 0 END;
END LOOP;
END IF;
params_sql_no_name := LOWER(quote_ident(funcschema) || '.' || quote_ident(funcname)) || params_sql_no_name || '';
params_sql := quote_ident(funcschema) || '.' || quote_ident(funcname) || params_sql;
drop table if exists tmp_grant;
create temporary table tmp_grant
AS
SELECT
substring(a, 1, position('=X' in a) -1) as grantee_name
, substring(a, position('=X' in a) + 3, char_length(a) - position('=X' in a)) as grantor_name
From regexp_split_to_table(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(proacl_txt, '}', ''), '{', ''), CHR(34) || chr(92), ''), CHR(34), ''), chr(92), ''), ',') a;
SELECT public.textcat_all('GRANT EXECUTE ON FUNCTION ' || params_sql_no_name || ' TO ' || quote_ident(grantee_name) || ';
') INTO grants
FROM tmp_grant a
WHERE grantee_name <> ''
AND NOT EXISTS (select * From tmp_grant b where b.grantor_name = a.grantee_name);
function_sql := '-- ' || params_sql_no_name || '
' || CASE WHEN in_param_cnt + out_param_cnt > 0 THEN '-- PARAMS ALL: ' || CAST(in_param_cnt + out_param_cnt as char(3)) || ', IN: ' || cast(in_param_cnt as char(3)) || ', OUT ' || CAST(out_param_cnt as char(3)) || '
' ELSE '' END || '
-- DROP FUNCTION IF EXISTS ' || params_sql_no_name || ';
CREATE OR REPLACE FUNCTION ' || params_sql || '
' || 'RETURNS ' || CASE WHEN return_type = 'record' then 'SETOF record' ELSE return_type END || '
LANGUAGE ' || lanname_txt || CASE WHEN prosecdef_b = true THEN ' SECURITY DEFINER' ELSE '' END || '
AS $' || '$
' || COALESCE(function_body, '') || '
$' || '$;';
function_sql := function_sql || '
-- ALTER FUNCTION ' || params_sql_no_name || ' OWNER TO ' || quote_ident(proc_owner) || ';' || COALESCE('
' || grants, '');
RETURN function_sql;
END $BODY$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION public.getfunctionddl(schema_name_like varchar(256), function_name_like varchar(256), OUT schema_name varchar(256), OUT function_name varchar(256), OUT owner varchar(256), OUT func_oid oid, OUT func_lang varchar(256), OUT ddl text)
RETURNS SETOF record AS $$
SELECT
CAST(ns.nspname as varchar(256)) as schema_name
, CAST(p.proname as varchar(256)) as proc_name
, cast(rolname as varchar(256)) as owner
, p.oid as func_oid
, CAST(lanname as varchar(256)) as func_lang
, public.getfunctionddl(p.oid) as ddl
FROM pg_proc p
INNER JOIN pg_namespace ns ON ns.oid = p.pronamespace
INNER JOIN pg_language pl ON pl.oid = prolang
INNER JOIN pg_roles rl ON rl.oid = proowner
WHERE ns.nspname ILIKE lower(coalesce($1, '%'))
AND p.proname ILIKE lower(coalesce($2, '%'))
AND lanname <> 'internal'
ORDER BY ns.nspname, p.proname, p.oid;
$$ LANGUAGE SQL;
-- HOW TO GET DEFINITION OF THE FUNCTION
SELECT * FROM public.getfunctionddl('%' /*schema_name_like*/,'%' /*function_name_like*/)
i have the following function, that generates dynamic query and at the end i want to insert result of dynamic query into table, but the error i get is `
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function report_get_result(integer) line 46 at SQL statement
SQL statement "SELECT report_get_result(20150131)"
PL/pgSQL function inline_code_block line 2 at PERFORM
********** Error **********'
body of the function is:
CREATE OR REPLACE FUNCTION report_get_result (
datekey integer
) returns setof logic_result_rcd
AS
$body$
DECLARE
LogicID integer;
SheetName text;
Row_ID text;
Column_ID text;
FromTable text;
Operation text;
Amount text;
CriteriaType_1 text;
Function_1 text;
Criteria_1 text;
CriteriaType_2 text;
Function_2 text;
Criteria_2 text;
CriteriaType_3 text;
Function_3 text;
Criteria_3 text;
sql text;
INC Integer;
begin
DROP TABLE IF EXISTS loans;
create temp table loans as
select * from loan.vfact_state_principal where "DateKey" = datekey;
DECLARE cursor_logic REFCURSOR;
BEGIN
OPEN cursor_logic for execute ('SELECT "LogicID" FROM logic_table_rcd');
LOOP
FETCH cursor_logic INTO INC;
if not found then exit;
end if;
BEGIN
SELECT LogicID = "LogicID"
,SheetName = "SheetName"
,Row_ID = "Row_ID"::text
,Column_ID = "Column_ID"::text
,FromTable = "FromTable"
,Operation = "Operation"
,Amount = "Amount"
,CriteriaType_1 = CASE WHEN "CriteriaType_1" <> '' THEN ('WHERE
' || "CriteriaType_1") ELSE '' END
,Function_1 = CASE WHEN "Function_1" is null THEN 'Empty' ELSE
"Function_1" END
,Criteria_1 = CASE WHEN "Criteria_1" is null THEN 'Empty' ELSE
"Criteria_1" END
,CriteriaType_2 = CASE WHEN "CriteriaType_2" <> '' THEN ' AND '
|| "CriteriaType_2" ELSE '' END
,Function_2 = CASE WHEN "Function_2" is null THEN 'Empty' ELSE
"Function_2" END
,Criteria_2 = CASE WHEN "Criteria_2" is null THEN 'Empty' ELSE
"Criteria_2" END
,CriteriaType_3 = CASE WHEN "CriteriaType_3" <> '' THEN ' AND '
|| "CriteriaType_3" ELSE '' END
,Function_3 = CASE WHEN "Function_3" is null THEN 'Empty' ELSE
"Function_3" END
,Criteria_3 = CASE WHEN "Criteria_3" is null THEN 'Empty' ELSE
"Criteria_3" END
FROM public.logic_table_rcd WHERE "LogicID" = INC;
sql:= 'INSERT INTO public.logic_result_rcd SELECT ' || INC::text || ', 1, '
|| EntityID::text || ', ' || DateKey::text || ', ' || 'RCD' || ', ' ||
SheetName::text || ', ' || Row_ID::text || ', ' || Column_ID::text || ', '
|| Operation || '(' || Amount || ')' || ' FROM ' || FromTable
|| CriteriaType_1 || ' ' || Function_1 || ' ' || Criteria_1
|| CriteriaType_2 || ' ' || Function_2 || ' ' || Criteria_2
|| CriteriaType_3 || ' ' || Function_3 || ' ' || Criteria_3;
RETURN QUERY EXECUTE sql;
END;
END LOOP;
CLOSE cursor_logic;
END;
END;
$body$
LANGUAGE plpgsql;
assign value with VAR_NAME := ''; construct, here is an example:
t=# create function f(_i int) returns table (a int) as $$
declare sql text;
begin
sql := format('select %s',_i);
return query execute sql;
end;
$$
language plpgsql
;
CREATE FUNCTION
t=# select * From f(3);
a
---
3
(1 row)
I there a way to copy the existing schema and generate new schema with another name in the same database in postgres.
Use pg_dump to dump your current schema in a SQL-formated file. Open the file, replace the schemaname with the new name and excute this script in your database to create the new schema and all other objects inside this schema.
Check out this PostgreSQL's wiki page. It contains a function for clone_schema as you required, but this function only clones tables. The page refers to this post, which contains a function that clones everything you need for the schema. This function worked well for me, I managed to execute it with JDBC API.
But I had some problems when the schema names contained - or capital letters. After a research I found out that the source of the problem is quote_ident() method. I changes the clone_schema function to work with any schema names. I share the new function here, hope it will help somebody:
-- Function: clone_schema(text, text)
-- DROP FUNCTION clone_schema(text, text);
CREATE OR REPLACE FUNCTION clone_schema(
source_schema text,
dest_schema text,
include_recs boolean)
RETURNS void AS
$BODY$
-- This function will clone all sequences, tables, data, views & functions from any existing schema to a new one
-- SAMPLE CALL:
-- SELECT clone_schema('public', 'new_schema', TRUE);
DECLARE
src_oid oid;
tbl_oid oid;
func_oid oid;
object text;
buffer text;
srctbl text;
default_ text;
column_ text;
qry text;
dest_qry text;
v_def text;
seqval bigint;
sq_last_value bigint;
sq_max_value bigint;
sq_start_value bigint;
sq_increment_by bigint;
sq_min_value bigint;
sq_cache_value bigint;
sq_log_cnt bigint;
sq_is_called boolean;
sq_is_cycled boolean;
sq_cycled char(10);
BEGIN
-- Check that source_schema exists
SELECT oid INTO src_oid
FROM pg_namespace
WHERE nspname = source_schema;
IF NOT FOUND
THEN
RAISE EXCEPTION 'source schema % does not exist!', source_schema;
RETURN ;
END IF;
-- Check that dest_schema does not yet exist
PERFORM nspname
FROM pg_namespace
WHERE nspname = dest_schema;
IF FOUND
THEN
RAISE EXCEPTION 'dest schema % already exists!', dest_schema;
RETURN ;
END IF;
EXECUTE 'CREATE SCHEMA "' || dest_schema || '"';
-- Create sequences
-- TODO: Find a way to make this sequence's owner is the correct table.
FOR object IN
SELECT sequence_name::text
FROM information_schema.sequences
WHERE sequence_schema = source_schema
LOOP
EXECUTE 'CREATE SEQUENCE "' || dest_schema || '".' || quote_ident(object);
srctbl := '"' || source_schema || '".' || quote_ident(object);
EXECUTE 'SELECT last_value, max_value, start_value, increment_by, min_value, cache_value, log_cnt, is_cycled, is_called
FROM "' || source_schema || '".' || quote_ident(object) || ';'
INTO sq_last_value, sq_max_value, sq_start_value, sq_increment_by, sq_min_value, sq_cache_value, sq_log_cnt, sq_is_cycled, sq_is_called ;
IF sq_is_cycled
THEN
sq_cycled := 'CYCLE';
ELSE
sq_cycled := 'NO CYCLE';
END IF;
EXECUTE 'ALTER SEQUENCE "' || dest_schema || '".' || quote_ident(object)
|| ' INCREMENT BY ' || sq_increment_by
|| ' MINVALUE ' || sq_min_value
|| ' MAXVALUE ' || sq_max_value
|| ' START WITH ' || sq_start_value
|| ' RESTART ' || sq_min_value
|| ' CACHE ' || sq_cache_value
|| sq_cycled || ' ;' ;
buffer := '"' || dest_schema || '".' || quote_ident(object);
IF include_recs
THEN
EXECUTE 'SELECT setval( ''' || buffer || ''', ' || sq_last_value || ', ' || sq_is_called || ');' ;
ELSE
EXECUTE 'SELECT setval( ''' || buffer || ''', ' || sq_start_value || ', ' || sq_is_called || ');' ;
END IF;
END LOOP;
-- Create tables
FOR object IN
SELECT TABLE_NAME::text
FROM information_schema.tables
WHERE table_schema = source_schema
AND table_type = 'BASE TABLE'
LOOP
buffer := '"' || dest_schema || '".' || quote_ident(object);
EXECUTE 'CREATE TABLE ' || buffer || ' (LIKE "' || source_schema || '".' || quote_ident(object)
|| ' INCLUDING ALL)';
IF include_recs
THEN
-- Insert records from source table
EXECUTE 'INSERT INTO ' || buffer || ' SELECT * FROM "' || source_schema || '".' || quote_ident(object) || ';';
END IF;
FOR column_, default_ IN
SELECT column_name::text,
REPLACE(column_default::text, source_schema, dest_schema)
FROM information_schema.COLUMNS
WHERE table_schema = dest_schema
AND TABLE_NAME = object
AND column_default LIKE 'nextval(%"' || source_schema || '"%::regclass)'
LOOP
EXECUTE 'ALTER TABLE ' || buffer || ' ALTER COLUMN ' || column_ || ' SET DEFAULT ' || default_;
END LOOP;
END LOOP;
-- add FK constraint
FOR qry IN
SELECT 'ALTER TABLE "' || dest_schema || '".' || quote_ident(rn.relname)
|| ' ADD CONSTRAINT ' || quote_ident(ct.conname) || ' ' || pg_get_constraintdef(ct.oid) || ';'
FROM pg_constraint ct
JOIN pg_class rn ON rn.oid = ct.conrelid
WHERE connamespace = src_oid
AND rn.relkind = 'r'
AND ct.contype = 'f'
LOOP
EXECUTE qry;
END LOOP;
-- Create views
FOR object IN
SELECT table_name::text,
view_definition
FROM information_schema.views
WHERE table_schema = source_schema
LOOP
buffer := '"' || dest_schema || '".' || quote_ident(object);
SELECT view_definition INTO v_def
FROM information_schema.views
WHERE table_schema = source_schema
AND table_name = quote_ident(object);
EXECUTE 'CREATE OR REPLACE VIEW ' || buffer || ' AS ' || v_def || ';' ;
END LOOP;
-- Create functions
FOR func_oid IN
SELECT oid
FROM pg_proc
WHERE pronamespace = src_oid
LOOP
SELECT pg_get_functiondef(func_oid) INTO qry;
SELECT replace(qry, source_schema, dest_schema) INTO dest_qry;
EXECUTE dest_qry;
END LOOP;
RETURN;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION clone_schema(text, text, boolean)
OWNER TO postgres;
I ran a few tests and found the result is referencing the source schema. So here's my improved version:
-- Function: clone_schema(source text, dest text, include_records boolean default true, show_details boolean default false)
-- DROP FUNCTION clone_schema(text, text, boolean, boolean);
CREATE OR REPLACE FUNCTION clone_schema(
source_schema text,
dest_schema text,
include_recs boolean DEFAULT true,
show_details boolean DEFAULT false)
RETURNS void AS
$BODY$
-- This function will clone all sequences, tables, data, views & functions from any existing schema to a new one
-- SAMPLE CALL:
-- SELECT clone_schema('public', 'new_schema');
-- SELECT clone_schema('public', 'new_schema', TRUE);
-- SELECT clone_schema('public', 'new_schema', TRUE, TRUE);
DECLARE
src_oid oid;
tbl_oid oid;
func_oid oid;
object text;
buffer text;
srctbl text;
default_ text;
column_ text;
qry text;
xrec record;
dest_qry text;
v_def text;
seqval bigint;
sq_last_value bigint;
sq_max_value bigint;
sq_start_value bigint;
sq_increment_by bigint;
sq_min_value bigint;
sq_cache_value bigint;
sq_log_cnt bigint;
sq_is_called boolean;
sq_is_cycled boolean;
sq_cycled char(10);
rec record;
source_schema_dot text = source_schema || '.';
dest_schema_dot text = dest_schema || '.';
BEGIN
-- Check that source_schema exists
SELECT oid INTO src_oid
FROM pg_namespace
WHERE nspname = quote_ident(source_schema);
IF NOT FOUND
THEN
RAISE NOTICE 'source schema % does not exist!', source_schema;
RETURN ;
END IF;
-- Check that dest_schema does not yet exist
PERFORM nspname
FROM pg_namespace
WHERE nspname = quote_ident(dest_schema);
IF FOUND
THEN
RAISE NOTICE 'dest schema % already exists!', dest_schema;
RETURN ;
END IF;
EXECUTE 'CREATE SCHEMA ' || quote_ident(dest_schema) ;
-- Defaults search_path to destination schema
PERFORM set_config('search_path', dest_schema, true);
-- Create sequences
-- TODO: Find a way to make this sequence's owner is the correct table.
FOR object IN
SELECT sequence_name::text
FROM information_schema.sequences
WHERE sequence_schema = quote_ident(source_schema)
LOOP
EXECUTE 'CREATE SEQUENCE ' || quote_ident(dest_schema) || '.' || quote_ident(object);
srctbl := quote_ident(source_schema) || '.' || quote_ident(object);
EXECUTE 'SELECT last_value, max_value, start_value, increment_by, min_value, cache_value, log_cnt, is_cycled, is_called
FROM ' || quote_ident(source_schema) || '.' || quote_ident(object) || ';'
INTO sq_last_value, sq_max_value, sq_start_value, sq_increment_by, sq_min_value, sq_cache_value, sq_log_cnt, sq_is_cycled, sq_is_called ;
IF sq_is_cycled
THEN
sq_cycled := 'CYCLE';
ELSE
sq_cycled := 'NO CYCLE';
END IF;
EXECUTE 'ALTER SEQUENCE ' || quote_ident(dest_schema) || '.' || quote_ident(object)
|| ' INCREMENT BY ' || sq_increment_by
|| ' MINVALUE ' || sq_min_value
|| ' MAXVALUE ' || sq_max_value
|| ' START WITH ' || sq_start_value
|| ' RESTART ' || sq_min_value
|| ' CACHE ' || sq_cache_value
|| sq_cycled || ' ;' ;
buffer := quote_ident(dest_schema) || '.' || quote_ident(object);
IF include_recs
THEN
EXECUTE 'SELECT setval( ''' || buffer || ''', ' || sq_last_value || ', ' || sq_is_called || ');' ;
ELSE
EXECUTE 'SELECT setval( ''' || buffer || ''', ' || sq_start_value || ', ' || sq_is_called || ');' ;
END IF;
IF show_details THEN RAISE NOTICE 'Sequence created: %', object; END IF;
END LOOP;
-- Create tables
FOR object IN
SELECT TABLE_NAME::text
FROM information_schema.tables
WHERE table_schema = quote_ident(source_schema)
AND table_type = 'BASE TABLE'
LOOP
buffer := dest_schema || '.' || quote_ident(object);
EXECUTE 'CREATE TABLE ' || buffer || ' (LIKE ' || quote_ident(source_schema) || '.' || quote_ident(object)
|| ' INCLUDING ALL)';
IF include_recs
THEN
-- Insert records from source table
EXECUTE 'INSERT INTO ' || buffer || ' SELECT * FROM ' || quote_ident(source_schema) || '.' || quote_ident(object) || ';';
END IF;
FOR column_, default_ IN
SELECT column_name::text,
REPLACE(column_default::text, source_schema, dest_schema)
FROM information_schema.COLUMNS
WHERE table_schema = dest_schema
AND TABLE_NAME = object
AND column_default LIKE 'nextval(%' || quote_ident(source_schema) || '%::regclass)'
LOOP
EXECUTE 'ALTER TABLE ' || buffer || ' ALTER COLUMN ' || column_ || ' SET DEFAULT ' || default_;
END LOOP;
IF show_details THEN RAISE NOTICE 'base table created: %', object; END IF;
END LOOP;
-- add FK constraint
FOR xrec IN
SELECT ct.conname as fk_name, rn.relname as tb_name, 'ALTER TABLE ' || quote_ident(dest_schema) || '.' || quote_ident(rn.relname)
|| ' ADD CONSTRAINT ' || quote_ident(ct.conname) || ' ' || replace(pg_get_constraintdef(ct.oid), source_schema_dot, '') || ';' as qry
FROM pg_constraint ct
JOIN pg_class rn ON rn.oid = ct.conrelid
WHERE connamespace = src_oid
AND rn.relkind = 'r'
AND ct.contype = 'f'
LOOP
IF show_details THEN RAISE NOTICE 'Creating FK constraint %.%...', xrec.tb_name, xrec.fk_name; END IF;
--RAISE NOTICE 'DEF: %', xrec.qry;
EXECUTE xrec.qry;
END LOOP;
-- Create functions
FOR xrec IN
SELECT proname as func_name, oid as func_oid
FROM pg_proc
WHERE pronamespace = src_oid
LOOP
IF show_details THEN RAISE NOTICE 'Creating function %...', xrec.func_name; END IF;
SELECT pg_get_functiondef(xrec.func_oid) INTO qry;
SELECT replace(qry, source_schema_dot, '') INTO dest_qry;
EXECUTE dest_qry;
END LOOP;
-- add Table Triggers
FOR rec IN
SELECT
trg.tgname AS trigger_name,
tbl.relname AS trigger_table,
CASE
WHEN trg.tgenabled='O' THEN 'ENABLED'
ELSE 'DISABLED'
END AS status,
CASE trg.tgtype::integer & 1
WHEN 1 THEN 'ROW'::text
ELSE 'STATEMENT'::text
END AS trigger_level,
CASE trg.tgtype::integer & 66
WHEN 2 THEN 'BEFORE'
WHEN 64 THEN 'INSTEAD OF'
ELSE 'AFTER'
END AS action_timing,
CASE trg.tgtype::integer & cast(60 AS int2)
WHEN 16 THEN 'UPDATE'
WHEN 8 THEN 'DELETE'
WHEN 4 THEN 'INSERT'
WHEN 20 THEN 'INSERT OR UPDATE'
WHEN 28 THEN 'INSERT OR UPDATE OR DELETE'
WHEN 24 THEN 'UPDATE OR DELETE'
WHEN 12 THEN 'INSERT OR DELETE'
WHEN 32 THEN 'TRUNCATE'
END AS trigger_event,
'EXECUTE PROCEDURE ' || (SELECT nspname FROM pg_namespace where oid = pc.pronamespace )
|| '.' || proname || '('
|| regexp_replace(replace(trim(trailing '\000' from encode(tgargs,'escape')), '\000',','),'{(.+)}','''{\1}''','g')
|| ')' as action_statement
FROM pg_trigger trg
JOIN pg_class tbl on trg.tgrelid = tbl.oid
JOIN pg_proc pc ON pc.oid = trg.tgfoid
WHERE trg.tgname not like 'RI_ConstraintTrigger%'
AND trg.tgname not like 'pg_sync_pg%'
AND tbl.relnamespace = (SELECT oid FROM pg_namespace where nspname = quote_ident(source_schema) )
LOOP
buffer := dest_schema || '.' || quote_ident(rec.trigger_table);
IF show_details THEN RAISE NOTICE 'Creating trigger % % % ON %...', rec.trigger_name, rec.action_timing, rec.trigger_event, rec.trigger_table; END IF;
EXECUTE 'CREATE TRIGGER ' || rec.trigger_name || ' ' || rec.action_timing
|| ' ' || rec.trigger_event || ' ON ' || buffer || ' FOR EACH '
|| rec.trigger_level || ' ' || replace(rec.action_statement, source_schema_dot, '');
END LOOP;
-- Create views
FOR object IN
SELECT table_name::text,
view_definition
FROM information_schema.views
WHERE table_schema = quote_ident(source_schema)
LOOP
buffer := dest_schema || '.' || quote_ident(object);
SELECT replace(view_definition, source_schema_dot, '') INTO v_def
FROM information_schema.views
WHERE table_schema = quote_ident(source_schema)
AND table_name = quote_ident(object);
IF show_details THEN RAISE NOTICE 'Creating view % AS %', object, regexp_replace(v_def, '[\n\r]+', ' ', 'g'); END IF;
EXECUTE 'CREATE OR REPLACE VIEW ' || buffer || ' AS ' || v_def || ';' ;
END LOOP;
RETURN;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
You can use this simple script :
DO LANGUAGE plpgsql
$body$
DECLARE
old_schema NAME = 'src_schema';
new_schema NAME = 'dst_schema';
tbl TEXT;
sql TEXT;
BEGIN
EXECUTE format('CREATE SCHEMA IF NOT EXISTS %I', new_schema);
FOR tbl IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema=old_schema
LOOP
sql := format(
'CREATE TABLE IF NOT EXISTS %I.%I '
'(LIKE %I.%I INCLUDING INDEXES INCLUDING CONSTRAINTS)'
, new_schema, tbl, old_schema, tbl);
EXECUTE sql;
sql := format(
'INSERT INTO %I.%I '
'SELECT * FROM %I.%I'
, new_schema, tbl, old_schema, tbl);
EXECUTE sql;
END LOOP;
END
$body$;
If by copy schema you mean copy a database, then just use TEMPLATE option to create a copy: CREATE DATABASE dbname_target TEMPLATE dbname_source;
This will copy data too. So you might want to create your own template if you need many copies. See Template Databases.
If you need only schema, then I suggest that you put your DB DDL scripts under source control (which is a good idea anyways) and have a separate (templated) script which will create the schema for you. Basically you have one SQL file, where you replace a ${schema_name} with your new schema name, and then execute this script on the database. In this way if you make a changes to this schema, you can also have scripts to update the schema to a new version, which you will have to do for every user schema in this case.
Using #IdanDavidi's solution, I was able to solve the case where sequences were owned by and referring to the source schema instead of the destination schema.
-- Function: clone_schema(text, text)
-- DROP FUNCTION clone_schema(text, text);
CREATE OR REPLACE FUNCTION clone_schema(
source_schema text,
dest_schema text,
include_recs boolean)
RETURNS void AS
$BODY$
-- This function will clone all sequences, tables, data, views & functions from any existing schema to a new one
-- SAMPLE CALL:
-- SELECT clone_schema('public', 'new_schema', TRUE);
DECLARE
src_oid oid;
tbl_oid oid;
func_oid oid;
table_rec record;
seq_rec record;
object text;
sequence_ text;
table_ text;
buffer text;
seq_buffer text;
table_buffer text;
srctbl text;
default_ text;
column_ text;
qry text;
dest_qry text;
v_def text;
seqval bigint;
sq_last_value bigint;
sq_max_value bigint;
sq_start_value bigint;
sq_increment_by bigint;
sq_min_value bigint;
sq_cache_value bigint;
sq_log_cnt bigint;
sq_is_called boolean;
sq_is_cycled boolean;
sq_cycled char(10);
BEGIN
-- Check that source_schema exists
SELECT oid INTO src_oid
FROM pg_namespace
WHERE nspname = source_schema;
IF NOT FOUND
THEN
RAISE EXCEPTION 'source schema % does not exist!', source_schema;
RETURN ;
END IF;
-- Check that dest_schema does not yet exist
PERFORM nspname
FROM pg_namespace
WHERE nspname = dest_schema;
IF FOUND
THEN
RAISE EXCEPTION 'dest schema % already exists!', dest_schema;
RETURN ;
END IF;
EXECUTE 'CREATE SCHEMA "' || dest_schema || '"';
-- Create tables
FOR object IN
SELECT TABLE_NAME::text
FROM information_schema.tables
WHERE table_schema = source_schema
AND table_type = 'BASE TABLE'
LOOP
buffer := '"' || dest_schema || '".' || quote_ident(object);
EXECUTE 'CREATE TABLE ' || buffer || ' (LIKE "' || source_schema || '".' || quote_ident(object)
|| ' INCLUDING ALL);';
IF include_recs
THEN
-- Insert records from source table
EXECUTE 'INSERT INTO ' || buffer || ' SELECT * FROM "' || source_schema || '".' || quote_ident(object) || ';';
END IF;
END LOOP;
-- add FK constraint
FOR qry IN
SELECT 'ALTER TABLE "' || dest_schema || '".' || quote_ident(rn.relname)
|| ' ADD CONSTRAINT ' || quote_ident(ct.conname) || ' ' || pg_get_constraintdef(ct.oid) || ';'
FROM pg_constraint ct
JOIN pg_class rn ON rn.oid = ct.conrelid
WHERE connamespace = src_oid
AND rn.relkind = 'r'
AND ct.contype = 'f'
LOOP
EXECUTE qry;
END LOOP;
-- Create sequences
FOR seq_rec IN
SELECT
s.sequence_name::text,
table_name,
column_name
FROM information_schema.sequences s
JOIN (
SELECT
substring(column_default from E'^nextval\\(''(?:[^"'']?.*["'']?\\.)?([^'']*)''(?:::text|::regclass)?\\)')::text as seq_name,
table_name,
column_name
FROM information_schema.columns
WHERE column_default LIKE 'nextval%'
AND table_schema = source_schema
) c ON c.seq_name = s.sequence_name
WHERE sequence_schema = source_schema
LOOP
seq_buffer := quote_ident(dest_schema) || '.' || quote_ident(seq_rec.sequence_name);
EXECUTE 'CREATE SEQUENCE ' || seq_buffer || ';';
qry := 'SELECT last_value, max_value, start_value, increment_by, min_value, cache_value, log_cnt, is_cycled, is_called
FROM "' || source_schema || '".' || quote_ident(seq_rec.sequence_name) || ';';
EXECUTE qry INTO sq_last_value, sq_max_value, sq_start_value, sq_increment_by, sq_min_value, sq_cache_value, sq_log_cnt, sq_is_cycled, sq_is_called ;
IF sq_is_cycled
THEN
sq_cycled := 'CYCLE';
ELSE
sq_cycled := 'NO CYCLE';
END IF;
EXECUTE 'ALTER SEQUENCE ' || seq_buffer
|| ' INCREMENT BY ' || sq_increment_by
|| ' MINVALUE ' || sq_min_value
|| ' MAXVALUE ' || sq_max_value
|| ' START WITH ' || sq_start_value
|| ' RESTART ' || sq_min_value
|| ' CACHE ' || sq_cache_value
|| ' OWNED BY ' || quote_ident(dest_schema ) || '.'
|| quote_ident(seq_rec.table_name) || '.'
|| quote_ident(seq_rec.column_name) || ' '
|| sq_cycled || ' ;' ;
IF include_recs
THEN
EXECUTE 'SELECT setval( ''' || seq_buffer || ''', ' || sq_last_value || ', ' || sq_is_called || ');' ;
ELSE
EXECUTE 'SELECT setval( ''' || seq_buffer || ''', ' || sq_start_value || ', ' || sq_is_called || ');' ;
END IF;
table_buffer := quote_ident(dest_schema) || '.' || quote_ident(seq_rec.table_name);
FOR table_rec IN
SELECT column_name::text AS column_,
REPLACE(column_default::text, source_schema, quote_ident(dest_schema)) AS default_
FROM information_schema.COLUMNS
WHERE table_schema = dest_schema
AND TABLE_NAME = seq_rec.table_name
AND column_default LIKE 'nextval(%' || seq_rec.sequence_name || '%::regclass)'
LOOP
EXECUTE 'ALTER TABLE ' || table_buffer || ' ALTER COLUMN ' || table_rec.column_ || ' SET DEFAULT nextval(' || quote_literal(seq_buffer) || '::regclass);';
END LOOP;
END LOOP;
-- Create views
FOR object IN
SELECT table_name::text,
view_definition
FROM information_schema.views
WHERE table_schema = source_schema
LOOP
buffer := '"' || dest_schema || '".' || quote_ident(object);
SELECT view_definition INTO v_def
FROM information_schema.views
WHERE table_schema = source_schema
AND table_name = quote_ident(object);
EXECUTE 'CREATE OR REPLACE VIEW ' || buffer || ' AS ' || v_def || ';' ;
END LOOP;
-- Create functions
FOR func_oid IN
SELECT oid
FROM pg_proc
WHERE pronamespace = src_oid
LOOP
SELECT pg_get_functiondef(func_oid) INTO qry;
SELECT replace(qry, source_schema, dest_schema) INTO dest_qry;
EXECUTE dest_qry;
END LOOP;
RETURN;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
This seems to be the best solution I came up to.
The idea is to use pg_dump with -O (no owner) and -o (oids) options to get plain text output without source schema and owner information.
Such output i filter through sed replacing the default entry
SET search_path = source_schema, pg_catalog;
with command to create the new schema and set the default search path to it
CREATE SCHEMA new_schema;
SET search_path = new_schema, pg_catalog;
After that I redirect the stream to psql logging to desired user and database to which copy of the schema will be transfered.
The final command to copy schema 'public' to schema '2016' in the same database 'b1' looks like this:
pg_dump -U postgres -Oo -n public -d b1 | sed 's/SET search_path = public, pg_catalog;/CREATE SCHEMA "2016";SET search_path = "2016", pg_catalog;/' | psql -U postgres -d b1
Please note that GRANTS are not transfered from the source schema to the new one.
In the case you are OK with only tables and columns (without constraints, keys etc.) this simple script could be helpful
DO LANGUAGE plpgsql
$body$
DECLARE
old_schema NAME = 'src_schema';
new_schema NAME = 'dst_schema';
tbl TEXT;
sql TEXT;
BEGIN
EXECUTE format('CREATE SCHEMA IF NOT EXISTS %I', new_schema);
FOR tbl IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema=old_schema
LOOP
sql := format(
'CREATE TABLE IF NOT EXISTS %I.%I '
'AS '
'SELECT * FROM %I.%I'
, new_schema, tbl, old_schema, tbl);
raise notice 'Sql: %', sql;
EXECUTE sql;
END LOOP;
END
$body$;