From b2aade0e4b43d3bddd1dfaa1a3fe0413e404afc7 Mon Sep 17 00:00:00 2001
From: Bruce Momjian <bruce@momjian.us>
Date: Mon, 4 Mar 2002 18:50:21 +0000
Subject: [PATCH] Improve libpgeasy API for multiple result sets, add example.

---
 doc/src/sgml/libpgeasy.sgml                |  66 ++++---
 src/interfaces/libpgeasy/examples/Makefile |  12 +-
 src/interfaces/libpgeasy/libpgeasy.c       | 196 +++++++++++++--------
 3 files changed, 154 insertions(+), 120 deletions(-)

diff --git a/doc/src/sgml/libpgeasy.sgml b/doc/src/sgml/libpgeasy.sgml
index 9196621c8cc..4bb1b7949bd 100644
--- a/doc/src/sgml/libpgeasy.sgml
+++ b/doc/src/sgml/libpgeasy.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/Attic/libpgeasy.sgml,v 2.8 2002/01/07 02:29:12 petere Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/Attic/libpgeasy.sgml,v 2.9 2002/03/04 18:50:20 momjian Exp $
 -->
 
  <chapter id="pgeasy">
@@ -11,7 +11,7 @@ $Header: /cvsroot/pgsql/doc/src/sgml/Attic/libpgeasy.sgml,v 2.8 2002/01/07 02:29
    <para>
     Written by Bruce Momjian
     (<email>pgman@candle.pha.pa.us</email>)
-    and last updated 2000-03-30
+    and last updated 2002-03-04
    </para>
   </note>
 
@@ -19,13 +19,12 @@ $Header: /cvsroot/pgsql/doc/src/sgml/Attic/libpgeasy.sgml,v 2.8 2002/01/07 02:29
    <application>pgeasy</application> allows you to cleanly interface
    to the <application>libpq</application> library, more like a 4GL
    SQL interface.  Refer to <xref linkend="libpq"> for more
-   information about <application>libpq</application>
+   information about <application>libpq</application>.
   </para>
 
   <para>
-   It consists of set of simplified C functions that encapsulate the
-   functionality of <application>libpq</application>.
-   The functions are:
+   It consists of a set of simplified C functions that encapsulate the
+   functionality of <application>libpq</application>. The functions are:
 
    <itemizedlist>
     <listitem>
@@ -88,53 +87,50 @@ void        set_result(PGresult *newres);
 </synopsis>
     </listitem>
 
-    <listitem>
-<synopsis>
-void        unset_result(PGresult *oldres);
-</synopsis>
-    </listitem>
    </itemizedlist>
   </para>
 
   <para>
-   Many functions return a structure or value, so you can do more work
+   Many functions return a structure or value, so you can work
    with the result if required.
   </para>
 
   <para>
-   You basically connect to the database with <function>connectdb</function>,
-   issue your query with <function>doquery</function>,
-   fetch the results with <function>fetch</function>,
-   and finish with <function>disconnectdb</function>.
+   You basically connect to the database with
+   <function>connectdb</function>, issue your query with
+   <function>doquery</function>, fetch the results with
+   <function>fetch</function>, and finish with
+   <function>disconnectdb</function>.
   </para>
 
   <para>
    For <literal>SELECT</literal> queries, <function>fetch</function>
