--
-- The Postgres specific hacks
--
-- Ben Adida (ben@adida.net)
--

-- Uncomment these two lines and comment the two below if
-- you have the RPM version of PostgreSQL
--
--create function plpgsql_call_handler() RETURNS opaque
--as '/usr/lib/pgsql/plpgsql.so' language 'c';
--
create function plpgsql_call_handler() RETURNS opaque
as '/usr/local/pgsql/lib/plpgsql.so' language 'c';

create trusted procedural language 'plpgsql'
HANDLER plpgsql_call_handler
LANCOMPILER 'PL/pgSQL';

-- standard sysdate call
create function date_standardize(datetime) returns varchar as '
declare
	the_date alias for $1;
begin
	return date_part(''year'',the_date) || ''-'' || lpad(date_part(''month'',the_date),2,''0'') || ''-'' || lpad(date_part(''day'',the_date),2,''0'');
end;
' language 'plpgsql';

-- DRB sez: ''now''::datetime gets evaluated at compile time, due to
-- the cast, apparently.  Not what we want!  ''now'' alone, cast
-- implicitly when the call or a return is executed, appears to work.
-- Gaack?  Yep.

create function sysdate_standard() returns varchar as '
begin
	return date_standardize(''now'');
end;
' language 'plpgsql';

-- sysdate hack to make things look somewhat alike
create function sysdate() returns datetime as '
begin
	return ''now'';
end;' language 'plpgsql';

-- DRB's hack for the system table 'dual' - the call to sysdate()
-- is executed each time dual's referenced.  Using a view like this
-- means that "select sysdate from dual" works fine.  Since a single
-- row is always returned, you can select any expression as well
-- and it works.

create view dual as select sysdate();
 
-- date trunc
create function trunc(datetime) returns datetime as '
declare
	the_date alias for $1;
begin
	return date_trunc(''day'',the_date);
end;
' language 'plpgsql';

create function trunc(datetime, varchar) returns datetime
as '
DECLARE
	the_date alias for $1;
	the_pattern alias for $2;
BEGIN
	return date_trunc(the_pattern, the_date);
END;
' language 'plpgsql';

-- Get the last day of a month
create function last_day(datetime) returns datetime
as '
DECLARE
	the_date alias for $1;
BEGIN
	return date_trunc(''Month'', the_date + ''1 month''::reltime) - ''1 day''::reltime;
END;
' language 'plpgsql';

-- Julian date
-- create function to_date_from_julian(numeric) 
-- returns datetime as '
-- DECLARE
--	the_julian alias for $1;
-- BEGIN
-- 	return ''0000-01-01 12:00:00''::datetime + (( the_julian - 1721058 ) || '' day'')::reltime;
-- END;
-- ' language 'plpgsql';

-- Julian date (as modified by Michael A. Cleverly, 30 mar 2000)
-- May need to be checked and reconciled with the above
create function to_date_from_julian(numeric) 
returns date as '
DECLARE
	the_julian alias for $1;
BEGIN
	return ''0000-01-01''::date + ( the_julian - 1721060 );
END;
' language 'plpgsql';

-- sign
-- drop function sign(float4);
create function sign(float4) returns integer as '
declare
	the_number alias for $1;
begin
	if the_number >=0 then return 1;
	else return -1;
	end if;
end;
' language 'plpgsql';

-- drop function sign(interval);
create function sign(interval) returns integer as '
declare
	the_interval alias for $1;
begin
	if date_part(''day'',the_interval) >=0 then return 1;
	else return -1;
	end if;
end;
' language 'plpgsql';


-- date stuff
create function date_num_days(timespan) returns numeric as '
DECLARE
	the_span alias for $1;
	num_days numeric;
BEGIN
	num_days:= date_part(''day'', the_span);
	num_days:= num_days + ((date_part(''hour'', the_span)/24)::numeric);

	RETURN num_days;
END;
' language 'plpgsql';

create function timespan_days(integer) returns timespan as '
DECLARE
	n_days alias for $1;
BEGIN
	return (n_days || '' days'')::timespan;
END;
' language 'plpgsql';

-- Mimic Oracle's negation of character-based pseudo-bools

create function logical_negation(char) returns char as '
BEGIN
	IF ($1 = ''f'') THEN RETURN ''t'';
	ELSE RETURN ''f'';
    END IF;
END;
' language 'plpgsql';

-- Negate a REAL bool, in case we get smart and use SQL92's built-in
-- bool type, which is fully supported by PG.  Of course, we should
-- weed out calls to this function for efficiency reasons, but this
-- will keep things running in the interim.

create function logical_negation(bool) returns bool as '
BEGIN
	RETURN NOT $1;
END;
' language 'plpgsql';


create function round(integer) returns integer
as '
DECLARE
	the_int alias for $1;
BEGIN
	return round(the_int,0);
END;
' language 'plpgsql';

-- Mimic Oracle's months_between built-in

create function months_between(timestamp, timestamp) returns real
as '
begin
    return date_part(''year'', age($1, $2)) * 12.0 + date_part(''month'', age($1, $2)) +
        date_part(''day'', age($1, $2)) / 31.0;
end;' language 'plpgsql';

-- Mimic Oracle's user_tab_columns table (thanks Ken Mayer!)

CREATE VIEW user_tab_columns AS
SELECT upper(c.relname) AS table_name, 
       upper(a.attname) AS column_name,
       CASE WHEN (t.typprtlen > 0)
       THEN t.typprtlen
       ELSE (a.atttypmod - 4)
       END AS data_length
FROM pg_class c, pg_attribute a, pg_type t
WHERE (a.attrelid = c.oid) AND (a.atttypid = t.oid) AND (a.attnum > 0);

