From 18e683438f1ba68509bdd1daca8b835209b25302 Mon Sep 17 00:00:00 2001 From: helge Date: Wed, 13 Jul 2005 10:16:57 +0000 Subject: [PATCH] improved support for content revisions git-svn-id: http://svn.opengroupware.org/SOPE/trunk@891 e4a50df8-12e2-0310-a44c-efbce7f8a7e3 --- sope-gdl1/GDLContentStore/ChangeLog | 4 ++ sope-gdl1/GDLContentStore/GCSFolder.h | 2 + sope-gdl1/GDLContentStore/GCSFolder.m | 68 ++++++++++++++++++++++++--- sope-gdl1/GDLContentStore/Version | 2 +- 4 files changed, 68 insertions(+), 8 deletions(-) diff --git a/sope-gdl1/GDLContentStore/ChangeLog b/sope-gdl1/GDLContentStore/ChangeLog index 36616725..e4a74484 100644 --- a/sope-gdl1/GDLContentStore/ChangeLog +++ b/sope-gdl1/GDLContentStore/ChangeLog @@ -1,5 +1,9 @@ 2005-07-13 Helge Hess + * GCSFolder.m: added -writeContent:toName:baseVersion: to support + consistent update operations (eg using etags), properly increase + content object version on update operations (v4.5.31) + * GCSFolderManager.m, GCSFolder.m: changed not to use EOF attribute-name 'beautification', eg 'c_name' will stay 'c_name' instead of being transformed into 'cName' (v4.5.30) diff --git a/sope-gdl1/GDLContentStore/GCSFolder.h b/sope-gdl1/GDLContentStore/GCSFolder.h index 243b263f..32d66dff 100644 --- a/sope-gdl1/GDLContentStore/GCSFolder.h +++ b/sope-gdl1/GDLContentStore/GCSFolder.h @@ -99,6 +99,8 @@ - (NSArray *)allSubFolderNames; - (NSString *)fetchContentWithName:(NSString *)_name; +- (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name + baseVersion:(unsigned int)_baseVersion; - (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name; - (NSException *)deleteContentWithName:(NSString *)_name; diff --git a/sope-gdl1/GDLContentStore/GCSFolder.m b/sope-gdl1/GDLContentStore/GCSFolder.m index ec6f1592..c912e7e8 100644 --- a/sope-gdl1/GDLContentStore/GCSFolder.m +++ b/sope-gdl1/GDLContentStore/GCSFolder.m @@ -165,6 +165,25 @@ static GCSStringFormatter *stringFormatter = nil; return [[self channelManager] canConnect:[self quickLocation]]; } +/* errors */ + +- (NSException *)errorVersionMismatchBetweenStoredVersion:(unsigned int)_store + andExpectedVersion:(unsigned int)_base +{ + NSDictionary *ui; + + ui = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithUnsignedInt:_base], + @"GCSExpectedVersion", + [NSNumber numberWithUnsignedInt:_store], + @"GCSStoredVersion", + nil]; + + return [NSException exceptionWithName:@"GCSVersionMismatch" + reason:@"Transaction conflict during a GCS modification." + userInfo:ui]; +} + /* operations */ - (NSArray *)subFolderNames { @@ -355,6 +374,7 @@ static GCSStringFormatter *stringFormatter = nil; - (NSString *)_generateUpdateStatementForRow:(NSDictionary *)_row tableName:(NSString *)_table whereColumn:(NSString *)_colname isEqualTo:(id)_value + andColumn:(NSString *)_colname2 isEqualTo:(id)_value2 { // TODO: move to NSDictionary category? NSMutableString *sql; @@ -369,11 +389,11 @@ static GCSStringFormatter *stringFormatter = nil; sql = [NSMutableString stringWithCapacity:512]; [sql appendString:@"UPDATE "]; [sql appendString:_table]; - + [sql appendString:@" SET "]; for (i = 0, count = [keys count]; i < count; i++) { id value; - + value = [_row objectForKey:[keys objectAtIndex:i]]; value = [self _formatRowValue:value]; @@ -388,10 +408,19 @@ static GCSStringFormatter *stringFormatter = nil; [sql appendString:@" = "]; [sql appendString:[self _formatRowValue:_value]]; + if (_colname2 != nil) { + [sql appendString:@" AND "]; + [sql appendString:_colname2]; + [sql appendString:@" = "]; + [sql appendString:[self _formatRowValue:_value2]]; + } + return sql; } -- (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name { +- (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name + baseVersion:(unsigned int)_baseVersion +{ EOAdaptorChannel *storeChannel, *quickChannel; NSMutableDictionary *quickRow, *contentRow; GCSFieldExtractor *extractor; @@ -429,6 +458,17 @@ static GCSStringFormatter *stringFormatter = nil; [self logWithFormat:@" version: %@", storedVersion]; isNewRecord = [storedVersion isNotNull] ? NO : YES; + /* check whether sequence matches */ + + if (_baseVersion != 0 /* use 0 to override check */) { + if (_baseVersion != [storedVersion unsignedIntValue]) { + /* version mismatch (concurrent update) */ + return [self errorVersionMismatchBetweenStoredVersion: + [storedVersion unsignedIntValue] + andExpectedVersion:_baseVersion]; + } + } + /* extract quick info */ extractor = [self->folderInfo quickExtractor]; @@ -452,7 +492,8 @@ static GCSStringFormatter *stringFormatter = nil; [contentRow setObject:[NSNumber numberWithInt:0] forKey:@"c_version"]; else { // TODO: increase version? - [contentRow setObject:[NSNumber numberWithInt:[storedVersion intValue]] + [contentRow setObject: + [NSNumber numberWithInt:([storedVersion intValue] + 1)] forKey:@"c_version"]; } [contentRow setObject:_content forKey:@"c_content"]; @@ -486,20 +527,28 @@ static GCSStringFormatter *stringFormatter = nil; if (!self->ofFlags.sameTableForQuick) { qsql = [self _generateUpdateStatementForRow:quickRow tableName:[self quickTableName] - whereColumn:@"c_name" isEqualTo:_name]; + whereColumn:@"c_name" isEqualTo:_name + andColumn:nil isEqualTo:nil]; } + + /* also ensure in the DB that the version keeps staying the same */ bsql = [self _generateUpdateStatementForRow:contentRow tableName:[self storeTableName] - whereColumn:@"c_name" isEqualTo:_name]; + whereColumn:@"c_name" isEqualTo:_name + andColumn:(_baseVersion != 0 ? @"c_version" : nil) + isEqualTo:(_baseVersion != 0 + ? [NSNumber numberWithUnsignedInt:_baseVersion] + : nil)]; } /* execute */ // TODO: execute in transactions - + if ((error = [storeChannel evaluateExpressionX:bsql]) != nil) { [self logWithFormat:@"ERROR(%s): cannot %s content '%@': %@", __PRETTY_FUNCTION__, isNewRecord ? "insert" : "update", bsql, error]; } + // TODO: should check whether a row was actually updated? if (error == nil && qsql != nil) { if ((error = [quickChannel evaluateExpressionX:qsql]) != nil) { @@ -531,6 +580,11 @@ static GCSStringFormatter *stringFormatter = nil; return error; } +- (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name { + /* this method does not check for concurrent writes */ + return [self writeContent:_content toName:_name baseVersion:0]; +} + - (NSException *)deleteContentWithName:(NSString *)_name { EOAdaptorChannel *storeChannel, *quickChannel; NSException *error; diff --git a/sope-gdl1/GDLContentStore/Version b/sope-gdl1/GDLContentStore/Version index 0d34fc44..d6d80e14 100644 --- a/sope-gdl1/GDLContentStore/Version +++ b/sope-gdl1/GDLContentStore/Version @@ -2,7 +2,7 @@ MAJOR_VERSION:=4 MINOR_VERSION:=5 -SUBMINOR_VERSION:=30 +SUBMINOR_VERSION:=31 # v4.5.29 requires libNGExtensions v4.5.161 # v4.5.26 does not require libNGiCal anymore -- 2.39.5