-- LegacyWorlds Beta 6
-- PostgreSQL database scripts
--
-- Logging functions and triggers
--
-- Copyright(C) 2004-2010, DeepClone Development
-- --------------------------------------------------------




-- --------------------------------------------------------------------------------------------------------------------------------------------------------------- --
-- --------------------------------------------------------------------------------------------------------------------------------------------------------------- --
-- LOG CLEANUP                                                                                                                                                     -- 
-- --------------------------------------------------------------------------------------------------------------------------------------------------------------- --
-- --------------------------------------------------------------------------------------------------------------------------------------------------------------- --


--
-- Remove expired entries from a log table
--
-- Parameters:
--	l_type	Log table to clean up
--

CREATE OR REPLACE FUNCTION sys.clean_log_table( l_type log_type )
		RETURNS VOID
		STRICT VOLATILE
		SECURITY INVOKER
	AS $$
DECLARE
	expiry		INT;
	expiry_int	INTERVAL;
BEGIN
	expiry := floor( sys.get_constant( 'log.maxAge.' || lower( l_type::TEXT ) ) );
	expiry_int := ( expiry || 's' )::INTERVAL;
	EXECUTE 'DELETE FROM ' || l_type::TEXT || '.logs WHERE t <= $1'
		USING now() - expiry_int;
END;
$$ LANGUAGE plpgsql;



--
-- Clean all log tables
--

CREATE OR REPLACE FUNCTION sys.clean_logs( )
		RETURNS VOID
		STRICT VOLATILE
		SECURITY DEFINER
	AS $$
DECLARE
	lt		log_type;
BEGIN
	FOR lt IN SELECT x FROM unnest( enum_range( NULL::log_type ) ) AS x
	LOOP
		PERFORM sys.clean_log_table( lt );
	END LOOP;
END;
$$ LANGUAGE plpgsql;

GRANT EXECUTE ON FUNCTION sys.clean_logs( ) TO :dbuser;




-- --------------------------------------------------------------------------------------------------------------------------------------------------------------- --
-- --------------------------------------------------------------------------------------------------------------------------------------------------------------- --
-- LOG APPENDERS                                                                                                                                                   -- 
-- --------------------------------------------------------------------------------------------------------------------------------------------------------------- --
-- --------------------------------------------------------------------------------------------------------------------------------------------------------------- --


--
-- Write an user log entry
--

CREATE OR REPLACE FUNCTION users.write_log( account_id INT , lv log_level , msg TEXT )
		RETURNS VOID
		STRICT VOLATILE
		SECURITY INVOKER
	AS $$
BEGIN
	INSERT INTO users.logs (credentials_id , level , message)
		VALUES ( account_id , lv , msg );
END;
$$ LANGUAGE plpgsql;



--
-- Write an administrative log entry
--

CREATE OR REPLACE FUNCTION admin.write_log( adm_id INT , lv log_level , msg TEXT )
		RETURNS VOID
		STRICT VOLATILE
		SECURITY INVOKER
	AS $$
BEGIN
	INSERT INTO admin.logs ( admin_id , level , message )
		VALUES ( adm_id , lv , msg );
END;
$$ LANGUAGE plpgsql;



--
-- Write a system log entry
--

CREATE OR REPLACE FUNCTION sys.write_log( cmp TEXT , lv log_level , msg TEXT , OUT entry_id BIGINT )
		STRICT VOLATILE
		SECURITY DEFINER
	AS $$
BEGIN
	INSERT INTO sys.logs( component , level , message )
		VALUES ( cmp , lv , msg )
		RETURNING id INTO entry_id;
END;
$$ LANGUAGE plpgsql;

GRANT EXECUTE ON FUNCTION sys.write_log( TEXT , log_level , TEXT ) TO :dbuser;


--
-- Remotely append a system log entry
--
-- This function is called from within transactions in order to write to the
-- system log. Unlike sys.write_log(), entries added through this function
-- will not be lost if the transaction is rolled back.
--
-- Since the function is meant to be called from SQL code, it does not return
-- anything as the identifier of the new entry is not required.
--
-- Parameters:
--	_component	The component that is appending to the log
--	_level		The log level
--	_message	The message to write
--

CREATE OR REPLACE FUNCTION sys.write_sql_log( _component TEXT , _level log_level , _message TEXT )
		RETURNS VOID
		STRICT VOLATILE
		SECURITY INVOKER
	AS $write_sql_log$
