Commits

Tuukka Norri committed 56b2357

Documentation and small fixes
- Got rid of the anti-pattern of calculating hash lazily since it makes the method thread-unsafe.
- Documented methods and IBOutlets.
- Updated page documentation to refer to current SQL function names.
- Added a table of handled database types instead of referring to datatypeassociations.plist.
- Made most FIXMEs fit on a single row to make listing them easier.
- Added error logging to quite a few places.

Comments (0)

Files changed (25)

Resources/BaseTenModifications.sql.m4

 -- Removes tracked modifications which are older than 5 minutes and set the modification timestamps 
 -- that have been left to null values from the last commit. Since the rows that have the same 
 -- backend PID as the current process might not yet be visible to other transactions. 
--- FIXME: If we knew the current transaction status, the WHERE clause could be rewritten as:
+-- FIXME: If we knew the current transaction status, the WHERE clause could be rewritten as follows:
 -- WHERE "baseten_modification_timestamp" IS NULL 
 --	   AND ("baseten_modification_backend_pid" != pg_backend_pid () OR pg_xact_status = 'IDLE');
 -- Also, if the connection is not autocommitting, we might end up doing some unnecessary work.

Sources/BXAbstractDescription.m

 /**
  * \brief An abstract superclass for various description classes.
  *
- * \note This class is thread-safe.
+ * \note This class's documented methods are thread-safe. Creating objects, however, is not.
+ * \note For this class to work in non-GC applications, the corresponding database context must be retained as well.
  * \ingroup descriptions
  */
 @implementation BXAbstractDescription

Sources/BXAttributeDescription.m

 
 /**
  * \brief An attribute description contains information about a column in a specific entity.
+ * \note This class's documented methods are thread-safe. Creating objects, however, is not.
+ * \note For this class to work in non-GC applications, the corresponding database context must be retained as well.
  * \ingroup descriptions
  */
 @implementation BXAttributeDescription
 	return kBXPropertyKindAttribute;
 }
 
+/** \brief Whether this attribute is an array or not. */
 - (BOOL) isArray
 {
 	return (kBXPropertyIsArray & mFlags ? YES : NO);

Sources/BXConstants.h

 BX_EXPORT NSString* const kBXGotDatabaseURINotification;
 BX_EXPORT NSString* const kBXAttributeKey;
 BX_EXPORT NSString* const kBXUnknownPredicatesKey;
+BX_EXPORT NSString* const kBXRelationshipKey;
 BX_EXPORT NSString* const kBXRelationshipsKey;
 BX_EXPORT NSString* const kBXPredicateKey;
 BX_EXPORT NSString* const kBXOwnerObjectVariableName;
 	kBXErrorUnknown,
 	kBXErrorIncompleteDatabaseURI,
 	kBXErrorPredicateNotAllowedForUpdateDelete,
-	kBXErrorGenericNetworkError
+	kBXErrorGenericNetworkError,
+	kBXErrorObjectAlreadyDeleted
 };
 
 enum BXModificationType

Sources/BXConstants.m

 NSString* const kBXGotDatabaseURINotification = @"kBXGotDatabaseURINotification";
 NSString* const kBXAttributeKey = @"kBXAttributeKey";
 NSString* const kBXUnknownPredicatesKey = @"kBXUnknownPredicatesKey";
+NSString* const kBXRelationshipKey = @"kBXRelationshipKey";
 NSString* const kBXRelationshipsKey = @"kBXRelationshipsKey";
 NSString* const kBXPredicateKey = @"kBXPredicateKey";
 NSString* const kBXOwnerObjectVariableName = @"BXOwnerObject";

Sources/BXContainerProxy.m

 
 - (NSString *) description
 {
-    return [NSString stringWithFormat: @"<%@: %p \n (%@ (%p) => %@)>: \n %@", 
+    return [NSString stringWithFormat: @"<%@: %p \n (%@ (%p) => %@): \n %@>", 
 		NSStringFromClass ([self class]), self, NSStringFromClass ([mOwner class]), mOwner, mKey, mContainer];
 }
 

Sources/BXDataModelCompiler.m

 }
 
 - (void) compileDataModel
