The attached patch adds support for loadable instrumentation plugins for procedural languages (as discussed at the anniversary summit). It also adds plugin support to the PL/pgSQL language handler.

We are using this plugin mechanism to load the PL/pgSQL debugger on demand (the debugger is not part of this patch, the patch is just the infrastructure that we need to load various instrumentation plugins).  We will also post some sample plugins (a PL/pgSQL profiler and a simple tracer), probably at the edb-debugger pgFoundry project site so you can see how to use the plugin mechanism.

A couple of notes:  to use a plugin, you must define a custom GUC variable that specifies the name of the shared-object that implements the plugin.  For example, to use the PL/pgSQL profiler (which is implement in $libdir/plugin_profiler.so), you would add the following to postgresql.conf

    custom_variable_classes = 'plpgsql'
   
    plpgsql.plugin = 'plugin_profiler'

The plpgsql.plugin variable is treated as an SUSET variable so you must be a superuser to change plugins.

Also, we define a helper function (in fmgr.c) named load_pl_plugin() that a language handler can use to load a plugin.  The plugin requires the name of the GUC variable (plplgsql.plugin, pljava.plugin, plperl.pluhgN, etc.) and a pointer to a language-specific struct that the plugin fills in with a set of function pointers (presumably, the language handler will call those functions at appropriate points in time).


            -- Korry


Index: src/backend/utils/fmgr/fmgr.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/utils/fmgr/fmgr.c,v
retrieving revision 1.101
diff -w -c -r1.101 fmgr.c
*** src/backend/utils/fmgr/fmgr.c	30 May 2006 21:21:30 -0000	1.101
--- src/backend/utils/fmgr/fmgr.c	28 Jul 2006 13:22:15 -0000
***************
*** 25,30 ****
--- 25,31 ----
  #include "utils/fmgrtab.h"
  #include "utils/lsyscache.h"
  #include "utils/syscache.h"
+ #include "utils/guc.h"
  
  /*
   * Declaration for old-style function pointer type.  This is now used only
***************
*** 69,76 ****
  	const Pg_finfo_record *inforec;		/* address of its info record */
  } CFuncHashTabEntry;
  
! static HTAB *CFuncHash = NULL;
  
  
  static void fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
  					   bool ignore_security);
--- 70,87 ----
  	const Pg_finfo_record *inforec;		/* address of its info record */
  } CFuncHashTabEntry;
  
! /*
!  * Hashtable for keeping track of language-handler plugins
!  */
! 
! typedef struct
! {
! 	char	varName[NAMEDATALEN];	/* Name of GUC variable for this handler      */
! 	char  * varValue;				/* A place to store the value of the variable */
! } pluginHashTabEntry;
  
+ static HTAB *CFuncHash = NULL;
+ static HTAB *pluginHash = NULL;
  
  static void fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
  					   bool ignore_security);
***************
*** 81,87 ****
  			  PGFunction user_fn, const Pg_finfo_record *inforec);
  static Datum fmgr_oldstyle(PG_FUNCTION_ARGS);
  static Datum fmgr_security_definer(PG_FUNCTION_ARGS);