-   allows you to pass pointers as parameters, and on return the variables
-   are filled with data from the binary cursor you opened.  These binary
-   cursors cannot be used if you are running the
-   <application>pgeasy</application>
-   client on a system with a different architecture than the database
-   server.  If you pass a NULL pointer parameter, the column is skipped.
-   <function>fetchwithnulls</function> allows you to retrieve the NULL
-   status of the field by passing an <literal>int*</literal>
-   after each result pointer, which returns true or false if the field is null.
-   You can always use <application>libpq</application> functions on the <structname>PGresult</structname> pointer returned
-   by <function>doquery</function>.
-   <function>reset_fetch</function> starts the fetch back at the beginning.
+   allows you to pass pointers as parameters, and on return the
+   variables are filled with data from the binary cursor you opened.
+   These binary cursors cannot be used if you are running the
+   <application>pgeasy</application> client on a system with a different
+   architecture than the database server. If you pass a NULL pointer
+   parameter, the column is skipped. <function>fetchwithnulls</function>
+   allows you to retrieve the NULL status of the field by passing an
+   <literal>int*</literal> after each result pointer, which returns true
+   or false to indicate if the field is null. You can always use
+   <application>libpq</application> functions on the
+   <structname>PGresult</structname> pointer returned by
+   <function>doquery</function>. <function>reset_fetch</function> starts
+   the fetch back at the beginning.
   </para>
 
   <para>
-   <function>get_result</function>,
-   <function>set_result</function>,
-   and
-   <function>unset_result</function>
-   allow you to handle multiple result sets at the same time.
+   <function>get_result</function> and <function>set_result</function>
+   allow you to handle multiple open result sets. Use
+   <function>get_result</function> to save a result into an application
+   variable. You can then later use <function>set_result</function> to
+   return to the previously save result.
   </para>
 
   <para>
-   There are several demonstration programs in the
-   source directory.
+   There are several demonstration programs in
+   <filename>pgsql/src/interfaces/libpgeasy/examples</>.
   </para>
  </chapter>
 
diff --git a/src/interfaces/libpgeasy/examples/Makefile b/src/interfaces/libpgeasy/examples/Makefile
index 1885bb425fb..af8dc0c32a7 100644
--- a/src/interfaces/libpgeasy/examples/Makefile
+++ b/src/interfaces/libpgeasy/examples/Makefile
@@ -4,23 +4,17 @@
 #    Makefile for pgeasy examples
 #
 # IDENTIFICATION
-#    $Header: /cvsroot/pgsql/src/interfaces/libpgeasy/examples/Attic/Makefile,v 1.2 2000/05/18 14:24:37 momjian Exp $
+#    $Header: /cvsroot/pgsql/src/interfaces/libpgeasy/examples/Attic/Makefile,v 1.3 2002/03/04 18:50:21 momjian Exp $
 #
 #-------------------------------------------------------------------------
 
 CFLAGS=-I/usr/local/pgsql/include
-TARGET = pginsert pgwordcount pgnulltest
+TARGET = pginsert pgwordcount pgnulltest pgmultiresult
 LDFLAGS = -L/usr/local/pgsql/lib -lpgeasy
 
 all : $(TARGET)
 
-pginsert:
-	gcc -o $@ $(CFLAGS) $@.c $(PGEASY) $(LDFLAGS)
-
-pgwordcount:
-	gcc -o $@ $(CFLAGS) $@.c $(PGEASY) $(LDFLAGS)
-
-pgnulltest:
+%: %.c
 	gcc -o $@ $(CFLAGS) $@.c $(PGEASY) $(LDFLAGS)
 
 clean:
diff --git a/src/interfaces/libpgeasy/libpgeasy.c b/src/interfaces/libpgeasy/libpgeasy.c
index 4bddece659c..40684a2cfe8 100644
--- a/src/interfaces/libpgeasy/libpgeasy.c
+++ b/src/interfaces/libpgeasy/libpgeasy.c
@@ -6,6 +6,7 @@
 #include <stdio.h>
 #include <string.h>
 #include <stdarg.h>
+#include <stdlib.h>
 
 #include "libpq-fe.h"
 #include "halt.h"
@@ -16,31 +17,33 @@
 #endif
 
 #ifndef TRUE
-#define TRUE 1
+#define TRUE	1
 #endif
 
 #ifndef FALSE
-#define FALSE 0
+#define FALSE	0
 #endif
 
 /* GLOBAL VARIABLES */
 static PGconn *conn;
 static PGresult *res = NULL;
 
