Tuukka Norri avatar Tuukka Norri committed bb3b4b1

Generalized the support for NSPredicates and NSExpressions
- NSPredicate can now take the object parameter for PostgreSQL expressions.
- Also added some basic configuration to database connections.

Comments (0)

Files changed (8)

Framework/Sources/PGTSAdditions.h

 @interface NSObject (PGTSAdditions)
 + (id) newForPGTSResultSet: (PGTSResultSet *) set withCharacters: (char *) value typeInfo: (PGTSTypeInfo *) typeInfo;
 - (char *) PGTSParameterLength: (int *) length connection: (PGTSConnection *) connection;
+- (NSString *) PGTSEscapedObjectParameter: (PGTSConnection *) connection;
 @end
 
 @interface NSArray (PGTSAdditions)

Framework/Sources/PGTSAdditions.m

 float
 strtof (const char * restrict nptr, char ** restrict endptr);
 
-@interface NSObject (PGTSAdditionsPrivate)
-- (char *) PGTSParameterLength: (int *) length;
-@end
-
 
 @interface NSDictionary (PGTSAdditionsPrivate)
 - (NSArray *) PGTSParameters1: (NSMutableArray *) parameters;
 
 - (char *) PGTSParameterLength: (int *) length connection: (PGTSConnection *) connection
 {
-    return [self PGTSParameterLength: length];
+	if (NULL != length)
+		*length = 0;
+	return NULL;
 }
 
-- (char *) PGTSParameterLength: (int *) length
+- (NSString *) PGTSEscapedObjectParameter: (PGTSConnection *) connection
 {
-    *length = 0;
-    return NULL;
+	NSString* rval = nil;
+	int length = 0;
+	char* charParameter = [self PGTSParameterLength: &length connection: connection];
+	if (NULL != charParameter)
+	{
+		PGconn* pgConn = [connection pgConnection];
+		char* escapedParameter = calloc (1 + 2 * length, sizeof (char));
+		PQescapeStringConn (pgConn, escapedParameter, charParameter, length, NULL);
+		const char* clientEncoding = PQparameterStatus (pgConn, "client_encoding");
+		NSCAssert1 (0 == strcmp ("UNICODE", clientEncoding), @"Expected client_encoding to be UNICODE (was: %s).", clientEncoding);
+		rval = [[[NSString alloc] initWithBytesNoCopy: escapedParameter length: strlen (escapedParameter)
+											 encoding: NSUTF8StringEncoding freeWhenDone: YES] autorelease];
+	}
+	return rval;
 }
 @end
 
     return [NSString stringWithUTF8String: value];
 }
 
-- (char *) PGTSParameterLength: (int *) length
+- (char *) PGTSParameterLength: (int *) length connection: (PGTSConnection *) connection
 {
+	const char* clientEncoding = PQparameterStatus ([connection pgConnection], "client_encoding");
+	NSCAssert1 (0 == strcmp ("UNICODE", clientEncoding), @"Expected client_encoding to be UNICODE (was: %s).", clientEncoding);
     const char* rval = [self UTF8String];
     if (NULL != length)
         *length = strlen (rval);
 	return data;
 }
 