-{
-	//FIXME: handle errors in asprintf and mkstemps.
-	
+{	
 	NSString* sourcePath = [mModelURL path];
 	char* pathFormat = NULL;
-	BOOL ok = NO;
 	if ([sourcePath hasSuffix: @".xcdatamodeld"])
 	{
 		asprintf (&pathFormat, "%s/BaseTen.datamodel.%u.XXXXX", 
 				  [NSTemporaryDirectory () UTF8String], getpid ());
-		ok = (NULL != mkdtemp (pathFormat));
+		if (! pathFormat)
+		{
+			BXLogError (@"asprintf returned NULL. errno was %d.", errno);
+			goto bail;
+		}
+		
+		if (! mkdtemp (pathFormat))
+		{
+			BXLogError (@"mkdtemp returned NULL. errno was %d.", errno);
+			goto bail;
+		}
 	}
 	else
 	{
 		asprintf (&pathFormat, "%s/BaseTen.datamodel.%u.XXXXX.mom", 
 				  [NSTemporaryDirectory () UTF8String], getpid ());
-		ok = (-1 != mkstemps (pathFormat, 5));
+		if (! pathFormat)
+		{
+			BXLogError (@"asprintf returned NULL.");
+			goto bail;
+		}
+		
+		if (-1 == mkstemps (pathFormat, 5))
+		{
+			BXLogError (@"mkstemps returned -1. errno was %d.", errno);
+			goto bail;
+		}
 	}
 	
-	if (ok)
-	{
-		NSString* targetPath = [NSString stringWithCString: pathFormat encoding: NSUTF8StringEncoding];
-		NSString* momcPath = [[self class] momcPath];
-		NSArray* arguments = [NSArray arrayWithObjects: sourcePath, targetPath, nil];
-		[self setCompiledModelURL: [NSURL fileURLWithPath: targetPath]];
-		mErrorPipe = [[NSPipe pipe] retain];
-		
-		mMomcTask = [[NSTask alloc] init];
-		[mMomcTask setLaunchPath: momcPath];
-		[mMomcTask setArguments: arguments];
-		[mMomcTask setStandardError: [mErrorPipe fileHandleForWriting]];
-		[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector (momcTaskFinished:) 
-													 name: NSTaskDidTerminateNotification object: mMomcTask];
-		[mMomcTask launch];
-	}
+	NSString* targetPath = [NSString stringWithCString: pathFormat encoding: NSUTF8StringEncoding];
+	NSString* momcPath = [[self class] momcPath];
+	NSArray* arguments = [NSArray arrayWithObjects: sourcePath, targetPath, nil];
+	[self setCompiledModelURL: [NSURL fileURLWithPath: targetPath]];
+	mErrorPipe = [[NSPipe pipe] retain];
 	
+	mMomcTask = [[NSTask alloc] init];
+	[mMomcTask setLaunchPath: momcPath];
+	[mMomcTask setArguments: arguments];
+	[mMomcTask setStandardError: [mErrorPipe fileHandleForWriting]];
+	[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector (momcTaskFinished:) 
+												 name: NSTaskDidTerminateNotification object: mMomcTask];
+	[mMomcTask launch];
+	
+bail:
 	if (pathFormat)
 		free (pathFormat);
 }

Sources/BXDatabaseContext.h

 	
 	/** \brief An NSWindow to which sheets are attached. \see -modalWindow */
 	IBOutlet NSWindow*						modalWindow;
+	/** \brief The context's delegate. \see -delegate */
 	IBOutlet id	<BXDatabaseContextDelegate>	delegate;
 	
 	enum BXConnectionErrorHandlingState		mConnectionErrorHandlingState;

Sources/BXDatabaseContext.m

 	mUsesKeychain = usesKeychain;
 }
 
+/** \brief Whether a known-to-work password will be stored into the default keychain. */
 - (BOOL) storesURICredentials
 {
 	return mShouldStoreURICredentials;
 }
 
