Commits

Tuukka Norri committed a1d65e6

Added a test for changes made using arbitrary SQL commands (fixes #211)

Comments (0)

Files changed (9)

BaseTen.xcodeproj/project.pbxproj

 /* End PBXAggregateTarget section */
 
 /* Begin PBXBuildFile section */
-		53015A850FBEF22400E52C1E /* BXArraySize.h in Headers */ = {isa = PBXBuildFile; fileRef = 53015A830FBEF18800E52C1E /* BXArraySize.h */; };
+		53015A850FBEF22400E52C1E /* BXArraySize.h in Headers */ = {isa = PBXBuildFile; fileRef = 53015A830FBEF18800E52C1E /* BXArraySize.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		531A21500DE431D1006C757A /* BXPGConnectionResetRecoveryAttempter.h in Headers */ = {isa = PBXBuildFile; fileRef = 531A214E0DE431D1006C757A /* BXPGConnectionResetRecoveryAttempter.h */; };
 		531A21510DE431D1006C757A /* BXPGConnectionResetRecoveryAttempter.m in Sources */ = {isa = PBXBuildFile; fileRef = 531A214F0DE431D1006C757A /* BXPGConnectionResetRecoveryAttempter.m */; };
 		531A21560DE4324D006C757A /* BXPGAutocommitConnectionResetRecoveryAttempter.h in Headers */ = {isa = PBXBuildFile; fileRef = 531A21540DE4324D006C757A /* BXPGAutocommitConnectionResetRecoveryAttempter.h */; };

UnitTests/Sources/BXArbitrarySQLTests.h

+//
+// BXArbitrarySQLTests.h
+// BaseTen
+//
+// Copyright (C) 2010 Marko Karppinen & Co. LLC.
+//
+// Before using this software, please review the available licensing options
+// by visiting http://basetenframework.org/licensing/ or by contacting
+// us at sales@karppinen.fi. Without an additional license, this software
+// may be distributed only in compliance with the GNU General Public License.
+//
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License, version 2.0,
+// as published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+//
+// $Id$
+//
+
+#import <SenTestingKit/SenTestingKit.h>
+#import "BXTestCase.h"
+
+@class PGTSConnection;
+@class OCMockObject;
+
+
+@interface BXArbitrarySQLTests : BXDatabaseTestCase
+{
+	PGTSConnection *mConnection;
+	BXEntityDescription *mEntity;
+	BXDatabaseObject *mT1, *mT2, *mT3, *mT4;
+	OCMockObject *mMock;
+}
+@end

UnitTests/Sources/BXArbitrarySQLTests.m

+//
+// BXArbitrarySQLTests.m
+// BaseTen
+//
+// Copyright (C) 2010 Marko Karppinen & Co. LLC.
+//
+// Before using this software, please review the available licensing options
+// by visiting http://basetenframework.org/licensing/ or by contacting
+// us at sales@karppinen.fi. Without an additional license, this software
+// may be distributed only in compliance with the GNU General Public License.
+//
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License, version 2.0,
+// as published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+//
+// $Id$
+//
+
+#import "BXArbitrarySQLTests.h"
+#import "MKCSenTestCaseAdditions.h"
+#import <BaseTen/PGTSConnection.h>
+#import <BaseTen/PGTSResultSet.h>
+#import <BaseTen/BXArraySize.h>
+#import <BaseTen/BXEnumerate.h>
+#import <OCMock/OCMock.h>
+
+__strong static NSString *kKVOCtx = @"BXArbitrarySQLTestsKVOObservingContext";
+
+
+@implementation BXArbitrarySQLTests
+- (void) setUp
+{
+	[super setUp];
+	
+	{
+		NSDictionary* connectionDictionary = [self connectionDictionary];
+		mConnection = [[PGTSConnection alloc] init];
+		STAssertTrue ([mConnection connectSync: connectionDictionary], [[mConnection connectionError] description]);	
+		
+		PGTSResultSet *res = nil;
+		MKCAssertNotNil ((res = [mConnection executeQuery: @"UPDATE test SET value = null"]));
+		STAssertTrue ([res querySucceeded], [[res error] description]);
+	}
+	
+	{
+		NSError *error = nil;
+		mEntity = [[mContext entityForTable: @"test" error: &error] retain];
+		STAssertNotNil (mEntity, [error description]);
+		
+		NSArray *res = [mContext executeFetchForEntity: mEntity withPredicate: nil error: &error];
+		STAssertNotNil (res, [error description]);
+		
+		BXEnumerate (currentObject, e, [res objectEnumerator])
+		{
+			NSInteger objectID = [[currentObject primitiveValueForKey: @"id"] integerValue];
+			switch (objectID)
+			{
+				case 1:
+					mT1 = [currentObject retain];
+					break;
+					
+				case 2:
+					mT2 = [currentObject retain];
+					break;
+					
+				case 3:
+					mT3 = [currentObject retain];
+					break;
+					
+				case 4:
+					mT4 = [currentObject retain];
+					
+				default:
+					break;
+			}
+		}
+	}
+	
+	MKCAssertNotNil (mT1);
+	MKCAssertNotNil (mT2);
+	MKCAssertNotNil (mT3);
+	MKCAssertNotNil (mT4);
+	
+	NSObject *dummy = [[[NSObject alloc] init] autorelease];
+	mMock = [[OCMockObject partialMockForObject: dummy] retain];
+	BXDatabaseObject *objects [] = {mT1, mT2, mT3, mT4};
+	
+	for (unsigned int i = 0, count = BXArraySize (objects); i < count; i++)
+	{
+		[objects [i] addObserver: (id) mMock
+					  forKeyPath: @"value" 
+						 options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew 
+						 context: kKVOCtx];
+		
+		if (3 != i)
+		{
+			// The change parameter should be a HC matcher, but such would only be needed here.
+			[[mMock expect] observeValueForKeyPath: @"value" ofObject: objects [i] change: OCMOCK_ANY context: kKVOCtx];
+		}
+	}
+	
+	// mT4 is not expected to change.
+	NSException *exc = [NSException exceptionWithName: NSInternalInconsistencyException
+											   reason: [NSString stringWithFormat: @"Object %@ changed.", mT4]
+											 userInfo: nil];
+	[[[mMock stub] andThrow: exc] observeValueForKeyPath: OCMOCK_ANY ofObject: mT4 change: OCMOCK_ANY context: kKVOCtx];	
+}
+
+
+- (void) tearDown
+{
+	[[mMock stub] observeValueForKeyPath: OCMOCK_ANY ofObject: OCMOCK_ANY change: OCMOCK_ANY context: kKVOCtx];
+	[mMock release];
+	
+	PGTSResultSet *res = nil;
+	MKCAssertNotNil ((res = [mConnection executeQuery: @"UPDATE test SET value = null"]));
+	STAssertTrue ([res querySucceeded], [[res error] description]);
+	
+	[mConnection release];
+	[mEntity release];
+	[mT1 release];
+	[mT2 release];
+	[mT3 release];
+	[mT4 release];
+	
+	[super tearDown];
+}
+
+
+- (void) test1UpdateUsingSQLUPDATE
+{	
+	PGTSResultSet *res = nil;
+	MKCAssertNotNil ((res = [mConnection executeQuery: @"UPDATE test SET value = $1 WHERE id != 4" parameters: @"test"]));
+	STAssertTrue ([res querySucceeded], [[res error] description]);
+	
+	[[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1.0]];
+	[mMock verify];
+}
+
+
+- (void) test2UpdateUsingSQLFunction
+{
+	NSString *fdecl = 
+	@"CREATE FUNCTION test_update_change () RETURNS VOID AS $$ "
+	@" UPDATE test SET value = 'test' WHERE id != 4; "
+	@"$$ VOLATILE LANGUAGE SQL";
+
+	NSString *queries [] = {@"BEGIN", fdecl, @"SELECT test_update_change ()", @"DROP FUNCTION test_update_change ()", @"COMMIT"};
+	for (unsigned int i = 0, count = BXArraySize (queries); i < count; i++)
+	{
+		PGTSResultSet *res = nil;
+		MKCAssertNotNil ((res = [mConnection executeQuery: queries [i]]));
+		STAssertTrue ([res querySucceeded], @"Error when executing '%@': %@", queries [i], [[res error] description]);
+	}
+	
+	[[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1.0]];
+	[mMock verify];
+}
+@end

UnitTests/Sources/BXModificationTests.h

+//
+// ModificationTests.h
+// BaseTen
+//
+// Copyright (C) 2006-2008 Marko Karppinen & Co. LLC.
+//
+// Before using this software, please review the available licensing options
+// by visiting http://basetenframework.org/licensing/ or by contacting
+// us at sales@karppinen.fi. Without an additional license, this software
+// may be distributed only in compliance with the GNU General Public License.
+//
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License, version 2.0,
+// as published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+//
+// $Id$
+//
+
+#import <SenTestingKit/SenTestingKit.h>
+#import "BXTestCase.h"
+
+
+@interface BXModificationTests : BXDatabaseTestCase 
+{
+}
+@end

UnitTests/Sources/BXModificationTests.m

+//
+// BXModificationTests.m
+// BaseTen
+//
+// Copyright (C) 2006-2010 Marko Karppinen & Co. LLC.
+//
+// Before using this software, please review the available licensing options
+// by visiting http://basetenframework.org/licensing/ or by contacting
+// us at sales@karppinen.fi. Without an additional license, this software
+// may be distributed only in compliance with the GNU General Public License.
+//
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License, version 2.0,
+// as published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+//
+// $Id$
+//
+
+#import <BaseTen/BaseTen.h>
+#import <BaseTen/BXDatabaseContextPrivate.h>
+#import <Foundation/Foundation.h>
+
+#import "BXModificationTests.h"
+#import "MKCSenTestCaseAdditions.h";
+
+
+@implementation BXModificationTests
+- (void) test1PkeyModification
+{    
+    BXEntityDescription* pkeytest = [mContext entityForTable: @"Pkeytest" error: nil];
+    NSError* error = nil;
+    MKCAssertNotNil (mContext);
+    MKCAssertNotNil (pkeytest);
+    
+    NSArray* res = [mContext executeFetchForEntity: pkeytest
+                                    withPredicate: [NSPredicate predicateWithFormat: @"Id = 1"]
+                                            error: &error];
+    STAssertNil (error, [error description]);
+    MKCAssertNotNil (res);
+    
+    MKCAssertTrue (1 == [res count]);
+    BXDatabaseObject* object = [res objectAtIndex: 0];
+    MKCAssertEquals ([[object valueForKey: @"Id"] intValue], 1);
+    MKCAssertEqualObjects ([object valueForKey: @"value"], @"a");
+    
+    [object setPrimitiveValue: [NSNumber numberWithInt: 4] forKey: @"Id"];
+    MKCAssertEquals ([[object valueForKey: @"Id"] intValue], 4);
+    [object setPrimitiveValue: @"d" forKey: @"value"];
+    
+    res = [[mContext executeFetchForEntity: pkeytest withPredicate: nil error: &error]
+        sortedArrayUsingDescriptors: [NSArray arrayWithObject: 
+            [[[NSSortDescriptor alloc] initWithKey: @"Id" ascending: YES] autorelease]]];
+    STAssertNil (error, [error description]);
+    MKCAssertNotNil (res);
+
+    MKCAssertTrue (3 == [res count]);
+    for (int i = 0; i < 3; i++)
+    {
+        int number = i + 2;
+        object = [res objectAtIndex: i];
+        MKCAssertEquals ([[object valueForKey: @"Id"] intValue], number);
+        NSString* value = [NSString stringWithFormat: @"%c", 'a' + number - 1];
+        MKCAssertEqualObjects ([object valueForKey: @"value"], value);
+    }
+    
+    [mContext rollback];
+}
+
+
+- (void) test2MassUpdateAndDelete
+{
+    BXEntityDescription* updatetest = [mContext entityForTable: @"updatetest" error: nil];
+    NSError* error = nil;
+    
+    NSArray* res = [mContext executeFetchForEntity: updatetest withPredicate: nil
+                                  returningFaults: NO error: &error];
+    NSArray* originalResult = res;
+    STAssertNil (error, [error description]);
+    MKCAssertNotNil (res);
+    MKCAssertTrue (5 == [res count]);
+    MKCAssertTrue (5 == [[NSSet setWithArray: [res valueForKey: @"value1"]] count]);
+
+    NSNumber* number = [NSNumber numberWithInt: 1];
+    //Doesn't really matter, which object we'll get
+    BXDatabaseObject* object = [res objectAtIndex: 3];
+    MKCAssertFalse ([number isEqual: [object valueForKey: @"value1"]]);
+    NSPredicate* predicate = [NSPredicate predicateWithFormat: @"id = %@", [object valueForKey: @"id"]];
+
+    //First update just one object
+	id value1Attr = [[updatetest attributesByName] objectForKey: @"value1"];
+    [mContext executeUpdateObject: nil
+						  entity: updatetest 
+                       predicate: predicate
+                  withDictionary: [NSDictionary dictionaryWithObject: number forKey: value1Attr]
+                           error: &error];
+    STAssertNil (error, [error description]);
+    MKCAssertEqualObjects (number, [object valueForKey: @"value1"]);
+    MKCAssertTrue (5 == [[NSSet setWithArray: [res valueForKey: @"value1"]] count]);
+    
+    //Then update multiple objects
+    number = [NSNumber numberWithInt: 2];
+    [mContext executeUpdateObject: nil
+						  entity: updatetest 
+                       predicate: nil
+                  withDictionary: [NSDictionary dictionaryWithObject: number forKey: value1Attr]
+                           error: &error];
+    STAssertNil (error, [error description]);
+    NSArray* values = [res valueForKey: @"value1"];
+    MKCAssertTrue (1 == [[NSSet setWithArray: values] count]);
+    MKCAssertEqualObjects (number, [values objectAtIndex: 0]);
+    
+    //Then update an object's primary key
+    number = [NSNumber numberWithInt: -1];
+	id idattr = [[updatetest attributesByName] objectForKey: @"id"];
+    MKCAssertTrue (5 == [[NSSet setWithArray: [res valueForKey: @"id"]] count]);
+    [mContext executeUpdateObject: object
+						  entity: updatetest
+                       predicate: predicate
+                  withDictionary: [NSDictionary dictionaryWithObject: number forKey: idattr]
+                           error: &error];
+    STAssertNil (error, [error description]);
+    MKCAssertTrue (5 == [[NSSet setWithArray: [res valueForKey: @"id"]] count]);
+    MKCAssertEqualObjects ([object valueForKey: @"id"], number);
+    
+    //Then delete an object
+    predicate = [NSPredicate predicateWithFormat: @"id = -1"];
+    [mContext executeDeleteFromEntity: updatetest withPredicate: predicate error: &error];
+    STAssertNil (error, [error description]);
+    res = [mContext executeFetchForEntity: updatetest withPredicate: nil
+                         returningFaults: NO error: &error];
+    MKCAssertTrue (4 == [res count]);
+    res = [mContext executeFetchForEntity: updatetest withPredicate: predicate
+                         returningFaults: NO error: &error];
+    MKCAssertTrue (0 == [res count]);
+    
+    //Finally delete all objects
+    [mContext executeDeleteFromEntity: updatetest withPredicate: nil error: &error];
+    STAssertNil (error, [error description]);
+    res = [mContext executeFetchForEntity: updatetest withPredicate: nil
+                         returningFaults: NO error: &error];
+    MKCAssertTrue (0 == [res count]);
+    originalResult = nil;
+    
+    [mContext rollback];
+}
+
+
+- (void) test3CreateAndDeleteWithArray
+{	
+	//Fetch a self-updating collection and expect its contents to change.
+    NSError* error = nil;
+    NSArray* array = nil;
+    BXEntityDescription* entity = [[mContext entityForTable: @"test" error: &error] retain];
+	STAssertNotNil (entity, [error description]);
+	array = [mContext executeFetchForEntity: entity withPredicate: nil returningFaults: NO 
+					   updateAutomatically: YES error: &error];
+    STAssertNotNil (array, [error description]);
+    NSUInteger count = [array count];
+    
+    //Create an object into the array using another connection.
+    BXDatabaseContext* context2 = [[BXDatabaseContext alloc] initWithDatabaseURI: [self databaseURI]];
+	[context2 setDelegate: self];
+    [context2 setAutocommits: NO];
+    MKCAssertNotNil (context2);
+    
+    BXDatabaseObject* object = [context2 createObjectForEntity: entity withFieldValues: nil error: &error];
+    STAssertNotNil (object, [error description]);
+    
+    //Commit the modification so we can see some results.
+    STAssertTrue ([context2 save: &error], [error description]);
+	[[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1.0]];
+    MKCAssertEquals ([array count], count + 1);
+    
+    STAssertTrue ([context2 executeDeleteObject: object error: &error], [error description]);
+    
+    //Again, commit.
+    STAssertTrue ([context2 save: &error], [error description]);    
+	[[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1.0]];
+    MKCAssertEquals (count, [array count]);
+	
+	[context2 disconnect];
+	[context2 release];
+}
+@end

UnitTests/Sources/ModificationTests.h

-//
-// ModificationTests.h
-// BaseTen
-//
-// Copyright (C) 2006-2008 Marko Karppinen & Co. LLC.
-//
-// Before using this software, please review the available licensing options
-// by visiting http://basetenframework.org/licensing/ or by contacting
-// us at sales@karppinen.fi. Without an additional license, this software
-// may be distributed only in compliance with the GNU General Public License.
-//
-//
-// This program is free software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License, version 2.0,
-// as published by the Free Software Foundation.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
-//
-// $Id$
-//
-
-#import <SenTestingKit/SenTestingKit.h>
-#import "BXTestCase.h"
-
-
-@interface ModificationTests : BXDatabaseTestCase 
-{
-}
-@end

UnitTests/Sources/ModificationTests.m

-//
-// ModificationTests.m
-// BaseTen
-//
-// Copyright (C) 2006-2008 Marko Karppinen & Co. LLC.
-//
-// Before using this software, please review the available licensing options
-// by visiting http://basetenframework.org/licensing/ or by contacting
-// us at sales@karppinen.fi. Without an additional license, this software
-// may be distributed only in compliance with the GNU General Public License.
-//
-//
-// This program is free software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License, version 2.0,
-// as published by the Free Software Foundation.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
-//
-// $Id$
-//
-
-#import <BaseTen/BaseTen.h>
-#import <BaseTen/BXDatabaseContextPrivate.h>
-#import <Foundation/Foundation.h>
-
-#import "ModificationTests.h"
-#import "MKCSenTestCaseAdditions.h";
-
-
-@implementation ModificationTests
-- (void) testPkeyModification
-{    
-    BXEntityDescription* pkeytest = [mContext entityForTable: @"Pkeytest" error: nil];
-    NSError* error = nil;
-    MKCAssertNotNil (mContext);
-    MKCAssertNotNil (pkeytest);
-    
-    NSArray* res = [mContext executeFetchForEntity: pkeytest
-                                    withPredicate: [NSPredicate predicateWithFormat: @"Id = 1"]
-                                            error: &error];
-    STAssertNil (error, [error description]);
-    MKCAssertNotNil (res);
-    
-    MKCAssertTrue (1 == [res count]);
-    BXDatabaseObject* object = [res objectAtIndex: 0];
-    MKCAssertEquals ([[object valueForKey: @"Id"] intValue], 1);
-    MKCAssertEqualObjects ([object valueForKey: @"value"], @"a");
-    
-    [object setPrimitiveValue: [NSNumber numberWithInt: 4] forKey: @"Id"];
-    MKCAssertEquals ([[object valueForKey: @"Id"] intValue], 4);
-    [object setPrimitiveValue: @"d" forKey: @"value"];
-    
-    res = [[mContext executeFetchForEntity: pkeytest withPredicate: nil error: &error]
-        sortedArrayUsingDescriptors: [NSArray arrayWithObject: 
-            [[[NSSortDescriptor alloc] initWithKey: @"Id" ascending: YES] autorelease]]];
-    STAssertNil (error, [error description]);
-    MKCAssertNotNil (res);
-
-    MKCAssertTrue (3 == [res count]);
-    for (int i = 0; i < 3; i++)
-    {
-        int number = i + 2;
-        object = [res objectAtIndex: i];
-        MKCAssertEquals ([[object valueForKey: @"Id"] intValue], number);
-        NSString* value = [NSString stringWithFormat: @"%c", 'a' + number - 1];
-        MKCAssertEqualObjects ([object valueForKey: @"value"], value);
-    }
-    
-    [mContext rollback];
-}
-
-- (void) testMassUpdateAndDelete
-{
-    BXEntityDescription* updatetest = [mContext entityForTable: @"updatetest" error: nil];
-    NSError* error = nil;
-    
-    NSArray* res = [mContext executeFetchForEntity: updatetest withPredicate: nil
-                                  returningFaults: NO error: &error];
-    NSArray* originalResult = res;
-    STAssertNil (error, [error description]);
-    MKCAssertNotNil (res);
-    MKCAssertTrue (5 == [res count]);
-    MKCAssertTrue (5 == [[NSSet setWithArray: [res valueForKey: @"value1"]] count]);
-
-    NSNumber* number = [NSNumber numberWithInt: 1];
-    //Doesn't really matter, which object we'll get
-    BXDatabaseObject* object = [res objectAtIndex: 3];
-    MKCAssertFalse ([number isEqual: [object valueForKey: @"value1"]]);
-    NSPredicate* predicate = [NSPredicate predicateWithFormat: @"id = %@", [object valueForKey: @"id"]];
-
-    //First update just one object
-	id value1Attr = [[updatetest attributesByName] objectForKey: @"value1"];
-    [mContext executeUpdateObject: nil
-						  entity: updatetest 
-                       predicate: predicate
-                  withDictionary: [NSDictionary dictionaryWithObject: number forKey: value1Attr]
-                           error: &error];
-    STAssertNil (error, [error description]);
-    MKCAssertEqualObjects (number, [object valueForKey: @"value1"]);
-    MKCAssertTrue (5 == [[NSSet setWithArray: [res valueForKey: @"value1"]] count]);
-    
-    //Then update multiple objects
-    number = [NSNumber numberWithInt: 2];
-    [mContext executeUpdateObject: nil
-						  entity: updatetest 
-                       predicate: nil
-                  withDictionary: [NSDictionary dictionaryWithObject: number forKey: value1Attr]
-                           error: &error];
-    STAssertNil (error, [error description]);
-    NSArray* values = [res valueForKey: @"value1"];
-    MKCAssertTrue (1 == [[NSSet setWithArray: values] count]);
-    MKCAssertEqualObjects (number, [values objectAtIndex: 0]);
-    
-    //Then update an object's primary key
-    number = [NSNumber numberWithInt: -1];
-	id idattr = [[updatetest attributesByName] objectForKey: @"id"];
-    MKCAssertTrue (5 == [[NSSet setWithArray: [res valueForKey: @"id"]] count]);
-    [mContext executeUpdateObject: object
-						  entity: updatetest
-                       predicate: predicate
-                  withDictionary: [NSDictionary dictionaryWithObject: number forKey: idattr]
-                           error: &error];
-    STAssertNil (error, [error description]);
-    MKCAssertTrue (5 == [[NSSet setWithArray: [res valueForKey: @"id"]] count]);
-    MKCAssertEqualObjects ([object valueForKey: @"id"], number);
-    
-    //Then delete an object
-    predicate = [NSPredicate predicateWithFormat: @"id = -1"];
-    [mContext executeDeleteFromEntity: updatetest withPredicate: predicate error: &error];
-    STAssertNil (error, [error description]);
-    res = [mContext executeFetchForEntity: updatetest withPredicate: nil
-                         returningFaults: NO error: &error];
-    MKCAssertTrue (4 == [res count]);
-    res = [mContext executeFetchForEntity: updatetest withPredicate: predicate
-                         returningFaults: NO error: &error];
-    MKCAssertTrue (0 == [res count]);
-    
-    //Finally delete all objects
-    [mContext executeDeleteFromEntity: updatetest withPredicate: nil error: &error];
-    STAssertNil (error, [error description]);
-    res = [mContext executeFetchForEntity: updatetest withPredicate: nil
-                         returningFaults: NO error: &error];
-    MKCAssertTrue (0 == [res count]);
-    originalResult = nil;
-    
-    [mContext rollback];
-}
-
-- (void) testCreateAndDeleteWithArray
-{	
-	//Fetch a self-updating collection and expect its contents to change.
-    NSError* error = nil;
-    NSArray* array = nil;
-    BXEntityDescription* entity = [[mContext entityForTable: @"test" error: nil] retain];
-    array = [mContext executeFetchForEntity: entity withPredicate: nil returningFaults: NO 
-					   updateAutomatically: YES error: &error];
-    STAssertNil (error, [error description]);
-    MKCAssertNotNil (array);
-    unsigned int count = [array count];
-    
-    //Create an object into the array using another connection.
-    BXDatabaseContext* context2 = [[BXDatabaseContext alloc] initWithDatabaseURI: [self databaseURI]];
-	[context2 setDelegate: self];
-    [context2 setAutocommits: NO];
-    MKCAssertNotNil (context2);
-    
-    BXDatabaseObject* object = [context2 createObjectForEntity: entity withFieldValues: nil error: &error];
-    STAssertNil (error, [error description]);
-    MKCAssertNotNil (object);
-    
-    //Commit the modification so we can see some results.
-    [context2 save: &error];
-    STAssertNil (error, [error description]);
-	[[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1.0]];
-    MKCAssertEquals ([array count], count + 1);
-    
-    [context2 executeDeleteObject: object error: &error];
-    STAssertNil (error, [error description]);
-    
-    //Again, commit.
-    [context2 save: &error];
-    STAssertNil (error, [error description]);    
-	[[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1.0]];
-    MKCAssertEquals (count, [array count]);
-	
-	[context2 disconnect];
-	[context2 release];
-}
-
-@end

UnitTests/Sources/TestLoader.m

 #import "ObjectIDTests.h"
 #import "CreateTests.h"
 #import "FetchTests.h"
-#import "ModificationTests.h"
+#import "BXModificationTests.h"
+#import "BXArbitrarySQLTests.h"
 #import "ForeignKeyTests.h"
 #import "ForeignKeyModificationTests.h"
 #import "MTOCollectionTest.h"
 							[ObjectIDTests class],
 							[CreateTests class],
 							[FetchTests class],
-							[ModificationTests class],
+							[BXModificationTests class],
+							[BXArbitrarySQLTests class],
 							[ForeignKeyTests class],
 							[ForeignKeyModificationTests class],
 							[MTOCollectionTest class],
 							[ToOneChangeNotificationTests class],
 							nil];
 	
-	testClasses = [NSArray arrayWithObject: [BXPredicateTests class]];
+	testClasses = [NSArray arrayWithObject: [BXArbitrarySQLTests class]];
 	
 	for (Class testCaseClass in testClasses)
 	{

UnitTests/UnitTests.xcodeproj/project.pbxproj

 		5317CEB40D1ACB0B00A48792 /* ObjectIDTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 530987C70B57B1C30010765D /* ObjectIDTests.m */; };
 		5317CEB50D1ACB0C00A48792 /* MTOCollectionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 53DAE2100AC4A0D200536DD0 /* MTOCollectionTest.m */; };
 		5317CEB60D1ACB0D00A48792 /* MTMCollectionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5321BEAC0AD4DCDD0011D0C8 /* MTMCollectionTest.m */; };
