Q:

How to create a pageable function in PostgreSQL

I have two tables: event and location
CREATE TABLE location
(
  location_id bigint NOT NULL,
  version bigint NOT NULL,
  active boolean NOT NULL,
  created timestamp without time zone NOT NULL,
  latitude double precision NOT NULL,
  longitude double precision NOT NULL,
  updated timestamp without time zone,
  CONSTRAINT location_pkey PRIMARY KEY (location_id)
)

CREATE TABLE event
(
  event_id bigint NOT NULL,
  version bigint NOT NULL,
  active boolean NOT NULL,
  created timestamp without time zone NOT NULL,
  end_date date,
  entry_fee numeric(19,2),
  location_id bigint NOT NULL,
  organizer_id bigint NOT NULL,
  start_date date NOT NULL,
  timetable_id bigint,
  updated timestamp without time zone,
  CONSTRAINT event_pkey PRIMARY KEY (event_id),
  CONSTRAINT fk_organizer FOREIGN KEY (organizer_id)
      REFERENCES "user" (user_id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT fk_timetable FOREIGN KEY (timetable_id)
      REFERENCES timetable (timetable_id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT fk_location FOREIGN KEY (location_id)
      REFERENCES location (location_id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)

Other tables are of lesser to no importance so they will not be shown (unless explicitly asked).
And for those tables, using cube and earthdistance pgsql extensions I've created the following function for finding all event_ids within a certain radius of a certain point.
CREATE OR REPLACE FUNCTION eventidswithinradius(
    lat double precision,
    lng double precision,
    radius double precision)
  RETURNS SETOF bigint AS
$BODY$
BEGIN
    RETURN QUERY SELECT event.event_id 
    FROM event 
    INNER JOIN location ON location.location_id = event.location_id
    WHERE earth_box( ll_to_earth(lat, lng), radius) @> ll_to_earth(location.latitude, location.longitude);
END;
$BODY$

And this works as expected. Now I wish to make it pageable, and am stuck on how to get all the necessary values (the table with paged contents and total count).
So far I've created this:
CREATE OR REPLACE FUNCTION pagedeventidswithinradius(
    IN lat double precision,
    IN lng double precision,
    IN radius double precision,
    IN page_size integer,
    IN page_offset integer)
  RETURNS TABLE( total_size integer , event_id bigint ) AS
$BODY$
DECLARE total integer;
BEGIN   
    SELECT COUNT(location.*) INTO total FROM location WHERE earth_box( ll_to_earth(lat, lng), radius) @> ll_to_earth(location.latitude, location.longitude);

    RETURN QUERY SELECT total, event.event_id as event_id
    FROM event
    INNER JOIN location ON location.location_id = event.location_id
    WHERE earth_box( ll_to_earth(lat, lng), radius) @> ll_to_earth(location.latitude, location.longitude)
    ORDER BY event_id   
    LIMIT page_size OFFSET page_offset;
END;
$BODY$

Here count is called only once and stored in a variable since I assumed that if I placed COUNT into the return query itself it would be called for each row.
This kind of works, but it is difficult to parse on the back-end since the result is in the form of (count, event_id), also count is needlessly repeated over all result rows. I was hoping I could simply add total as an OUT param and have the function return the table and fill the OUT variable with total count, however it seems this is not allowed. I can always have the count be a separate function but I was wondering if there is a better way to approach this issue? 

A:

No, there isn't really a better option. You want two different types of quantities so you need two queries. You can improve upon your function, however:
CREATE FUNCTION eventidswithinradius(lat float8, long float8, radius float8) RETURNS SETOF bigint AS $BODY$
  SELECT event.event_id 
  FROM event 
  JOIN location l USING (location_id)
  WHERE earth_box(ll_to_earth(lat, lng), radius) @> ll_to_earth(l.latitude, l.longitude);
$BODY$ LANGUAGE sql STRICT;

As a LANGUAGE sql function it is more efficient than as a PL/pgSQL function, plus you can do your paging on the outside:
SELECT *
FROM eventidswithinradius(121.056, 14.582, 3000)
LIMIT 15 OFFSET 1;

Internally the query planner will resolve the function call to its underlying query and apply the paging directly to that level.
Get the total with the obvious:
SELECT count(id)
FROM eventidswithinradius(121.056, 14.582, 3000);

