CREATE TABLE chunks (
    id serial,
    relation_id oid not null,
    chunk_id int not null,
    PRIMARY KEY (id)
);

/*
Consistency Unique ID  - CUID

Format:
- 41 bits for time in milliseconds (gives us 69 years)
- 13 bits that represent chunk ID (allow to create 8192 chunks)
- 10 bits that represent an auto-incrementing sequence, modulus 1024
  (can be generated 1024 IDs, per chunk, per millisecond
*/
create type cuid as (created_at timestamp, chunk_id bigint, seq_id bigint);

create or replace
function next_id(chunk_id int, def_value text) returns bigint
    language 'plpgsql' as $$
declare
    result bigint;
    our_epoch bigint := 1451595600000; -- 2016
    seq_id bigint;
    now_millis bigint;
begin
    -- use def_value from base table (maybe will be better avoid this)
    execute format('select %s', def_value) into seq_id;
    select floor(extract(epoch from clock_timestamp()) * 1000) into now_millis;
    result := (now_millis - our_epoch) << 23;
    result := result | (chunk_id << 10);
    result := result | (seq_id);
    return result;
end;
$$;

create or replace
function unpack_cuid(id bigint) returns cuid
    language 'plpgsql' as $$
declare
    our_epoch bigint := 1451595600000; -- 2016
    created_at timestamp;
    chunk_id bigint;
    seq_id bigint;
begin
    select to_timestamp(((id >> 23) + our_epoch)* 0.001) into created_at;
    chunk_id = (id & ((1 << 23) - 1) ) >> 10;
    seq_id = id & ((1 << 10) - 1);
    return row(created_at, chunk_id, seq_id)::cuid;
end;
$$;

create or replace
    function create_chunk(table_name text, chunk_id int) returns int
language 'plpgsql' as $$
declare
    def_value text;
    query_tpl text :=
'create table {table_name}_{chunk_id} (
    like {table_name} including all,
    check( (unpack_cuid(id)).chunk_id = {chunk_id})
);
alter table {table_name}_{chunk_id} inherit {table_name};
alter table {table_name}_{chunk_id} alter column id
    set default next_id({chunk_id}, ''{def_value}'');';
begin
    if exists (
        select * from chunks
            where
                chunks.relation_id = create_chunk.table_name::regclass and
                chunks.chunk_id  = create_chunk.chunk_id
        ) then
        return null;
    end if;

    --- use default value from base table
    select column_default into def_value
    from information_schema.columns as is_columns
    where is_columns.table_name = create_chunk.table_name and column_name = 'id';

    insert into chunks (relation_id, chunk_id)
        values(table_name::regclass, chunk_id);

    execute replace(
            replace(
            replace(query_tpl,
                '{table_name}', table_name),
                '{chunk_id}', trim(to_char(chunk_id, '000'))),
                '{def_value}', replace(def_value, '''', ''''''));
    return chunk_id;
end;
$$;

create or replace
function create_chunks(
        table_name text, from_number int, to_number int) returns int[]
language 'plpgsql' as $$
declare
    result int[];
begin
    select array(
        select * from (
            select create_chunk(table_name, num) as id
            from generate_series(from_number, to_number) as num
        ) as r
        where r.id is not null
    ) into result;
    return result;
end;
$$;
