From 7bfe31114c6ca02b3efa5f75f884423099ac370b Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Fri, 15 Mar 2013 15:26:01 +0200
Subject: [PATCH 1/1] Eliminate looping through chain of results in
 performance critical codepaths.

This patch eliminates O(n^2) behavior on the number of chained results in
SC_execute. This makes it feasible to deal with large parameter arrays, for
example.

The idea is to track the owner statement of each result object, so that when
we need to check if a result object is already in the chain of results of a
statement, we just need to look at the owner field, not loop through the
whole chain. Also, keep track of the last result in the chain in
StatementClass, so that we don't need to iterate through it when appending
to the end.
---
 qresult.c   |    2 ++
 qresult.h   |    1 +
 statement.c |   72 +++++++++++++++++++++++++++++++++++++++++++++++------------
 statement.h |    2 ++
 4 files changed, 63 insertions(+), 14 deletions(-)

diff --git a/qresult.c b/qresult.c
index 9ad0814..5bb17fc 100644
--- a/qresult.c
+++ b/qresult.c
@@ -163,6 +163,7 @@ QR_Constructor()
 		rv->command = NULL;
 		rv->notice = NULL;
 		rv->conn = NULL;
+		rv->stmt = NULL;
 		rv->next = NULL;
 		rv->pstatus = 0;
 		rv->count_backend_allocated = 0;
@@ -1234,6 +1235,7 @@ inolog("id='%c' response_length=%d\n", id, response_length);
 					CC_on_abort(conn, CONN_DEAD);
 					return FALSE;
 				}
+				self->next->stmt = self->stmt;
 				QR_set_cache_size(self->next, self->cache_size);
 				self = self->next;
 				if (!QR_fetch_tuples(self, conn, NULL, LastMessageType))
diff --git a/qresult.h b/qresult.h
index 3893bda..108f1c0 100644
--- a/qresult.h
+++ b/qresult.h
@@ -55,6 +55,7 @@ struct QResultClass_
 	ColumnInfoClass *fields;	/* the Column information */
 	ConnectionClass *conn;		/* the connection this result is using
 								 * (backend) */
+	StatementClass *stmt;		/* the statement this result belongs to. */
 	QResultClass	*next;		/* the following result class */
 
 	/* Stuff for declare/fetch tuples */
diff --git a/statement.c b/statement.c
index 697d142..be21548 100644
--- a/statement.c
+++ b/statement.c
@@ -383,6 +383,7 @@ SC_Constructor(ConnectionClass *conn)
 		rv->hdbc = conn;
 		rv->phstmt = NULL;
 		rv->result = NULL;
+		rv->lastres = NULL;
 		rv->curres = NULL;
 		rv->catalog_result = FALSE;
 		rv->prepare = NON_PREPARE_STATEMENT;
@@ -525,7 +526,7 @@ SC_Destructor(StatementClass *self)
 void
 SC_init_Result(StatementClass *self)
 {
-	self->result = self->curres = NULL;
+	self->result = self->lastres = self->curres = NULL;
 	self->curr_param_result = 0;
 	mylog("SC_init_Result(%x)", self);
 }
@@ -537,9 +538,22 @@ SC_set_Result(StatementClass *self, QResultClass *res)
 	{
 		mylog("SC_set_Result(%x, %x)", self, res);
 		QR_Destructor(self->result);
-		self->result = self->curres = res;
+		self->result = self->lastres = self->curres = res;
 		if (NULL != res)
+		{
+			/*
+			 * Set the owner of 'res', and all results chained
+			 * to it. They all belong to this statement now.
+			 */
+			QResultClass *t;
+			for (t = res; t != NULL; t = t->next)
+			{
+				t->stmt = self;
+				/* also update lastres while we're at it */
+				self->lastres = t;
+			}
 			self->curr_param_result = 1;
+		}
 	}
 }
 
@@ -1573,6 +1587,28 @@ PGAPI_StmtError(	SQLHSTMT	hstmt,
 		pcbErrorMsg, flag);
 }
 
+/*
+ * Get the last result in the results chain.
+ */
+QResultClass *
+SC_get_Lastres(StatementClass *self)
+{
+	QResultClass *res;
+
+	res = self->lastres;
+	if (res == NULL)
+		res = NULL;
+	else
+	{
+		while (res->next != NULL)
+			res = res->next;
+	}
+
+	/* update the cached value while we're at it */
+	self->lastres = res;
+	return res;
+}
+
 time_t
 SC_get_time(StatementClass *stmt)
 {
@@ -1978,7 +2014,7 @@ SC_execute(StatementClass *self)
 				SC_set_error(self, STMT_EXEC_ERROR, "Execute request error", func);
 			goto cleanup;
 		}
-		for (res = SC_get_Result(self); NULL != res && NULL != res->next; res = res->next) ;
+		res = SC_get_Lastres(self);
 inolog("get_Result=%p %p %d\n", res, SC_get_Result(self), self->curr_param_result);
 		if (!(res = SendSyncAndReceive(self, self->curr_param_result ? res : NULL, "bind_and_execute")))
 		{
@@ -2193,20 +2229,28 @@ inolog("res->next=%p\n", tres);
 			CC_abort(conn);
 #endif /* _LEGACY_MODE_ */
 	}
-	if (!SC_get_Result(self))
-		SC_set_Result(self, res);
-	else
-	{
-		QResultClass	*last;
 
-		for (last = SC_get_Result(self); NULL != last->next; last = last->next)
+	/*
+	 * Add 'res' to the end of the result chain, if it's not in the chain
+	 * already.
+	 */
+	if (res->stmt != self)
+	{
+		/* the result should not belong to any other statement */
+		/* assert (res->stmt == NULL) */
+		QResultClass	*last = SC_get_Lastres(self);
+		if (last == NULL)
+			SC_set_Result(self, res);
+		else
 		{
-			if (last == res)
-				break;
-		}
-		if (last != res)
+			QResultClass *t;
+			for (t = res; t != NULL; t = t->next)
+				t->stmt = self;
+
 			last->next = res;
-		self->curr_param_result = 1;
+			self->curr_param_result = 1;
+			res->stmt = self;
+		}
 	}
 
 	ipdopts = SC_get_IPDF(self);
diff --git a/statement.h b/statement.h
index 21785ae..110cf22 100644
--- a/statement.h
+++ b/statement.h
@@ -182,6 +182,7 @@ struct StatementClass_
 								 * statement belongs to */
 	QResultClass *result;		/* result of the current statement */
 	QResultClass *curres;		/* the current result in the chain */
+	QResultClass *lastres;		/* last result in the result->next chain */
 	HSTMT FAR  *phstmt;
 	StatementOptions options;
 	StatementOptions options_orig;
@@ -290,6 +291,7 @@ void SC_set_Result(StatementClass *self, QResultClass *res);
 #define SC_get_Result(a)  (a->result)
 #define SC_set_Curres(a, b)  (a->curres = b)
 #define SC_get_Curres(a)  (a->curres)
+extern QResultClass *SC_get_Lastres(StatementClass *self);
 #define SC_get_ARD(a)  (a->ard)
 #define SC_get_APD(a)  (a->apd)
 #define SC_get_IRD(a)  (a->ird)
-- 
1.7.10.4