+/** \brief Set whether a known-to-work password should be stored into the default keychain. */
 - (void) setStoresURICredentials: (BOOL) shouldStore
 {
 	mShouldStoreURICredentials = shouldStore;
 	mConnectionErrorHandlingState = (shouldAllow ? kBXConnectionErrorNone : kBXConnectionErrorNoReconnect);
 }
 
+/** \brief Whether SSL is currently in use. */
 - (BOOL) isSSLInUse
 {
 	return [mDatabaseInterface isSSLInUse];
 }
 
+/** \brief Whether queries are logged to stdout. */
 - (BOOL) logsQueries
 {
 	return [mDatabaseInterface logsQueries];
 }
 
+/** \brief Set whether queries are logged to stdout. */
 - (void) setLogsQueries: (BOOL) shouldLog
 {
 	[mDatabaseInterface setLogsQueries: shouldLog];
 		if (! [self connectSync: &localError])
 			goto error;
 		
-		//FIXME: check the entity.
-		
 		NSArray* objects = [self executeFetchForEntity: (BXEntityDescription *) [anID entity] 
 										 withPredicate: [anID predicate] returningFaults: NO error: &localError];
 		if (localError) goto error;
  * \param       error           If an error occurs, this pointer is set to an NSError instance.
  *                              May be NULL.
  * \return                      An NSArray that reflects the state of the database at query 
- *                              execution time, or an automatically updating NSArray proxy.
+ *                              execution time, or a BXArrayProxy.
  */
 - (NSArray *) executeFetchForEntity: (BXEntityDescription *) entity withPredicate: (NSPredicate *) predicate 
                     returningFaults: (BOOL) returnFaults updateAutomatically: (BOOL) shouldUpdate error: (NSError **) error
  * \param       error           If an error occurs, this pointer is set to an NSError instance.
  *                              May be NULL.
  * \return                      An NSArray that reflects the state of the database at query 
- *                              execution time, or an automatically updating NSArray proxy.
+ *                              execution time, or a BXArrayProxy.
  */
 - (NSArray *) executeFetchForEntity: (BXEntityDescription *) entity withPredicate: (NSPredicate *) predicate 
                     excludingFields: (NSArray *) excludedFields updateAutomatically: (BOOL) shouldUpdate error: (NSError **) error
 @end
 
 
+//FIXME: move these elsewhere.
 @implementation BXDatabaseContext (Keychain)
 
 static SecKeychainAttribute

Sources/BXDatabaseContextDelegateProtocol.h

  */
 - (enum BXSSLMode) SSLModeForDatabaseContext: (BXDatabaseContext *) ctx;
 
-//FIXME: documentation needed.
-- (void) databaseContext: (BXDatabaseContext *) ctx validatingEntity: (BXEntityDescription *) entity entitiesLeft: (NSUInteger) count;
 - (void) databaseContextGotDatabaseURI: (BXDatabaseContext *) ctx;
 
 /**

Sources/BXDatabaseObject.m

 }
 
 /**
+ * \internal
  * \brief Is the given selector a setter or a getter?
  * \return 2 if setter, 1 if getter, 0 if neither
  */
 }
 
 
