create or replace function agg_first(
    IN p_state       anyelement,
    IN p_new_element anyelement
    )
    returns anyelement
    language sql
    as $$
select coalesce( p_state, p_new_element );
$$;

create aggregate first(anyelement) (
    sfunc = agg_first,
    stype = anyelement
);

create or replace function agg_last(
    IN p_state       anyelement,
    IN p_new_element anyelement
    )
    returns anyelement
    language sql
    as $$
select coalesce( p_new_element, p_state );
$$;

create aggregate last(anyelement) (
    sfunc = agg_last,
    stype = anyelement
);

create or replace function agg_first(
    IN p_state       anyarray,
    IN p_new_element anyelement,
    IN p_limit       int4
    )
    returns anyarray
    language sql
    as $$
select case
    when coalesce( array_length( p_state, 1 ), 0 ) < p_limit
         then p_state || p_new_element
    else p_state
    end;
$$;

create aggregate first(anyelement, int4) (
    sfunc = agg_first,
    stype = anyarray,
    initcond = '{}'
);

create or replace function agg_last(
    IN p_state       anyarray,
    IN p_new_element anyelement,
    IN p_limit       int4
    )
    returns anyarray
    language sql
    as $$
select ( case
    when array_length( p_state, 1 ) >= p_limit
        then p_state[(array_upper(p_state, 1) - p_limit + 2):]
    else
        p_state
    end ) || p_new_element;
$$;

CREATE AGGREGATE last(anyelement, int4) (
    sfunc = agg_last,
    stype = anyarray,
    initcond = '{}'
);