-		5317CEB70D1ACB0E00A48792 /* ModificationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5373A65D0A6D3F9A0036424B /* ModificationTests.m */; };
+		5317CEB70D1ACB0E00A48792 /* BXModificationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5373A65D0A6D3F9A0036424B /* BXModificationTests.m */; };
 		5317CEB80D1ACB0E00A48792 /* ForeignKeyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 53EAD1B50A668733008F3CE5 /* ForeignKeyTests.m */; };
 		5317CEBA0D1ACB1000A48792 /* EntityTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 531E118C0A6A3A6C003A0F23 /* EntityTests.m */; };
 		5317CEBC0D1ACB1100A48792 /* ConnectTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 530A439E0A5C114E00A4722B /* ConnectTest.m */; };
 		5375C2520F3BAD88002CBF78 /* BXDatabaseContextTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5375C2510F3BAD88002CBF78 /* BXDatabaseContextTests.m */; };
 		537E32C50FAF078600539A83 /* BXSQLTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 537E32C40FAF078600539A83 /* BXSQLTests.m */; };
 		537E35CA0FAF280D00539A83 /* BXDatabaseObjectTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 535717AD0CDE69C60084DD92 /* BXDatabaseObjectTests.m */; };
+		538102BE119823D8006A6017 /* BXArbitrarySQLTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 538102BD119823D8006A6017 /* BXArbitrarySQLTests.m */; };
 		538263C80CD0EC8100CF7E2D /* ForeignKeyModificationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 539C51290C4E4EE000080667 /* ForeignKeyModificationTests.m */; };
 		5387ACFC0FA8F56A00866E19 /* PGTSNotificationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5387ACFB0FA8F56A00866E19 /* PGTSNotificationTests.m */; };
 		5392E6630FC1B0C70056DF6A /* BXSSLConnectionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5392E6620FC1B0C70056DF6A /* BXSSLConnectionTests.m */; };
 		536205A30FB84DE800ACAE57 /* PGTSParameterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PGTSParameterTests.m; path = Sources/PGTSParameterTests.m; sourceTree = "<group>"; };
 		536C46380F2E1CED003A8231 /* PGTSMetadataTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PGTSMetadataTests.h; path = Sources/PGTSMetadataTests.h; sourceTree = "<group>"; };
 		536C46390F2E1CED003A8231 /* PGTSMetadataTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PGTSMetadataTests.m; path = Sources/PGTSMetadataTests.m; sourceTree = "<group>"; };
-		5373A65C0A6D3F9A0036424B /* ModificationTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ModificationTests.h; path = Sources/ModificationTests.h; sourceTree = "<group>"; };
-		5373A65D0A6D3F9A0036424B /* ModificationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ModificationTests.m; path = Sources/ModificationTests.m; sourceTree = "<group>"; };
+		5373A65C0A6D3F9A0036424B /* BXModificationTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BXModificationTests.h; path = Sources/BXModificationTests.h; sourceTree = "<group>"; };
+		5373A65D0A6D3F9A0036424B /* BXModificationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BXModificationTests.m; path = Sources/BXModificationTests.m; sourceTree = "<group>"; };
 		5375C2500F3BAD88002CBF78 /* BXDatabaseContextTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BXDatabaseContextTests.h; path = Sources/BXDatabaseContextTests.h; sourceTree = "<group>"; };
 		5375C2510F3BAD88002CBF78 /* BXDatabaseContextTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BXDatabaseContextTests.m; path = Sources/BXDatabaseContextTests.m; sourceTree = "<group>"; };
 		537E32C30FAF078600539A83 /* BXSQLTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BXSQLTests.h; path = Sources/BXSQLTests.h; sourceTree = "<group>"; };
 		537E32C40FAF078600539A83 /* BXSQLTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BXSQLTests.m; path = Sources/BXSQLTests.m; sourceTree = "<group>"; };
+		538102BC119823D8006A6017 /* BXArbitrarySQLTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BXArbitrarySQLTests.h; path = Sources/BXArbitrarySQLTests.h; sourceTree = "<group>"; };
+		538102BD119823D8006A6017 /* BXArbitrarySQLTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BXArbitrarySQLTests.m; path = Sources/BXArbitrarySQLTests.m; sourceTree = "<group>"; };
 		5387ACFA0FA8F56A00866E19 /* PGTSNotificationTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PGTSNotificationTests.h; path = Sources/PGTSNotificationTests.h; sourceTree = "<group>"; };
 		5387ACFB0FA8F56A00866E19 /* PGTSNotificationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PGTSNotificationTests.m; path = Sources/PGTSNotificationTests.m; sourceTree = "<group>"; };
 		5392E6610FC1B0C70056DF6A /* BXSSLConnectionTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BXSSLConnectionTests.h; path = Sources/BXSSLConnectionTests.h; sourceTree = "<group>"; };
 				53BD96760A5C1DC3001C950C /* CreateTests.m */,
 				53BD965A0A5C1BA5001C950C /* FetchTests.h */,
 				53BD965B0A5C1BA5001C950C /* FetchTests.m */,