-- (char *) PGTSParameterLength: (int *) length
+- (char *) PGTSParameterLength: (int *) length connection: (PGTSConnection *) connection
 {
     const char* rval = [self bytes];
     if (NULL != length)
 
 
 @implementation NSNumber (PGTSAdditions)
-- (char *) PGTSParameterLength: (int *) length
+- (char *) PGTSParameterLength: (int *) length connection: (PGTSConnection *) connection
 {
-    return [[self description] PGTSParameterLength: length];
+    return [[self description] PGTSParameterLength: length connection: connection];
 }
 
 + (id) newForPGTSResultSet: (PGTSResultSet *) set withCharacters: (char *) value typeInfo: (PGTSTypeInfo *) typeInfo

Framework/Sources/PGTSConnectionPrivate.m

         {
             PQsetnonblocking (connection, 0); //We don't want to call PQsendquery etc. multiple times
             PQsetClientEncoding (connection, "UNICODE"); //Use UTF-8
+			PQexec (connection, "SET standard_conforming_strings TO true");
+			PQexec (connection, "SET datestyle TO 'ISO, YMD'");
             //FIXME: set other things, such as the date format, as well
             PQsetNoticeProcessor (connection, &PGTSNoticeProcessor, (void *) self);
 

TigerFramework/Sources/NSExpression+PGTSAdditions.m

 // $Id$
 //
 
-#import <PGTS/PGTSConstants.h>
+#import <PGTS/PGTS.h>
 
 #import "NSExpression+PGTSAdditions.h"
 #import "PGTSTigerConstants.h"
 
 
-@interface NSObject (PGTSAdditions)
+@interface NSObject (PGTSTigerAdditions)
 - (id) PGTSConstantExpressionValue: (NSMutableDictionary *) context;
 @end
 
 AddParameter (id parameter, NSMutableDictionary* context)
 {
     NSCAssert (nil != context, @"Expected context not to be nil");
-    
-    //First increment the parameter index. We start from zero, since indexing in
-    //NSArray is zero-based. The kPGTSParameterIndexKey will have the total count
-    //of parameters, however.
-    NSNumber* indexObject = [context objectForKey: kPGTSParameterIndexKey];
-    if (nil == indexObject)
-    {
-        indexObject = [NSNumber numberWithInt: 0];
-    }
-    int index = [indexObject intValue];
-    [context setObject: [NSNumber numberWithInt: index + 1] forKey: kPGTSParameterIndexKey];
-    
-    NSMutableArray* parameters = [context objectForKey: kPGTSParametersKey];
-    if (nil == parameters)
-    {
-        parameters = [NSMutableArray array];
-        [context setObject: parameters forKey: kPGTSParametersKey];
-    }
-    [parameters insertObject: parameter atIndex: index];
-    
-    //Return the index used in the query
-    int count = index + 1;
-    NSCAssert4 ([parameters count] == count, @"Expected count to be %d, was %d.\n\tparameter:\t%@ \n\tcontext:\t%@", 
-                [parameters count], count, parameter, context);
-    return [NSString stringWithFormat: @"$%d", count];
+    NSString* rval = nil;
+	if (YES == [[context objectForKey: kPGTSExpressionParametersVerbatimKey] boolValue])
+	{
+		PGTSConnection* connection = [context objectForKey: kPGTSConnectionKey];
+		rval = [NSString stringWithFormat: @"'%@'", [parameter PGTSEscapedObjectParameter: connection]];
+	}
+	else
+	{
+		//First increment the parameter index. We start from zero, since indexing in
+		//NSArray is zero-based. The kPGTSParameterIndexKey will have the total count
+		//of parameters, however.
+		NSNumber* indexObject = [context objectForKey: kPGTSParameterIndexKey];
+		if (nil == indexObject)
+		{
+			indexObject = [NSNumber numberWithInt: 0];
+		}
+		int index = [indexObject intValue];
+		[context setObject: [NSNumber numberWithInt: index + 1] forKey: kPGTSParameterIndexKey];
+		
+		NSMutableArray* parameters = [context objectForKey: kPGTSParametersKey];
+		if (nil == parameters)
+		{
+			parameters = [NSMutableArray array];
+			[context setObject: parameters forKey: kPGTSParametersKey];
+		}
+		[parameters insertObject: parameter atIndex: index];
+		
+		//Return the index used in the query
+		int count = index + 1;
+		NSCAssert4 ([parameters count] == count, @"Expected count to be %d, was %d.\n\tparameter:\t%@ \n\tcontext:\t%@", 
+					[parameters count], count, parameter, context);
+		rval = [NSString stringWithFormat: @"$%d", count];
+	}
+	return rval;
 }
 
 
 - (id) PGTSValueWithObject: (id) anObject context: (NSMutableDictionary *) context
 {
     id rval = nil;
+	NSExpression* keyPathExpression = self;
     switch ([self expressionType])
     {
         case NSConstantValueExpressionType:
             
         case NSEvaluatedObjectExpressionType:
         case NSVariableExpressionType:
-            //default behavior
-            rval = AddParameter ([self expressionValueWithObject: anObject context: context], context);
-            break;
+		{
+            //default behavior unless the expression evaluates into a key path expression
+			id evaluated = [self expressionValueWithObject: anObject context: context];
+			if (! ([evaluated isKindOfClass: [NSExpression class]] && [evaluated expressionType] == NSKeyPathExpressionType))
+			{
+				rval = AddParameter (evaluated, context);
+				break;
+			}
+			else
+			{
+				keyPathExpression = evaluated;
+				//Fall through
+			}
+		}
             
         case NSKeyPathExpressionType:
         {
             //database.table.field
             //Simple dividing into components for now
-            NSArray* components = [[[self keyPath] componentsSeparatedByString: @"."] valueForKey: @"PGTSQuotedString"];
+            NSArray* components = [[[keyPathExpression keyPath] componentsSeparatedByString: @"."] valueForKey: @"PGTSQuotedString"];
             rval = [components componentsJoinedByString: @"."];
             break;
         }   
     return rval;
 }
 
+- (char *) PGTSParameterLength: (int *) length connection: (PGTSConnection *) connection
+{
+	id objectValue = nil;
+	switch ([self expressionType])
+	{
+		case NSConstantValueExpressionType:
+			objectValue = [self constantValue];
+			break;
+			
+		case NSEvaluatedObjectExpressionType:
+			objectValue = [self keyPath];
+			break;
+			
+		case NSVariableExpressionType:
+		case NSKeyPathExpressionType:
+		case NSFunctionExpressionType:
+		default:
+			break;
+	}
+	
+	return [objectValue PGTSParameterLength: length connection: connection];
+}
+
 @end

TigerFramework/Sources/NSPredicate+PGTSAdditions.h

 
 @interface NSPredicate (PGTSAdditions)
 - (NSString *) PGTSWhereClauseWithContext: (NSMutableDictionary *) context;
+- (NSString *) PGTSExpressionWithObject: (id) anObject context: (NSMutableDictionary *) context;
 @end

TigerFramework/Sources/NSPredicate+PGTSAdditions.m

 #import <PGTS/PGTSFunctions.h>
 #import <PGTS/PGTSConstants.h>
 
+//FIXME: since the where clause methods produce an expression which could be used elsewhere as well, the methods should be renamed.
+
 @implementation NSPredicate (PGTSAdditions)
 
 - (NSString *) PGTSWhereClauseWithContext: (NSMutableDictionary *) context
 {
+	return [self PGTSExpressionWithObject: nil context: context];
+}
+
+- (NSString *) PGTSExpressionWithObject: (id) anObject context: (NSMutableDictionary *) context
+{
     NSString* rval = nil;
     Class tpClass = NSClassFromString (@"NSTruePredicate");
     Class fpClass = NSClassFromString (@"NSFalsePredicate");
         rval = @"(true)";
     else if (nil != fpClass && [self isKindOfClass: fpClass])
         rval = @"(false)";
-    //Otherwise return nil since we override this method anyway
+	//Otherwise we return nil since this method gets overridden anyway.
     return rval;
 }
 @end
 @implementation NSCompoundPredicate (PGTSAdditions)
 - (NSString *) PGTSWhereClauseWithContext: (NSMutableDictionary *) context
 {
-    NSAssert (nil != [context objectForKey: kPGTSConnectionKey], @"Did you remember to set connection to kPGTSConnectionKey in context?");
+	return [self PGTSExpressionWithObject: nil context: context];
+}
+
+- (NSString *) PGTSExpressionWithObject: (id) anObject context: (NSMutableDictionary *) context
+{
+    NSAssert1 (nil != [context objectForKey: kPGTSConnectionKey], @"Did you remember to set connection to %@ in context?", kPGTSConnectionKey);
     NSString* rval = nil;
     NSArray* subpredicates = [self subpredicates];
     NSMutableArray* parts = [NSMutableArray arrayWithCapacity: [subpredicates count]];
     TSEnumerate (currentPredicate, e, [subpredicates objectEnumerator])
-        [parts addObject: [currentPredicate PGTSWhereClauseWithContext: context]];
+        [parts addObject: [currentPredicate PGTSExpressionWithObject: anObject context: context]];
     
     NSString* glue = nil;
     NSCompoundPredicateType type = [self compoundPredicateType];
 @implementation NSComparisonPredicate (PGTSAdditions)
 - (NSString *) PGTSWhereClauseWithContext: (NSMutableDictionary *) context
 {
+	return [self PGTSExpressionWithObject: nil context: context];
+}
+
+- (NSString *) PGTSExpressionWithObject: (id) anObject context: (NSMutableDictionary *) context
+{
     NSString* rval = [NSString stringWithFormat: @"(%@) %@ (%@)",
-        [[self leftExpression] PGTSValueWithObject: nil context: context], 
+        [[self leftExpression] PGTSValueWithObject: anObject context: context], 
         [self PGTSOperator], 
-        [[self rightExpression] PGTSValueWithObject: nil context: context]];
+        [[self rightExpression] PGTSValueWithObject: anObject context: context]];
     return rval;
 }
 

TigerFramework/Sources/PGTSTigerConstants.h

 extern NSString* const kPGTSUnsupportedPredicateOperatorTypeException;
 extern NSString* const kPGTSParametersKey;
 extern NSString* const kPGTSParameterIndexKey;
+extern NSString* const kPGTSExpressionParametersVerbatimKey;

TigerFramework/Sources/PGTSTigerConstants.m

 NSString* const kPGTSUnsupportedPredicateOperatorTypeException = @"kPGTSUnsupportedPredicateOperatorTypeException";
 NSString* const kPGTSParametersKey = @"kPGTSParametersKey";
 NSString* const kPGTSParameterIndexKey = @"kPGTSParameterIndexKey";
+NSString* const kPGTSExpressionParametersVerbatimKey = @"kPGTSExpressionParametersVerbatimKey";
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.