From aad71b40ca781c59d3b458a18a42bc70ced2eb7c Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon, 28 Jul 2003 18:33:18 +0000
Subject: [PATCH] Add error stack traceback support for SQL-language functions.

---
 src/backend/executor/functions.c     | 86 ++++++++++++++++++++++++++--
 src/backend/optimizer/util/clauses.c | 28 ++++++++-
 2 files changed, 107 insertions(+), 7 deletions(-)

diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index bda57fa321..c8df7ccb83 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -1,14 +1,14 @@
 /*-------------------------------------------------------------------------
  *
  * functions.c
- *	  Routines to handle functions called from the executor
+ *	  Execution of SQL-language functions
  *
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.68 2003/07/21 17:05:09 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.69 2003/07/28 18:33:18 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -54,7 +54,6 @@ typedef struct local_es
  * An SQLFunctionCache record is built during the first call,
  * and linked to from the fn_extra field of the FmgrInfo struct.
  */
-
 typedef struct
 {
 	int			typlen;			/* length of the return type */
@@ -88,6 +87,7 @@ static void postquel_sub_params(SQLFunctionCachePtr fcache,
 static Datum postquel_execute(execution_state *es,
 				 FunctionCallInfo fcinfo,
 				 SQLFunctionCachePtr fcache);
+static void sql_exec_error_callback(void *arg);
 static void ShutdownSQLFunction(Datum arg);
 
 
@@ -323,15 +323,15 @@ postquel_getnext(execution_state *es)
 static void
 postquel_end(execution_state *es)
 {
+	/* mark status done to ensure we don't do ExecutorEnd twice */
+	es->status = F_EXEC_DONE;
+
 	/* Utility commands don't need Executor. */
 	if (es->qd->operation != CMD_UTILITY)
 		ExecutorEnd(es->qd);
 
 	FreeQueryDesc(es->qd);
-
 	es->qd = NULL;
-
-	es->status = F_EXEC_DONE;
 }
 
 /* Build ParamListInfo array representing current arguments */
@@ -492,6 +492,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
 {
 	MemoryContext oldcontext;
 	SQLFunctionCachePtr fcache;
+	ErrorContextCallback sqlerrcontext;
 	execution_state *es;
 	Datum		result = 0;
 
@@ -502,6 +503,14 @@ fmgr_sql(PG_FUNCTION_ARGS)
 	 */
 	oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
 
+	/*
+	 * Setup error traceback support for ereport()
+	 */
+	sqlerrcontext.callback = sql_exec_error_callback;
+	sqlerrcontext.arg = fcinfo->flinfo;
+	sqlerrcontext.previous = error_context_stack;
+	error_context_stack = &sqlerrcontext;
+
 	/*
 	 * Initialize fcache (build plans) if first time through.
 	 */
@@ -580,6 +589,8 @@ fmgr_sql(PG_FUNCTION_ARGS)
 			}
 		}
 
+		error_context_stack = sqlerrcontext.previous;
+
 		MemoryContextSwitchTo(oldcontext);
 
 		return result;
@@ -618,11 +629,74 @@ fmgr_sql(PG_FUNCTION_ARGS)
 		}
 	}
 
+	error_context_stack = sqlerrcontext.previous;
+
 	MemoryContextSwitchTo(oldcontext);
 
 	return result;
 }
 