-				5373A65C0A6D3F9A0036424B /* ModificationTests.h */,
-				5373A65D0A6D3F9A0036424B /* ModificationTests.m */,
+				5373A65C0A6D3F9A0036424B /* BXModificationTests.h */,
+				5373A65D0A6D3F9A0036424B /* BXModificationTests.m */,
+				538102BC119823D8006A6017 /* BXArbitrarySQLTests.h */,
+				538102BD119823D8006A6017 /* BXArbitrarySQLTests.m */,
 				53EAD1B40A668733008F3CE5 /* ForeignKeyTests.h */,
 				53EAD1B50A668733008F3CE5 /* ForeignKeyTests.m */,
 				539C51280C4E4EE000080667 /* ForeignKeyModificationTests.h */,
 				5317CEB40D1ACB0B00A48792 /* ObjectIDTests.m in Sources */,
 				5317CEB50D1ACB0C00A48792 /* MTOCollectionTest.m in Sources */,
 				5317CEB60D1ACB0D00A48792 /* MTMCollectionTest.m in Sources */,
-				5317CEB70D1ACB0E00A48792 /* ModificationTests.m in Sources */,
+				5317CEB70D1ACB0E00A48792 /* BXModificationTests.m in Sources */,
 				5317CEB80D1ACB0E00A48792 /* ForeignKeyTests.m in Sources */,
 				5317CEBA0D1ACB1000A48792 /* EntityTests.m in Sources */,
 				5317CEBC0D1ACB1100A48792 /* ConnectTest.m in Sources */,
 				53C4C61C119439BF003FB842 /* NSArray+BaseTenAdditionsTests.m in Sources */,
 				53C4C62111943C8E003FB842 /* NSPredicate+BaseTenAdditionsTests.m in Sources */,
 				53C4C67B119443E1003FB842 /* BXMetadataTests.m in Sources */,
+				538102BE119823D8006A6017 /* BXArbitrarySQLTests.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};