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 {
- (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;
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];
[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;
[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];
[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"];
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) {
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;