Thread: arrays and functions in plpgsql

arrays and functions in plpgsql

From
Todd Kover
Date:
I'm trying to write my own aggregate function.  I'm getting messed up
in both the sfunc and finalfunc and I'm reasonably certain it's because
I'm not understanding how to pass arrays in properly.  I didn't piece it
together from the docs and I didn't find examples of how to do this.

I'm running postgresql 7.4.3.

I have:

create or replace function float8_jitter_sum(float8[])
        returns float8 as '
declare
        v_state ALIAS FOR $1;
        v_avg float8;
BEGIN
        v_avg := NULL;
        if v_state[1] != 0 THEN
                v_avg := v_state[0] / v_state[1];
        END IF;
        return v_avg;
END;
' language 'plpgsql';

as an eventual final function.  When I call it by hand via:

select float8_jitter_sum('{5,6,.3}');
select float8_jitter_sum('{5,9}');

I get back nothing where I think I should get back division of the first
two elements of the array.

What am I missing?

If you're particularly curious, I've attached the entirity of what
my aggregate code looks like with an example table and select.  (I'm
basically adding up the variance between an ordered list of elements,
skipping things where the previous element is NULL).  Since I havne't
gotten past the above, I haven't really looked closely into what else
I'm doing wrong (I'm certain I am because the final select gives me
an array value error), but if you spot something obvious, that'd be
appreciated to.

thanks in advance,
-Todd

----<snip>----
create or replace function float8_jitter_add(float8[], interval)
        returns float8[3] as '
declare
        v_old_state ALIAS FOR $1;
        v_rtt ALIAS FOR $2;
        v_state[3] float8;
BEGIN
        IF v_state is NULL THEN
                v_state[0] := 0;
                v_state[1] := 0;
                v_state[2] := NULL;
        ELSIF v_rtt IS NOT NULL THEN
                if v_state[2] IS NOT NULL THEN
                        v_state[0] := v_old_state[0] + (v_old_state[2] - v_rtt);
                        v_state[1] := v_old_state[1] + 1;
                END IF;
                v_state[2] := v_tt;
        ELSE
                v_state[2] := NULL;
        END IF;
        return v_state;
END;
' language 'plpgsql';

create or replace function float8_jitter_sum(float8[])
        returns float8 as '
declare
        v_state ALIAS FOR $1;
        v_avg float8;
BEGIN
        v_avg := NULL;
        if v_state[1] != 0 THEN
                v_avg := v_state[0] / v_state[1];
        END IF;
        return v_avg;
END;
' language 'plpgsql';

drop aggregate jitter( interval );
create aggregate jitter (
        basetype = interval,
        sfunc = float8_jitter_add,
        stype = float8[],
        finalfunc = float8_jitter_sum,
        initcond = '(NULL,NULL)'
);


create table test (
        thing   integer,
        start   timestamp,
        finish  timestamp
);

insert into test values (1, current_timestamp, current_timestamp + '1 sec');
insert into test values (1, current_timestamp, current_timestamp + '2 sec');

select thing, jitter(finish-start) from test group by thing;


Re: arrays and functions in plpgsql

From
Tom Lane
Date:
Todd Kover <kovert@omniscient.com> writes:
> I have:

> create or replace function float8_jitter_sum(float8[])
>         returns float8 as '
> declare
>         v_state ALIAS FOR $1;
>         v_avg float8;
> BEGIN
>         v_avg := NULL;
>         if v_state[1] != 0 THEN
>                 v_avg := v_state[0] / v_state[1];
>         END IF;
>         return v_avg;
> END;
> ' language 'plpgsql';

Array indexes start from one by default, so I think you need

        if v_state[2] != 0 THEN
                v_avg := v_state[1] / v_state[2];

> create aggregate jitter (
>         basetype = interval,
>         sfunc = float8_jitter_add,
>         stype = float8[],
>         finalfunc = float8_jitter_sum,
>         initcond = '(NULL,NULL)'
> );

