Peter Eisentraut <pete...@gmx.net> writes:
> At this point, all that is appropriate is some documentation of the C
> API.  If the contrib example you have in mind is short enough, it might
> as well become part of the example in the documentation.

Please find attached a patch against the documentation, containing a
full code example of what I had in mind. The contrib would only be
useful to include if we want to ship something usable.

As you might want to tinker with the code in the docs patch and easily
check that it still runs, I include another patch with the new contrib
module. I don't expect that to get commited, of course, but I had to do
it to check the code so I'd better just share it, right?

Regards,
-- 
Dimitri Fontaine
http://2ndQuadrant.fr     PostgreSQL : Expertise, Formation et Support

*** /dev/null
--- b/contrib/noddl/Makefile
***************
*** 0 ****
--- 1,17 ----
+ # contrib/lo/Makefile
+ 
+ MODULES = noddl
+ 
+ EXTENSION = noddl
+ DATA = noddl--1.0.sql
+ 
+ ifdef USE_PGXS
+ PG_CONFIG = pg_config
+ PGXS := $(shell $(PG_CONFIG) --pgxs)
+ include $(PGXS)
+ else
+ subdir = contrib/noddl
+ top_builddir = ../..
+ include $(top_builddir)/src/Makefile.global
+ include $(top_srcdir)/contrib/contrib-global.mk
+ endif
*** /dev/null
--- b/contrib/noddl/noddl--1.0.sql
***************
*** 0 ****
--- 1,12 ----
+ /* contrib/lo/lo--1.0.sql */
+ 
+ -- complain if script is sourced in psql, rather than via CREATE EXTENSION
+ \echo Use "CREATE EXTENSION lo" to load this file. \quit
+ 
+ CREATE FUNCTION noddl()
+         RETURNS pg_catalog.event_trigger
+              AS 'noddl'
+              LANGUAGE C;
+ 
+ CREATE EVENT TRIGGER noddl on ddl_command_start
+    execute procedure noddl();
*** /dev/null
--- b/contrib/noddl/noddl.c
***************
*** 0 ****
--- 1,35 ----
+ /*
+  *	PostgreSQL definitions for noddl event trigger extension.
+  *
+  *	contrib/noddl/noddl.c
+  */
+ 
+ #include "postgres.h"
+ #include "commands/event_trigger.h"
+ 
+ 
+ PG_MODULE_MAGIC;
+ 
+ /* forward declarations */
+ Datum		noddl(PG_FUNCTION_ARGS);
+ 
+ 
+ /*
+  * This is the trigger that protects us from orphaned large objects
+  */
+ PG_FUNCTION_INFO_V1(noddl);
+ 
+ Datum
+ noddl(PG_FUNCTION_ARGS)
+ {
+ 	EventTriggerData *trigdata = (EventTriggerData *) fcinfo->context;
+ 
+ 	if (!CALLED_AS_EVENT_TRIGGER(fcinfo))		/* internal error */
+ 		elog(ERROR, "not fired by event trigger manager");
+ 
+ 	ereport(ERROR,
+ 			(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ 			 errmsg("command %s denied", trigdata->tag)));
+ 
+     PG_RETURN_NULL();
+ }
*** /dev/null
--- b/contrib/noddl/noddl.control
***************
*** 0 ****
--- 1,4 ----
+ # noddl extension
+ comment = 'Event Trigger to deny DDL operations'
+ default_version = '1.0'
+ relocatable = true
*** a/doc/src/sgml/event-trigger.sgml
--- b/doc/src/sgml/event-trigger.sgml
***************
*** 607,610 ****
--- 607,861 ----
     </table>
    </sect1>
  