-static int	tuple;		/* stores fetch location */
+static int	tuple;				/* stores fetch location */
 
-#define ON_ERROR_STOP	0
-#define ON_ERROR_CONTINUE		1
+#define ON_ERROR_STOP		0
+#define ON_ERROR_CONTINUE	1
 
-static int	on_error_state = ON_ERROR_STOP;	/* halt on errors? */
+static int	on_error_state = ON_ERROR_STOP;		/* halt on errors? */
+
+static int	user_has_res = FALSE;
+
+static void add_res_tuple(void);
+static void get_res_tuple(void);
+static void del_res_tuple(void);
 
-static int	in_result_block = FALSE;		
-static int	was_get_unset_result = FALSE;
 
 /*
- *
  *	connectdb - returns PGconn structure
- *
  */
 PGconn *
 connectdb(char *options)
@@ -53,17 +56,14 @@ connectdb(char *options)
 	return conn;
 }
 
+
 /*
- *
  *		disconnectdb
- *
  */
 void
 disconnectdb()
 {
-	if (res != NULL &&
-		in_result_block == FALSE &&
-		was_get_unset_result == FALSE)
+	if (res != NULL && user_has_res == FALSE)
 	{
 		PQclear(res);
 		res = NULL;
@@ -72,20 +72,17 @@ disconnectdb()
 	PQfinish(conn);
 }
 
+
 /*
- *
  *	doquery - returns PGresult structure
- *
  */
 PGresult *
 doquery(char *query)
 {
-	if (res != NULL &&
-		in_result_block == FALSE &&
-		was_get_unset_result == FALSE)
+	if (res != NULL && user_has_res == FALSE)
 		PQclear(res);
 
-	was_get_unset_result = FALSE;
+	user_has_res = FALSE;
 	res = PQexec(conn, query);
 
 	if (on_error_state == ON_ERROR_STOP &&
@@ -105,11 +102,10 @@ doquery(char *query)
 	return res;
 }
 
+
 /*
- *
  *	fetch - returns tuple number (starts at 0), or the value END_OF_TUPLES
  *			NULL pointers are skipped
- *
  */
 int
 fetch(void *param,...)
@@ -142,8 +138,8 @@ fetch(void *param,...)
 	return tuple++;
 }
 
+
 /*
- *
  *		fetchwithnulls - returns tuple number (starts at 0),
  *																						or the value END_OF_TUPLES
  *		Returns TRUE or FALSE into null indicator variables
@@ -185,10 +181,19 @@ fetchwithnulls(void *param,...)
 	return tuple++;
 }
 
+
+/*
+ *	reset_fetch
+ */
+void
+reset_fetch()
+{
+	tuple = 0;
+}
+
+
 /*
- *
  *	on_error_stop
- *
  */
 void
 on_error_stop()
@@ -196,10 +201,9 @@ on_error_stop()
 	on_error_state = ON_ERROR_STOP;
 }
 
+
 /*
- *
  *	on_error_continue
- *
  */
 void
 on_error_continue()
@@ -209,92 +213,132 @@ on_error_continue()
 
 
 /*
- *
  *	get_result
- *
  */
 PGresult *
 get_result()
 {
-	char	   *cmdstatus = PQcmdStatus(res);
+	if (res == NULL)
+		halt("get_result called with no result pointer used\n");
 
-	was_get_unset_result = TRUE;
+	/* delete it if it is already there; we are about to re-add it */
+	del_res_tuple();
 
-	/* we have to store the fetch location somewhere */
-	/* XXX THIS IS A NO-NO */
-	cmdstatus[0] = NUL;
-	memcpy(&cmdstatus[1], &tuple, sizeof(tuple));
+	/* we have to store the fetch location */
+	add_res_tuple();
+
+	user_has_res = TRUE;
 
 	return res;
 }
 
