Commits

Tuukka Norri committed a4be5a2

Update modifications
- Column numbers are now fetched as part of update notifications. However, they are not made use of.
- Simplified the quoting function in PL.
- Enabled comparison on update.

Comments (0)

Files changed (5)

Resources/BaseTenModifications.sql.m4

 
 changequote(`{{', `}}')
 -- ' -- Fix for syntax coloring in SQL mode.
-define({{_bx_version_}}, {{0.936}})dnl
-define({{_bx_compat_version_}}, {{0.22}})dnl
+define({{_bx_version_}}, {{0.937}})dnl
+define({{_bx_compat_version_}}, {{0.23}})dnl
 
 
 \unset ON_ERROR_ROLLBACK
 GRANT EXECUTE ON FUNCTION "baseten".quote_ident (TEXT, TEXT) TO basetenread;
 
 
+CREATE FUNCTION "baseten".quote_ident_bx (TEXT) RETURNS TEXT AS $$
+	SELECT "baseten".quote_ident ('baseten', $1);
+$$ IMMUTABLE STRICT LANGUAGE SQL;
+REVOKE ALL PRIVILEGES ON FUNCTION "baseten".quote_ident_bx (TEXT) FROM PUBLIC;
+GRANT EXECUTE ON FUNCTION "baseten".quote_ident_bx (TEXT) TO basetenread;
+
+
 -- Debugging helpers
 CREATE FUNCTION "baseten".oidof (TEXT, TEXT) RETURNS "baseten".reltype AS $$
 	SELECT c.oid, n.nspname, c.relname
 	fdecl :=
 		'CREATE OR REPLACE FUNCTION "baseten".' || quote_ident (fname) || ' () RETURNS TRIGGER AS $$ ' ||
 		'BEGIN ' ||
-			'INSERT INTO ' || "baseten".quote_ident ('baseten', mtable) || ' (' ||
+			'INSERT INTO ' || "baseten".quote_ident_bx (mtable) || ' (' ||
 				'"baseten_modification_type", "baseten_modification_cols", ' || array_to_string (pkey, ', ') ||
 			') VALUES (' || 
 				'''I'', null, ' || array_to_string ("baseten".array_prepend_each ('NEW.', pkey), ', ') || 
 	query := 
 		'CREATE TRIGGER ' || quote_ident ("baseten"._mod_rule ('INSERT')) || ' ' ||
 			'AFTER INSERT ON ' || "baseten".quote_ident (rel) || ' ' || 
-			'FOR EACH ROW EXECUTE PROCEDURE ' || "baseten".quote_ident ('baseten', fname) || ' ()'; 
+			'FOR EACH ROW EXECUTE PROCEDURE ' || "baseten".quote_ident_bx (fname) || ' ()'; 
 	EXECUTE fdecl;
 	EXECUTE query;
-	EXECUTE 'REVOKE ALL PRIVILEGES ON FUNCTION ' || "baseten".quote_ident ('baseten', fname) || ' () FROM PUBLIC';
+	EXECUTE 'REVOKE ALL PRIVILEGES ON FUNCTION ' || "baseten".quote_ident_bx (fname) || ' () FROM PUBLIC';
 	RETURN;
 END;
 $marker$ VOLATILE STRICT LANGUAGE PLPGSQL;
 BEGIN
 	rel := "baseten".reltype (relid);
 	insertion := 
-		'INSERT INTO ' || "baseten".quote_ident ('baseten', "baseten"._mod_table (relid)) || ' (' ||
+		'INSERT INTO ' || "baseten".quote_ident_bx ("baseten"._mod_table (relid)) || ' (' ||
 			'"baseten_modification_type", "baseten_modification_cols", id' ||
 		') VALUES (' || 
 			'''I'', null, ' || default_value || 
 			'AS ON UPDATE TO ' || "baseten".quote_ident (rel) || ' ' ||
 			'WHERE ' || "baseten".enable_update_where_clause (pkey) || ' ' ||
 			'DO ALSO (' ||
-				'INSERT INTO ' || "baseten".quote_ident ('baseten', "baseten"._mod_table (relid)) || ' (' ||
+				'INSERT INTO ' || "baseten".quote_ident_bx ("baseten"._mod_table (relid)) || ' (' ||
 					'"baseten_modification_type", "baseten_modification_cols", ' || array_to_string (pkey, ', ') ||
 				') SELECT ' ||
 					'''U'', ' ||
 	pkey_values := array_to_string ("baseten".array_prepend_each (refname, pkey_fields), ', ');
 
 	RETURN
-		'INSERT INTO ' || "baseten".quote_ident ('baseten', "baseten"._mod_table (relid)) || ' (' ||
+		'INSERT INTO ' || "baseten".quote_ident_bx ("baseten"._mod_table (relid)) || ' (' ||
 			'"baseten_modification_type", "baseten_modification_cols", ' || pkey ||
 		') VALUES (' || 
 			'''' || operation || ''', null, ' || pkey_values || 
 
 	-- Locking
 	query := 