This initcond will not work either, since that's not valid syntax for an
array (and we don't yet support nulls as array elements anyway).
But you are already testing for v_state IS NULL, so just leave out the
initcond and let it default to a NULL array.

>         IF v_state is NULL THEN
>                 v_state[0] := 0;
>                 v_state[1] := 0;
>                 v_state[2] := NULL;

This is going to be a problem too.  You could write

        v_state := ''{0,0,0}'';

but initializing the array one-element-at-a-time won't work in 7.4.
(I think it will work in 8.0, FWIW.)  You'll have to invent some
convention other than NULL for the third entry, also.  Maybe use
a 4-element array and let the 4th element be 1 or 0 according to
whether the 3rd element is really meaningful?

            regards, tom lane

Re: arrays and functions in plpgsql

From
Todd Kover
Date:
 > You'll have to invent some convention other than NULL for the third
 > entry, also.  Maybe use a 4-element array and let the 4th element be
 > 1 or 0 according to whether the 3rd element is really meaningful?

Thanks.  All your suggestions helped a bunch and make sense.  Although
I'm running into an issue:

create or replace function float8_jitter_add(float8[], interval)
        returns float8[3] as '
declare
        v_old_state ALIAS FOR $1;
        v_rtt ALIAS FOR $2;
        v_state[3] float8;
BEGIN
        IF v_state is NULL THEN
                v_state = ''{0, 0, 0, 0}'';
        ELSIF v_rtt IS NOT NULL THEN
                if v_state[4] = 1 THEN
                        v_state[1] := v_old_state[2] + (v_old_state[3] - v_rtt);
                        v_state[2] := v_old_state[2] + 1;
                END IF;
                v_state[3] := v_rtt;
                v_state[4] := 1;
        ELSE
                v_state[4] := 0;
        END IF;
        return v_state;
END;
' language 'plpgsql';


testdb=# select float8_jitter_add('{.1,.2,.3,1}', 5);
ERROR:  "$1" is declared CONSTANT
CONTEXT:  compile of PL/pgSQL function "float8_jitter_add" near line 12

Near as I can tell, I'm not reassigning $1 (or v_old_state) and line 12
is the END IF, which looks to be to be ok as do the lines around it.

I'm probably missing something obvious..

thanks again,
-Todd

Re: arrays and functions in plpgsql

From
Tom Lane
Date:
Todd Kover <kovert@omniscient.com> writes:
>         v_state[3] float8;

Hmm.  I'm not sure what the plpgsql parser will make of that.  I think
you probably wanted
          v_state float8[3];

(note that you really want [4], not that it actually matters since PG
doesn't enforce the array size; float8[] would do as well)

Also, I think you need to change v_state to v_old_state in several more
places than you did, or else assign v_old_state to v_state up front.

            regards, tom lane

Re: arrays and functions in plpgsql

From
Todd Kover
Date:
 > >         v_state[3] float8;
 >
 > Hmm.  I'm not sure what the plpgsql parser will make of that.  I think
 > you probably wanted
 >     v_state float8[3];

oops.  That was me randomly trying things I thought I understood.  (I
thought I got all those :-)

 > (note that you really want [4], not that it actually matters since PG
 > doesn't enforce the array size; float8[] would do as well)
 >
 > Also, I think you need to change v_state to v_old_state in several more
 > places than you did, or else assign v_old_state to v_state up front.

indeed I did.  Still have the same problem, though:

create or replace function float8_jitter_add(float8[], interval)
    returns float8[] as '
declare
    v_old_state ALIAS FOR $1;
    v_rtt ALIAS FOR $2;
    v_state float8[];
BEGIN
    IF v_old_state is NULL THEN
        v_state = ''{0, 0, 0, 0}'';
    ELSIF v_rtt IS NOT NULL THEN
        if v_old_state[4] = 1 THEN
            v_state[1] := v_old_state[2] + (v_old_state[3] - v_rtt);
            v_state[2] := v_old_state[2] + 1;
        END IF;
        v_state[3] := v_rtt;
        v_state[4] := 1;
    ELSE
        v_state[4] := 0;
    END IF;
    return v_state;
END;
' language 'plpgsql';

testdb=# select float8_jitter_add('{.1,.2,.3,1}', 5);
ERROR:  "$1" is declared CONSTANT
CONTEXT:  compile of PL/pgSQL function "float8_jitter_add" near line 12

-Todd

Re: arrays and functions in plpgsql

From
Tom Lane
Date:
Todd Kover <kovert@omniscient.com> writes:
> indeed I did.  Still have the same problem, though:

> create or replace function float8_jitter_add(float8[], interval)
> ...
> testdb=# select float8_jitter_add('{.1,.2,.3,1}', 5);
> ERROR:  "$1" is declared CONSTANT
> CONTEXT:  compile of PL/pgSQL function "float8_jitter_add" near line 12

Hmm, that's weird ... [ cut ... paste ... ]

...
CREATE FUNCTION
regression=# select float8_jitter_add('{.1,.2,.3,1}', 5);
ERROR:  function float8_jitter_add("unknown", integer) does not exist
HINT:  No function matches the given name and argument types. You may need to add explicit type casts.

[ think ... ]  Oh.  You are creating float8_jitter_add(float8[], interval)
whereas this call is going to invoke float8_jitter_add(float8[], integer)
or something compatible with that.  You're seeing a syntax error in a
different, pre-existing function with a similar name.

FWIW, 8.0 has a number of improvements in error reporting that will
hopefully make this sort of problem more transparent.

            regards, tom lane