! 
  
  /*
   * Lookup routines for builtin-function table.	We can search by either Oid
--- 92,100 ----
  			  PGFunction user_fn, const Pg_finfo_record *inforec);
  static Datum fmgr_oldstyle(PG_FUNCTION_ARGS);
  static Datum fmgr_security_definer(PG_FUNCTION_ARGS);
! static char * findPluginName(const char * varName);
! static void createPluginHash(void);
! static const char * assign_plugin(const char *newval, bool doit, GucSource source);
  
  /*
   * Lookup routines for builtin-function table.	We can search by either Oid
***************
*** 2081,2083 ****
--- 2094,2286 ----
  
  	return argtype;
  }
+ 
+ /*--------------------------------------------------------------------------
+  * Support routines for loadable procedural-language instrumentation plugins
+  *--------------------------------------------------------------------------
+  */
+ 
+ /*
+  * Load a plugin for a procedural-language handler (or just about 
+  * anything else).
+  *
+  * A language handler (such as pl_handler.c) can call this function
+  * to load an instrumentation plugin (typically a structure that 
+  * contains a set of function pointers that the language handler 
+  * will invoke at appropriate points during the execution of a PL
+  * function).  
+  *
+  * 'hooks' will typically point to a language-specific structure
+  * that the plugin will populate with function pointers (and any
+  * other data required by the language handler).
+  *
+  * 'varName' is the name of a GUC variable (such as 'plpgsql.plugin')
+  * that determines the name of the plugin's shared-object library.
+  *
+  * For example, the PL/pgSQL language handler calls load_pl_plugin()
+  * like this:
+  *		PLpgSQL_plugin 	hookFunctions;
+  *
+  *		load_pl_plugin(&hookFunctions, "plpgsql.plugin" );
+  *
+  * Note that load_pl_plugin() allocates space (in pluginHash) for 
+  * the GUC variables so callers don't have to deal with that.
+  *
+  * Also note that there is (currently) no corresponding unload_pl_plugin()
+  * function - if you want to change plugins, you have to change the 
+  * value of the GUC variable, and then LOAD 'plpgsql' again.
+  */
+ 
+ void 
+ load_pl_plugin(void * hooks, char * varName)
+ {
+ 	plugin_loader   loader;
+ 	char		  * pluginName;
+ 
+ 	Assert(hooks != NULL);
+ 	Assert(varName != NULL);
+ 
+ 	/* Ensure that our variable name->plugin name hash exists */
+ 	createPluginHash();
+ 
+ 	/* See if we have a GUC setting for the given variable name */
+ 	if ((pluginName = findPluginName(varName)) == NULL )
+ 		return;
+ 
+ 	/* 
+ 	 * If the pluginName contains a ':' we assume that the user 
+ 	 * gave us the name of the plugin loader function (following
+ 	 * the ':') otherwise, we look for a function whose name is
+ 	 * 'load_plugin'.
+ 	 */
+ 
+ 	if (strstr(pluginName, ":") == NULL)
+ 	{
+ 		loader = (plugin_loader)load_external_function(pluginName, "load_plugin", true, NULL);
+ 	}
+ 	else
+ 	{
+ 		/* 
+ 		 * The user gave us a string of the form 'libName:funcName' - pick it apart,
+ 		 * load the given libName, and search for the given funcName within that 
+ 		 * library
+ 		 */
+ 
+ 		char * libName  = pstrdup(pluginName);
+ 		char * sep      = strstr(libName, ":");
+ 		char * funcName = sep+1;
+ 
+ 		*sep = '\0';
+ 
+ 		loader = (plugin_loader)load_external_function(libName, funcName, true, NULL);
+ 
+ 		pfree(libName);
+ 	}
+ 
+ 	/*
+ 	 * Call the loader function that we found (note - load_external_function()
+ 	 * throws an error if it can't find the loader function so its safe to call
+ 	 * *loader without checking for NULL.
+ 	 */
+ 
+ 	Assert(loader != NULL);
+ 
+ 	(*loader)(hooks);
+ }
+ 
+ /*
+  * Creates the hash table that keeps track of which PL plugins we have loaded.
+  * The key to this hash is the name of the GUC variable (such as 'plpgsql.plugin')
+  * and the data for each entry is simply a char pointer that points to the value
+  * of that variable.
+  *
+  * Note: you can call this function as many times as you like, it only does 
+  * interesting work the first time you call it.
+  */
+ 
+ static void
+ createPluginHash(void)
+ {
+ 	if (pluginHash == NULL)
+ 	{
+ 		HASHCTL	ctl;
+ 
+ 		ctl.keysize   = NAMEDATALEN;
+ 		ctl.entrysize = sizeof( pluginHashTabEntry );
+ 
+ 		pluginHash = hash_create( "PL Plugin Cache", 5, &ctl, HASH_ELEM);
+ 
+ 		Assert(pluginHash != NULL);
+ 	}
+ }
+ 
+ /*
+  * findPluginName() creates a new (custom classed) GUC variable named 'varName'
+  * and returns the value (possibly NULL) of that variable.  Presumably, varName
+  * is the name of a PL plugin library.
+  */
+ 
+ static char *
+ findPluginName(const char * varName)
+ {
+ 	char	             key[NAMEDATALEN] = {0};
+ 	pluginHashTabEntry * hentry;
+ 	bool				 found;
+ 
+ 	Assert(pluginHash != NULL);
+ 	
+ 	/* Add the variable to our pluginHash if it's not already there */
+ 	snprintf(key, NAMEDATALEN - 1, "%s", varName);
+ 
+ 	hentry = (pluginHashTabEntry *) hash_search(pluginHash, key, HASH_ENTER, &found);
+ 
+ 	if (!found)
+ 	{
+ 		/*
+ 		 * We didn't find 'varName' in our hash, tell the GUC mechanism about 
+ 		 * this variable.
+ 		 *
+ 		 * NOTE: We must define a USERSET variable, but we really want an SUSET 
+ 		 * variable.  Apparently, you can't really create SUSET custom-classed 
+ 		 * variables.  So, we create a USERSET variable instead and use an assign-
+ 		 * hook to enforce privileges.
+ 		 */
+ 
+ 		DefineCustomStringVariable( varName,
+ 		    "Name of instrumentation plugin to use when procedural-language function is invoked",
+ 			 NULL, &hentry->varValue, PGC_USERSET, assign_plugin, NULL );
+ 	}
+ 
+ 	return hentry->varValue;
+ }
+ 
+ /*
+  * This function is an assign-hook for custom-classed PL plugin 
+  * GUC variables. We want to prevent non-superusers from loading
+  * arbitrary PL plugins so we check privileges here.
+  */
+ 
+ static const char *
+ assign_plugin(const char *newval, bool doit, GucSource source)
+ {
+ 	if (doit)
+ 	{
+ 		/*
+ 		 * If this plugin name is being modified by a client 
+ 		 * application or by the user (i.e. a SET command),
+ 		 * make sure that we have superuser() privileges.
+ 		 */
+ 		if (source > PGC_S_USER)
+ 		{
+ 			if( !superuser())
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ 						 errmsg("must be superuser to modify plugin name")));
+ 		}
+ 
+ 		return strdup(newval);
+ 	}
+ 
+ 	return newval;
+ }
+ 
Index: src/include/fmgr.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/fmgr.h,v
retrieving revision 1.45
diff -w -c -r1.45 fmgr.h
*** src/include/fmgr.h	31 May 2006 20:58:09 -0000	1.45
--- src/include/fmgr.h	28 Jul 2006 13:22:19 -0000
***************
*** 340,345 ****
--- 340,347 ----
  	int		namedatalen;		/* NAMEDATALEN */
  } Pg_magic_struct;
  