+
+/*
+ * error context callback to let us supply a call-stack traceback
+ */
+static void
+sql_exec_error_callback(void *arg)
+{
+	FmgrInfo   *flinfo = (FmgrInfo *) arg;
+	SQLFunctionCachePtr fcache = (SQLFunctionCachePtr) flinfo->fn_extra;
+	char	   *fn_name;
+
+	fn_name = get_func_name(flinfo->fn_oid);
+	/* safety check, shouldn't happen */
+	if (fn_name == NULL)
+		return;
+
+	/*
+	 * Try to determine where in the function we failed.  If there is a
+	 * query with non-null QueryDesc, finger it.  (We check this rather
+	 * than looking for F_EXEC_RUN state, so that errors during ExecutorStart
+	 * or ExecutorEnd are blamed on the appropriate query; see postquel_start
+	 * and postquel_end.)
+	 */
+	if (fcache)
+	{
+		execution_state *es;
+		int		query_num;
+
+		es = fcache->func_state;
+		query_num = 1;
+		while (es)
+		{
+			if (es->qd)
+			{
+				errcontext("SQL function \"%s\" query %d",
+						   fn_name, query_num);
+				break;
+			}
+			es = es->next;
+			query_num++;
+		}
+		if (es == NULL)
+		{
+			/*
+			 * couldn't identify a running query; might be function entry,
+			 * function exit, or between queries.
+			 */
+			errcontext("SQL function \"%s\"", fn_name);
+		}
+	}
+	else
+	{
+		/* must have failed during init_sql_fcache() */
+		errcontext("SQL function \"%s\" during startup", fn_name);
+	}
+
+	/* free result of get_func_name (in case this is only a notice) */
+	pfree(fn_name);
+}
+
+
 /*
  * callback function in case a function-returning-set needs to be shut down
  * before it has been run to completion
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 5df7e1c3cb..6060f462e8 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.147 2003/07/25 00:01:08 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.148 2003/07/28 18:33:18 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -70,6 +70,7 @@ static Node *substitute_actual_parameters(Node *expr, int nargs, List *args,
 										  int *usecounts);
 static Node *substitute_actual_parameters_mutator(Node *node,
 					 substitute_actual_parameters_context *context);
+static void sql_inline_error_callback(void *arg);
 static Expr *evaluate_expr(Expr *expr, Oid result_type);
 
 
@@ -1730,6 +1731,7 @@ inline_function(Oid funcid, Oid result_type, List *args,
 	bool		isNull;
 	MemoryContext oldcxt;
 	MemoryContext mycxt;
+	ErrorContextCallback sqlerrcontext;
 	List	   *raw_parsetree_list;
 	List	   *querytree_list;
 	Query	   *querytree;
@@ -1780,6 +1782,15 @@ inline_function(Oid funcid, Oid result_type, List *args,
 		}
 	}
 
+	/*
+	 * Setup error traceback support for ereport().  This is so that we can
+	 * finger the function that bad information came from.
+	 */
+	sqlerrcontext.callback = sql_inline_error_callback;
+	sqlerrcontext.arg = funcform;
+	sqlerrcontext.previous = error_context_stack;
+	error_context_stack = &sqlerrcontext;
+
 	/*
 	 * Make a temporary memory context, so that we don't leak all the
 	 * stuff that parsing might create.
@@ -1926,12 +1937,15 @@ inline_function(Oid funcid, Oid result_type, List *args,
 	newexpr = eval_const_expressions_mutator(newexpr,
 											 lconso(funcid, active_fns));
 
+	error_context_stack = sqlerrcontext.previous;
+
 	return (Expr *) newexpr;
 
 	/* Here if func is not inlinable: release temp memory and return NULL */
 fail:
 	MemoryContextSwitchTo(oldcxt);
 	MemoryContextDelete(mycxt);
+	error_context_stack = sqlerrcontext.previous;
 
 	return NULL;
 }
@@ -1978,6 +1992,18 @@ substitute_actual_parameters_mutator(Node *node,
 								   (void *) context);
 }
 
+/*
+ * error context callback to let us supply a call-stack traceback
+ */
+static void
+sql_inline_error_callback(void *arg)
+{
+	Form_pg_proc funcform = (Form_pg_proc) arg;
+
+	errcontext("SQL function \"%s\" during inlining",
+			   NameStr(funcform->proname));
+}
+
 /*
  * evaluate_expr: pre-evaluate a constant expression
  *
-- 
2.40.0