-		'CREATE TABLE ' || "baseten".quote_ident ('baseten', lock_table) || ' (' ||
+		'CREATE TABLE ' || "baseten".quote_ident_bx (lock_table) || ' (' ||
 			'"baseten_lock_relid" INTEGER NOT NULL DEFAULT ' || relid_ || ', ' ||
 			pkey_decl ||
 		') INHERITS ("baseten".lock)';
 	EXECUTE query;
-	EXECUTE 'REVOKE ALL PRIVILEGES ON ' || "baseten".quote_ident ('baseten', lock_table) || ' FROM PUBLIC';
-	EXECUTE 'GRANT SELECT ON ' || "baseten".quote_ident ('baseten', lock_table) || ' TO basetenread';
-	EXECUTE 'GRANT INSERT ON ' || "baseten".quote_ident ('baseten', lock_table) || ' TO basetenuser';
-	EXECUTE 'COMMENT ON TABLE ' || "baseten".quote_ident ('baseten', lock_table) || ' IS ''' || rel.nspname || '.' || rel.relname || '''';
+	EXECUTE 'REVOKE ALL PRIVILEGES ON ' || "baseten".quote_ident_bx (lock_table) || ' FROM PUBLIC';
+	EXECUTE 'GRANT SELECT ON ' || "baseten".quote_ident_bx (lock_table) || ' TO basetenread';
+	EXECUTE 'GRANT INSERT ON ' || "baseten".quote_ident_bx (lock_table) || ' TO basetenuser';
+	EXECUTE 'COMMENT ON TABLE ' || "baseten".quote_ident_bx (lock_table) || ' IS ''' || rel.nspname || '.' || rel.relname || '''';
 
 	-- Trigger for the _lock_ table
 	query :=
 		'CREATE TRIGGER "lock_row" ' ||
-		'AFTER INSERT ON ' || "baseten".quote_ident ('baseten', lock_table) || ' ' ||
+		'AFTER INSERT ON ' || "baseten".quote_ident_bx (lock_table) || ' ' ||
 		'FOR EACH STATEMENT EXECUTE PROCEDURE "baseten".lock_notify (''' || "baseten"._lock_notification (reloid) || ''')';
 	EXECUTE query;
 
 
 	-- Modifications
 	query :=
-		'CREATE TABLE ' || "baseten".quote_ident ('baseten', mod_table) || ' (' ||
+		'CREATE TABLE ' || "baseten".quote_ident_bx (mod_table) || ' (' ||
 			'"baseten_modification_relid" INTEGER NOT NULL DEFAULT ' || relid_ || ', ' ||
 			pkey_decl ||
 		') INHERITS ("baseten".modification)';
 	EXECUTE query;
-	EXECUTE 'REVOKE ALL PRIVILEGES ON ' || "baseten".quote_ident ('baseten', mod_table) || ' FROM PUBLIC';
-	EXECUTE 'GRANT INSERT ON ' || "baseten".quote_ident ('baseten', mod_table) || ' TO basetenuser';
-	EXECUTE 'GRANT SELECT ON ' || "baseten".quote_ident ('baseten', mod_table) || ' TO basetenread';
-	EXECUTE 'COMMENT ON TABLE ' || "baseten".quote_ident ('baseten', mod_table) || ' IS ''' || rel.nspname || '.' || rel.relname || '''';
+	EXECUTE 'REVOKE ALL PRIVILEGES ON ' || "baseten".quote_ident_bx (mod_table) || ' FROM PUBLIC';
+	EXECUTE 'GRANT INSERT ON ' || "baseten".quote_ident_bx (mod_table) || ' TO basetenuser';
+	EXECUTE 'GRANT SELECT ON ' || "baseten".quote_ident_bx (mod_table) || ' TO basetenread';
+	EXECUTE 'COMMENT ON TABLE ' || "baseten".quote_ident_bx (mod_table) || ' IS ''' || rel.nspname || '.' || rel.relname || '''';
 	
 	-- Triggers for the _modification_ table
 	query :=
 		'CREATE TRIGGER "modify_table" ' ||
-		'AFTER INSERT ON ' || "baseten".quote_ident ('baseten', mod_table) || ' ' ||
+		'AFTER INSERT ON ' || "baseten".quote_ident_bx (mod_table) || ' ' ||
 		'FOR EACH STATEMENT EXECUTE PROCEDURE "baseten".mod_notify (''' || "baseten"._mod_notification (reloid) || ''')';
 	EXECUTE query;
 	
 	ELSE
 		PERFORM "baseten".enable_table_insert (reloid, pkey) ;
 	END IF;
-	--PERFORM "baseten".enable_update_non_pkey (reloid, pkey);
-	PERFORM "baseten".enable_other ('update', reloid, pkey);
+	--PERFORM "baseten".enable_other ('update', reloid, pkey);
+	PERFORM "baseten".enable_update_non_pkey (reloid, pkey);
 	PERFORM "baseten".enable_other ('update_pk', reloid, pkey);
 	PERFORM "baseten".enable_other ('delete', reloid, pkey);
 
 		INTO STRICT pkey, order_by;
 	date_str := COALESCE (earliest_date, '-infinity');
 	ignored_be_pid := COALESCE (ignored_be_pid, 0);
-	columns := '"baseten_modification_type", "baseten_modification_timestamp", "baseten_modification_insert_timestamp", ' || pkey;
+	columns := '"baseten_modification_type", "baseten_modification_cols", "baseten_modification_timestamp", "baseten_modification_insert_timestamp", ' || pkey;
 	
 	PERFORM "baseten".mod_cleanup (idle_xact);
 	query :=
 		'SELECT ' || columns || ' FROM (' ||
 			'SELECT DISTINCT ON (' || pkey || ') ' || columns || ' ' ||
-			'FROM ' || "baseten".quote_ident ('baseten', mtable) || ' ' ||
+			'FROM ' || "baseten".quote_ident_bx (mtable) || ' ' ||
 			'WHERE ("baseten_modification_timestamp" > ''' || date_str || '''::timestamp OR "baseten_modification_timestamp" IS NULL) AND ' ||
 				'baseten_modification_backend_pid != ' || ignored_be_pid || ' ' ||
 			'ORDER BY ' || order_by || ', "baseten_modification_type" ASC' ||
 		'UNION ' || -- Not UNION ALL
 		'SELECT ' || columns || ' FROM (' ||
 			'SELECT DISTINCT ON (' || pkey || ') ' || columns || ' ' ||
-			'FROM ' || "baseten".quote_ident ('baseten', mtable) || ' ' ||
+			'FROM ' || "baseten".quote_ident_bx (mtable) || ' ' ||
 			'WHERE ("baseten_modification_type" = ''D'' OR "baseten_modification_type" = ''I'') AND ' ||
 				'("baseten_modification_timestamp" > ''' || date_str || '''::timestamp OR "baseten_modification_timestamp" IS NULL) AND ' ||
 				'baseten_modification_backend_pid != ' || ignored_be_pid || ' ' ||

Sources/BXDatabaseContext.m

 
 - (void) undoUpdateObjects: (NSArray *) objectIDs 
 					oldIDs: (NSArray *) oldIDs 
+				attributes: (NSArray *) updatedAttributes
 		  createdSavepoint: (BOOL) createdSavepoint 
 			   updatedPkey: (BOOL) updatedPkey 
 				   oldPkey: (NSDictionary *) oldPkey
 			}
 		}
 	}
-	[self updatedObjectsInDatabase: oldIDs faultObjects: YES];
+	[self updatedObjectsInDatabase: oldIDs attributes: updatedAttributes faultObjects: YES];
 }
 @end
 
 }
 
 //FIXME: clean me up.
-- (void) updatedObjectsInDatabase: (NSArray *) givenObjectIDs faultObjects: (BOOL) shouldFault
+- (void) updatedObjectsInDatabase: (NSArray *) givenObjectIDs attributes: (NSArray *) attrs faultObjects: (BOOL) shouldFault
 {
     if (0 < [givenObjectIDs count])
     {
                 if (nil == localError)
                 {
                     //This is needed for self-updating collections. See the deletion method.
-					[self updatedObjectsInDatabase: oldIDs faultObjects: NO];
+					[self updatedObjectsInDatabase: oldIDs attributes: [aDict allKeys] faultObjects: NO];
                     
                     //For redo
                     BXInvocationRecorder* recorder = [BXInvocationRecorder recorder];
                     //Undo manager does things in reverse order.
 					[[mUndoManager prepareWithInvocationTarget: self] undoUpdateObjects: objectIDs
 																				 oldIDs: oldIDs
+																			 attributes: [aDict allKeys]
 																	   createdSavepoint: createdSavepoint
 																			updatedPkey: updatedPkey
 																				oldPkey: oldPkey

Sources/BXDatabaseContextPrivate.h

 //- (void) reregisterObjects: (NSArray *) objectIDs values: (NSDictionary *) pkeyValues;
 - (void) undoUpdateObjects: (NSArray *) objectIDs 
 					oldIDs: (NSArray *) oldIDs 
+				attributes: (NSArray *) updatedAttributes
 		  createdSavepoint: (BOOL) createdSavepoint 
 			   updatedPkey: (BOOL) updatedPkey 
 				   oldPkey: (NSDictionary *) oldPkey

Sources/BXInterface.h

 - (BOOL) connectedToDatabase: (BOOL) connected async: (BOOL) async error: (NSError **) error;
 - (void) connectionLost: (NSError *) error;
 - (void) addedObjectsToDatabase: (NSArray *) objectIDs;
-- (void) updatedObjectsInDatabase: (NSArray *) objectIDs faultObjects: (BOOL) shouldFault;
+- (void) updatedObjectsInDatabase: (NSArray *) objectIDs attributes: (NSArray *) changedAttributes faultObjects: (BOOL) shouldFault;
 - (void) deletedObjectsFromDatabase: (NSArray *) objectIDs;
 - (void) lockedObjectsInDatabase: (NSArray *) objectIDs status: (enum BXObjectLockStatus) status;
 - (void) unlockedObjectsInDatabase: (NSArray *) objectIDs;

Sources/BXPGModificationHandler.mm

 #import "BXDatabaseObjectIDPrivate.h"
 #import "PGTSScannedMemoryAllocator.h"
 #import "PGTSHOM.h"
+#import "BXEnumerate.h"
 #import <tr1/unordered_map>
 
 typedef std::tr1::unordered_map <unichar, NSMutableArray*,
 	NSString* queryFormat = 
 	@"SELECT * FROM \"baseten\".modification ($1, $2, $3, $4) "
 	@"AS m ( "
-	@"\"baseten_modification_type\" character (1), "
-	@"\"baseten_modification_timestamp\" timestamp (6) without time zone, "
-	@"\"baseten_modification_insert_timestamp\" timestamp (6) without time zone, "
+	@"\"baseten_modification_type\" CHARACTER (1), "
+	@"\"baseten_modification_cols\" INT2 [], "
+	@"\"baseten_modification_timestamp\" TIMESTAMP (6) WITHOUT TIME ZONE, "
+	@"\"baseten_modification_insert_timestamp\" TIMESTAMP (6) WITHOUT TIME ZONE, "
 	@"%@)";
     mQueryString = [[NSString alloc] initWithFormat: queryFormat, pkeyString];
 }
 	PGTSResultSet* res = [mConnection executeQuery: mQueryString parameters: 
 						  [NSNumber numberWithInteger: mIdentifier], [NSNumber numberWithBool: isIdle], 
 						  mLastCheck, [NSNumber numberWithInt: backendPID]];
+	BXPGTableDescription *rel = [mInterface tableForEntity: mEntity];
+	NSDictionary *attributesByName = [mEntity attributesByName];
 	BXAssertVoidReturn ([res querySucceeded], @"Expected query to succeed: %@", [res error]);
 	
 	//Update the timestamp.
 	
 	//Sort the changes by type.
 	ChangeMap* changes = new ChangeMap (3);
+	NSMutableArray *changedAttrs = [NSMutableArray arrayWithCapacity: [res count]];
 	[res goBeforeFirstRow];
     while ([res advanceRow])
     {
-		NSDictionary* row = [res currentRowAsDictionary];
-		unichar modificationType = [[row valueForKey: @"baseten_modification_type"] characterAtIndex: 0];                            
+		unichar modificationType = [[res valueForKey: @"baseten_modification_type"] characterAtIndex: 0];                            
 		NSMutableArray* objectIDs = (* changes) [modificationType];
 		if (! objectIDs)
 		{
 			(* changes) [modificationType] = objectIDs;
 		}
 		
-		BXDatabaseObjectID* objectID = [BXDatabaseObjectID IDWithEntity: mEntity primaryKeyFields: row];
+		BXDatabaseObjectID* objectID = [BXDatabaseObjectID IDWithEntity: mEntity primaryKeyFields: [res currentRowAsDictionary]];
 		[objectIDs addObject: objectID];
+		
+		if ('U' == modificationType)
+		{
+			NSArray *columnIndices = [res valueForKey: @"baseten_modification_cols"];
+			NSMutableArray *attrs = [NSMutableArray arrayWithCapacity: [columnIndices count]];
+			BXEnumerate (currentIndex, e, [columnIndices objectEnumerator])
+			{
+				PGTSColumnDescription *col = [rel columnAtIndex: [currentIndex integerValue]];
+				BXAttributeDescription *attr = [attributesByName objectForKey: [col name]];
+				[attrs addObject: attr];
+			}
+			
+			[changedAttrs addObject: attrs];
+		}
 	}
 	
 	//Send changes.
 			case 'I':
 				[[mInterface databaseContext] addedObjectsToDatabase: objectIDs];
 				break;
-				
+								
 			case 'U':
-				[[mInterface databaseContext] updatedObjectsInDatabase: objectIDs faultObjects: YES];
+				[[mInterface databaseContext] updatedObjectsInDatabase: objectIDs attributes: changedAttrs faultObjects: YES];
 				break;
 				
 			case 'D':