+ typedef void (*plugin_loader)(void *);
+ 
  /* The actual data block contents */
  #define PG_MODULE_MAGIC_DATA \
  { \
***************
*** 480,485 ****
--- 482,489 ----
  extern Oid	get_fn_expr_argtype(FmgrInfo *flinfo, int argnum);
  extern Oid	get_call_expr_argtype(fmNodePtr expr, int argnum);
  
+ extern void load_pl_plugin(void * hooks, char * varName);
+ 
  /*
   * Routines in dfmgr.c
   */
Index: src/pl/plpgsql/src/pl_exec.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v
retrieving revision 1.174
diff -w -c -r1.174 pl_exec.c
*** src/pl/plpgsql/src/pl_exec.c	13 Jul 2006 16:49:20 -0000	1.174
--- src/pl/plpgsql/src/pl_exec.c	28 Jul 2006 13:22:22 -0000
***************
*** 255,260 ****
--- 255,266 ----
  	exec_set_found(&estate, false);
  
  	/*
+ 	 * Let the instrumentation plugin peek at this function
+ 	 */
+ 	if (plugin.func_beg)
+ 		plugin.func_beg(&estate, func);
+ 
+ 	/*
  	 * Now call the toplevel block of statements
  	 */
  	estate.err_text = NULL;
***************
*** 389,394 ****
--- 395,407 ----
  		}
  	}
  
+ 	/*
+ 	 * Tell the (optional) plugin that we've finished executing this 
+ 	 * function
+ 	 */
+ 	if (plugin.func_end)
+ 		plugin.func_end(&estate, func);
+ 
  	/* Clean up any leftover temporary memory */
  	FreeExprContext(estate.eval_econtext);
  	estate.eval_econtext = NULL;
***************
*** 583,588 ****
--- 596,607 ----
  	exec_set_found(&estate, false);
  
  	/*
+ 	 * Let the instrumentation plugin peek at this function
+ 	 */
+ 	if (plugin.func_beg)
+ 		plugin.func_beg(&estate, func);
+ 
+ 	/*
  	 * Now call the toplevel block of statements
  	 */
  	estate.err_text = NULL;
***************
*** 635,640 ****
--- 654,666 ----
  		rettup = SPI_copytuple((HeapTuple) (estate.retval));
  	}
  
+ 	/*
+ 	 * Tell the (optional) plugin that we've finished executing this 
+ 	 * function
+ 	 */
+ 	if (plugin.func_end)
+ 		plugin.func_end(&estate, func);
+ 
  	/* Clean up any leftover temporary memory */
  	FreeExprContext(estate.eval_econtext);
  	estate.eval_econtext = NULL;
***************
*** 1039,1044 ****
--- 1065,1074 ----
  	save_estmt = estate->err_stmt;
  	estate->err_stmt = stmt;
  
+ 	/* Let the plugin know that we are about to execute this statement */
+ 	if (plugin.stmt_beg)
+ 		plugin.stmt_beg(estate, stmt);
+ 
  	CHECK_FOR_INTERRUPTS();
  
  	switch (stmt->cmd_type)
***************
*** 1130,1135 ****
--- 1160,1169 ----
  
  	estate->err_stmt = save_estmt;
  
+ 	/* Let the plugin know that we have finished executing this statement */
+ 	if (plugin.stmt_end)
+ 		plugin.stmt_end(estate, stmt);
+ 
  	return rc;
  }
  