BEGIN
	PERFORM dblink_connect( 'cn_logging' , 'srv_logging' );
	PERFORM * FROM dblink( 'cn_logging' ,
		'SELECT * FROM sys.write_log( '
			|| quote_literal( _component ) || ' , '''
			|| _level::text || '''::log_level , '
			|| quote_literal( _message ) || ' )'
	) AS ( entry_id bigint );
	PERFORM dblink_disconnect( 'cn_logging' );
END;
$write_sql_log$ LANGUAGE plpgsql;

GRANT EXECUTE ON FUNCTION sys.write_sql_log( TEXT , log_level , TEXT ) TO :dbuser;



--
-- Append an exception log entry
--

CREATE OR REPLACE FUNCTION sys.append_exception( l_id BIGINT , cname TEXT , msg TEXT , OUT entry_id BIGINT )
		CALLED ON NULL INPUT
		VOLATILE
		SECURITY DEFINER
	AS $$
DECLARE
	d	INT;
BEGIN
	SELECT INTO d max( depth ) FROM sys.exceptions
		WHERE log_id = l_id;
	IF d IS NULL THEN
		d := 0;
	END IF;
	d := d + 1;
	
	INSERT INTO sys.exceptions ( log_id , depth , exc_class , message )
		VALUES ( l_id , d , cname , msg )
		RETURNING id INTO entry_id;
END;
$$ LANGUAGE plpgsql;

GRANT EXECUTE ON FUNCTION sys.append_exception( BIGINT , TEXT , TEXT ) TO :dbuser;



--
-- Append a stack trace entry
--

CREATE OR REPLACE FUNCTION sys.append_trace( e_id BIGINT , loc TEXT , f_name TEXT , lnb INT )
		RETURNS VOID
		CALLED ON NULL INPUT
		VOLATILE
		SECURITY DEFINER
	AS $$
DECLARE
	d	INT;
BEGIN
	SELECT INTO d max( depth ) FROM sys.stack_traces
		WHERE exception_id = e_id;
	IF d IS NULL THEN
		d := 0;
	END IF;
	d := d + 1;

	INSERT INTO sys.stack_traces ( exception_id	, depth , location , file_name , line_number )
		VALUES ( e_id , d , loc , f_name , lnb );
END;
$$ LANGUAGE plpgsql;

GRANT EXECUTE ON FUNCTION sys.append_trace( BIGINT , TEXT , TEXT , INT ) TO :dbuser;




-- --------------------------------------------------------------------------------------------------------------------------------------------------------------- --
-- --------------------------------------------------------------------------------------------------------------------------------------------------------------- --
-- LOGS VIEW FOR EASY SELECTION                                                                                                                                    -- 
-- --------------------------------------------------------------------------------------------------------------------------------------------------------------- --
-- --------------------------------------------------------------------------------------------------------------------------------------------------------------- --


CREATE VIEW sys.system_logs_view
	AS SELECT l.id , l.t , l.component , l.level , l.message ,
			( CASE WHEN e.id IS NOT NULL THEN l.id ELSE NULL END )::BIGINT AS exception_id
		FROM sys.logs l
			LEFT OUTER JOIN sys.exceptions e ON e.log_id = l.id AND e.depth = 1;

GRANT SELECT ON sys.system_logs_view TO :dbuser;


CREATE VIEW sys.admin_logs_view
	AS SELECT NULL::BIGINT AS id , l.t , a.appear_as AS component , l.level ,
			l.message , NULL::BIGINT AS exception_id
		FROM admin.logs l
			INNER JOIN admin.administrators a ON a.id = l.admin_id;

GRANT SELECT ON sys.admin_logs_view TO :dbuser;


CREATE VIEW sys.player_logs_view
	AS SELECT NULL::BIGINT AS id , l.t , a.address AS component , l.level ,
			l.message , NULL::BIGINT AS exception_id
		FROM users.logs l
			INNER JOIN users.addresses a ON a.id = l.credentials_id;

GRANT SELECT ON sys.player_logs_view TO :dbuser;




-- --------------------------------------------------------------------------------------------------------------------------------------------------------------- --
-- --------------------------------------------------------------------------------------------------------------------------------------------------------------- --
-- ADMIN ERROR REPORT FUNCTION                                                                                                                                     -- 
-- --------------------------------------------------------------------------------------------------------------------------------------------------------------- --
-- --------------------------------------------------------------------------------------------------------------------------------------------------------------- --


CREATE OR REPLACE FUNCTION admin.get_error_entries( )
		RETURNS SETOF BIGINT
		STRICT VOLATILE
		SECURITY DEFINER
	AS $$
DECLARE
	last_report	TIMESTAMP WITHOUT TIME ZONE;
	new_report	TIMESTAMP WITHOUT TIME ZONE;
	e_id		BIGINT;
BEGIN
	new_report := now( );
	SELECT INTO last_report last_error_recap FROM sys.status FOR UPDATE;
	UPDATE sys.status SET last_error_recap = new_report;
	
	RETURN QUERY SELECT id FROM sys.system_logs_view
		WHERE t >= last_report AND t < new_report AND exception_id IS NOT NULL
		ORDER BY t DESC; 
END;
$$ LANGUAGE plpgsql;

GRANT EXECUTE ON FUNCTION admin.get_error_entries( ) TO :dbuser;