+static NSMutableDictionary*
+ErrorUserInfo (NSString* localizedName, NSString* localizedError, BXDatabaseObject* object, BXAttributeDescription* attr, BXRelationshipDescription* rel)
+{
+	NSMutableDictionary* userInfo = [NSMutableDictionary dictionary];
+	if (localizedError)
+	{
+		[userInfo setObject: localizedError forKey: NSLocalizedFailureReasonErrorKey];
+		[userInfo setObject: localizedError forKey: NSLocalizedRecoverySuggestionErrorKey];
+	}
+
+	if (localizedName)
+		[userInfo setObject: localizedName forKey: NSLocalizedDescriptionKey];
+
+	if (object)
+		[userInfo setObject: object forKey: kBXObjectKey];
+	
+	if (attr)
+		[userInfo setObject: attr forKey: kBXAttributeKey];
+	
+	if (rel)
+		[userInfo setObject: rel forKey: kBXRelationshipKey];
+	
+	return userInfo;
+}
+
+
+static NSError*
+DatabaseError (NSInteger errorCode, NSString* localizedTitle, NSString* localizedError, BXDatabaseObject* object, BXAttributeDescription* attr, BXRelationshipDescription* rel)
+{
+	ExpectCR (localizedTitle, nil);
+	ExpectCR (localizedError, nil);
+	NSMutableDictionary* userInfo = ErrorUserInfo (localizedTitle, localizedError, object, attr, rel);
+	NSError* retval = [NSError errorWithDomain: kBXErrorDomain 
+										  code: kBXErrorNullConstraintNotSatisfied
+									  userInfo: userInfo];
+	return retval;
+}
+
+
 @implementation NSString (BXDatabaseObjectAdditions)
 - (NSString *) BXAttributeName
 {
 	BOOL retval = NO;
 	if ([self isDeleted])
 	{
-		//Already deleted.
-		//FIXME: set outError.
+		if (outError)
+		{
+			NSString* title = BXLocalizedString (@"deletionUnsuccessful", @"Couldn't delete the object.", @"Error title");
+			NSString* message = BXLocalizedString (@"objectAlreadyDeleted", @"The object has already been deleted.", @"Error description");
+			NSError* error = DatabaseError (kBXErrorObjectAlreadyDeleted, title, message, self, nil, nil);
+			*outError = error;
+		}
 	}
 	else if ([[self entity] hasCapability: kBXEntityCapabilityRelationships])
 	{
 			if (NSDenyDeleteRule == [inverse deleteRule] && 
 				nil != [self primitiveValueForKey: [currentRel name]])
 			{
-				//Deletion denied.
-				//FIXME: set outError.
+				if (outError)
+				{
+					NSString* title = BXLocalizedString (@"deletionUnsuccessful", @"Couldn't delete the object.", @"Error title");
+					NSString* message = BXLocalizedString (@"objectInUseByRelationship", @"The object is referenced by a relationship that doesn't allow deletion.", @"Error description");
+					NSError* error = DatabaseError (kBXErrorObjectAlreadyDeleted, title, message, self, nil, currentRel);
+					*outError = error;					
+				}
 				retval = NO;
 				break;
 			}
 	}
 }
 
+
 - (BOOL) checkNullConstraintForValue: (id *) ioValue key: (NSString *) key error: (NSError **) outError
 {
-	BOOL rval = YES;
+	BOOL retval = YES;
 	BXEntityDescription* entity = [mObjectID entity];
 	BXAssertValueReturn (NULL != ioValue, NO, @"Expected ioValue not to be NULL.");
 	BXAssertValueReturn ([entity isValidated], NO, @"Expected entity %@ to have been validated earlier.", entity);
 	BXAttributeDescription* attribute = [[entity attributesByName] objectForKey: key];
 	if (NO == [attribute isOptional] && (nil == value || [NSNull null] == value))
 	{
-		rval = NO;
+		retval = NO;
 		if (NULL != outError)
 		{
+			NSString* title = BXLocalizedString (@"databaseError", @"Database Error", @"Title for a sheet");
 			NSString* message = BXLocalizedString (@"nullValueGivenForNonOptionalField", @"This field requires a non-null value.", @"Error description");
-            NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
-                BXSafeObj (attribute), kBXAttributeKey,
-                BXLocalizedString (@"databaseError", @"Database Error", @"Title for a sheet"), NSLocalizedDescriptionKey,
-                BXSafeObj (message), NSLocalizedFailureReasonErrorKey,
-                BXSafeObj (message), NSLocalizedRecoverySuggestionErrorKey,
-                nil];
-			NSError* error = [NSError errorWithDomain: kBXErrorDomain 
-												 code: kBXErrorNullConstraintNotSatisfied
-											 userInfo: userInfo];
+			NSError* error = DatabaseError (kBXErrorNullConstraintNotSatisfied, title, message, self, attribute, nil);
 			*outError = error;
 		}
 	}
-	return rval;
+	return retval;
 }
 
 - (id) valueForUndefinedKey: (NSString *) aKey

Sources/BXDatabaseObjectID.m

 
 - (unsigned int) hash
 {
-    if (0 == mHash)
-    {
-        mHash = [mURIRepresentation BXHash];
-    }
     return mHash;
 }
 
         mURIRepresentation = [anURI retain];
         mEntity = [anEntity retain];
         mRegistered = NO;