***************
*** 2204,2209 ****
--- 2238,2252 ----
  	 * child of simple_eval_estate.
  	 */
  	estate->eval_econtext = CreateExprContext(simple_eval_estate);
+ 
+ 	/*
+ 	 * Let the plugin see this function before we initialize any
+ 	 * local PL/pgSQL variables - note that we also give the plugin
+ 	 * a few function pointers so it can call back into the executor
+ 	 * for doing things like variable assignments and stack traces
+ 	 */
+ 	if (plugin.func_setup)
+ 		plugin.func_setup(estate, func, plpgsql_exec_error_callback, exec_assign_expr);
  }
  
  /* ----------
Index: src/pl/plpgsql/src/pl_handler.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/pl/plpgsql/src/pl_handler.c,v
retrieving revision 1.29
diff -w -c -r1.29 pl_handler.c
*** src/pl/plpgsql/src/pl_handler.c	30 May 2006 22:12:16 -0000	1.29
--- src/pl/plpgsql/src/pl_handler.c	28 Jul 2006 13:22:24 -0000
***************
*** 23,28 ****
--- 23,29 ----
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
  #include "utils/syscache.h"
+ #include "fmgr.h"
  
  extern DLLIMPORT bool check_function_bodies;
  
***************
*** 32,37 ****
--- 33,40 ----
  
  static void plpgsql_init_all(void);
  
+ PLpgSQL_plugin plugin = {0};	/* Hooks for (optional) instrumentation plugin */
+ 
  
  /*
   * plpgsql_init()			- postmaster-startup safe initialization
***************
*** 48,53 ****
--- 51,59 ----
  	plpgsql_HashTableInit();
  	RegisterXactCallback(plpgsql_xact_cb, NULL);
  	plpgsql_firstcall = false;
+ 
+ 	/* Load any plugins identified by the plpgsql.plugin GUC variable */
+ 	load_pl_plugin(&plugin, "plpgsql.plugin");
  }
  
  /*
Index: src/pl/plpgsql/src/plpgsql.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v
retrieving revision 1.77
diff -w -c -r1.77 plpgsql.h
*** src/pl/plpgsql/src/plpgsql.h	11 Jul 2006 17:26:59 -0000	1.77
--- src/pl/plpgsql/src/plpgsql.h	28 Jul 2006 13:22:24 -0000
***************
*** 621,629 ****
--- 621,661 ----
  	PLpgSQL_function *err_func; /* current func */
  	PLpgSQL_stmt *err_stmt;		/* current stmt */
  	const char *err_text;		/* additional state info */
+ 	void       *plugin_info;	/* reserved for use by optional plugin */
  } PLpgSQL_execstate;
  
  
+ /*
+  * A PLpgSQL_plugin structure represents an instrumentation plugin.
+  * We keep one of these structures (in pl_handler) and initialize 
+  * it by loading a plugin (if desired). This structure is basically
+  * a collection of function pointers - at various points in pl_exec.c,
+  * we call those functions (if they are non-NULL) to give the plugin
+  * a chance to watch what we are doing.
+  *
+  *	func_setup is called when we start a function, before we've initialized
+  *  the local variables (that is, the PL/pgSQL variables defined by the 
+  *  function).
+  *
+  *  func_beg is called when we start a function, after we've initialized
+  *  the local variables
+  *
+  *  func_end is called at the end of a function
+  *
+  *  stmt_beg and stmt_end are called before and after (respectively) each
+  *  statement
+  */
+ 
+ typedef struct
+ {
+ 	void (*func_setup)(PLpgSQL_execstate * estate, PLpgSQL_function * func, void (*error_callback)(void *arg), void (*assign_expr)( PLpgSQL_execstate *estate, PLpgSQL_datum *target, PLpgSQL_expr *expr));
+ 	void (*func_beg)(PLpgSQL_execstate * estate, PLpgSQL_function * func);
+ 	void (*func_end)(PLpgSQL_execstate * estate, PLpgSQL_function * func);
+ 	void (*stmt_beg)(PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt);
+ 	void (*stmt_end)(PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt);
+ } PLpgSQL_plugin;
+ 
+ 
  /**********************************************************************
   * Global variable declarations
   **********************************************************************/
***************
*** 645,650 ****
--- 677,684 ----
  extern bool plpgsql_check_syntax;
  extern MemoryContext compile_tmp_cxt;
  
+ extern PLpgSQL_plugin plugin;
+ 
  /**********************************************************************
   * Function declarations
   **********************************************************************/
---------------------------(end of broadcast)---------------------------
TIP 4: Have you searched our list archives?

               http://archives.postgresql.org

Reply via email to