diff --git a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java index 5a0cf11..414ba46 100644 --- a/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java +++ b/legacyworlds-server-beans-system/src/main/java/com/deepclone/lw/beans/sys/ConstantsRegistrarBean.java @@ -32,6 +32,8 @@ public class ConstantsRegistrarBean // Misc. game-related values cDesc = "Game updates - batch size."; defs.add( new ConstantDefinition( "game.batchSize" , "Game (misc)" , cDesc , 20.0 , 1.0 , true ) ); + cDesc = "Event processing - batch size."; + defs.add( new ConstantDefinition( "game.events.batchSize" , "Game (misc)" , cDesc , 100.0 , 1.0 , true ) ); cDesc = "Population growth factor."; defs.add( new ConstantDefinition( "game.growthFactor" , "Game (misc)" , cDesc , 50.0 , 1.0 , true ) ); cDesc = "Increase to the population growth factor caused by reanimation centres."; diff --git a/legacyworlds-server-data/db-structure/parts/020-extensions.sql b/legacyworlds-server-data/db-structure/parts/020-extensions.sql index 9adc475..50db2a8 100644 --- a/legacyworlds-server-data/db-structure/parts/020-extensions.sql +++ b/legacyworlds-server-data/db-structure/parts/020-extensions.sql @@ -28,3 +28,8 @@ CREATE USER MAPPING FOR :dbuser OPTIONS ( user :dbuser_string , password :dbupass ); GRANT USAGE ON FOREIGN SERVER srv_logging TO :dbuser; + +/* The events sytem uses the hash store extension to convert events from the + * queues to main storage. + */ +CREATE EXTENSION hstore; \ No newline at end of file diff --git a/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql b/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql index 36bd27f..356d9e2 100644 --- a/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql +++ b/legacyworlds-server-data/db-structure/parts/030-data/170-events.sql @@ -239,6 +239,28 @@ CREATE SEQUENCE events.event_id_sequence; GRANT SELECT,UPDATE ON events.event_id_sequence TO :dbuser; +/* + * Pending events + * -------------- + * + * This table is updated when events are inserted into queues and when they + * are converted. It tracks events that need to be converted. + * + * Note: the table does not have a primary key or foreign keys; it is simply + * a cache which must not get in the way of inserts into queues. + * + * Warning: a record in this table may be orphaned, i.e. there could be no + * corresponding entry in the queue table. + */ +DROP TABLE IF EXISTS events.pending_events CASCADE; +CREATE TABLE events.pending_events( + /* Event identifier */ + event_id BIGINT NOT NULL , + /* Event type */ + evdef_id VARCHAR(48) NOT NULL +); + + /* * Event storage table * ------------------- diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/170-event-definitions.sql b/legacyworlds-server-data/db-structure/parts/040-functions/170-event-definitions.sql index 564c971..f593da8 100644 --- a/legacyworlds-server-data/db-structure/parts/040-functions/170-event-definitions.sql +++ b/legacyworlds-server-data/db-structure/parts/040-functions/170-event-definitions.sql @@ -707,6 +707,33 @@ REVOKE EXECUTE FROM PUBLIC; +/* + * Trigger used to insert entries into pending_events + * -------------------------------------------------- + * + * This function is used as a trigger to insert queued events into the + * events.pending_events table. + * + * Parameters: + * TG_ARGV[0] Event type identifier + */ +DROP FUNCTION IF EXISTS events.tgf_eq_insert( ); +CREATE FUNCTION events.tgf_eq_insert( ) + RETURNS TRIGGER + LANGUAGE PLPGSQL + STRICT VOLATILE SECURITY DEFINER + AS $tgf_eq_insert$ +BEGIN + INSERT INTO events.pending_events( event_id , evdef_id ) + VALUES ( NEW.event_id , TG_ARGV[0] ); + RETURN NEW; +END; +$tgf_eq_insert$; + +REVOKE EXECUTE + ON FUNCTION events.tgf_eq_insert( ) + FROM PUBLIC; + /* * Create the event queuing table for a new definition @@ -734,14 +761,14 @@ CREATE FUNCTION events.evdef_create_queue_table( _evdef_id TEXT ) AS $evdef_create_queue_table$ DECLARE + _tsfx TEXT; _rec RECORD; _qstr TEXT; _fname TEXT; BEGIN - - _qstr := 'CREATE TABLE events.eq_' || replace( _evdef_id , '-' , '_' ) - || $tbl_def$ ( + _tsfx := replace( _evdef_id , '-' , '_' ); + _qstr := 'CREATE TABLE events.eq_' || _tsfx || $tbl_def$ ( event_id BIGINT NOT NULL PRIMARY KEY DEFAULT nextval('events.event_id_sequence'::regclass) , event_rtime TIMESTAMP WITHOUT TIME ZONE NOT NULL @@ -782,9 +809,14 @@ BEGIN _qstr := _qstr || ');'; EXECUTE _qstr; - _qstr := 'GRANT INSERT ON TABLE events.eq_' || replace( _evdef_id , '-' , '_' ) + _qstr := 'GRANT INSERT ON TABLE events.eq_' || _tsfx || ' TO $init_code$ || $1 || $init_code$;'; EXECUTE _qstr; + + _qstr := 'CREATE TRIGGER tg_eqi_' || _tsfx || ' AFTER INSERT ON events.eq_' + || _tsfx || ' FOR EACH ROW EXECUTE PROCEDURE events.tgf_eq_insert(''' + || _evdef_id || ''');'; + EXECUTE _qstr; END; $evdef_create_queue_table$; $init_code$ USING $1; END; $init_func$; SELECT _temp_init_func(:dbuser_string); diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/172-event-storage.sql b/legacyworlds-server-data/db-structure/parts/040-functions/172-event-storage.sql new file mode 100644 index 0000000..ed351fa --- /dev/null +++ b/legacyworlds-server-data/db-structure/parts/040-functions/172-event-storage.sql @@ -0,0 +1,183 @@ +-- LegacyWorlds Beta 6 +-- PostgreSQL database scripts +-- +-- Functions that extract event data from the queue tables +-- and store them in the long term storage tables. +-- +-- Copyright(C) 2004-2012, DeepClone Development +-- -------------------------------------------------------- + + +/* + * Entity fields table + * ------------------- + * + * This table, which is filled when the database is created, associates entity + * types with fields in the events.field_values table. + */ +DROP TABLE IF EXISTS events._entity_fields; +CREATE TABLE events._entity_fields( + efdef_entity events.entity_field_type NOT NULL PRIMARY KEY , + field_name NAME NOT NULL , + field_type NAME NOT NULL +); +INSERT INTO events._entity_fields VALUES + ( 'EMP' , 'empire_id' , 'INT' ) , + ( 'PLN' , 'planet_id' , 'INT' ) , + ( 'FLT' , 'fleet_id' , 'BIGINT' ) , + ( 'ALL' , 'alliance_id' , 'INT' ) , + ( 'BAT' , 'battle_id' , 'BIGINT' ) , + ( 'ADM' , 'admin_id' , 'INT' ) , + ( 'BUG' , 'bug_report_id' , 'BIGINT' ); + + + +/* + * Process a pending event + * ----------------------- + * + * /!\ INTERNAL FUNCTION /!\ + * + * This procedure processes a single row from an event queue. + * + * If the specified event identifier actually exists in the queue, it is + * converted and stored in the main events tables, then the queue entry is + * removed. + * + * In all cases, the entry is deleted from events.pending_events. + * + * Parameters: + * _event_id The event's identifier + * _evdef_id The event's type + * + * Returns: + * ??? TRUE + */ +DROP FUNCTION IF EXISTS events.eq_process_event( BIGINT , TEXT ); +CREATE FUNCTION events.eq_process_event( + _event_id BIGINT , + _evdef_id TEXT ) + RETURNS BOOLEAN + LANGUAGE PLPGSQL + STRICT VOLATILE SECURITY INVOKER + AS $eq_process_event$ + +DECLARE + _tbl TEXT; + _qstr TEXT; + _qentry RECORD; + _nfound INT; + _efdef RECORD; + +BEGIN + + _tbl := 'events.eq_' || replace( _evdef_id , '-' , '_' ); + _qstr := 'SELECT event_rtime , event_gtime , empire_id FROM ' + || _tbl || ' WHERE event_id = $1'; + EXECUTE _qstr INTO _qentry USING _event_id; + GET DIAGNOSTICS _nfound = ROW_COUNT; + + IF _nfound > 0 + THEN + + INSERT INTO events.events_v2 ( + event_id , evdef_id , event_rtime , event_gtime , empire_id + ) VALUES ( + _event_id , _evdef_id , _qentry.event_rtime , + _qentry.event_gtime , _qentry.empire_id + ); + + _qstr := format( $field_acquisition$ + SELECT efdef_id , field_name , field_type , + ( efdef_type = 'I18N') AS i18n , _sq1.value + FROM events.field_definitions + INNER JOIN ( + SELECT (each(hstore(_tbl))).* + FROM %s _tbl + WHERE _tbl.event_id = $1 + ) _sq1 ON _sq1.key = 'ef_' || replace(efdef_id , '-','_') + LEFT OUTER JOIN events._entity_fields + USING ( efdef_entity ) + WHERE evdef_id = $2 + $field_acquisition$ , _tbl ); + + FOR _efdef IN EXECUTE _qstr USING _event_id , _evdef_id + LOOP + _qstr := 'INSERT INTO events.field_values(event_id,evdef_id,efdef_id,efval_litteral' + || ( CASE + WHEN _efdef.field_name IS NOT NULL + THEN ',' || _efdef.field_name + WHEN _efdef.i18n + THEN ',string_id' + ELSE + '' + END ) || ') '; + + IF _efdef.i18n + THEN + _qstr := _qstr || $i18n_query$ + SELECT $1,$2,$3,_str.id::TEXT||' '||_str.name,_str.id + FROM defs.strings _str + WHERE _str.name = $4 + $i18n_query$; + ELSIF _efdef.field_name IS NOT NULL + THEN + _qstr := _qstr || format( 'VALUES($1,$2,$3,$4,$4::%s)' , _efdef.field_type ); + ELSE + _qstr := _qstr || 'VALUES($1,$2,$3,$4)'; + END IF; + + EXECUTE _qstr USING _event_id , _evdef_id , _efdef.efdef_id , _efdef.value; + END LOOP; + + END IF; + + DELETE FROM events.pending_events + WHERE event_id = _event_id; + RETURN TRUE; + +END; +$eq_process_event$; + +REVOKE EXECUTE + ON FUNCTION events.eq_process_event( BIGINT , TEXT ) + FROM PUBLIC; + + + +/* + * Process pending events + * ---------------------- + * + * This procedure processes events from the queues. Each time the procedure is + * called, it will process at most "game.events.batchSize" events. + * + * Returns: + * ??? TRUE if events were processed, FALSE if the queues are + * empty + */ +DROP FUNCTION IF EXISTS events.eq_process( ); +CREATE FUNCTION events.eq_process( ) + RETURNS BOOLEAN + LANGUAGE PLPGSQL + STRICT VOLATILE SECURITY DEFINER + AS $eq_process$ + +DECLARE + _limit INT; + +BEGIN + _limit := sys.get_constant( 'game.events.batchSize' ); + PERFORM events.eq_process_event( event_id , evdef_id ) + FROM events.pending_events + LIMIT _limit; + RETURN FOUND; +END; +$eq_process$; + +REVOKE EXECUTE + ON FUNCTION events.eq_process( ) + FROM PUBLIC; +GRANT EXECUTE + ON FUNCTION events.eq_process( ) + TO :dbuser; \ No newline at end of file