sogo/SOPE/GDLContentStore/GCSFolder.m

1172 lines
33 KiB
Mathematica
Raw Normal View History

/*
Copyright (C) 2004-2007 SKYRIX Software AG
Copyright (C) 2007 Helge Hess
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo 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 Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "GCSFolder.h"
#include "GCSFolderManager.h"
#include "GCSFolderType.h"
#include "GCSChannelManager.h"
#include "GCSFieldExtractor.h"
#include "NSURL+GCS.h"
#include "EOAdaptorChannel+GCS.h"
#include "EOQualifier+GCS.h"
#include "GCSStringFormatter.h"
#include "common.h"
#include <GDLAccess/EOEntity.h>
#include <GDLAccess/EOAttribute.h>
#include <GDLAccess/EOSQLQualifier.h>
#include <GDLAccess/EOAdaptorContext.h>
#define CHECKERROR() \
if (error) { \
[[storeChannel adaptorContext] rollbackTransaction]; \
[[quickChannel adaptorContext] rollbackTransaction]; \
[self logWithFormat:@"ERROR(%s): cannot %s content : %@", \
__PRETTY_FUNCTION__, isNewRecord ? "insert" : "update", error]; \
return error; \
} \
@implementation GCSFolder
static BOOL debugOn = NO;
static BOOL doLogStore = NO;
static Class NSStringClass = Nil;
static Class NSNumberClass = Nil;
static Class NSCalendarDateClass = Nil;
static GCSStringFormatter *stringFormatter = nil;
+ (void)initialize {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
debugOn = [ud boolForKey:@"GCSFolderDebugEnabled"];
doLogStore = [ud boolForKey:@"GCSFolderStoreDebugEnabled"];
NSStringClass = [NSString class];
NSNumberClass = [NSNumber class];
NSCalendarDateClass = [NSCalendarDate class];
stringFormatter = [GCSStringFormatter sharedFormatter];
}
- (id)initWithPath:(NSString *)_path primaryKey:(id)_folderId
folderTypeName:(NSString *)_ftname folderType:(GCSFolderType *)_ftype
location:(NSURL *)_loc quickLocation:(NSURL *)_qloc
aclLocation:(NSURL *)_aloc
folderManager:(GCSFolderManager *)_fm
{
if (![_loc isNotNull]) {
[self errorWithFormat:@"missing quicktable parameter!"];
[self release];
return nil;
}
if ((self = [super init])) {
self->folderManager = [_fm retain];
self->folderInfo = [_ftype retain];
self->folderId = [_folderId copy];
self->folderName = [[_path lastPathComponent] copy];
self->path = [_path copy];
self->location = [_loc retain];
self->quickLocation = _qloc ? [_qloc retain] : [_loc retain];
self->aclLocation = [_aloc retain];
self->folderTypeName = [_ftname copy];
self->ofFlags.requiresFolderSelect = 0;
self->ofFlags.sameTableForQuick =
[self->location isEqualTo:self->quickLocation] ? 1 : 0;
}
return self;
}
- (id)init {
return [self initWithPath:nil primaryKey:nil
folderTypeName:nil folderType:nil
location:nil quickLocation:nil
aclLocation:nil
folderManager:nil];
}
- (id)initWithPath:(NSString *)_path primaryKey:(id)_folderId
folderTypeName:(NSString *)_ftname folderType:(GCSFolderType *)_ftype
location:(NSURL *)_loc quickLocation:(NSURL *)_qloc
folderManager:(GCSFolderManager *)_fm
{
return [self initWithPath:_path primaryKey:_folderId folderTypeName:_ftname
folderType:_ftype location:_loc quickLocation:_qloc
aclLocation:nil
folderManager:_fm];
}
- (void)dealloc {
[self->folderManager release];
[self->folderInfo release];
[self->folderId release];
[self->folderName release];
[self->path release];
[self->location release];
[self->quickLocation release];
[self->aclLocation release];
[self->folderTypeName release];
[super dealloc];
}
/* accessors */
- (NSNumber *)folderId {
return self->folderId;
}
- (NSString *)folderName {
return self->folderName;
}
- (NSString *)path {
return self->path;
}
- (NSURL *)location {
return self->location;
}
- (NSURL *)quickLocation {
return self->quickLocation;
}
- (NSURL *)aclLocation {
return self->aclLocation;
}
- (NSString *)folderTypeName {
return self->folderTypeName;
}
- (GCSFolderManager *)folderManager {
return self->folderManager;
}
- (GCSChannelManager *)channelManager {
return [[self folderManager] channelManager];
}
- (NSString *)storeTableName {
return [[self location] gcsTableName];
}
- (NSString *)quickTableName {
return [[self quickLocation] gcsTableName];
}
- (NSString *)aclTableName {
return [[self aclLocation] gcsTableName];
}
- (BOOL)isQuickInfoStoredInContentTable {
return self->ofFlags.sameTableForQuick ? YES : NO;
}
/* channels */
- (EOAdaptorChannel *)acquireStoreChannel {
return [[self channelManager] acquireOpenChannelForURL:[self location]];
}
- (EOAdaptorChannel *)acquireQuickChannel {
return [[self channelManager] acquireOpenChannelForURL:[self quickLocation]];
}
- (EOAdaptorChannel *)acquireAclChannel {
return [[self channelManager] acquireOpenChannelForURL:[self aclLocation]];
}
- (void)releaseChannel:(EOAdaptorChannel *)_channel {
[[self channelManager] releaseChannel:_channel];
if (debugOn) [self debugWithFormat:@"released channel: %@", _channel];
}
- (BOOL)canConnectStore {
return [[self channelManager] canConnect:[self location]];
}
- (BOOL)canConnectQuick {
return [[self channelManager] canConnect:[self quickLocation]];
}
- (BOOL)canConnectAcl {
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",
self, @"GCSFolder",
nil];
return [NSException exceptionWithName:@"GCSVersionMismatch"
reason:@"Transaction conflict during a GCS modification."
userInfo:ui];
}
- (NSException *)errorExtractorReturnedNoQuickRow:(id)_extractor
forContent:(NSString *)_content
{
NSDictionary *ui;
ui = [NSDictionary dictionaryWithObjectsAndKeys:
self, @"GCSFolder",
_extractor, @"GCSExtractor",
_content, @"GCSContent",
nil];
return [NSException exceptionWithName:@"GCSExtractFailed"
reason:@"Quickfield extractor did not return a result!"
userInfo:ui];
}
/* operations */
- (NSArray *)subFolderNames {
return [[self folderManager] listSubFoldersAtPath:[self path]
recursive:NO];
}
- (NSArray *)allSubFolderNames {
return [[self folderManager] listSubFoldersAtPath:[self path]
recursive:YES];
}
- (NSDictionary *) _fetchValueOfColumns: (NSArray *) _cols
inContentWithName: (NSString *) _name
ignoreDeleted: (BOOL) ignoreDeleted
{
EOAdaptorChannel *channel;
NSException *error;
NSDictionary *row;
NSArray *attrs;
NSMutableString *sql;
if ((channel = [self acquireStoreChannel]) == nil) {
[self errorWithFormat:@"could not open storage channel!"];
return nil;
}
/* generate SQL */
sql = [NSMutableString stringWithFormat: @"SELECT %@"
@" FROM %@"
@" WHERE c_name = '%@'",
[_cols componentsJoinedByString: @","],
[self storeTableName], _name];
if (ignoreDeleted)
[sql appendString: @" AND (c_deleted != 1 OR c_deleted IS NULL)"];
/* run SQL */
if ((error = [channel evaluateExpressionX:sql]) != nil) {
[self errorWithFormat:@"%s: cannot execute SQL '%@': %@",
__PRETTY_FUNCTION__, sql, error];
[self releaseChannel:channel];
return nil;
}
/* fetch results */
attrs = [channel describeResults:NO /* do not beautify names */];
row = [channel fetchAttributes: attrs withZone: NULL];
if (row)
[channel cancelFetch];
/* release and return result */
[self releaseChannel:channel];
return row;
}
- (NSDictionary *) recordOfEntryWithName: (NSString *) name
{
NSDictionary *row;
NSMutableDictionary *record;
NSArray *columns;
NSString *strValue;
int intValue;
columns = [NSArray arrayWithObjects: @"c_content", @"c_version",
@"c_creationdate", @"c_lastmodified", nil];
row = [self _fetchValueOfColumns: columns
inContentWithName: name
ignoreDeleted: YES];
if (row)
{
record = [NSMutableDictionary dictionaryWithCapacity: 5];
strValue = [row objectForKey: @"c_content"];
if (![strValue isNotNull])
strValue = @"";
[record setObject: strValue forKey: @"c_content"];
[record setObject: [row objectForKey: @"c_version"]
forKey: @"c_version"];
intValue = [[row objectForKey: @"c_creationdate"] intValue];
[record
setObject: [NSCalendarDate dateWithTimeIntervalSince1970: intValue]
forKey: @"c_creationdate"];
intValue = [[row objectForKey: @"c_lastmodified"] intValue];
[record
setObject: [NSCalendarDate dateWithTimeIntervalSince1970: intValue]
forKey: @"c_lastmodified"];
}
else
record = nil;
return record;
}
- (NSNumber *) deletionOfEntryWithName: (NSString *) name
{
NSDictionary *row;
row = [self _fetchValueOfColumns: [NSArray arrayWithObject: @"c_deleted"]
inContentWithName: name
ignoreDeleted: NO];
return [row objectForKey: @"c_deleted"];
}
- (NSDictionary *)fetchContentsOfAllFiles {
/*
Note: try to avoid the use of this method! The key of the dictionary
will be filename, the value the content.
*/
NSMutableDictionary *result;
EOAdaptorChannel *channel;
NSException *error;
NSDictionary *row;
NSArray *attrs;
NSString *sql;
if ((channel = [self acquireStoreChannel]) == nil) {
[self errorWithFormat:@"%s: could not open storage channel!",
__PRETTY_FUNCTION__];
return nil;
}
/* generate SQL */
sql = @"SELECT c_name, c_content FROM ";
sql = [sql stringByAppendingString:[self storeTableName]];
/* run SQL */
if ((error = [channel evaluateExpressionX:sql]) != nil) {
[self logWithFormat:@"ERROR(%s): cannot execute SQL '%@': %@",
__PRETTY_FUNCTION__, sql, error];
[self releaseChannel:channel];
return nil;
}
/* fetch results */
result = [NSMutableDictionary dictionaryWithCapacity:128];
attrs = [channel describeResults:NO /* do not beautify names */];
while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil) {
NSString *cName, *cContent;
cName = [row objectForKey:@"c_name"];
cContent = [row objectForKey:@"c_content"];
if (![cName isNotNull]) {
[self errorWithFormat:@"missing c_name in row: %@", row];
continue;
}
if (![cContent isNotNull]) {
[self errorWithFormat:@"missing c_content in row: %@", row];
continue;
}
[result setObject:cContent forKey:cName];
}
/* release and return result */
[self releaseChannel:channel];
return result;
}
/* writing content */
- (NSString *)_formatRowValue:(id)_value {
if (![_value isNotNull])
return @"NULL";
if ([_value isKindOfClass:NSStringClass])
return [stringFormatter stringByFormattingString:_value];
if ([_value isKindOfClass:NSNumberClass]) {
#if GNUSTEP_BASE_LIBRARY
_value = [_value stringValue];
return ([(NSString *)_value hasPrefix:@"Y"] ||
[(NSString *)_value hasPrefix:@"N"])
? (id)([_value boolValue] ? @"1" : @"0")
: _value;
#endif
return [_value stringValue];
}
if ([_value isKindOfClass:NSCalendarDateClass]) {
/* be smart ... convert to timestamp. Note: we loose precision. */
char buf[256];
snprintf(buf, sizeof(buf), "%i", (int)[_value timeIntervalSince1970]);
return [NSString stringWithCString:buf];
}
[self errorWithFormat:@"cannot handle value class: %@", [_value class]];
return nil;
}
- (NSString *)_generateInsertStatementForRow:(NSDictionary *)_row
tableName:(NSString *)_table
{
// TODO: move to NSDictionary category?
NSMutableString *sql;
NSArray *keys;
unsigned i, count;
if (_row == nil || _table == nil)
return nil;
keys = [_row allKeys];
sql = [NSMutableString stringWithCapacity:512];
[sql appendString:@"INSERT INTO "];
[sql appendString:_table];
[sql appendString:@" ("];
for (i = 0, count = [keys count]; i < count; i++) {
if (i != 0) [sql appendString:@", "];
[sql appendString:[keys objectAtIndex:i]];
}
[sql appendString:@") VALUES ("];
for (i = 0, count = [keys count]; i < count; i++) {
id value;
if (i != 0) [sql appendString:@", "];
value = [_row objectForKey:[keys objectAtIndex:i]];
value = [self _formatRowValue:value];
[sql appendString:value];
}
[sql appendString:@")"];
return sql;
}
- (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;
NSArray *keys;
unsigned i, count;
if (_row == nil || _table == nil)
return nil;
keys = [_row allKeys];
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];
if (i != 0) [sql appendString:@", "];
[sql appendString:[keys objectAtIndex:i]];
[sql appendString:@" = "];
[sql appendString:value];
}
[sql appendString:@" WHERE "];
[sql appendString:_colname];
[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;
}
- (EOEntity *) _entityWithName: (NSString *) _name
{
EOAttribute *attribute;
EOEntity *entity;
entity = AUTORELEASE([[EOEntity alloc] init]);
[entity setName: _name];
[entity setExternalName: _name];
attribute = AUTORELEASE([[EOAttribute alloc] init]);
[attribute setName: @"c_name"];
[attribute setColumnName: @"c_name"];
[entity addAttribute: attribute];
return entity;
}
- (EOSQLQualifier *) _qualifierUsingWhereColumn:(NSString *)_colname
isEqualTo:(id)_value
andColumn:(NSString *)_colname2
isEqualTo:(id)_value2
entity: (EOEntity *)_entity
{
EOSQLQualifier *qualifier;
if (_colname2 == nil)
{
qualifier = [[EOSQLQualifier alloc] initWithEntity: _entity
qualifierFormat: @"%A = %@", _colname, [self _formatRowValue:_value]];
}
else
{
qualifier = [[EOSQLQualifier alloc] initWithEntity: _entity
qualifierFormat: @"%A = %@ AND %A = %@", _colname, [self _formatRowValue:_value],
_colname2, [self _formatRowValue:_value2]];
}
return AUTORELEASE(qualifier);
}
- (void) _purgeRecordWithName: (NSString *) recordName
{
NSString *delSql, *table;
EOAdaptorChannel *channel;
channel = [self acquireStoreChannel];
table = [self storeTableName];
delSql = [NSString stringWithFormat: @"DELETE FROM %@"
@" WHERE c_name = %@", table,
[self _formatRowValue: recordName]];
[channel evaluateExpressionX: delSql];
[self releaseChannel: channel];
}
- (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name
baseVersion:(unsigned int)_baseVersion
{
EOAdaptorChannel *storeChannel, *quickChannel;
NSMutableDictionary *quickRow, *contentRow;
NSDictionary *currentRow;
GCSFieldExtractor *extractor;
NSException *error;
NSNumber *storedVersion;
BOOL isNewRecord, hasInsertDelegate, hasUpdateDelegate;
NSCalendarDate *nowDate;
NSNumber *now;
/* check preconditions */
if (_name == nil) {
return [NSException exceptionWithName:@"GCSStoreException"
reason:@"no content filename was provided"
userInfo:nil];
}
if (_content == nil) {
return [NSException exceptionWithName:@"GCSStoreException"
reason:@"no content was provided"
userInfo:nil];
}
/* run */
error = nil;
nowDate = [NSCalendarDate date];
now = [NSNumber numberWithUnsignedInt:[nowDate timeIntervalSince1970]];
if (doLogStore)
[self logWithFormat:@"should store content: '%@'\n%@", _name, _content];
currentRow = [self _fetchValueOfColumns: [NSArray arrayWithObjects:
@"c_version",
@"c_deleted", nil]
inContentWithName: _name
ignoreDeleted: NO];
storedVersion = [currentRow objectForKey: @"c_version"];
if (doLogStore)
[self logWithFormat:@" version: %@", storedVersion];
isNewRecord = [storedVersion isNotNull] ? NO : YES;
if (!isNewRecord)
{
if ([[currentRow objectForKey: @"c_deleted"] intValue] > 0)
{
[self _purgeRecordWithName: _name];
isNewRecord = 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];
if ((quickRow = [extractor extractQuickFieldsFromContent:_content]) == nil) {
return [self errorExtractorReturnedNoQuickRow:extractor
forContent:_content];
}
[quickRow setObject:_name forKey:@"c_name"];
if (doLogStore)
[self logWithFormat:@" store quick: %@", quickRow];
/* make content row */
contentRow = [NSMutableDictionary dictionaryWithCapacity:16];
if (self->ofFlags.sameTableForQuick)
[contentRow addEntriesFromDictionary:quickRow];
[contentRow setObject:_name forKey:@"c_name"];
if (isNewRecord) [contentRow setObject:now forKey:@"c_creationdate"];
[contentRow setObject:now forKey:@"c_lastmodified"];
if (isNewRecord)
[contentRow setObject:[NSNumber numberWithInt:0] forKey:@"c_version"];
else {
// TODO: increase version?
[contentRow setObject:
[NSNumber numberWithInt:([storedVersion intValue] + 1)]
forKey:@"c_version"];
}
[contentRow setObject:_content forKey:@"c_content"];
/* open channels */
if ((storeChannel = [self acquireStoreChannel]) == nil) {
[self errorWithFormat:@"%s: could not open storage channel!",
__PRETTY_FUNCTION__];
return nil;
}
if (!self->ofFlags.sameTableForQuick) {
if ((quickChannel = [self acquireQuickChannel]) == nil) {
[self errorWithFormat:@"%s: could not open quick channel!",
__PRETTY_FUNCTION__];
[self releaseChannel:storeChannel];
return nil;
}
}
/* we check if we can call directly methods on our adaptor
channel delegate. If not, we generate SQL ourself since it'll
be a little bit faster and less complex than using GDL to do so */
hasInsertDelegate = [[storeChannel delegate]
respondsToSelector: @selector(adaptorChannel:willInsertRow:forEntity:)];
hasUpdateDelegate = [[storeChannel delegate]
respondsToSelector: @selector(adaptorChannel:willUpdateRow:describedByQualifier:)];
[[storeChannel adaptorContext] beginTransaction];
[[quickChannel adaptorContext] beginTransaction];
if (isNewRecord) {
if (!self->ofFlags.sameTableForQuick) {
error = (hasInsertDelegate ? [quickChannel insertRowX: quickRow
forEntity: [self _entityWithName: [self quickTableName]]]
: [quickChannel evaluateExpressionX: [self _generateInsertStatementForRow: quickRow
tableName: [self quickTableName]]]);
CHECKERROR();
}
error = (hasInsertDelegate ? [storeChannel insertRowX: contentRow
forEntity: [self _entityWithName: [self storeTableName]]]
: [storeChannel evaluateExpressionX: [self _generateInsertStatementForRow: contentRow
tableName: [self storeTableName]]]);
CHECKERROR();
}
else {
if (!self->ofFlags.sameTableForQuick) {
error = (hasUpdateDelegate ? [quickChannel updateRowX: quickRow
describedByQualifier: [self _qualifierUsingWhereColumn: @"c_name"
isEqualTo: _name andColumn: nil isEqualTo: nil
entity: [self _entityWithName: [self quickTableName]]]]
: [quickChannel evaluateExpressionX: [self _generateUpdateStatementForRow: quickRow
tableName: [self quickTableName]
whereColumn: @"c_name" isEqualTo: _name
andColumn: nil isEqualTo: nil]]);
CHECKERROR();
}
error = (hasUpdateDelegate ? [storeChannel updateRowX: contentRow
describedByQualifier: [self _qualifierUsingWhereColumn: @"c_name" isEqualTo: _name
andColumn: (_baseVersion != 0 ? (id)@"c_version" : (id)nil)
isEqualTo: (_baseVersion != 0 ? [NSNumber numberWithUnsignedInt:_baseVersion] : (NSNumber *)nil)
entity: [self _entityWithName: [self storeTableName]]]]
: [storeChannel evaluateExpressionX: [self _generateUpdateStatementForRow: contentRow tableName:[self storeTableName]
whereColumn: @"c_name" isEqualTo: _name
andColumn: (_baseVersion != 0 ? (id)@"c_version" : (id)nil)
isEqualTo: (_baseVersion != 0 ? [NSNumber numberWithUnsignedInt: _baseVersion] : (NSNumber *)nil)]]);
CHECKERROR();
}
[[storeChannel adaptorContext] commitTransaction];
[[quickChannel adaptorContext] commitTransaction];
[self releaseChannel: storeChannel];
if (!self->ofFlags.sameTableForQuick) [self releaseChannel: quickChannel];
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;
NSString *delsql;
NSCalendarDate *nowDate;
/* check preconditions */
if (_name == nil) {
return [NSException exceptionWithName:@"GCSDeleteException"
reason:@"no content filename was provided"
userInfo:nil];
}
if (doLogStore)
[self logWithFormat:@"should delete content: '%@'", _name];
/* open channels */
if ((storeChannel = [self acquireStoreChannel]) == nil) {
[self errorWithFormat:@"could not open storage channel!"];
return nil;
}
if (self->ofFlags.sameTableForQuick)
quickChannel = nil;
else {
if ((quickChannel = [self acquireQuickChannel]) == nil) {
[self errorWithFormat:@"could not open quick channel!"];
[self releaseChannel:storeChannel];
return nil;
}
}
/* delete rows */
nowDate = [NSCalendarDate calendarDate];
delsql = [@"UPDATE " stringByAppendingString:[self storeTableName]];
delsql = [delsql stringByAppendingString:@" SET c_deleted = 1"];
delsql = [delsql stringByAppendingFormat:@", c_lastmodified = %u",
(unsigned int) [nowDate timeIntervalSince1970]];
delsql = [delsql stringByAppendingString:@" WHERE c_name="];
delsql = [delsql stringByAppendingString:[self _formatRowValue:_name]];
if ((error = [storeChannel evaluateExpressionX:delsql]) != nil) {
[self errorWithFormat:
@"%s: cannot delete content '%@': %@",
__PRETTY_FUNCTION__, delsql, error];
}
else if (!self->ofFlags.sameTableForQuick) {
/* content row deleted, now delete the quick row */
delsql = [@"DELETE FROM " stringByAppendingString:[self quickTableName]];
delsql = [delsql stringByAppendingString:@" WHERE c_name="];
delsql = [delsql stringByAppendingString:[self _formatRowValue:_name]];
if ((error = [quickChannel evaluateExpressionX:delsql]) != nil) {
[self errorWithFormat:
@"%s: cannot delete quick row '%@': %@",
__PRETTY_FUNCTION__, delsql, error];
/*
Note: we now have a "broken" record, needs to be periodically GCed by
a script!
*/
}
}
/* release channels and return */
[self releaseChannel:storeChannel];
if (!self->ofFlags.sameTableForQuick)
[self releaseChannel:quickChannel];
return error;
}
- (NSException *)deleteFolder {
EOAdaptorChannel *channel;
NSString *delsql;
NSString *table;
/* open channels */
if ((channel = [self acquireStoreChannel]) == nil) {
[self errorWithFormat:@"could not open channel!"];
return nil;
}
/* delete rows */
table = [self storeTableName];
if ([table length] > 0) {
delsql = [@"DROP TABLE " stringByAppendingString: table];
[channel evaluateExpressionX:delsql];
}
table = [self quickTableName];
if ([table length] > 0) {
delsql = [@"DROP TABLE " stringByAppendingString: table];
[channel evaluateExpressionX:delsql];
}
table = [self aclTableName];
if ([table length] > 0) {
delsql = [@"DROP TABLE " stringByAppendingString: table];
[channel evaluateExpressionX:delsql];
}
[self releaseChannel:channel];
return nil;
}
- (NSString *)columnNameForFieldName:(NSString *)_fieldName {
return _fieldName;
}
/* SQL generation */
- (NSString *)generateSQLForSortOrderings:(NSArray *)_so {
NSMutableString *sql;
unsigned i, count;
if ((count = [_so count]) == 0)
return nil;
sql = [NSMutableString stringWithCapacity:(count * 16)];
for (i = 0; i < count; i++) {
EOSortOrdering *so;
NSString *column;
SEL sel;
so = [_so objectAtIndex:i];
sel = [so selector];
column = [self columnNameForFieldName:[so key]];
if (i > 0) [sql appendString:@", "];
if (sel_eq(sel, EOCompareAscending)) {
[sql appendString:column];
[sql appendString:@" ASC"];
}
else if (sel_eq(sel, EOCompareDescending)) {
[sql appendString:column];
[sql appendString:@" DESC"];
}
else if (sel_eq(sel, EOCompareCaseInsensitiveAscending)) {
[sql appendString:@"UPPER("];
[sql appendString:column];
[sql appendString:@") ASC"];
}
else if (sel_eq(sel, EOCompareCaseInsensitiveDescending)) {
[sql appendString:@"UPPER("];
[sql appendString:column];
[sql appendString:@") DESC"];
}
else {
[self logWithFormat:@"cannot handle sort selector in store: %@",
NSStringFromSelector(sel)];
}
}
return sql;
}
- (NSString *)generateSQLForQualifier:(EOQualifier *)_q {
NSMutableString *ms;
if (_q == nil) return nil;
ms = [NSMutableString stringWithCapacity:32];
[_q _gcsAppendToString:ms];
return ms;
}
/* fetching */
- (NSArray *)fetchFields:(NSArray *)_flds
fetchSpecification:(EOFetchSpecification *)_fs
{
EOQualifier *qualifier;
NSArray *sortOrderings;
EOAdaptorChannel *channel;
NSException *error;
NSMutableString *sql;
NSArray *attrs;
NSMutableArray *results;
NSDictionary *row;
qualifier = [_fs qualifier];
sortOrderings = [_fs sortOrderings];
#if 0
[self logWithFormat:@"FETCH: %@", _flds];
[self logWithFormat:@" MATCH: %@", _q];
#endif
/* generate SQL */
sql = [NSMutableString stringWithCapacity:256];
[sql appendString:@"SELECT "];
if (_flds == nil)
[sql appendString:@"*"];
else {
unsigned i, count;
count = [_flds count];
for (i = 0; i < count; i++) {
if (i > 0) [sql appendString:@", "];
[sql appendString:[self columnNameForFieldName:[_flds objectAtIndex:i]]];
}
}
[sql appendString:@" FROM "];
[sql appendString:[self quickTableName]];
if (qualifier != nil) {
[sql appendString:@" WHERE "];
[sql appendString:[self generateSQLForQualifier:qualifier]];
}
if ([sortOrderings count] > 0) {
[sql appendString:@" ORDER BY "];
[sql appendString:[self generateSQLForSortOrderings:sortOrderings]];
}
#if 0
/* limit */
[sql appendString:@" LIMIT "]; // count
[sql appendString:@" OFFSET "]; // index from 0
#endif
/* open channel */
if ((channel = [self acquireStoreChannel]) == nil) {
[self errorWithFormat:@" could not open storage channel!"];
return nil;
}
/* run SQL */
if ((error = [channel evaluateExpressionX:sql]) != nil) {
[self errorWithFormat:@"%s: cannot execute quick-fetch SQL '%@': %@",
__PRETTY_FUNCTION__, sql, error];
[self releaseChannel:channel];
return nil;
}
/* fetch results */
results = [NSMutableArray arrayWithCapacity:64];
attrs = [channel describeResults:NO /* do not beautify names */];
while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil)
[results addObject:row];
/* release channels */
[self releaseChannel:channel];
return results;
}
- (NSArray *)fetchFields:(NSArray *)_flds matchingQualifier:(EOQualifier *)_q {
EOFetchSpecification *fs;
if (_q == nil)
fs = nil;
else {
fs = [EOFetchSpecification fetchSpecificationWithEntityName:
[self folderName]
qualifier:_q
sortOrderings:nil];
}
return [self fetchFields:_flds fetchSpecification:fs];
}
- (NSArray *)fetchAclWithSpecification:(EOFetchSpecification *)_fs {
EOQualifier *qualifier;
NSArray *sortOrderings;
EOAdaptorChannel *channel;
NSException *error;
NSMutableString *sql;
NSArray *attrs;
NSMutableArray *results;
NSDictionary *row;
qualifier = [_fs qualifier];
sortOrderings = [_fs sortOrderings];
#if 0
[self logWithFormat:@"FETCH: %@", _flds];
[self logWithFormat:@" MATCH: %@", _q];
#endif
/* generate SQL */
sql = [NSMutableString stringWithCapacity:256];
[sql appendString:@"SELECT c_uid, c_object, c_role"];
[sql appendString:@" FROM "];
[sql appendString:[self aclTableName]];
if (qualifier != nil) {
[sql appendString:@" WHERE "];
[sql appendString:[self generateSQLForQualifier:qualifier]];
}
if ([sortOrderings count] > 0) {
[sql appendString:@" ORDER BY "];
[sql appendString:[self generateSQLForSortOrderings:sortOrderings]];
}
#if 0
/* limit */
[sql appendString:@" LIMIT "]; // count
[sql appendString:@" OFFSET "]; // index from 0
#endif
/* open channel */
if ((channel = [self acquireAclChannel]) == nil) {
[self errorWithFormat:@"could not open acl channel!"];
return nil;
}
/* run SQL */
if ((error = [channel evaluateExpressionX:sql]) != nil) {
[self errorWithFormat:@"%s: cannot execute acl-fetch SQL '%@': %@",
__PRETTY_FUNCTION__, sql, error];
[self releaseChannel:channel];
return nil;
}
/* fetch results */
results = [NSMutableArray arrayWithCapacity:64];
attrs = [channel describeResults:NO /* do not beautify names */];
while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil)
[results addObject:row];
/* release channels */
[self releaseChannel:channel];
return results;
}
- (NSArray *) fetchAclMatchingQualifier:(EOQualifier *)_q {
EOFetchSpecification *fs;
if (_q == nil)
fs = nil;
else {
fs = [EOFetchSpecification fetchSpecificationWithEntityName:
[self folderName]
qualifier:_q
sortOrderings:nil];
}
return [self fetchAclWithSpecification:fs];
}
- (void) deleteAclMatchingQualifier:(EOQualifier *)_q {
EOFetchSpecification *fs;
if (_q != nil) {
fs = [EOFetchSpecification fetchSpecificationWithEntityName:
[self folderName]
qualifier:_q
sortOrderings:nil];
[self deleteAclWithSpecification:fs];
}
}
- (void)deleteAclWithSpecification:(EOFetchSpecification *)_fs
{
EOQualifier *qualifier;
EOAdaptorChannel *channel;
NSException *error;
NSMutableString *sql;
qualifier = [_fs qualifier];
if (qualifier != nil) {
sql = [NSMutableString stringWithCapacity:256];
[sql appendString:@"DELETE FROM "];
[sql appendString:[self aclTableName]];
[sql appendString:@" WHERE "];
[sql appendString:[self generateSQLForQualifier:qualifier]];
/* open channel */
if ((channel = [self acquireAclChannel]) == nil) {
[self errorWithFormat:@"could not open acl channel!"];
return;
}
/* run SQL */
if ((error = [channel evaluateExpressionX:sql]) != nil) {
[self errorWithFormat:@"%s: cannot execute acl-fetch SQL '%@': %@",
__PRETTY_FUNCTION__, sql, error];
[self releaseChannel:channel];
return;
}
[self releaseChannel:channel];
}
}
/* description */
- (NSString *)description {
NSMutableString *ms;
id tmp;
ms = [NSMutableString stringWithCapacity:256];
[ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
if (self->folderId)
[ms appendFormat:@" id=%@", self->folderId];
else
[ms appendString:@" no-id"];
if ((tmp = [self path])) [ms appendFormat:@" path=%@", tmp];
if ((tmp = [self folderTypeName])) [ms appendFormat:@" type=%@", tmp];
if ((tmp = [self location]))
[ms appendFormat:@" loc=%@", [tmp absoluteString]];
[ms appendString:@">"];
return ms;
}
@end /* GCSFolder */