Tuukka Norri avatar Tuukka Norri committed cfe6c3d

Thread safety for PGTSResultSet, PGTSQuery, PGTSQueryDescription

Comments (0)

Files changed (5)

Sources/PGTSConnection.h

     id <PGTSConnectionDelegate> mDelegate; //Weak
 	
 	BOOL mDidDisconnectOnSleep;
-	BOOL mProcessingNotifications;
-	BOOL mLogsQueries;
+	volatile BOOL mLogsQueries;
 }
 - (id) init;
 - (void) dealloc;
 - (int) socket;
 - (SSL *) SSLStruct;
 - (BOOL) canSend;
+- (BXSocketDescriptor *) socketDescriptor;
 
 - (id <PGTSCertificateVerificationDelegate>) certificateVerificationDelegate;
 - (void) setCertificateVerificationDelegate: (id <PGTSCertificateVerificationDelegate>) anObject;
 - (BOOL) logsQueries;
 - (void) setLogsQueries: (BOOL) flag;
 
-- (void) logIfNeeded: (PGTSResultSet *) res;
+- (void) logQueryIfNeeded: (PGTSQuery *) query;
+- (void) logResultIfNeeded: (PGTSResultSet *) res;
 @end
 
 

Sources/PGTSConnection.mm

 }
 
 
+- (BXSocketDescriptor *) socketDescriptor
+{
+	id retval = nil;
+	@synchronized (self)
+	{
+		retval = [[mSocketDescriptor retain] autorelease];
+	}
+	return retval;
+}
+
+
 - (PGresult *) execQuery: (const char *) query
 {
 	PGresult *res = NULL;
 
 		if ([self canSend])
 		{
-			if (mLogsQueries)
-				[mDelegate PGTSConnection: self sentQueryString: query];
-			
 			res = PQexec (mConnection, query);
 			if (BASETEN_POSTGRESQL_SEND_QUERY_ENABLED ())
 			{
 			}
 		}
 	}
+	
+	if (res && mLogsQueries)
+		[mDelegate PGTSConnection: self sentQueryString: query];
+	
 	return res;
 }
 
 }
 
 
-- (void) logIfNeeded: (PGTSResultSet *) res
+- (void) logQueryIfNeeded: (PGTSQuery *) query
 {
-	@synchronized (self)
-	{
-		if (mLogsQueries)
-			[mDelegate PGTSConnection: self receivedResultSet: res];
-	}
+	if (mLogsQueries)
+		[mDelegate PGTSConnection: self sentQuery: query];
+}
+
+
+- (void) logResultIfNeeded: (PGTSResultSet *) res
+{
+	if (mLogsQueries)
+		[mDelegate PGTSConnection: self receivedResultSet: res];
 }
 
 
 
 - (void) _processNotifications
 {
+	NSMutableArray *notifications = nil;
+	
 	@synchronized (self)
 	{
-		//Notifications may cause methods to be called. They might require a specific order
-		//(e.g. self-updating collections in BaseTen), which breaks if this is called recursively.
-		//Hence we prevent it.
-		if (! mProcessingNotifications)
+		PGnotify* pgNotification = NULL;
+		while ((pgNotification = PQnotifies (mConnection)))
 		{
-			mProcessingNotifications = YES;
-			PGnotify* pgNotification = NULL;
-			while ((pgNotification = PQnotifies (mConnection)))
-			{
-				NSString* name = [NSString stringWithUTF8String: pgNotification->relname];
-				PGTSNotification* notification = [[[PGTSNotification alloc] init] autorelease];
-				[notification setBackendPID: pgNotification->be_pid];
-				[notification setNotificationName: name];
-				BASETEN_POSTGRESQL_RECEIVED_NOTIFICATION (self, pgNotification->be_pid, pgNotification->relname, pgNotification->extra);		
-				PQfreeNotify (pgNotification);
-				[mDelegate PGTSConnection: self gotNotification: notification];
-			}    
-			mProcessingNotifications = NO;
-		}
+			if (! notifications)
+				notifications = [NSMutableArray array];
+			
+			NSString* name = [NSString stringWithUTF8String: pgNotification->relname];
+			PGTSNotification* notification = [[PGTSNotification alloc] init];
+			[notification setBackendPID: pgNotification->be_pid];
+			[notification setNotificationName: name];
+			BASETEN_POSTGRESQL_RECEIVED_NOTIFICATION (self, pgNotification->be_pid, pgNotification->relname, pgNotification->extra);		
+			PQfreeNotify (pgNotification);
+			
+			[notifications addObject: notification];
+			[notification release];
+		}    
 	}
