create function get_collation(p_master_class oid) returns text language sql as $$
select  l.collname::text
from    master m
join    pg_range r
on      r.rngtypid = m.range_type
left
join    pg_collation l
on      l.oid = r.rngcollation
where   m.master_class = p_master_class;
$$;

comment on function get_collation(p_master_class oid)
is 'return the text name of the collation for this partition key, if any';

create function get_exclusion_constraint_name(p_partition_class oid) returns text language sql strict as $$
select  c.relname::text
from    pg_class c
where   c.oid = p_partition_class;
$$;

comment on function get_exclusion_constraint_name(p_partition_class oid)
is 'generate the name of the exclusion constraint for this partition';

create function drop_exclusion_constraint(p_partition_class oid) returns void language plpgsql as $$
begin
    execute format('alter table %s drop constraint %I',
                    p_partition_class::regclass::text,
                    get_exclusion_constraint_name(p_partition_class));
end
$$;

comment on function drop_exclusion_constraint(p_partition_class oid)
is 'internal use only';

create function create_exclusion_constraint(p_partition_class oid) returns void language plpgsql as $$
begin
    execute format('alter table %s add constraint %I check (%s)',
                    p_partition_class::regclass::text,
                    get_exclusion_constraint_name(p_partition_class),
                    (   select  where_clause(m.partition_attribute,
                                            p.range,
                                            m.range_type::regtype::text,
                                            get_collation(m.master_class))
                        from    partition p
                        join    master m
                        on      m.master_class = p.master_class
                        where   p.partition_class = p_partition_class));
end
$$;

comment on function create_exclusion_constraint(p_partition_class oid)
is 'create the exclusion constraint for a given partition';

create function refresh_exclusion_constraint(p_partition_class oid) returns boolean
language plpgsql set search_path from current as $$
begin
    perform drop_exclusion_constraint(p_partition_class);
    perform create_exclusion_constraint(p_partition_class);
    return true;
end
$$;

comment on function refresh_exclusion_constraint(p_partition_class oid)
is 'drop and recreate exclusion constraints';

create or replace function partition_reflect() returns trigger
language plpgsql as $$
begin
    if TG_OP = 'UPDATE' then
        if new.master_class <> old.master_class then
            raise exception '%', 'Cannot modify master_class';
        elsif new.partition_number <> old.partition_number then
            raise exception '%', 'Cannot modify partition_number';
        elsif new.partition_class <> old.partition_class then
            raise exception '%', 'Cannot modify partition_class';
        elsif new.range is distinct from old.range then
            perform drop_exclusion_constraint(new.partition_class);
            perform create_exclusion_constraint(new.partition_class);
        end if;
    end if;

    if TG_OP = 'INSERT' then
        -- complete the inheritance
        execute format('alter table %s inherit %s',
                        new.partition_class::regclass::text,
                        new.master_class::regclass::text);
        perform create_exclusion_constraint(new.partition_class);
    end if;

    return new;
end
$$;

create function collate_clause(p_collation text) returns text language sql strict as $$
select format(' COLLATE "%s"',p_collation);
$$;

comment on function collate_clause(p_collation text)
is 'create a COLLATE "X" clause if and only if p_collation is not null';

create function where_clause(p_col text, p_range text, p_range_type oid, p_collation text default null) returns text
language sql set search_path from current as $$
select  case
            when i.lower = i.upper then format('%I = %L',p_col,i.lower)
            when i.lower_inf and i.upper_inf then 'true'
            when i.empty then 'false'
            else    case
                        when i.lower_inf then ''
                        when i.lower_inc then format('%I%s >= %L',p_col,collate_clause(p_collation),i.lower)
                        else format('%I%s > %L',p_col,collate_clause(p_collation),i.lower)
                    end ||
                    case
                        when not i.lower_inf and not i.upper_inf then ' and ' 
                        else ''
                    end ||
                    case
                        when i.upper_inf then ''
                        when i.upper_inc then format('%I%s <= %L',p_col,collate_clause(p_collation),i.upper)
                        else format('%I%s < %L',p_col,collate_clause(p_collation),i.upper)
                    end
        end
from    range_type_info(p_range,p_range_type) i
$$;

comment on function where_clause(text,text,oid,text)
is E'construct a WHERE clause that would exactly fit the given column, range, and range_type';

create function where_clause(p_col text, p_range text, p_range_type text, p_collation text default null) returns text
language sql set search_path from current as $$
select  where_clause(p_col,p_range,p_range_type::regtype,p_collation);  
$$;

comment on function where_clause(text,text,text,text)
is E'construct a WHERE clause that would exactly fit the given column, range, and range_type';

create or replace function where_clause(p_partition_class oid) returns text
language sql set search_path from current as $$
select  where_clause(m.partition_attribute,p.range,m.range_type,get_collation(p.master_class))
from    partition p
join    master m
on      m.master_class = p.master_class
where   p.partition_class = p_partition_class;
$$;

drop function where_clause(text,text,oid);
drop function where_clause(text,text,text);

-- grant execute on all functions in this extension to role range_partitioning 
do $$
declare r record;
begin
    for r in (  select	p.proname, pg_get_function_identity_arguments(p.oid) as args
                from	pg_proc p
                join	pg_depend d on d.objid = p.oid and d.deptype = 'e'
                join	pg_extension x on x.oid = d.refobjid
                where   x.extname = 'range_partitioning' )
    loop
        execute format('grant execute on function %s(%s) to range_partitioning',r.proname,r.args);
    end loop;
end;
$$;