-        mHash = 0;
+        mHash = [mURIRepresentation BXHash];
     }
     return self;
 }

Sources/BXEntityDescription.m

 
 
 /**
- * An entity description contains information about a specific table
- * in a given database.
+ * \brief An entity description contains information about a specific table or view in a given database.
+ *
  * Only one entity description instance is created for a combination of a database
  * URI, a schema and a table.
  *
- * \note This class is thread-safe.
+ * \note This class's documented methods are thread-safe. Creating objects, however, is not.
+ * \note For this class to work in non-GC applications, the corresponding database context must be retained as well.
  * \ingroup descriptions
  */
 @implementation BXEntityDescription
  */
 - (NSArray *) objectIDs
 {
-	return [mObjectIDs allObjects];
+	id retval = nil;
+	@synchronized (mObjectIDs)
+	{
+		retval = [mObjectIDs allObjects];
+	}
+	return retval;
 }
 
 static int
  * The entity will be validated after a database connection has been made. Afterwards, 
  * #fields, #primaryKeyFields, #attributesByName and #relationshipsByName return meaningful values.
  *
- * \note To call this safely, mValidationLock should be acquired first. Our validation methods do this, though.
+ * \note To call this safely, \em mValidationLock should be acquired first. Our validation methods do this, though.
  */
+//FIXME: should we have a separate variable for validation status?
 - (BOOL) isValidated
 {
 	return (mFlags & kBXEntityIsValidated) ? YES : NO;
 	return [[mRelationships retain] autorelease];
 }
 
-
 - (BOOL) hasCapability: (enum BXEntityCapability) aCapability
 {
 	return (mCapabilities & aCapability ? YES : NO);
 }
 
+/** 
+ * \brief Whether this entity is marked as a view or not. 
+ */
 - (BOOL) isEnabled
 {
 	return (mFlags & kBXEntityIsEnabled) ? YES : NO;
 {
     @synchronized (mSuperEntities)
     {
-        //FIXME: We only implement cascading notifications from "root tables" to 
-        //inheriting tables and not vice-versa.
+        //FIXME: We only implement cascading notifications from "root tables" to inheriting tables and not vice-versa.
         //FIXME: only single entity supported for now.
         BXAssertVoidReturn (0 == [mSuperEntities count], @"Expected inheritance/dependant relations not to have been set.");
         BXAssertVoidReturn (1 == [entities count], @"Multiple inheritance/dependant relations is not supported.");
 {
     @synchronized (mSubEntities)
     {
-        //FIXME: We only implement cascading notifications from "root tables" to 
-        //inheriting tables and not vice-versa.
+        //FIXME: We only implement cascading notifications from "root tables" to inheriting tables and not vice-versa.
         [mSubEntities addObject: entity];
     }
 }

Sources/BXException.m

 #import "BXConstants.h"
 
 /**
- * A BaseTen-specific exception.
+ * \brief A BaseTen-specific exception.
  * \ingroup baseten
  */
 @implementation BXException

Sources/BXPGCertificateVerificationDelegate.m

 #import "BXPGCertificateVerificationDelegate.h"
 #import "BXDatabaseContextPrivate.h"
 #import "BXSafetyMacros.h"
+#import "BXLogger.h"
 #import <openssl/x509.h>
 
 
 		else
 		{
 			//FIXME: create an error indicating that the certificates have changed.
+			BXLogError (@"Certificates seem to have changed between connection attempts?");
 			retval = NO;
 		}
 	}

Sources/BXPGInterface.m

 					//Optionality
 					//FIXME: all relationships are now treated as optional. NULL constraints should be checked, though.
 					[rel setOptional: YES];
-					
-					//FIXME: delete rule.
-
+		
 					if ('m' == kind)
 					{
 						Oid dstconoid = [[relationships valueForKey: @"dstconoid"] PGTSOidValue];

Sources/BXPGManualCommitTransactionHandler.m

 		{
 			retval = NO;
 			//FIXME: handle the error.
+			BXLogError (@"Transaction status had a strange value: %d", status);
 		}
 	}
 	return retval;
 	else 
 	{
 		//FIXME: set the error.
+		BXLogError (@"Transaction status had a strange value: %d", status);
 	}
 	return retval;
 }

Sources/BXPGTransactionHandler.m

 		default:
 			[self sendPlaceholderResultTo: delegate callback: callback succeeded: NO userInfo: userInfo];
 			//FIXME: set an error.
+			BXLogError (@"Transaction status had a strange value: %d", status);
 			break;
 	}
 }
 			
 		default:
 			//FIXME: set an error.