+   <sect1 id="event-trigger-interface">
+    <title>Writing Event Trigger Functions in C</title>
+ 
+    <indexterm zone="event-trigger-interface">
+     <primary>event trigger</primary>
+     <secondary>in C</secondary>
+    </indexterm>
+ 
+    <para>
+     This section describes the low-level details of the interface to an
+     event trigger function. This information is only needed when writing
+     event trigger functions in C. If you are using a higher-level language
+     then these details are handled for you. In most cases you should
+     consider using a procedural language before writing your event triggers
+     in C. The documentation of each procedural language explains how to
+     write an event trigger in that language.
+    </para>
+ 
+    <para>
+     Event trigger functions must use the <quote>version 1</> function
+     manager interface.
+    </para>
+ 
+    <para>
+     When a function is called by the event trigger manager, it is not passed
+     any normal arguments, but it is passed a <quote>context</> pointer
+     pointing to a <structname>EventTriggerData</> structure. C functions can
+     check whether they were called from the event trigger manager or not by
+     executing the macro:
+ <programlisting>
+ CALLED_AS_EVENT_TRIGGER(fcinfo)
+ </programlisting>
+     which expands to:
+ <programlisting>
+ ((fcinfo)-&gt;context != NULL &amp;&amp; IsA((fcinfo)-&gt;context, EventTriggerData))
+ </programlisting>
+     If this returns true, then it is safe to cast
+     <literal>fcinfo-&gt;context</> to type <literal>EventTriggerData
+     *</literal> and make use of the pointed-to
+     <structname>EventTriggerData</> structure.  The function must
+     <emphasis>not</emphasis> alter the <structname>EventTriggerData</>
+     structure or any of the data it points to.
+    </para>
+ 
+    <para>
+     <structname>struct EventTriggerData</structname> is defined in
+     <filename>commands/event_trigger.h</filename>:
+ 
+ <programlisting>
+ typedef struct EventTriggerData
+ {
+ 	NodeTag		type;
+ 	const char     *event;				/* event name */
+ 	Node	       *parsetree;			/* parse tree */
+ 	const char     *tag;				/* command tag */
+ } EventTriggerData;
+ </programlisting>
+ 
+     where the members are defined as follows:
+ 
+     <variablelist>
+      <varlistentry>
+       <term><structfield>type</></term>
+       <listitem>
+        <para>
+         Always <literal>T_EventTriggerData</literal>.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+      <varlistentry>
+       <term><structfield>tg_event</></term>
+       <listitem>
+        <para>
+         Describes the event for which the function is called. On of:
+ 
+         <variablelist>
+          <varlistentry>
+           <term><literal>"ddl_command_start"</literal></term>
+           <listitem>
+            <para>
+             The event trigger is run first thing in the command
+             implementation, nothing did happen yet (no object is locked, the
+             system didn't check if the object(s) still exist, etc).
+            </para>
+           </listitem>
+          </varlistentry>
+ 
+          <varlistentry>
+           <term><literal>"ddl_command_end"</literal></term>
+           <listitem>
+            <para>
+             The event trigger is run as the last step in the command
+             implementation, while the locks are still kept when locks have
+             been taken.
+            </para>
+           </listitem>
+          </varlistentry>
+ 
+          <varlistentry>
+           <term><literal>"sql_drop"</literal></term>
+           <listitem>
+            <para>
+             The event trigger is run at the last step in the command
+             implementation, unless there's
+             a <literal>"ddl_command_end"</literal> event trigger registered
+             against the same command too. This event is only fired when
+             a <literal>DROP</literal> command did occur.
+            </para>
+           </listitem>
+          </varlistentry>
+ 
+         </variablelist>
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+      <varlistentry>
+       <term><structfield>parsetree</></term>
+       <listitem>
+        <para>
+         A pointer to a structure describing the relation that the trigger fired for.
+         Look at <filename>utils/rel.h</> for details about
+         this structure.  The most interesting things are
+         <literal>tg_relation-&gt;rd_att</> (descriptor of the relation
+         tuples) and <literal>tg_relation-&gt;rd_rel-&gt;relname</>
+         (relation name; the type is not <type>char*</> but
+         <type>NameData</>; use
+         <literal>SPI_getrelname(tg_relation)</> to get a <type>char*</> if you
+         need a copy of the name).
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+      <varlistentry>
+       <term><structfield>tag</></term>
+       <listitem>
+        <para>
+         The command tag against which the Event Trigger is run.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+     </variablelist>
+    </para>
+ 
+    <para>
+     An event trigger function must return a <symbol>NULL</> pointer
+     (<emphasis>not</> an SQL null value, that is, do not
+     set <parameter>isNull</parameter> true).
+    </para>
+   </sect1>
+ 
+   <sect1 id="event-trigger-example">
+    <title>A Complete Event Trigger Example</title>
+ 
+    <para>
+     Here is a very simple example of an event trigger function written in C.
+     (Examples of triggers written in procedural languages can be found in
+     the documentation of the procedural languages.)
+    </para>
+ 
+    <para>
+     The function <function>noddl</> raises an exception each time it's
+     called, preventing the <literal>command</literal> to run, whatever the
+     command is.
+    </para>
+ 
+    <para>
+     This is the source code of the trigger function:
+ <programlisting><![CDATA[
+ /*
+  *	PostgreSQL definitions for noddl event trigger extension.
+  *
+  *	contrib/noddl/noddl.c
+  */
+ 
+ #include "postgres.h"
+ #include "commands/event_trigger.h"
+ 
+ 
+ PG_MODULE_MAGIC;
+ 
+ /* forward declarations */
+ Datum		noddl(PG_FUNCTION_ARGS);
+ 
+ 
+ /*
+  * This is the trigger that protects us from orphaned large objects
+  */
+ PG_FUNCTION_INFO_V1(noddl);
+ 
+ Datum
+ noddl(PG_FUNCTION_ARGS)
+ {
+ 	EventTriggerData *trigdata = (EventTriggerData *) fcinfo->context;
+ 
+ 	if (!CALLED_AS_EVENT_TRIGGER(fcinfo))		/* internal error */
+ 		elog(ERROR, "not fired by event trigger manager");
+ 
+ 	ereport(ERROR,
+ 			(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ 			 errmsg("command %s denied", trigdata->tag)));
+ 
+     PG_RETURN_NULL();
+ }
+ ]]>
+ </programlisting>
+    </para>
+ 
+    <para>
+     After you have compiled the source code (see <xref
+     linkend="dfunc">), declare the function and the triggers:
+ <programlisting>
+ CREATE FUNCTION noddl()
+         RETURNS pg_catalog.event_trigger
+              AS 'noddl'
+              LANGUAGE C;
+ 
+ CREATE EVENT TRIGGER noddl on ddl_command_start
+    execute procedure noddl();
+ </programlisting>
+    </para>
+ 
+    <para>
+     Now you can test the operation of the trigger:
+ <screen>
+ =# \dy
+                      List of event triggers
+  Name  |       Event       | Owner | Enabled | Procedure | Tags 
+ -------+-------------------+-------+---------+-----------+------
+  noddl | ddl_command_start | dim   | enabled | noddl     | 
+ (1 row)
+ 
+ =# CREATE TABLE foo(id serial);
+ ERROR:  command CREATE TABLE denied
+ </screen>
+    </para>
+ 
+    <para>
+     In order to be able to run some DDL commands when you need to do so,
+     either <literal>DROP</literal> the event trigger or just disable it in
+     the DDL transaction, like so:
+ <programlisting>
+ BEGIN;
+ ALTER EVENT TRIGGER noddl DISABLE;
+ CREATE TABLE foo(id serial);
+ COMMIT;
+ </programlisting>
+    </para>
+   </sect1>
  </chapter>
*** a/doc/src/sgml/event-trigger.sgml
--- b/doc/src/sgml/event-trigger.sgml
***************
*** 607,610 ****
--- 607,861 ----
     </table>
    </sect1>
  
+   <sect1 id="event-trigger-interface">
+    <title>Writing Event Trigger Functions in C</title>
+ 
+    <indexterm zone="event-trigger-interface">
+     <primary>event trigger</primary>
+     <secondary>in C</secondary>
+    </indexterm>
+ 
+    <para>
+     This section describes the low-level details of the interface to an
+     event trigger function. This information is only needed when writing
+     event trigger functions in C. If you are using a higher-level language
+     then these details are handled for you. In most cases you should
+     consider using a procedural language before writing your event triggers
+     in C. The documentation of each procedural language explains how to
+     write an event trigger in that language.
+    </para>
+ 
+    <para>
+     Event trigger functions must use the <quote>version 1</> function
+     manager interface.
+    </para>
+ 
+    <para>
+     When a function is called by the event trigger manager, it is not passed
+     any normal arguments, but it is passed a <quote>context</> pointer
+     pointing to a <structname>EventTriggerData</> structure. C functions can
+     check whether they were called from the event trigger manager or not by
+     executing the macro:
+ <programlisting>
+ CALLED_AS_EVENT_TRIGGER(fcinfo)
+ </programlisting>
+     which expands to:
+ <programlisting>
+ ((fcinfo)-&gt;context != NULL &amp;&amp; IsA((fcinfo)-&gt;context, EventTriggerData))
+ </programlisting>
+     If this returns true, then it is safe to cast
+     <literal>fcinfo-&gt;context</> to type <literal>EventTriggerData
+     *</literal> and make use of the pointed-to
+     <structname>EventTriggerData</> structure.  The function must
+     <emphasis>not</emphasis> alter the <structname>EventTriggerData</>
+     structure or any of the data it points to.
+    </para>
+ 
+    <para>
+     <structname>struct EventTriggerData</structname> is defined in
+     <filename>commands/event_trigger.h</filename>:
+ 
+ <programlisting>
+ typedef struct EventTriggerData
+ {
+ 	NodeTag		type;
+ 	const char     *event;				/* event name */
+ 	Node	       *parsetree;			/* parse tree */
+ 	const char     *tag;				/* command tag */
+ } EventTriggerData;
+ </programlisting>
+ 
+     where the members are defined as follows:
+ 
+     <variablelist>
+      <varlistentry>
+       <term><structfield>type</></term>
+       <listitem>
+        <para>
+         Always <literal>T_EventTriggerData</literal>.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+      <varlistentry>
+       <term><structfield>tg_event</></term>
+       <listitem>
+        <para>
+         Describes the event for which the function is called. On of:
+ 
+         <variablelist>
+          <varlistentry>
+           <term><literal>"ddl_command_start"</literal></term>
+           <listitem>
+            <para>
+             The event trigger is run first thing in the command
+             implementation, nothing did happen yet (no object is locked, the
+             system didn't check if the object(s) still exist, etc).
+            </para>
+           </listitem>
+          </varlistentry>
+ 
+          <varlistentry>
+           <term><literal>"ddl_command_end"</literal></term>
+           <listitem>
+            <para>
+             The event trigger is run as the last step in the command
+             implementation, while the locks are still kept when locks have
+             been taken.
+            </para>
+           </listitem>
+          </varlistentry>
+ 
+          <varlistentry>
+           <term><literal>"sql_drop"</literal></term>
+           <listitem>
+            <para>
+             The event trigger is run at the last step in the command
+             implementation, unless there's
+             a <literal>"ddl_command_end"</literal> event trigger registered
+             against the same command too. This event is only fired when
+             a <literal>DROP</literal> command did occur.
+            </para>
+           </listitem>
+          </varlistentry>
+ 
+         </variablelist>
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+      <varlistentry>
+       <term><structfield>parsetree</></term>
+       <listitem>
+        <para>
+         A pointer to a structure describing the relation that the trigger fired for.
+         Look at <filename>utils/rel.h</> for details about
+         this structure.  The most interesting things are
+         <literal>tg_relation-&gt;rd_att</> (descriptor of the relation
+         tuples) and <literal>tg_relation-&gt;rd_rel-&gt;relname</>
+         (relation name; the type is not <type>char*</> but
+         <type>NameData</>; use
+         <literal>SPI_getrelname(tg_relation)</> to get a <type>char*</> if you
+         need a copy of the name).
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+      <varlistentry>
+       <term><structfield>tag</></term>
+       <listitem>
+        <para>
+         The command tag against which the Event Trigger is run.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+     </variablelist>
+    </para>
+ 
+    <para>
+     An event trigger function must return a <symbol>NULL</> pointer
+     (<emphasis>not</> an SQL null value, that is, do not
+     set <parameter>isNull</parameter> true).
+    </para>
+   </sect1>
+ 
+   <sect1 id="event-trigger-example">
+    <title>A Complete Event Trigger Example</title>
+ 
+    <para>
+     Here is a very simple example of an event trigger function written in C.
+     (Examples of triggers written in procedural languages can be found in
+     the documentation of the procedural languages.)
+    </para>
+ 
+    <para>
+     The function <function>noddl</> raises an exception each time it's
+     called, preventing the <literal>command</literal> to run, whatever the
+     command is.
+    </para>
+ 
+    <para>
+     This is the source code of the trigger function:
+ <programlisting><![CDATA[
+ /*
+  *	PostgreSQL definitions for noddl event trigger extension.
+  *
+  *	contrib/noddl/noddl.c
+  */
+ 
+ #include "postgres.h"
+ #include "commands/event_trigger.h"
+ 
+ 
+ PG_MODULE_MAGIC;
+ 
+ /* forward declarations */
+ Datum		noddl(PG_FUNCTION_ARGS);
+ 
+ 
+ /*
+  * This is the trigger that protects us from orphaned large objects
+  */
+ PG_FUNCTION_INFO_V1(noddl);
+ 
+ Datum
+ noddl(PG_FUNCTION_ARGS)
+ {
+ 	EventTriggerData *trigdata = (EventTriggerData *) fcinfo->context;
+ 
+ 	if (!CALLED_AS_EVENT_TRIGGER(fcinfo))		/* internal error */
+ 		elog(ERROR, "not fired by event trigger manager");
+ 
+ 	ereport(ERROR,
+ 			(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ 			 errmsg("command %s denied", trigdata->tag)));
+ 
+     PG_RETURN_NULL();
+ }
+ ]]>
+ </programlisting>
+    </para>
+ 
+    <para>
+     After you have compiled the source code (see <xref
+     linkend="dfunc">), declare the function and the triggers:
+ <programlisting>
+ CREATE FUNCTION noddl()
+         RETURNS pg_catalog.event_trigger
+              AS 'noddl'
+              LANGUAGE C;
+ 
+ CREATE EVENT TRIGGER noddl on ddl_command_start
+    execute procedure noddl();
+ </programlisting>
+    </para>
+ 
+    <para>
+     Now you can test the operation of the trigger:
+ <screen>
+ =# \dy
+                      List of event triggers
+  Name  |       Event       | Owner | Enabled | Procedure | Tags 
+ -------+-------------------+-------+---------+-----------+------
+  noddl | ddl_command_start | dim   | enabled | noddl     | 
+ (1 row)
+ 
+ =# CREATE TABLE foo(id serial);
+ ERROR:  command CREATE TABLE denied
+ </screen>
+    </para>
+ 
+    <para>
+     In order to be able to run some DDL commands when you need to do so,
+     either <literal>DROP</literal> the event trigger or just disable it in
+     the DDL transaction, like so:
+ <programlisting>
+ BEGIN;
+ ALTER EVENT TRIGGER noddl DISABLE;
+ CREATE TABLE foo(id serial);
+ COMMIT;
+ </programlisting>
+    </para>
+   </sect1>
  </chapter>
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to