]> granicus.if.org Git - postgis/commitdiff
Add in the history table convenience functions, a README and some manual examples...
authorPaul Ramsey <pramsey@cleverelephant.ca>
Tue, 17 Nov 2009 23:27:21 +0000 (23:27 +0000)
committerPaul Ramsey <pramsey@cleverelephant.ca>
Tue, 17 Nov 2009 23:27:21 +0000 (23:27 +0000)
git-svn-id: http://svn.osgeo.org/postgis/trunk@4851 b70326c6-7e19-0410-871a-916f4a2858ee

extras/history_table/README [new file with mode: 0644]
extras/history_table/history_table.sql [new file with mode: 0644]

diff --git a/extras/history_table/README b/extras/history_table/README
new file mode 100644 (file)
index 0000000..bf1dab2
--- /dev/null
@@ -0,0 +1,95 @@
+= HISTORY TRACKING =
+
+Suppose you have a table of data that represents the "current state" of a particular geographic feature. A parcels table, or a roads table, or a fruit trees table, whatever. Generally, GIS tools understand a table as a single entity into which they can update, insert and delete rows from. How you do allow common GIS tools to work against your data, while maintaining and audit trail of what changes have been made, by whom, and what the past state of the data is?
+
+This extra module provides some utility functions for creating and maintaining history. 
+
+If you have a table 'roads', this module will maintain a 'roads_history' side table, which contains all the columns of the parent table, and the following additional columns:
+
+ history_id      | integer                     | not null default 
+ date_added      | timestamp without time zone | not null default now()
+ date_deleted    | timestamp without time zone | 
+ last_operation  | character varying(30)       | not null
+ active_user     | character varying(90)       | not null default "current_user"()
+ current_version | text                        | not null
+
+When you insert a new record into 'roads' a record is automatically inserted into 'roads_history', with the 'date_added' filled in the 'date_deleted' set to NULL, a unique 'history_id', a 'last_operation' of 'INSERT' and 'active_user' set.
+
+When you delete a record in 'roads', the record in the history table is *not* deleted, but the 'date_deleted' is set to the current date.
+
+When you update a record in 'roads', the current record has 'date_deleted' filled in and a new record is created with the 'date_added' set and 'date_deleted' NULL.
+
+With this information maintained, it is possible to retrieve the history of any record in the roads table:
+
+  SELECT * FROM roads_history WHERE roads_pk = 111;
+
+Or, to retrieve a view of the roads table at any point in the past:
+
+  SELECT * FROM roads_history 
+    WHERE date_added < 'January 1, 2001' AND 
+          ( date_deleted >= 'January 1, 2001' OR date_deleted IS NULL );
+
+== USING THE CONVENIENCE FUNCTIONS ==
+
+To history-enable a table using this module
+
+ * Load the history_table.sql file.
+ * Run the initalization routine:
+   SELECT postgis_install_history();
+ * Enable history on a table:
+   SELECT postgis_enable_history('public', 'roads', 'the_geom');
+
+With the example above, you'll now have a 'roads_history' table, and a 'historic_information' metadata table.
+
+
+== USING YOUR OWN FUNCTIONS ==
+
+To enable history from scratch, you need to make the history table and update rules from scratch.
+
+Here's an example using a simple table of points.
+
+CREATE TABLE my_points (
+  id SERIAL PRIMARY KEY,
+  name VARCHAR(32)
+);
+SELECT AddGeometryColumn('my_points', 'geom', 4326, 'POINT', 2);
+
+CREATE TABLE my_points_history (
+  history_id SERIAL PRIMARY KEY,
+  id INTEGER,
+  name VARCHAR(32),
+  added TIMESTAMP NOT NULL DEFAULT now(),
+  deleted TIMESTAMP,
+  added_by VARCHAR(64) NOT NULL DEFAULT CURRENT_USER,
+  deleted_by VARCHAR(64)
+);
+SELECT AddGeometryColumn('my_points_history', 'geom', 4326, 'POINT', 2);
+CREATE INDEX my_points_history_deleted ON my_points_history (deleted);
+CREATE INDEX my_points_history_added ON my_points_history (added);
+CREATE INDEX my_points_history_id ON my_points_history (id);
+
+CREATE OR REPLACE RULE my_points_insert_rule AS 
+ON INSERT TO my_points DO (
+INSERT INTO my_points_history ( id, name, geom ) 
+  VALUES ( NEW.id, NEW.name, NEW.geom );
+);
+
+
+CREATE OR REPLACE RULE my_points_update AS
+ON UPDATE TO my_points DO (
+UPDATE my_points_history SET 
+  deleted = now(),
+  deleted_by = CURRENT_USER
+WHERE id = OLD.id AND OLD.deleted IS NULL;
+INSERT INTO my_points_history ( id, name, geom )
+VALUES ( NEW.id, NEW.name, NEW.geom );
+);
+
+CREATE OR REPLACE RULE my_points_delete AS
+ON DELETE TO my_points DO (
+UPDATE my_points_history SET
+  deleted = now(),
+  deleted_by = CURRENT_USER
+WHERE id = OLD.id AND deleted IS NULL;
+);
+
diff --git a/extras/history_table/history_table.sql b/extras/history_table/history_table.sql
new file mode 100644 (file)
index 0000000..690ea32
--- /dev/null
@@ -0,0 +1,232 @@
+-- PUBLIC FUNCTIONS --
+
+create or replace function postgis_install_history() returns void as
+$$
+--this function creates a table that will hold some interesting values for managing history tables
+--later functions will be added
+BEGIN
+
+       IF exists(select 1 FROM information_schema.tables WHERE table_name = 'historic_information') = true THEN
+               raise notice 'The table historic_information already exists. Could not create it.';
+       ELSE
+       execute 'create table historic_information(table_id serial not null,table_name varchar(100) not null,primary_field varchar(100) not null, geometry_field varchar(100) not null, constraint history_tables_pk primary key(table_id,table_name));';
+       END IF;
+
+END
+$$
+language 'plpgsql';
+
+--end build_history_table
+
+
+--im open to suggestions for the names of the functions.
+--just realized that one is build_history_table and the other create_...
+CREATE OR REPLACE FUNCTION postgis_enable_history(p_schema text,p_table text,p_geometry_field text) returns boolean as
+$$
+DECLARE
+
+v_current_table text;
+v_history_table text;
+
+v_geometry_type text; --checks for the type of p_geometry_field
+v_dimensions integer; --checks for the ndims in p_geometry_field
+v_srid integer;       --checks for the srid in p_geometry_field
+v_gid text;           --checks the name of the pk column in p_table
+
+--SQL statement that will create the historic table
+v_table_sql text;
+
+--SQL statement that will perform an update on geometry_columns
+v_update_geometry_sql text; 
+
+--SQL statement that will perform an update on historic_tables
+v_update_history_sql text;
+
+BEGIN
+
+       --determines the name of current table
+       v_current_table:= p_schema || '.' || p_table;
+       --determines the name of historic table
+       v_history_table:= p_schema || '.' || p_table || '_history';
+
+       --sql to determine the values of geometry type, srid and ndims
+       v_geometry_type:= (SELECT "type" FROM public.geometry_columns WHERE f_table_schema = p_schema AND f_table_name = p_table AND f_geometry_column = p_geometry_field);
+       v_dimensions:= (SELECT coord_dimension FROM public.geometry_columns WHERE f_table_schema = p_schema AND f_table_name = p_table AND f_geometry_column = p_geometry_field);
+       v_srid:= (SELECT srid FROM public.geometry_columns WHERE f_table_schema = p_schema AND f_table_name = p_table AND f_geometry_column = p_geometry_field);
+       v_gid:= (SELECT column_name FROM information_schema.key_column_usage WHERE table_schema = p_schema AND table_name = p_table);
+       --end sql
+       
+       --generate sql for creating the historic table
+       v_table_sql:= 'CREATE TABLE ' || v_history_table || 
+       '(' ||
+       'history_id serial not null,' ||
+       'date_added timestamp not null default now(),' ||
+       'date_deleted timestamp default null,' ||
+       'last_operation varchar(30) not null,' ||
+       'active_user varchar(90) not null default CURRENT_USER,' ||
+       'current_version text not null,' ||
+       'like ' || v_current_table || ',' ||
+       'CONSTRAINT ' || p_table || '_history_pk primary key(history_id));';
+       --end sql
+
+       --update geometry columns
+       v_update_geometry_sql:='INSERT INTO public.geometry_columns(f_table_catalog,f_table_schema,f_table_name,f_geometry_column,coord_dimension,srid,type) values (' ||
+       quote_literal('') || ',' ||
+       quote_literal(p_schema) || ',' ||
+       quote_literal(p_table || '_history') || ',' ||
+       quote_literal(p_geometry_field) || ',' ||
+       v_dimensions::text || ',' ||
+       v_srid::text || ',' ||
+       quote_literal(v_geometry_type) || ');';
+       --end update geometry_columns
+       
+       --insert into historic_tables
+       v_update_history_sql:='INSERT INTO public.historic_information(table_id,table_name,primary_field,geometry_field) VALUES (' ||
+       'DEFAULT,' ||
+       quote_literal(v_history_table) || ',' || 
+       quote_literal(v_gid) || ',' ||
+       quote_literal(p_geometry_field) || ');';
+       --end update historic tables
+
+       execute v_table_sql;
+       execute v_update_geometry_sql;
+       execute v_update_history_sql;
+
+       execute _postgis_add_insert_rule(p_schema,p_table,v_gid);
+       execute _postgis_add_delete_rule(p_schema,p_table,v_gid);
+       execute _postgis_add_update_rule(p_schema,p_table,v_gid);
+       execute _postgis_create_history_indexes(p_schema,p_table,p_geometry_field);
+
+       return true;
+       
+END
+$$
+language 'plpgsql';
+
+--end create_history_table
+
+-- PRIVATE FUNCTIONS --
+
+--add_insert_rule
+CREATE OR REPLACE FUNCTION _postgis_add_insert_rule(p_schema text,p_table text,p_gid_field text) returns void as
+$$
+DECLARE
+
+v_sql text;
+
+BEGIN
+
+       v_sql:= 'CREATE OR REPLACE RULE ' || p_table || '_history_insert as ON INSERT TO ' || p_schema || '.' || p_table ||
+       ' DO (' ||
+       'INSERT INTO ' || p_schema || '.' || p_table || '_history VALUES(' ||
+       'DEFAULT,' || --history_id nextval()
+       'DEFAULT,' || --date_added now()
+       'NULL,' || --date_deleted 
+       quote_literal('INSERT') || ',' || --operation
+       'DEFAULT,' ||
+       'NEW.' || p_gid_field || ',' ||
+       'NEW.*));';
+
+       execute v_sql;
+
+END
+$$
+language 'plpgsql';    
+--end add_insert_rule
+
+--add_update_rule
+CREATE OR REPLACE FUNCTION _postgis_add_update_rule(p_schema text,p_table text,p_gid_field text) returns void as
+$$
+DECLARE
+
+v_sql text;
+
+BEGIN
+
+       v_sql:= 'CREATE OR REPLACE RULE ' || p_table || '_history_update as ON UPDATE TO ' || p_schema || '.' || p_table ||
+       ' DO (' ||
+       'UPDATE ' || p_schema || '.' || p_table || '_history SET ' ||
+       'date_deleted = now(),' ||
+       'active_user = CURRENT_USER,' ||
+       'current_version = ' || 'NEW.' || p_gid_field || ',' ||
+       'last_operation = ' || quote_literal('UPDATE') ||
+       'WHERE ' || p_gid_field || ' = OLD.' || p_gid_field || ';' || -- end of the update statement
+       'INSERT INTO ' || p_schema || '.' || p_table || '_history VALUES (' ||
+       'DEFAULT,' || --history_id nextval()
+       'DEFAULT,' || --date_added now()
+       'NULL,' || --date_deleted 
+       quote_literal('INSERT') || ',' || --operation
+       'DEFAULT,' ||
+       'NEW.' || p_gid_field || ',' ||
+       'NEW.*););';
+
+       execute v_sql;
+
+END
+$$
+language 'plpgsql';
+--end add_update_rule
+
+--add_delete_rule
+CREATE OR REPLACE FUNCTION _postgis_add_delete_rule(p_schema text,p_table text,p_gid_field text) returns void as
+$$
+DECLARE
+
+v_sql text;
+
+BEGIN
+
+       v_sql:= 'CREATE OR REPLACE RULE ' || p_table || '_history_delete as ON DELETE TO ' || p_schema || '.' || p_table ||
+       ' DO (' ||
+       'UPDATE ' || p_schema || '.' || p_table || '_history SET ' ||
+       'date_deleted = now(),' ||
+       'active_user = CURRENT_USER,' ||
+       'current_version = ' || quote_literal('-9999') || ',' ||
+       'last_operation = ' || quote_literal('DELETED') ||
+       'WHERE ' || p_gid_field || ' = OLD.' || p_gid_field || ');';
+
+       execute v_sql;
+
+END
+$$
+language 'plpgsql';
+--end ad__delete_rule
+
+--create indexes function
+CREATE OR REPLACE FUNCTION _postgis_create_history_indexes(p_schema text, p_table text, p_geometry_field text) returns void as
+$$
+DECLARE
+
+v_geomindex_sql text;
+v_dateindex_sql text;
+v_userindex_sql text;
+v_operindex_sql text;
+
+BEGIN
+       v_geomindex_sql:= 'CREATE INDEX ' || 'idx_' || p_table || '_geometry_history' ||
+       ' ON ' || p_schema || '.' || p_table || '_history USING GIST(' || p_geometry_field || ');';
+
+       v_dateindex_sql:= 'CREATE INDEX ' || 'idx_' || p_table || '_date_history' ||
+       ' ON ' || p_schema || '.' || p_table || '_history (date_added,date_deleted);';
+
+       v_userindex_sql:= 'CREATE INDEX ' || 'idx_' || p_table || '_user_history' ||
+       ' ON ' || p_schema || '.' || p_table || '_history(active_user);';
+
+       v_operindex_sql:= 'CREATE INDEX ' || 'idx_' || p_table || '_oper_history' ||
+       ' ON ' || p_schema || '.' || p_table || '_history (last_operation);';
+
+       execute v_geomindex_sql;
+       execute v_dateindex_sql;
+       execute v_userindex_sql;
+       execute v_operindex_sql;
+
+END
+$$
+language 'plpgsql'
+--end create indexes
+
+/*TODO LIST:
+
+CREATE A FUNCTION THAT WILL DROP A CERTAIN HISTORIC TABLE AND REMOVE ITS ITENS FROM GEOMERTY_COLUMNS AND HISTORIC_INFORMATION
+CREATE A FUNCTION TO POPULATE ALL THE EXISTING RECORDS TO THE HISTORIC TABLE, AS A INSERT
+*/
\ No newline at end of file