+			BXLogError (@"Transaction status had a strange value: %d", status);
 			break;
 	}
 	return retval;

Sources/BXPropertyDescription.m

 
 /**
  * \brief A superclass for various description classes.
+ * \note This class's documented methods are thread-safe. Creating objects, however, is not.
+ * \note For this class to work in non-GC applications, the corresponding database context must be retained as well.
  * \ingroup descriptions
  */
 @implementation BXPropertyDescription
 	return (mFlags & kBXPropertyOptional ? YES : NO);
 }
 
+/** \brief The property's subtype. */
 - (enum BXPropertyKind) propertyKind
 {
 	return kBXPropertyNoKind;

Sources/BXRelationshipDescription.m

  * \brief A description for one-to-many relationships and a superclass for others.
  *
  * Relationships between entities are defined with foreign keys in the database.
+ * \note This class's documented methods are thread-safe. Creating objects, however, is not.
  * \note For this class to work in non-GC applications, the corresponding database context must be retained as well.
  * \ingroup descriptions
  */
 
 /**
  * \brief Inverse relationship for this relationship.
- *
- * In BaseTen, inverse relationships always exist.
  */
 - (BXRelationshipDescription *) inverseRelationship
 {
 	mIsInverse = aBool;
 }
 
+/** \brief Whether this relationship is inverse. */
 - (BOOL) isInverse
 {
 	return mIsInverse;
     else
     {
     	//First remove old objects from the relationship, then add new ones.
-    	//FIXME: this could be configurable by the user unless we want to look for
-    	//       non-empty or maximum size constraints, which are likely CHECK clauses.
+    	//FIXME: this could be configurable by the user unless we want to look for non-empty or maximum size constraints, which are likely CHECK clauses.
     	//FIXME: these should be inside a transaction. Use the undo manager?
 		
 		BXDatabaseObject* oldTarget = nil;
 /**
  * \mainpage Introduction
  *
- * BaseTen is a new, open source Cocoa database framework for working with PostgreSQL databases. BaseTen 
+ * BaseTen is an open source Cocoa database framework for working with PostgreSQL databases. BaseTen 
  * has been designed with familiar, Core Data -like semantics and APIs. 
  *
  * The BaseTen feature highlights include:
  * \page general_usage Using BaseTen framework
  *
  * \li \subpage overview
+ * \li \subpage accessing_values
  * \li \subpage getting_started
- * \li \subpage accessing_values
  * \li \subpage tracking_changes
  * \li \subpage using_appkit_classes
+ * \li \subpage database_types
  * \li \subpage postgresql_installation
  * \li \subpage building_baseten
  * \li \subpage limitations
  *     described above.
  *
  * In addition to using BaseTen Assistant, it is possible to enable and disable tables with SQL functions.
- * The functions are <em>baseten.prepareformodificationobserving</em> and <em>baseten.cancelmodificationobserving</em>
- * and they take an oid as an argument.
+ * The functions are <em>baseten.enable</em> and <em>baseten.disable</em> and they take an oid as an argument.
  *
- * Views' primary keys are stored in <em>baseten.viewprimarykey</em>. The table has three columns: \em nspname, 
+ * Views' primary keys are stored in <em>baseten.view_pkey</em>. The table has three columns: \em nspname, 
  * \em relname and \em attname, which correspond to the view's schema name, the view's name and each primary 
  * key column's name respectively. They also make up the table's primary key. In addition to using 
  * BaseTen Assistant, it is possible to determine a view's primary key by inserting rows into the table.
  *
  * Relationships that involve views are stored in automatically-generated tables. These may be refreshed view
- * the SQL function <em>baseten.refreshcaches</em>. BaseTen Assistant does this automatically.
+ * the SQL function <em>baseten.refresh_caches</em>. BaseTen Assistant does this automatically.
  */
 
 /**
  * <tt>-valueForKey:</tt> and <tt>-setValue:forKey:</tt>. The key will be the column name. As with 
  * NSManagedObject, methods like <tt>-&lt;key&gt;</tt> and <tt>-set&lt;Key&gt;:</tt> are also automatically available.
  *
- * Column values are converted to Foundation objects based on the column type. The type conversion is
- * defined in the file <em>datatypeassociations.plist</em>. Currently, there is no way to affect the type conversion,
- * and modifying the file is not recommended. Instead, custom getters may be written for preprocessing
+ * Column values are converted to Foundation objects based on the column type. Currently, there is no way to 
+ * affect the type conversion. Instead, custom getters may be written for preprocessing
  * fetched objects. To support this, the column values may also be accessed using 
  * <tt>-primitiveValueForKey:</tt>. Similarly <tt>-setPrimitiveValue:forKey:</tt> may be used to set a column 
  * value.
  *
+ * Currently handled types are listed in \ref database_types.
+ *
  *
  * \section accessing_relationships Accessing relationships
  *
  */
 
 /**
+ * \page database_types Handled PostgreSQL types
+ *
+ * Composite types, domains, pseudo-types and types not listed here are currently returned as NSData. 
+ * Various array types are returned as NSArrays of the respective type.
+ *
+ * <table>
+ *     <caption>Type conversion</caption>
+ *     <tr>
+ *         <th><strong>PostgreSQL type</strong></th>
+ *         <th><strong>Cocoa type</strong></th>
+ *     </tr>
+ *     <tr>
+ *         <td>bit</td>
+ *         <td>NSData</td>
+ *     </tr>
+ *     <tr>
+ *         <td>bool</td>
+ *         <td>NSNumber</td>
+ *     </tr>
+ *     <tr>
+ *         <td>bpchar</td>
+ *         <td>NSString</td>
+ *     </tr> 
+ *     <tr>
+ *         <td>bytea</td>
+ *         <td>NSData</td>
+ *     </tr>
+ *     <tr>
+ *         <td>char</td>
+ *         <td>NSString</td>
+ *     </tr>
+ *     <tr>
+ *         <td>date</td>
+ *         <td>NSCalendarDate<sup>\ref database_types_ref_1 "1"</sup></td>
+ *     </tr>
+ *     <tr>
+ *         <td>float4</td>
+ *         <td>NSNumber</td>
+ *     </tr>
+ *     <tr>
+ *         <td>float8</td>
+ *         <td>NSNumber</td>
+ *     </tr>
+ *     <tr>
+ *         <td>int2</td>
+ *         <td>NSNumber</td>
+ *     </tr>
+ *     <tr>
+ *         <td>int2vector</td>
+ *         <td>NSArray of NSNumbers</td>
+ *     </tr> 
+ *     <tr>
+ *         <td>int4</td>
+ *         <td>NSNumber</td>
+ *     </tr>
+ *     <tr>
+ *         <td>int8</td>
+ *         <td>NSNumber</td>
+ *     </tr>
+ *     <tr>
+ *         <td>name</td>
+ *         <td>NSString</td>
+ *     </tr>
+ *     <tr>
+ *         <td>numeric</td>
+ *         <td>NSDecimalNumber</td>
+ *     </tr>
+ *     <tr>
+ *         <td>oid</td>
+ *         <td>NSNumber</td>
+ *     </tr>
+ *     <tr>
+ *         <td>point</td>
+ *         <td>NSValue</td>
+ *     </tr>
+ *     <tr>
+ *         <td>text</td>
+ *         <td>NSString</td>
+ *     </tr>
+ *     <tr>
+ *         <td>timestamp</td>
+ *         <td>NSDate</td>
+ *     </tr>
+ *     <tr>
+ *         <td>timestamptz</td>
+ *         <td>NSCalendarDate<sup>\ref database_types_ref_1 "1"</sup></td>
+ *     </tr>
+ *     <tr>
+ *         <td>tinterval</td>
+ *         <td>NSNumber</td>
+ *     </tr>
+ *     <tr>
+ *         <td>varbit</td>
+ *         <td>NSData</td>
+ *     </tr>
+ *     <tr>
+ *         <td>varchar</td>
+ *         <td>NSString</td>
+ *     </tr>
+ *     <tr>
+ *         <td>uuid</td>
+ *         <td>NSString</td>
+ *     </tr>
+ * </table>
+ * \anchor database_types_ref_1 1. Subject to change as NSCalendarDate might become deprecated.
+ */
+
+/**
  * \page postgresql_installation PostgreSQL installation
  *
  * Here's a brief tutorial on PostgreSQL installation.
  * in the \em Documentation folder.
  *
  *
- * \section building_for_the_release_dmg Building for the release DMG
+ * \section building_for_the_release_dmg Building for the release disk image
  *
  * The files needed to build the release disk image are in the SVN repository as well. Doxygen is needed during 
  * the process. To create the DMG, follow these steps:
  *     <li>From the checked-out directory, <tt>cd ReleaseDMG</tt>.</li>
  *     <li>The default location for the built files is <em>~/Build/BaseTen-dmg-build</em>. To set a custom path, edit the \em SYMROOT variable in <em>create_release_dmg.sh</em>.</li>
  *     <li>
- *         Do <tt>./create_release_dmg.sh</tt>. The build DMG will appear in the ReleaseDMG folder.
+ *         Do <tt>./create_release_dmg.sh</tt>. The built DMG will appear in the ReleaseDMG folder.
  *         <ul>
  *             <li>If you don't have LaTeX installed, do <tt>./create_release_dmg.sh -&ndash;without-latex</tt> instead. The PDF manual won't be included on the DMG, though.</li>
  *         </ul>
  * \page limitations Limitations in current version
  * 
  * These are some of the most severe limitations in the current version.
- * \li Practically all public classes are non-thread-safe, so thread safety must be enforced externally if it's required.
+ * \li Most public classes are non-thread-safe, so thread safety must be enforced externally if it's required.
  *     Furthermore, all queries must be performed from the thread in which the context made a database connection. This could change
  *     in the future, so it is best to create and handle a context only in one thread.
  * \li Any serialization mechanism has not been implemented for BXDatabaseObject.

Sources/NSAttributeDescription+BXPGAdditions.m

 				}
 				else
 				{
-					//FIXME: report the error! We don't understand other key paths than length.
+					//FIXME: report the error in some other way. We don't understand other key paths than length.
+					BXLogError (@"Predicate %@ wasn't understood.", [predicate predicateFormat]);
 					retval = nil;
 				}
 			}

Sources/PGTSAbstractDescription.mm

 	{
 		[mName release];
 		mName = [aString copy];
+		mHash = [mName hash];
 	}
 }
 
 
 - (unsigned int) hash
 {
-    if (0 == mHash)
-        mHash = ([mName hash]);
     return mHash;
 }
 @end

Sources/PGTSConnection.mm

 	[self execQuery: "SET standard_conforming_strings TO true"];
 	[self execQuery: "SET datestyle TO 'ISO, YMD'"];
 	PQsetNoticeReceiver (connection, &NoticeReceiver, (void *) self);
-	//FIXME: set other things as well?
 	
 	//Create a runloop source to receive data asynchronously.
 	CFSocketContext context = {0, self, NULL, NULL, NULL};

Sources/PGTSFoundationObjects.m

 	size_t resultLength = 0;
 	unsigned char *unescaped = PQunescapeBytea ((unsigned char*) value, &resultLength);
 	
-	if (NULL == unescaped)
-	{
-		BXLogError (@"PQunescapeBytea failed for characters: %s", value); //FIXME: Handle error?
-		return nil;
-	}
+	//FIXME: Handle the error?
+	BXAssertValueReturn (unescaped, nil, @"PQunescapeBytea failed for characters: %s", value);
 	
     NSData *data = [[self class] dataWithBytes: unescaped length: resultLength];
 	PQfreemem (unescaped);