+	
+	for (PGTSNotification *notification in notifications)
+		[mDelegate PGTSConnection: self gotNotification: notification];
 }
 
 
 - (int) _sendNextQuery
 {
 	int retval = -1;
+	ExpectR ([mSocketDescriptor isLocked], retval);
+
 	@synchronized (self)
 	{
-		ExpectR ([mSocketDescriptor isLocked], retval);
-
 		PGTSQueryDescription* desc = [mQueue objectAtIndex: 0];
 		if (nil != desc)
 		{

Sources/PGTSQuery.m

 #import "PGTSConnectionPrivate.h"
 #import "PGTSProbes.h"
 #import "BXHOM.h"
+#import "BXLogger.h"
+#import "BXSocketDescriptor.h"
 
 
 @implementation PGTSQuery
 		if (! (i == nParams - 1))
 			[desc appendString: @", "];
 	}
-	char* retval = strdup ([desc UTF8String] ?: "");
+	char *retval = strdup ([desc UTF8String] ?: "");
 	
 	//For GC.
 	[desc self];
 
 
 - (int) sendQuery: (PGTSConnection *) connection
-{    
+{
     int retval = 0;
 	//For some reason, libpq doesn't receive signal or EPIPE from send if network is down. Hence we check it here.
-	@synchronized (self)
+	if ([connection canSend])
 	{
-		if ([connection canSend])
+		ExpectR ([[connection socketDescriptor] isLocked], retval);
+		@synchronized (self)
 		{
 			NSUInteger nParams = [self parameterCount];
 			NSArray* parameterObjects = [[mParameters BX_Collect] PGTSParameter: connection];
 			
-			const char** paramValues  = calloc (nParams, sizeof (char *));
-			Oid*   paramTypes   = calloc (nParams, sizeof (Oid));
-			int*   paramLengths = calloc (nParams, sizeof (int));
-			int*   paramFormats = calloc (nParams, sizeof (int));
+			char const **paramValues = calloc (nParams, sizeof (char *));
+			Oid *paramTypes   = calloc (nParams, sizeof (Oid));
+			int *paramLengths = calloc (nParams, sizeof (int));
+			int *paramFormats = calloc (nParams, sizeof (int));
 			
 			for (int i = 0; i < nParams; i++)
 			{
 			
 			if (BASETEN_POSTGRESQL_SEND_QUERY_ENABLED ())
 			{
-				char* params = CopyParameterString (nParams, paramValues, paramFormats);
-				char* query = strdup ([mQuery UTF8String] ?: "");
+				char *params = CopyParameterString (nParams, paramValues, paramFormats);
+				char *query = strdup ([mQuery UTF8String] ?: "");
 				BASETEN_POSTGRESQL_SEND_QUERY (connection, retval, query, params);
 				free (query);
 				free (params);
 				retval = PQsendQuery ([connection pgConnection], [mQuery UTF8String]);
 			}
 			
-			if ([connection logsQueries])
-				[(id) [connection delegate] PGTSConnection: connection sentQuery: self];
-			
 			free (paramTypes);
 			free (paramValues);
 			free (paramLengths);
 			//For GC.
 			[parameterObjects self];
 		}
+		
+		[connection logQueryIfNeeded: self];
 	}
     return retval;
 }

Sources/PGTSQueryDescription.m

 #import "PGTSQuery.h"
 #import "PGTSConnectionPrivate.h"
 #import "PGTSProbes.h"
+#import "BXLogger.h"
+#import "BXSocketDescriptor.h"
 #import <BaseTen/libpq-fe.h>
 #import <libkern/OSAtomic.h>
 
 
 - (PGTSResultSet *) receiveForConnection: (PGTSConnection *) connection
 {	
-    PGTSResultSet* retval = nil;
+	Expect ([[connection socketDescriptor] isLocked]);
+	
+    PGTSResultSet* retval = nil;	
     PGconn* pgConn = [connection pgConnection];
     PGresult* result = PQgetResult (pgConn);
 	
-	@synchronized (self)
+	if (result)
 	{
-		if (result)
-		{
-			retval = [PGTSResultSet resultWithPGresult: result connection: connection];
-			[retval setIdentifier: mIdentifier];
-			[retval setUserInfo: mUserInfo];
-			
-			[connection logIfNeeded: retval];
-			[mDelegate performSelector: mCallback withObject: retval];
-		}
-		else
+		retval = [PGTSResultSet resultWithPGresult: result connection: connection];
+		[retval setIdentifier: mIdentifier];
+		[retval setUserInfo: mUserInfo];
+		[mDelegate performSelector: mCallback withObject: retval];
+	}
+	else
+	{
+		@synchronized (self)
 		{
 			mFinished = YES;
-			
-			if (BASETEN_POSTGRESQL_FINISH_QUERY_ENABLED ())
-				BASETEN_POSTGRESQL_FINISH_QUERY ();
 		}
+		
+		if (BASETEN_POSTGRESQL_FINISH_QUERY_ENABLED ())
+			BASETEN_POSTGRESQL_FINISH_QUERY ();
 	}
+	
+	if (result)
+		[connection logResultIfNeeded: retval];
+	
     return retval;
 }
 

Sources/PGTSResultSet.mm

 
 @interface PGTSConcreteResultSet : PGTSResultSet
 {
-    PGTSConnection* mConnection; //Weak
-	PGresult* mResult;
+    PGTSConnection *mConnection;
+	PGresult *mResult;
 	int mCurrentRow;
     int mFields;
     int mTuples;
     NSInteger mIdentifier;
-    FieldIndexMap* mFieldIndices;
-    FieldClassMap* mFieldClasses;
+    FieldIndexMap *mFieldIndices;
+    FieldClassMap *mFieldClasses;
     Class mRowClass;
 	id mUserInfo;
     
 
 - (PGTSConnection *) connection
 {
-    return mConnection;
+	id retval = nil;
+	@synchronized (self)
+	{
+		retval = [[mConnection retain] autorelease];
+	}
+    return retval;
 }
 
+
 - (void) dealloc
 {
 	PQclear (mResult);
     [super dealloc];
 }
 
+
 - (void) finalize
 {
 	PQclear (mResult);
     [super finalize];
 }
 
+
 - (NSString *) description
 {
     return [NSString stringWithFormat: @"<%@ (%p) cr: %d t: %d f: %d kc: %d ok: %d>",
         [self class], self, mCurrentRow, mTuples, mFields, mKnowsFieldClasses, [self querySucceeded]];
 }
 
+
 - (ExecStatusType) status
 {
-	return PQresultStatus (mResult);
+	ExecStatusType retval = PGRES_EMPTY_QUERY;
+	@synchronized (self)
+	{
+		retval = PQresultStatus (mResult);
+	}
+	return retval;
 }
 
+
 - (BOOL) querySucceeded
 {
     ExecStatusType s = [self status];
     return (PGRES_FATAL_ERROR != s && PGRES_BAD_RESPONSE != s);
 }
 
+
 + (id) resultWithPGresult: (PGresult *) aResult connection: (PGTSConnection *) aConnection
 {
 	return [[[self alloc] initWithPGResult: aResult connection: aConnection] autorelease];
 }
 
+
 - (id) initWithPGResult: (PGresult *) result connection: (PGTSConnection *) aConnection;
 {
 	if ((self = [super init]))
 	return self;
 }
 
+
 - (void) setDeterminesFieldClassesAutomatically: (BOOL) aBool
 {
-    mDeterminesFieldClassesFromDB = aBool;
+	@synchronized (self)
+	{
+	    mDeterminesFieldClassesFromDB = aBool;
+	}
 }
 
+
 - (void) fetchFieldDescriptions
 {
-	mKnowsFieldClasses = YES;
-    
-#if 0
-	Oid* oidVector = (Oid *) calloc (mFields + 1, sizeof (Oid));
-	for (int i = 0; i < mFields; i++)
-		oidVector [i] = PQftype (mResult, i);
-	oidVector [mFields] = InvalidOid;
-#endif
-
 	PGTSDatabaseDescription* db = [mConnection databaseDescription];
-#if 0
-	//Warm-up the cache.
-	[db typesWithOids: oidVector];
-	free (oidVector);
-	oidVector = NULL;
-#endif
+	NSDictionary* deserializationDictionary = [mConnection deserializationDictionary];
 	
-    NSDictionary* deserializationDictionary = [mConnection deserializationDictionary];
-    for (int i = 0; i < mFields; i++)
-    {
-        PGTSTypeDescription* type = [db typeWithOid: PQftype (mResult, i)];
-        NSString* name = [type name];
-		Class aClass = [deserializationDictionary objectForKey: name];
-		
-		if (! aClass)
+	@synchronized (self)
+	{
+		mKnowsFieldClasses = YES;
+		for (int i = 0; i < mFields; i++)
 		{
-			//Check for arrays. Other types that satisfy the condition (like int2vector and oidvector) 
-			//should have an entry in deserializationDictionary, if they are used.
-			if (-1 == [type length] && InvalidOid != [type elementOid])
+			PGTSTypeDescription* type = [db typeWithOid: PQftype (mResult, i)];
+			NSString* name = [type name];
+			Class aClass = [deserializationDictionary objectForKey: name];
+			
+			if (! aClass)
 			{
-				aClass = [NSArray class];
+				//Check for arrays. Other types that satisfy the condition (like int2vector and oidvector) 
+				//should have an entry in deserializationDictionary, if they are used.
+				if (-1 == [type length] && InvalidOid != [type elementOid])
+				{
+					aClass = [NSArray class];
+				}
+				else
+				{
+					//Handle other types by kind.
+					switch ([type kind]) 
+					{
+						case 'e':
+							aClass = [NSString class];
+							break;
+							
+						case 'c':
+							//FIXME: handle composite types.
+						case 'd':
+							//FIXME: handle domains.
+						case 'p':
+							//FIXME: handle pseudo-types. (On the other hand, this shouldn't get reached.)
+						case 'b':
+						default:
+							aClass = [NSData class];					
+							break;
+					}				
+				}
 			}
-			else
-			{
-				//Handle other types by kind.
-				switch ([type kind]) 
-				{
-					case 'e':
-						aClass = [NSString class];
-						break;
-						
-					case 'c':
-						//FIXME: handle composite types.
-					case 'd':
-						//FIXME: handle domains.
-					case 'p':
-						//FIXME: handle pseudo-types. (On the other hand, this shouldn't get reached.)
-					case 'b':
-					default:
-						aClass = [NSData class];					
-						break;
-				}				
-			}
+			
+			[self setClass: aClass forFieldAtIndex: i];
 		}
-		
-		[self setClass: aClass forFieldAtIndex: i];
 	}
 }
 
+
 - (int) numberOfFields
 {
-	return mFields;
+	int retval = 0;
+	@synchronized (self)
+	{
+		retval = mFields;
+	}
+	return retval;
 }
 
+
 - (NSArray *) resultAsArray
 {
 	NSMutableArray* retval = [NSMutableArray arrayWithCapacity: [self count]];
-	[self goBeforeFirstRow];
-	while ([self advanceRow]) 
+	@synchronized (self)
 	{
-		[retval addObject: [self currentRowAsDictionary]];
+		[self goBeforeFirstRow];
+		while ([self advanceRow]) 
+		{
+			[retval addObject: [self currentRowAsDictionary]];
+		}
 	}
 	return retval;
 }
 
+
 - (NSInteger) identifier
 {
-    return mIdentifier;
+	NSInteger retval = 0;
+	@synchronized (self)
+	{
+		retval = mIdentifier;
+	}
+    return retval;
 }
 
+
 - (void) setIdentifier: (NSInteger) anIdentifier
 {
-    mIdentifier = anIdentifier;
+	@synchronized (self)
+	{
+	    mIdentifier = anIdentifier;
+	}
 }
 
+
 - (NSString *) errorString
 {
-	return [NSString stringWithUTF8String: PQresultErrorMessage (mResult)];
+	NSString *retval = nil;
+	@synchronized (self)
+	{
+		retval = [NSString stringWithUTF8String: PQresultErrorMessage (mResult)];
+	}
+	return retval;
 }
 
+
 - (NSError *) error
 {
-	return [[self class] errorForPGresult: mResult];
+	NSError *retval = nil;
+	@synchronized (self)
+	{
+		[[self class] errorForPGresult: mResult];
+	}
+	return retval;
 }
+
 	
 - (PGresult *) PGresult
 {
-	return mResult;
+	PGresult *retval = NULL;
+	@synchronized (self)
+	{
+		retval = mResult;
+	}
+	return retval;
 }
 @end
 
 
+
 @implementation PGTSConcreteResultSet (RowAccessors)
-
 - (BOOL) isAtEnd
 {
-    return (mCurrentRow == mTuples - 1);
+	BOOL retval = NO;
+	@synchronized (self)
+	{
+		retval = (mCurrentRow == mTuples - 1);
+	}
+    return retval;
 }
 
+
 - (int) currentRow
 {
-    return mCurrentRow;
+	int retval = 0;
+	@synchronized (self)
+	{
+		retval = mCurrentRow;
+	}
+    return retval;
 }
 
+
 - (id <PGTSResultRowProtocol>) currentRowAsObject
 {
     id retval = [[[mRowClass alloc] init] autorelease];
     return retval;
 }
 
+
 - (void) setRowClass: (Class) aClass
 {
     if ([aClass conformsToProtocol: @protocol (PGTSResultRowProtocol)])
-        mRowClass = aClass;
+	{
+		@synchronized (self)
+		{
+	        mRowClass = aClass;
+		}
+	}
     else
     {
         //FIXME: localize me.
     }
 }
 
+
 - (void) setValuesFromRow: (int) rowIndex target: (id) targetObject nullPlaceholder: (id) nullPlaceholder
 {
-    FieldIndexMap::const_iterator iterator = mFieldIndices->begin ();
-    while (mFieldIndices->end () != iterator)
-    {
-        NSString* fieldname = iterator->first;
-		int index = iterator->second;
-		id value = [self valueForFieldAtIndex: index row: rowIndex];
-        if (! value)
-            value = nullPlaceholder;
-        @try
-        {
-            [targetObject setValue: value forKey: fieldname];
-        }
-        @catch (id e)
-        {
-        }
-        iterator++;
-    }
+	@synchronized (self)
+	{
+		FieldIndexMap::const_iterator iterator = mFieldIndices->begin ();
+		while (mFieldIndices->end () != iterator)
+		{
+			NSString* fieldname = iterator->first;
+			int index = iterator->second;
+			id value = [self valueForFieldAtIndex: index row: rowIndex];
+			if (! value)
+				value = nullPlaceholder;
+			@try
+			{
+				[targetObject setValue: value forKey: fieldname];
+			}
+			@catch (id e)
+			{
+			}
+			iterator++;
+		}
+	}
 }
 
 /**
  */
 - (NSDictionary *) currentRowAsDictionary
 {
-    NSMutableDictionary* retval = [NSMutableDictionary dictionaryWithCapacity: mFields];
-    [self setValuesFromRow: mCurrentRow target: retval nullPlaceholder: [NSNull null]];
+    NSMutableDictionary* retval = nil;
+	@synchronized (self)
+	{
+		retval = [NSMutableDictionary dictionaryWithCapacity: mFields];
+		[self setValuesFromRow: mCurrentRow target: retval nullPlaceholder: [NSNull null]];
+	}
     return retval;
 }
 
+
 /** \brief Move to the beginning of the result set */
 - (void) goBeforeFirstRow
 {
     [self goToRow: -1];
 }
 
+
 - (BOOL) goToRow: (int) aRow
 {
     BOOL retval = NO;
-	if (-1 <= aRow && aRow < mTuples)
+	@synchronized (self)
 	{
-		mCurrentRow = aRow;
-		retval = YES;
+		if (-1 <= aRow && aRow < mTuples)
+		{
+			mCurrentRow = aRow;
+			retval = YES;
+		}
 	}
 	return retval;
 }
 
+
 - (void) goBeforeFirstRowUsingFunction: (NSComparisonResult (*)(PGTSResultSet*, void*)) comparator context: (void *) context
 								   low: (const int) low high: (const int) high
 {
 
 - (void) goBeforeFirstRowUsingFunction: (NSComparisonResult (*)(PGTSResultSet*, void*)) comparator context: (void *) context
 {
-	if (! mKnowsFieldClasses && mDeterminesFieldClassesFromDB)
-        [self fetchFieldDescriptions];
-	
-	//low is -1 because the first row might apply and -advanceRow will probably will be called after this method.
-	//high is mTuples (instead of mTuples - 1) in case even the last row doesn't apply and we set it to current.
-	[self goBeforeFirstRowUsingFunction: comparator context: context low: -1 high: mTuples];
+	@synchronized (self)
+	{
+		if (! mKnowsFieldClasses && mDeterminesFieldClassesFromDB)
+			[self fetchFieldDescriptions];
+		
+		//low is -1 because the first row might apply and -advanceRow will probably will be called after this method.
+		//high is mTuples (instead of mTuples - 1) in case even the last row doesn't apply and we set it to current.
+		[self goBeforeFirstRowUsingFunction: comparator context: context low: -1 high: mTuples];
+	}
 }
 
 
 {
 	struct kv_compare_st ctx = {columnName, value};
 	[self goBeforeFirstRowUsingFunction: &KVCompare context: &ctx];
-	ExpectV (mCurrentRow == -1 || NSOrderedAscending == [[self valueForKey: columnName row: mCurrentRow] compare: value]);
-	ExpectV (mCurrentRow == mTuples - 1 || NSOrderedAscending != [[self valueForKey: columnName row: mCurrentRow + 1] compare: value]);
+	@synchronized (self)
+	{
+		ExpectV (mCurrentRow == -1 || NSOrderedAscending == [[self valueForKey: columnName row: mCurrentRow] compare: value]);
+		ExpectV (mCurrentRow == mTuples - 1 || NSOrderedAscending != [[self valueForKey: columnName row: mCurrentRow + 1] compare: value]);
+	}
 }
 
+
 - (int) count
 {
-	return mTuples;
+	int retval = 0;
+	@synchronized (self)
+	{
+		retval = mTuples;
+	}
+	return retval;
 }
 
+
 - (unsigned long long) numberOfRowsAffectedByCommand
 {
-	return strtoull (PQcmdTuples (mResult), NULL, 10);
+	unsigned long long retval = 0;
+	@synchronized (self)
+	{
+		retval = strtoull (PQcmdTuples (mResult), NULL, 10);
+	}
+	return retval;
 }
 
+
 - (BOOL) advanceRow
 {
     BOOL retval = NO;
-    
-    if (! mKnowsFieldClasses && mDeterminesFieldClassesFromDB)
-        [self fetchFieldDescriptions];
-    
-    //Row numbering is zero-based.
-    //The number is initially -1. 
-	if (mCurrentRow < mTuples - 1)
+	
+	@synchronized (self)
 	{
-        mCurrentRow++;
-		retval = YES;
+		if (! mKnowsFieldClasses && mDeterminesFieldClassesFromDB)
+			[self fetchFieldDescriptions];
+		
+		//Row numbering is zero-based.
+		//The number is initially -1. 
+		if (mCurrentRow < mTuples - 1)
+		{
+			mCurrentRow++;
+			retval = YES;
+		}
 	}
 	return retval;
 }
 
+
 - (void) setUserInfo: (id) userInfo
 {
-	if (mUserInfo != userInfo)
+	@synchronized (self)
 	{
-		[mUserInfo release];
-		mUserInfo = [userInfo retain];
+		if (mUserInfo != userInfo)
+		{
+			[mUserInfo release];
+			mUserInfo = [userInfo retain];
+		}
 	}
 }
 
+
 - (id) userInfo
 {
-	return mUserInfo;
+	id retval = nil;
+	@synchronized (self)
+	{
+		retval = [[mUserInfo retain] autorelease];
+	}
+	return retval;
 }
 @end
 
  */
 - (BOOL) setClass: (Class) aClass forKey: (NSString *) aName
 {
-    return [self setClass: aClass forFieldAtIndex: (* mFieldIndices) [aName]];
+    BOOL retval = NO;
+	@synchronized (self)
+	{
+		retval = [self setClass: aClass forFieldAtIndex: (* mFieldIndices) [aName]];
+	}
+	return retval;
 }
 
 
 - (BOOL) setClass: (Class) aClass forFieldAtIndex: (int) fieldIndex
 {
     BOOL retval = NO;
-    if (fieldIndex < mFields)
-    {
-        (* mFieldClasses) [fieldIndex] = aClass;
-        retval = YES;
-    }
+	@synchronized (self)
+	{
+		if (fieldIndex < mFields)
+		{
+			(* mFieldClasses) [fieldIndex] = aClass;
+			retval = YES;
+		}
+	}
     return retval;
 }
 /** @} */
 - (id) valueForFieldAtIndex: (int) columnIndex row: (int) rowIndex
 {
     id retval = nil;
-    if (! ((columnIndex < mFields) && (-1 < rowIndex) && (rowIndex < mTuples)))
-    {
-		[NSException raise: kPGTSFieldNotFoundException format: @"No field for index %d.", columnIndex];
-    }
-    
-    if (! PQgetisnull (mResult, rowIndex, columnIndex))
-    {
-        Class objectClass = (* mFieldClasses) [columnIndex];
-        if (! objectClass)
-            objectClass = [NSData class];
-        char* value = PQgetvalue (mResult, rowIndex, columnIndex);
-        PGTSTypeDescription* type = [[mConnection databaseDescription] typeWithOid: PQftype (mResult, columnIndex)];
-        retval = [[objectClass copyForPGTSResultSet: self withCharacters: value type: type columnIndex: columnIndex] autorelease];
-    }
+	PGTSDatabaseDescription *db = [mConnection databaseDescription];
+	@synchronized (self)
+	{
+		if (! ((columnIndex < mFields) && (-1 < rowIndex) && (rowIndex < mTuples)))
+		{
+			[NSException raise: kPGTSFieldNotFoundException format: @"No field for index %d.", columnIndex];
+		}
+		
+		if (! PQgetisnull (mResult, rowIndex, columnIndex))
+		{
+			Class objectClass = (* mFieldClasses) [columnIndex];
+			if (! objectClass)
+				objectClass = [NSData class];
+			char* value = PQgetvalue (mResult, rowIndex, columnIndex);
+			PGTSTypeDescription* typeDesc = [db typeWithOid: PQftype (mResult, columnIndex)];
+			retval = [[objectClass copyForPGTSResultSet: self withCharacters: value type: typeDesc columnIndex: columnIndex] autorelease];
+		}
+	}
     return retval;
 }
 
+
 - (id) valueForFieldAtIndex: (int) columnIndex
 {
-    return [self valueForFieldAtIndex: columnIndex row: mCurrentRow];
+	id retval = nil;
+	@synchronized (self)
+	{
+		retval = [self valueForFieldAtIndex: columnIndex row: mCurrentRow];
+	}
+    return retval;
 }
 
+
 - (id) valueForKey: (NSString *) aName row: (int) rowIndex
 {
-    FieldIndexMap::const_iterator iter = mFieldIndices->find (aName);
-    if (mFieldIndices->end () == iter)
-    {
-        @throw [NSException exceptionWithName: kPGTSFieldNotFoundException reason: nil 
-                                     userInfo: [NSDictionary dictionaryWithObjectsAndKeys:
-                                         aName, kPGTSFieldnameKey,
-                                         self,  kPGTSResultSetKey,
-                                         nil]];
-    }
-    int columnIndex = iter->second;
-    return [self valueForFieldAtIndex: columnIndex row: rowIndex];
+	id retval = nil;
+	@synchronized (self)
+	{
+		FieldIndexMap::const_iterator iter = mFieldIndices->find (aName);
+		if (mFieldIndices->end () == iter)
+		{
+			@throw [NSException exceptionWithName: kPGTSFieldNotFoundException reason: nil 
+										 userInfo: [NSDictionary dictionaryWithObjectsAndKeys:
+													aName, kPGTSFieldnameKey,
+													self,  kPGTSResultSetKey,
+													nil]];
+		}
+		int columnIndex = iter->second;
+		retval = [self valueForFieldAtIndex: columnIndex row: rowIndex];
+	}
+    return retval;
 }
 
 - (id) valueForKey: (NSString *) aName
 {
-    return [self valueForKey: aName row: mCurrentRow];
+	id retval = nil;
+	@synchronized (self)
+	{
+		retval = [self valueForKey: aName row: mCurrentRow];
+	}
+    return retval;
 }
+@end
 
-@end
 
 
 @implementation PGTSResultSet
     return [[[PGTSConcreteResultSet alloc] initWithPGResult: aResult connection: aConnection] autorelease];
 }
 
+
 + (NSError *) errorForPGresult: (PGresult *) result
 {
 	NSError* retval = nil;
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.