+
 /*
- *
  *	set_result
- *
  */
 void
 set_result(PGresult *newres)
 {
-
-	char	   *cmdstatus = PQcmdStatus(res);
-
 	if (newres == NULL)
 		halt("set_result called with null result pointer\n");
 
-	if (res != NULL && was_get_unset_result == FALSE)
+	if (res != NULL && user_has_res == FALSE)
 	{
-		if (in_result_block == FALSE)
-			PQclear(res);
-		else
-		{
-			/* XXX THIS IS A NO-NO */
-			cmdstatus[0] = NUL;
-			memcpy(&cmdstatus[1], &tuple, sizeof(tuple));
-		}
+		/*
+		 * Basically, throw away res. We can't return to it because the
+		 * user doesn't have the res pointer.
+		 */
+		del_res_tuple();
+		PQclear(res);
 	}
 
-	in_result_block = TRUE;
-	was_get_unset_result = FALSE;
-
-	cmdstatus = PQcmdStatus(newres);
-	memcpy(&tuple, &cmdstatus[1], sizeof(tuple));
+	user_has_res = FALSE;
 
 	res = newres;
+
+	get_res_tuple();
 }
 
 
 /*
- *
- *	unset_result
- *
+ *	Routines to store res/tuple mapping
+ *	This is used to keep track of fetch locations while using get/set on
+ *	result sets.
+ *	Auto-growing array is used, with empty slots marked by res == NULL
  */
-void
-unset_result(PGresult *oldres)
+
+static struct res_tuple
 {
-	char	   *cmdstatus = PQcmdStatus(oldres);
+	PGresult   *res;
+	int			tuple;
+}	*res_tuple = NULL;
 
-	if (oldres == NULL)
-		halt("unset_result called with null result pointer\n");
+static int	res_tuple_len = 0;
 
-	if (in_result_block == FALSE)
-		halt("Unset of result without being set.\n");
 
-	was_get_unset_result = TRUE;
+/*
+ * add_res_tuple
+ */
+static void
+add_res_tuple(void)
+{
+	int			i,
+				new_res_tuple_len = res_tuple_len ? res_tuple_len * 2 : 1;
+
+	for (i = 0; i < res_tuple_len; i++)
+		/* Put it in an empty slot */
+		if (res_tuple[i].res == NULL)
+		{
+			res_tuple[i].res = res;
+			res_tuple[i].tuple = tuple;
+		}
+
+	/* Need to grow array */
+	res_tuple = realloc(res_tuple, new_res_tuple_len * sizeof(struct res_tuple));
+
+	/* clear new elements */
+	for (i = res_tuple_len; i < new_res_tuple_len; i++)
+	{
+		res_tuple[i].res = NULL;
+		res_tuple[i].tuple = 0;
+	}
 
-	/* XXX THIS IS A NO-NO */
-	cmdstatus[0] = NUL;
-	memcpy(&cmdstatus[1], &tuple, sizeof(tuple));
-	in_result_block = FALSE;
+	/* recursion to add entry */
+	add_res_tuple();
 }
 
+
 /*
- *
- *	reset_fetch
- *
+ * get_res_tuple
  */
-void
-reset_fetch()
+static void
+get_res_tuple(void)
 {
-	tuple = 0;
+	int			i;
+
+	for (i = 0; i < res_tuple_len; i++)
+		if (res_tuple[i].res == res)
+		{
+			tuple = res_tuple[i].tuple;
+			return;
+		}
+	halt("get_res_tuple called with invalid result pointer\n");
+}
+
+
+/*
+ * del_res_tuple
+ */
+static void
+del_res_tuple(void)
+{
+	int			i;
+
+	for (i = 0; i < res_tuple_len; i++)
+		if (res_tuple[i].res == res)
+		{
+			res_tuple[i].res = NULL;
+			return;
+		}
+	return;
 }
-- 
GitLab