--- /dev/null
+CFLAGS += -I$(shell pg_config --includedir-server)
+CC = gcc
+MODULE_FILENAME = $(PWD)/libWFS_locks.so
+
+ifneq ($(findstring 7.1,$(shell pg_config --version)),)
+ USE_VERSION=71
+else
+ ifneq ($(findstring 7.2,$(VERSION)),)
+ USE_VERSION=72
+ else
+ ifneq ($(findstring 7.3,$(VERSION)),)
+ USE_VERSION=73
+ else
+ ifneq ($(findstring 7.4,$(VERSION)),)
+ USE_VERSION=74
+ else
+ USE_VERSION=80
+ endif
+ endif
+ endif
+endif
+
+
+all: libWFS_locks.so WFS_locks.sql
+
+libWFS_locks.so: WFS_locks.c
+ $(CC) $(CFLAGS) $(LDFLAGS) -shared -o $@ $<
+
+WFS_locks.sql: WFS_locks.sql.in
+ cpp -P -traditional-cpp -DUSE_VERSION=$(USE_VERSION) $< | sed -e 's:@MODULE_FILENAME@:$(MODULE_FILENAME):g' | grep -v '^#' > $@
+
+clean:
+ rm -f libWFS_locks.so WFS_locks.sql
--- /dev/null
+
+Thu Mar 24 17:25:48 CET 2005
+----------------------------
+
+This module and associated pl/pgsql functions have been implemented
+to provide long locking support required by Web Feature Service
+specification (https://portal.opengeospatial.org/files/?artifact_id=7176)
+
+It is based on original work by David Blasby <dblasby@openplans.org>
+and has been modified and packaged by Sandro Santilli <strk@refractions.net>.
+
+
+ Usage:
+
+ -- Check updates and deletes of rows in
+ -- given table for being authorized.
+ -- Identify rows using <column> value.
+ SELECT CheckAuth([<schema>], <table>, <column>)
+
+ -- Set lock/authorization for specific row in table
+ -- <authid> is a text value, <expires> is a timestamp
+ -- defaulting to now()+1hour.
+ -- Returns 1 if lock has been assigned, 0 otherwise
+ -- (already locked by other auth)
+ SELECT LockRow([<schema>], <table>, <rowid>, <authid>, [<expires>])
+
+ -- Remove all locks held by specified authorization id.
+ -- Returns the number of locks released.
+ SELECT UnlockRows(<authid>)
+
+ -- Add an authorization token to be used in current
+ -- transaction.
+ SELECT AddAuth(<authid>)
+
+ WARNING! users must use serializable transaction level
+ (see http://www.postgresql.org/docs/7.4/static/transaction-iso.html)
+ otherwise locking mechanism would break
+
+
+ --strk;
--- /dev/null
+#include "postgres.h"
+#include "executor/spi.h" /* this is what you need to work with SPI */
+#include "commands/trigger.h" /* ... and triggers */
+#include "utils/lsyscache.h" /* for get_namespace_name() */
+
+//#define PGIS_DEBUG 1
+
+Datum check_authorization(PG_FUNCTION_ARGS);
+
+/*
+ * This trigger will check for authorization before
+ * allowing UPDATE or DELETE of specific rows.
+ * Rows are identified by the provided column.
+ * Authorization info is extracted by the
+ * "authorization_table"
+ *
+ */
+PG_FUNCTION_INFO_V1(check_authorization);
+Datum check_authorization(PG_FUNCTION_ARGS)
+{
+ TriggerData *trigdata = (TriggerData *) fcinfo->context;
+ char *colname;
+ HeapTuple rettuple;
+ TupleDesc tupdesc;
+ int SPIcode;
+ char query[1024];
+ const char *pk_id = NULL;
+ SPITupleTable *tuptable;
+ HeapTuple tuple;
+ char *lockcode;
+ char *authtable = "authorization_table";
+
+
+ /* Make sure trigdata is pointing at what I expect */
+ if (!CALLED_AS_TRIGGER(fcinfo))
+ elog(ERROR,"check_authorization: not fired by trigger manager");
+
+ if (! (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event) ||
+ TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) )
+ elog(ERROR,"check_authorization: not fired by update or delete");
+
+ rettuple = trigdata->tg_newtuple;
+ tupdesc = trigdata->tg_relation->rd_att;
+
+ /* Connect to SPI manager */
+ SPIcode = SPI_connect();
+
+ if (SPIcode != SPI_OK_CONNECT)
+ {
+ elog(ERROR,"check_authorization: could not connect to SPI");
+ PG_RETURN_NULL() ;
+ }
+
+ colname = trigdata->tg_trigger->tgargs[0];
+ pk_id = SPI_getvalue(trigdata->tg_trigtuple, tupdesc,
+ SPI_fnumber(tupdesc, colname));
+
+#if PGIS_DEBUG
+ elog(NOTICE,"check_authorization(%s.%s.%s=%s)",
+ nspname, relname, colname, pk_id);
+#endif
+
+ sprintf(query,"SELECT authid FROM \"%s\" WHERE expires >= now() AND toid = '%d' AND rid = '%s'", authtable, trigdata->tg_relation->rd_id, pk_id);
+
+#if PGIS_DEBUG > 1
+ elog(NOTICE,"about to execute :%s", query);
+#endif
+
+ SPIcode = SPI_exec(query,0);
+ if (SPIcode !=SPI_OK_SELECT )
+ elog(ERROR,"couldnt execute to test for lock :%s",query);
+
+ if (!SPI_processed )
+ {
+#if PGIS_DEBUG
+ elog(NOTICE,"there is NOT a lock on row '%s'", pk_id);
+#endif
+ SPI_finish();
+ return PointerGetDatum(trigdata->tg_trigtuple);
+ }
+
+ // there is a lock - check to see if I have rights to it!
+
+ tuptable = SPI_tuptable;
+ tupdesc = SPI_tuptable->tupdesc;
+ tuple = tuptable->vals[0];
+ lockcode = SPI_getvalue(tuple, tupdesc, 1);
+
+#if PGIS_DEBUG
+ elog(NOTICE,"there is a lock on this row!");
+#endif
+
+ // check to see if temp_lock_have_table table exists
+ // (it might not exist if they own no locks
+ sprintf(query,"SELECT * FROM pg_class WHERE relname = 'temp_lock_have_table'");
+ SPIcode = SPI_exec(query,0);
+ if (SPIcode != SPI_OK_SELECT )
+ elog(ERROR,"couldnt execute to test for lockkey temp table :%s",query);
+ if (SPI_processed==0)
+ {
+ SPI_finish();
+
+ elog(ERROR,"Unauthorized modification (requires auth: '%s')",
+ lockcode);
+ elog(NOTICE,"Modificaton of row '%s' requires authorization '%s'",
+ pk_id, lockcode);
+ // ignore requested delete or update
+ return PointerGetDatum(trigdata->tg_trigtuple);
+ }
+
+ sprintf(query, "SELECT * FROM temp_lock_have_table WHERE lockcode ='%s'",lockcode);
+
+#if PGIS_DEBUG
+ elog(NOTICE,"about to execute :%s", query);
+#endif
+
+ SPIcode = SPI_exec(query,0);
+ if (SPIcode != SPI_OK_SELECT )
+ elog(ERROR,"couldnt execute to test for lock aquire: %s", query);
+
+ if (SPI_processed >0)
+ {
+#if PGIS_DEBUG
+ elog(NOTICE,"I own the lock - I can modify the row");
+#endif
+ SPI_finish();
+ return PointerGetDatum(rettuple);
+ }
+
+ SPI_finish();
+
+ elog(ERROR,"Unauthorized modification (requires auth: '%s')",
+ lockcode);
+ elog(NOTICE,"Modificaton of row '%s' requires authorization '%s'",
+ pk_id, lockcode);
+ // ignore the command
+ return PointerGetDatum(trigdata->tg_trigtuple);
+
+}
--- /dev/null
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+--
+-- $Id$
+--
+-- PostGIS - Spatial Types for PostgreSQL
+-- http://postgis.refractions.net
+-- Copyright 2001-2003 Refractions Research Inc.
+--
+-- This is free software; you can redistribute and/or modify it under
+-- the terms of the GNU General Public Licence. See the COPYING file.
+--
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+#define CREATEFUNCTION CREATE OR REPLACE FUNCTION
+
+#if USE_VERSION > 72
+# define _IMMUTABLE_STRICT IMMUTABLE STRICT
+# define _IMMUTABLE IMMUTABLE
+# define _STABLE_STRICT STABLE STRICT
+# define _STABLE STABLE
+# define _VOLATILE_STRICT VOLATILE STRICT
+# define _VOLATILE VOLATILE
+# define _STRICT STRICT
+#else
+# define _IMMUTABLE_STRICT with(iscachable,isstrict)
+# define _IMMUTABLE with(iscachable)
+# define _STABLE_STRICT with(isstrict)
+# define _STABLE
+# define _VOLATILE_STRICT with(isstrict)
+# define _VOLATILE
+# define _STRICT with(isstrict)
+#endif
+
+
+-----------------------------------------------------------------------
+-- LONG TERM LOCKING
+-----------------------------------------------------------------------
+
+-- UnlockRows(authid)
+-- removes all locks held by the given auth
+-- returns the number of locks released
+CREATEFUNCTION UnlockRows(text)
+ RETURNS int
+ AS '
+DECLARE
+ ret int;
+BEGIN
+ EXECUTE ''DELETE FROM authorization_table where authid = '' ||
+ quote_literal($1);
+
+ GET DIAGNOSTICS ret = ROW_COUNT;
+
+ RETURN ret;
+END;
+'
+LANGUAGE 'plpgsql' _VOLATILE_STRICT;
+
+-- LockRow([schema], table, rowid, auth, [expires])
+-- Returns 1 if successfully obtained the lock, 0 otherwise
+CREATEFUNCTION LockRow(text, text, text, text, timestamp)
+ RETURNS int
+ AS '
+DECLARE
+ myschema alias for $1;
+ mytable alias for $2;
+ myrid alias for $3;
+ authid alias for $4;
+ expires alias for $5;
+ ret int;
+ mytoid oid;
+ myrec RECORD;
+
+BEGIN
+ EXECUTE ''DELETE FROM authorization_table WHERE expires < now()'';
+
+ SELECT c.oid INTO mytoid FROM pg_class c, pg_namespace n
+ WHERE c.relname = mytable
+ AND c.relnamespace = n.oid
+ AND n.nspname = myschema;
+
+ -- RAISE NOTICE ''toid: %'', mytoid;
+
+ FOR myrec IN SELECT * FROM authorization_table WHERE
+ toid = mytoid AND rid = myrid
+ LOOP
+ IF myrec.authid != authid THEN
+ RETURN 0;
+ ELSE
+ RETURN 1;
+ END IF;
+ END LOOP;
+
+ EXECUTE ''INSERT INTO authorization_table VALUES (''||
+ quote_literal(mytoid)||'',''||quote_literal(myrid)||
+ '',''||quote_literal(expires)||
+ '',''||quote_literal(authid) ||'')'';
+
+ GET DIAGNOSTICS ret = ROW_COUNT;
+
+ RETURN ret;
+END;'
+LANGUAGE 'plpgsql' _VOLATILE_STRICT;
+
+-- LockRow(schema, table, rid, authid);
+CREATEFUNCTION LockRow(text, text, text, text)
+ RETURNS int
+ AS
+'SELECT LockRow($1, $2, $3, $4, now()::timestamp+''1:00'');'
+ LANGUAGE 'sql' _VOLATILE_STRICT;
+
+-- LockRow(table, rid, authid);
+CREATEFUNCTION LockRow(text, text, text)
+ RETURNS int
+ AS
+#if USE_VERSION >= 73
+'SELECT LockRow(current_schema(), $1, $2, $3, now()::timestamp+''1:00'');'
+#else
+'SELECT LockRow('''', $1, $2, $3, now()::timestamp+''1:00'');'
+#endif
+ LANGUAGE 'sql' _VOLATILE_STRICT;
+
+-- LockRow(schema, table, rid, expires);
+CREATEFUNCTION LockRow(text, text, text, timestamp)
+ RETURNS int
+ AS
+#if USE_VERSION >= 73
+'SELECT LockRow(current_schema(), $1, $2, $3, $4);'
+#else
+'SELECT LockRow('''', $1, $2, $3, $4);'
+#endif
+ LANGUAGE 'sql' _VOLATILE_STRICT;
+
+
+CREATEFUNCTION AddAuth(text)
+ RETURNS BOOLEAN
+ AS '
+DECLARE
+ lockid alias for $1;
+ okay boolean;
+ myrec record;
+BEGIN
+ -- check to see if table exists
+ -- if not, CREATE TEMP TABLE mylock (transid xid, lockcode text)
+ okay := ''f'';
+ FOR myrec IN SELECT * FROM pg_class WHERE relname = ''temp_lock_have_table'' LOOP
+ okay := ''t'';
+ END LOOP;
+ IF (okay <> ''t'') THEN
+ CREATE TEMP TABLE temp_lock_have_table (lockcode text)
+ ON COMMIT DELETE ROWS;
+ END IF;
+
+ -- INSERT INTO mylock VALUES ( $1)
+ EXECUTE ''INSERT INTO temp_lock_have_table VALUES ( ''||
+ quote_literal(lockid) ||'')'';
+
+ RETURN true::boolean;
+END;
+'
+LANGUAGE PLPGSQL;
+
+
+-- CheckAuth( <schema>, <table>, <ridcolumn> )
+CREATEFUNCTION CheckAuth(text, text, text)
+ RETURNS INT
+ AS '
+DECLARE
+ schema text;
+BEGIN
+
+#if USE_VERSION >= 73
+ if ( $1 != '''' ) THEN
+ schema = $1;
+ ELSE
+ SELECT current_schema() into schema;
+ END IF;
+#endif
+
+ EXECUTE ''CREATE TRIGGER check_auth BEFORE UPDATE OR DELETE ON ''
+#if USE_VERSION >= 73
+ || quote_ident(schema) || ''.'' || quote_ident($2)
+#else
+ || quote_ident($2)
+#endif
+ ||'' FOR EACH ROW EXECUTE PROCEDURE CheckAuthTrigger(''
+ || quote_literal($3) || '')'';
+ RETURN 0;
+END;
+'
+LANGUAGE 'plpgsql';
+
+-- CheckAuth(<table>, <ridcolumn>)
+CREATEFUNCTION CheckAuth(text, text)
+ RETURNS INT
+ AS
+ 'SELECT CheckAuth('''', $1, $2)'
+ LANGUAGE 'SQL';
+
+CREATEFUNCTION CheckAuthTrigger()
+ RETURNS trigger AS
+ '@MODULE_FILENAME@', 'check_authorization'
+ LANGUAGE C;
+
+
+---------------------------------------------------------------
+-- END
+---------------------------------------------------------------
+