]> err.no Git - sope/blob - sope-gdl1/GDLContentStore/GCSFolder.m
clarified some code
[sope] / sope-gdl1 / GDLContentStore / GCSFolder.m
1 /*
2   Copyright (C) 2004-2007 SKYRIX Software AG
3   Copyright (C) 2007      Helge Hess
4
5   This file is part of OpenGroupware.org.
6
7   OGo is free software; you can redistribute it and/or modify it under
8   the terms of the GNU Lesser General Public License as published by the
9   Free Software Foundation; either version 2, or (at your option) any
10   later version.
11
12   OGo is distributed in the hope that it will be useful, but WITHOUT ANY
13   WARRANTY; without even the implied warranty of MERCHANTABILITY or
14   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
15   License for more details.
16
17   You should have received a copy of the GNU Lesser General Public
18   License along with OGo; see the file COPYING.  If not, write to the
19   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
20   02111-1307, USA.
21 */
22
23 #include "GCSFolder.h"
24 #include "GCSFolderManager.h"
25 #include "GCSFolderType.h"
26 #include "GCSChannelManager.h"
27 #include "GCSFieldExtractor.h"
28 #include "NSURL+GCS.h"
29 #include "EOAdaptorChannel+GCS.h"
30 #include "EOQualifier+GCS.h"
31 #include "GCSStringFormatter.h"
32 #include "common.h"
33
34 @implementation GCSFolder
35
36 static BOOL debugOn    = NO;
37 static BOOL doLogStore = NO;
38
39 static Class NSStringClass       = Nil;
40 static Class NSNumberClass       = Nil;
41 static Class NSCalendarDateClass = Nil;
42
43 static GCSStringFormatter *stringFormatter = nil;
44
45 + (void)initialize {
46   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
47   
48   debugOn         = [ud boolForKey:@"GCSFolderDebugEnabled"];
49   doLogStore      = [ud boolForKey:@"GCSFolderStoreDebugEnabled"];
50
51   NSStringClass       = [NSString class];
52   NSNumberClass       = [NSNumber class];
53   NSCalendarDateClass = [NSCalendarDate class];
54   
55   stringFormatter = [GCSStringFormatter sharedFormatter];
56 }
57
58 - (id)initWithPath:(NSString *)_path primaryKey:(id)_folderId
59   folderTypeName:(NSString *)_ftname folderType:(GCSFolderType *)_ftype
60   location:(NSURL *)_loc quickLocation:(NSURL *)_qloc
61   aclLocation:(NSURL *)_aloc
62   folderManager:(GCSFolderManager *)_fm
63 {
64   if (![_loc isNotNull]) {
65     [self errorWithFormat:@"missing quicktable parameter!"];
66     [self release];
67     return nil;
68   }
69   
70   if ((self = [super init])) {
71     self->folderManager  = [_fm    retain];
72     self->folderInfo     = [_ftype retain];
73     
74     self->folderId       = [_folderId copy];
75     self->folderName     = [[_path lastPathComponent] copy];
76     self->path           = [_path   copy];
77     self->location       = [_loc    retain];
78     self->quickLocation  = _qloc ? [_qloc   retain] : [_loc retain];
79     self->aclLocation    = [_aloc   retain];
80     self->folderTypeName = [_ftname copy];
81
82     self->ofFlags.requiresFolderSelect = 0;
83     self->ofFlags.sameTableForQuick = 
84       [self->location isEqualTo:self->quickLocation] ? 1 : 0;
85   }
86   return self;
87 }
88 - (id)init {
89   return [self initWithPath:nil primaryKey:nil
90                folderTypeName:nil folderType:nil 
91                location:nil quickLocation:nil
92                aclLocation:nil
93                folderManager:nil];
94 }
95 - (id)initWithPath:(NSString *)_path primaryKey:(id)_folderId
96   folderTypeName:(NSString *)_ftname folderType:(GCSFolderType *)_ftype
97   location:(NSURL *)_loc quickLocation:(NSURL *)_qloc
98   folderManager:(GCSFolderManager *)_fm
99 {
100   return [self initWithPath:_path primaryKey:_folderId folderTypeName:_ftname
101                folderType:_ftype location:_loc quickLocation:_qloc
102                aclLocation:nil
103                folderManager:_fm];
104 }
105
106 - (void)dealloc {
107   [self->folderManager  release];
108   [self->folderInfo     release];
109   [self->folderId       release];
110   [self->folderName     release];
111   [self->path           release];
112   [self->location       release];
113   [self->quickLocation  release];
114   [self->aclLocation    release];
115   [self->folderTypeName release];
116   [super dealloc];
117 }
118
119 /* accessors */
120
121 - (NSNumber *)folderId {
122   return self->folderId;
123 }
124
125 - (NSString *)folderName {
126   return self->folderName;
127 }
128 - (NSString *)path {
129   return self->path;
130 }
131
132 - (NSURL *)location {
133   return self->location;
134 }
135 - (NSURL *)quickLocation {
136   return self->quickLocation;
137 }
138 - (NSURL *)aclLocation {
139   return self->aclLocation;
140 }
141
142 - (NSString *)folderTypeName {
143   return self->folderTypeName;
144 }
145
146 - (GCSFolderManager *)folderManager {
147   return self->folderManager;
148 }
149 - (GCSChannelManager *)channelManager {
150   return [[self folderManager] channelManager];
151 }
152
153 - (NSString *)storeTableName {
154   return [[self location] gcsTableName];
155 }
156 - (NSString *)quickTableName {
157   return [[self quickLocation] gcsTableName];
158 }
159 - (NSString *)aclTableName {
160   return [[self aclLocation] gcsTableName];
161 }
162
163 - (BOOL)isQuickInfoStoredInContentTable {
164   return self->ofFlags.sameTableForQuick ? YES : NO;
165 }
166
167 /* channels */
168
169 - (EOAdaptorChannel *)acquireStoreChannel {
170   return [[self channelManager] acquireOpenChannelForURL:[self location]];
171 }
172 - (EOAdaptorChannel *)acquireQuickChannel {
173   return [[self channelManager] acquireOpenChannelForURL:[self quickLocation]];
174 }
175 - (EOAdaptorChannel *)acquireAclChannel {
176   return [[self channelManager] acquireOpenChannelForURL:[self aclLocation]];
177 }
178
179 - (void)releaseChannel:(EOAdaptorChannel *)_channel {
180   [[self channelManager] releaseChannel:_channel];
181   if (debugOn) [self debugWithFormat:@"released channel: %@", _channel];
182 }
183
184 - (BOOL)canConnectStore {
185   return [[self channelManager] canConnect:[self location]];
186 }
187 - (BOOL)canConnectQuick {
188   return [[self channelManager] canConnect:[self quickLocation]];
189 }
190 - (BOOL)canConnectAcl {
191   return [[self channelManager] canConnect:[self quickLocation]];
192 }
193
194 /* errors */
195
196 - (NSException *)errorVersionMismatchBetweenStoredVersion:(unsigned int)_store
197   andExpectedVersion:(unsigned int)_base
198 {
199   NSDictionary *ui;
200
201   ui = [NSDictionary dictionaryWithObjectsAndKeys:
202                        [NSNumber numberWithUnsignedInt:_base],  
203                        @"GCSExpectedVersion",
204                        [NSNumber numberWithUnsignedInt:_store],
205                        @"GCSStoredVersion",
206                        self, @"GCSFolder",
207                        nil];
208   
209   return [NSException exceptionWithName:@"GCSVersionMismatch"
210                       reason:@"Transaction conflict during a GCS modification."
211                       userInfo:ui];
212 }
213
214 - (NSException *)errorExtractorReturnedNoQuickRow:(id)_extractor
215   forContent:(NSString *)_content
216 {
217   NSDictionary *ui;
218
219   ui = [NSDictionary dictionaryWithObjectsAndKeys:
220                        self,       @"GCSFolder",
221                        _extractor, @"GCSExtractor",
222                        _content,   @"GCSContent",
223                        nil];
224   return [NSException exceptionWithName:@"GCSExtractFailed"
225                       reason:@"Quickfield extractor did not return a result!"
226                       userInfo:ui];
227 }
228
229 /* operations */
230
231 - (NSArray *)subFolderNames {
232   return [[self folderManager] listSubFoldersAtPath:[self path]
233                                recursive:NO];
234 }
235 - (NSArray *)allSubFolderNames {
236   return [[self folderManager] listSubFoldersAtPath:[self path]
237                                recursive:YES];
238 }
239
240 - (id)_fetchValueOfColumn:(NSString *)_col inContentWithName:(NSString *)_name{
241   EOAdaptorChannel *channel;
242   NSException  *error;
243   NSDictionary *row;
244   NSArray      *attrs;
245   NSString     *result;
246   NSString     *sql;
247   
248   if ((channel = [self acquireStoreChannel]) == nil) {
249     [self errorWithFormat:@"could not open storage channel!"];
250     return nil;
251   }
252   
253   /* generate SQL */
254   
255   sql = @"SELECT ";
256   sql = [sql stringByAppendingString:_col];
257   sql = [sql stringByAppendingString:@" FROM "];
258   sql = [sql stringByAppendingString:[self storeTableName]];
259   sql = [sql stringByAppendingString:@" WHERE \"c_name\" = '"];
260   sql = [sql stringByAppendingString:_name];
261   sql = [sql stringByAppendingString:@"'"];
262   
263   /* run SQL */
264   
265   if ((error = [channel evaluateExpressionX:sql]) != nil) {
266     [self errorWithFormat:@"%s: cannot execute SQL '%@': %@", 
267             __PRETTY_FUNCTION__, sql, error];
268     [self releaseChannel:channel];
269     return nil;
270   }
271   
272   /* fetch results */
273   
274   result = nil;
275   attrs  = [channel describeResults:NO /* do not beautify names */];
276   if ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil) {
277     result = [[[row objectForKey:_col] copy] autorelease];
278     if (![result isNotNull]) result = nil;
279     [channel cancelFetch];
280   }
281   
282   /* release and return result */
283   
284   [self releaseChannel:channel];
285   return result;
286 }
287
288 - (NSNumber *)versionOfContentWithName:(NSString *)_name {
289   return [self _fetchValueOfColumn:@"c_version" inContentWithName:_name];
290 }
291
292 - (NSString *)fetchContentWithName:(NSString *)_name {
293   return [self _fetchValueOfColumn:@"c_content" inContentWithName:_name];
294 }
295
296 - (NSDictionary *)fetchContentsOfAllFiles {
297   /*
298     Note: try to avoid the use of this method! The key of the dictionary
299           will be filename, the value the content.
300   */
301   NSMutableDictionary *result;
302   EOAdaptorChannel *channel;
303   NSException  *error;
304   NSDictionary *row;
305   NSArray      *attrs;
306   NSString     *sql;
307   
308   if ((channel = [self acquireStoreChannel]) == nil) {
309     [self errorWithFormat:@"%s: could not open storage channel!",
310             __PRETTY_FUNCTION__];
311     return nil;
312   }
313   
314   /* generate SQL */
315   
316   sql = @"SELECT \"c_name\", \"c_content\" FROM ";
317   sql = [sql stringByAppendingString:[self storeTableName]];
318   
319   /* run SQL */
320   
321   if ((error = [channel evaluateExpressionX:sql]) != nil) {
322     [self logWithFormat:@"ERROR(%s): cannot execute SQL '%@': %@", 
323             __PRETTY_FUNCTION__, sql, error];
324     [self releaseChannel:channel];
325     return nil;
326   }
327   
328   /* fetch results */
329   
330   result = [NSMutableDictionary dictionaryWithCapacity:128];
331   attrs  = [channel describeResults:NO /* do not beautify names */];
332   while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil) {
333     NSString *cName, *cContent;
334     
335     cName    = [row objectForKey:@"c_name"];
336     cContent = [row objectForKey:@"c_content"];
337     
338     if (![cName isNotNull]) {
339       [self errorWithFormat:@"missing c_name in row: %@", row];
340       continue;
341     }
342     if (![cContent isNotNull]) {
343       [self errorWithFormat:@"missing c_content in row: %@", row];
344       continue;
345     }
346     
347     [result setObject:cContent forKey:cName];
348   }
349   
350   /* release and return result */
351   
352   [self releaseChannel:channel];
353   return result;
354 }
355
356 /* writing content */
357
358 - (NSString *)_formatRowValue:(id)_value {
359   if (![_value isNotNull])
360     return @"NULL";
361
362   if ([_value isKindOfClass:NSStringClass])
363     return [stringFormatter stringByFormattingString:_value];
364
365   if ([_value isKindOfClass:NSNumberClass]) {
366 #if GNUSTEP_BASE_LIBRARY
367     _value = [_value stringValue];
368     return ([(NSString *)_value hasPrefix:@"Y"] || 
369             [(NSString *)_value hasPrefix:@"N"])
370       ? (id)([_value boolValue] ? @"1" : @"0")
371       : _value;
372 #endif
373     return [_value stringValue];
374   }
375   
376   if ([_value isKindOfClass:NSCalendarDateClass]) {
377     /* be smart ... convert to timestamp. Note: we loose precision. */
378     char buf[256];
379     snprintf(buf, sizeof(buf), "%i", (int)[_value timeIntervalSince1970]);
380     return [NSString stringWithCString:buf];
381   }
382   
383   [self errorWithFormat:@"cannot handle value class: %@", [_value class]];
384   return nil;
385 }
386
387 - (NSString *)_generateInsertStatementForRow:(NSDictionary *)_row
388   tableName:(NSString *)_table
389 {
390   // TODO: move to NSDictionary category?
391   NSMutableString *sql;
392   NSArray  *keys;
393   unsigned i, count;
394   
395   if (_row == nil || _table == nil)
396     return nil;
397   
398   keys = [_row allKeys];
399   
400   sql = [NSMutableString stringWithCapacity:512];
401   [sql appendString:@"INSERT INTO "];
402   [sql appendString:_table];
403   [sql appendString:@" ("];
404   
405   for (i = 0, count = [keys count]; i < count; i++) {
406     if (i != 0) [sql appendString:@", "];
407     [sql appendString:[keys objectAtIndex:i]];
408   }
409   
410   [sql appendString:@") VALUES ("];
411   
412   for (i = 0, count = [keys count]; i < count; i++) {
413     id value;
414     
415     if (i != 0) [sql appendString:@", "];
416     value = [_row objectForKey:[keys objectAtIndex:i]];
417     value = [self _formatRowValue:value];
418     [sql appendString:value];
419   }
420   
421   [sql appendString:@")"];
422   return sql;
423 }
424
425 - (NSString *)_generateUpdateStatementForRow:(NSDictionary *)_row
426   tableName:(NSString *)_table
427   whereColumn:(NSString *)_colname isEqualTo:(id)_value
428   andColumn:(NSString *)_colname2  isEqualTo:(id)_value2
429 {
430   // TODO: move to NSDictionary category?
431   NSMutableString *sql;
432   NSArray  *keys;
433   unsigned i, count;
434   
435   if (_row == nil || _table == nil)
436     return nil;
437   
438   keys = [_row allKeys];
439   
440   sql = [NSMutableString stringWithCapacity:512];
441   [sql appendString:@"UPDATE "];
442   [sql appendString:_table];
443   
444   [sql appendString:@" SET "];
445   for (i = 0, count = [keys count]; i < count; i++) {
446     id value;
447     
448     value = [_row objectForKey:[keys objectAtIndex:i]];
449     value = [self _formatRowValue:value];
450     
451     if (i != 0) [sql appendString:@", "];
452     [sql appendString:[keys objectAtIndex:i]];
453     [sql appendString:@" = "];
454     [sql appendString:value];
455   }
456   
457   [sql appendString:@" WHERE "];
458   [sql appendString:_colname];
459   [sql appendString:@" = "];
460   [sql appendString:[self _formatRowValue:_value]];
461   
462   if (_colname2 != nil) {
463     [sql appendString:@" AND "];
464     [sql appendString:_colname2];
465     [sql appendString:@" = "];
466     [sql appendString:[self _formatRowValue:_value2]];
467   }
468   
469   return sql;
470 }
471
472 - (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name
473   baseVersion:(unsigned int)_baseVersion
474 {
475   EOAdaptorChannel    *storeChannel, *quickChannel;
476   NSMutableDictionary *quickRow, *contentRow;
477   GCSFieldExtractor   *extractor;
478   NSException         *error;
479   NSNumber            *storedVersion;
480   BOOL                isNewRecord;
481   NSCalendarDate      *nowDate;
482   NSNumber            *now;
483   NSString            *qsql, *bsql;
484
485   /* check preconditions */
486   
487   if (_name == nil) {
488     return [NSException exceptionWithName:@"GCSStoreException"
489                         reason:@"no content filename was provided"
490                         userInfo:nil];
491   }
492   if (_content == nil) {
493     return [NSException exceptionWithName:@"GCSStoreException"
494                         reason:@"no content was provided"
495                         userInfo:nil];
496   }
497   
498   /* run */
499
500   error   = nil;
501   nowDate = [NSCalendarDate date];
502   now     = [NSNumber numberWithUnsignedInt:[nowDate timeIntervalSince1970]];
503   
504   if (doLogStore)
505     [self logWithFormat:@"should store content: '%@'\n%@", _name, _content];
506   
507   storedVersion = [self versionOfContentWithName:_name];
508   if (doLogStore)
509     [self logWithFormat:@"  version: %@", storedVersion];
510   isNewRecord = [storedVersion isNotNull] ? NO : YES;
511   
512   /* check whether sequence matches */
513   
514   if (_baseVersion != 0 /* use 0 to override check */) {
515     if (_baseVersion != [storedVersion unsignedIntValue]) {
516       /* version mismatch (concurrent update) */
517       return [self errorVersionMismatchBetweenStoredVersion:
518                      [storedVersion unsignedIntValue]
519                    andExpectedVersion:_baseVersion];
520     }
521   }
522   
523   /* extract quick info */
524   
525   extractor = [self->folderInfo quickExtractor];
526   if ((quickRow = [extractor extractQuickFieldsFromContent:_content]) == nil) {
527     return [self errorExtractorReturnedNoQuickRow:extractor
528                  forContent:_content];
529   }
530   
531   [quickRow setObject:_name forKey:@"c_name"];
532   
533   if (doLogStore)
534     [self logWithFormat:@"  store quick: %@", quickRow];
535   
536   /* make content row */
537   
538   contentRow = [NSMutableDictionary dictionaryWithCapacity:16];
539   
540   if (self->ofFlags.sameTableForQuick)
541     [contentRow addEntriesFromDictionary:quickRow];
542   
543   [contentRow setObject:_name forKey:@"c_name"];
544   if (isNewRecord) [contentRow setObject:now forKey:@"c_creationdate"];
545   [contentRow setObject:now forKey:@"c_lastmodified"];
546   if (isNewRecord)
547     [contentRow setObject:[NSNumber numberWithInt:0] forKey:@"c_version"];
548   else {
549     // TODO: increase version?
550     [contentRow setObject:
551                   [NSNumber numberWithInt:([storedVersion intValue] + 1)]
552                 forKey:@"c_version"];
553   }
554   [contentRow setObject:_content forKey:@"c_content"];
555   
556   /* open channels */
557   
558   if ((storeChannel = [self acquireStoreChannel]) == nil) {
559     [self errorWithFormat:@"%s: could not open storage channel!",
560             __PRETTY_FUNCTION__];
561     return nil;
562   }
563   if (!self->ofFlags.sameTableForQuick) {
564     if ((quickChannel = [self acquireQuickChannel]) == nil) {
565       [self errorWithFormat:@"%s: could not open quick channel!",
566               __PRETTY_FUNCTION__];
567       [self releaseChannel:storeChannel];
568       return nil;
569     }
570   }
571
572   /* generate SQL */
573   
574   qsql = nil;
575   if (isNewRecord) { /* insert */
576     if (!self->ofFlags.sameTableForQuick) {
577       qsql = [self _generateInsertStatementForRow:quickRow 
578                    tableName:[self quickTableName]];
579     }
580     bsql = [self _generateInsertStatementForRow:contentRow
581                  tableName:[self storeTableName]];
582   }
583   else {
584     if (!self->ofFlags.sameTableForQuick) {
585       qsql = [self _generateUpdateStatementForRow:quickRow
586                    tableName:[self quickTableName]
587                    whereColumn:@"c_name" isEqualTo:_name
588                    andColumn:nil isEqualTo:nil];
589     }
590     
591     /* also ensure in the DB that the version keeps staying the same */
592     bsql = [self _generateUpdateStatementForRow:contentRow
593                  tableName:[self storeTableName]
594                  whereColumn:@"c_name" isEqualTo:_name
595                  andColumn:(_baseVersion != 0 ? (id)@"c_version" : (id)nil)
596                  isEqualTo:(_baseVersion != 0
597                             ? [NSNumber numberWithUnsignedInt:_baseVersion] 
598                             : (NSNumber *)nil)];
599   }
600   
601   /* execute */
602   // TODO: execute in transactions
603   
604   if ((error = [storeChannel evaluateExpressionX:bsql]) != nil) {
605     [self logWithFormat:@"ERROR(%s): cannot %s content '%@': %@", 
606           __PRETTY_FUNCTION__, isNewRecord ? "insert" : "update", bsql, error];
607   }
608   // TODO: should check whether a row was actually updated?
609   
610   if (error == nil && qsql != nil) {
611     if ((error = [quickChannel evaluateExpressionX:qsql]) != nil) {
612       NSString    *delsql;
613       NSException *delErr;
614         
615       [self logWithFormat:@"ERROR(%s): cannot %s quick '%@': %@", 
616               __PRETTY_FUNCTION__, isNewRecord ? "insert" : "update", 
617               qsql, error];
618       
619       if (isNewRecord) {
620         /* insert in quick failed, so delete in content table */
621         
622         delsql = [@"DELETE FROM " stringByAppendingString:
623                      [self storeTableName]];
624         delsql = [delsql stringByAppendingString:@" WHERE c_name="];
625         delsql = [delsql stringByAppendingString:[self _formatRowValue:_name]];
626         if ((delErr = [storeChannel evaluateExpressionX:delsql]) != nil) {
627             [self errorWithFormat:
628                   @"%s: could not delete content '%@' after quick-fail:"
629                   @" %@", __PRETTY_FUNCTION__, delsql, error];
630         }
631       }
632     }
633   }
634   
635   [self releaseChannel:storeChannel];
636   if (!self->ofFlags.sameTableForQuick) [self releaseChannel:quickChannel];
637   return error;
638 }
639
640
641 - (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name {
642   /* this method does not check for concurrent writes */
643   return [self writeContent:_content toName:_name baseVersion:0];
644 }
645
646 - (NSException *)deleteContentWithName:(NSString *)_name {
647   EOAdaptorChannel *storeChannel, *quickChannel;
648   NSException *error;
649   NSString *delsql;
650   
651   /* check preconditions */
652   
653   if (_name == nil) {
654     return [NSException exceptionWithName:@"GCSDeleteException"
655                         reason:@"no content filename was provided"
656                         userInfo:nil];
657   }
658   
659   if (doLogStore)
660     [self logWithFormat:@"should delete content: '%@'", _name];
661   
662   /* open channels */
663   
664   if ((storeChannel = [self acquireStoreChannel]) == nil) {
665     [self errorWithFormat:@"could not open storage channel!"];
666     return nil;
667   }
668   if (!self->ofFlags.sameTableForQuick) {
669     if ((quickChannel = [self acquireQuickChannel]) == nil) {
670       [self errorWithFormat:@"could not open quick channel!"];
671       [self releaseChannel:storeChannel];
672       return nil;
673     }
674   }
675   
676   /* delete rows */
677
678   delsql = [@"DELETE FROM " stringByAppendingString:[self storeTableName]];
679   delsql = [delsql stringByAppendingString:@" WHERE c_name="];
680   delsql = [delsql stringByAppendingString:[self _formatRowValue:_name]];
681   if ((error = [storeChannel evaluateExpressionX:delsql]) != nil) {
682     [self errorWithFormat:
683             @"%s: cannot delete content '%@': %@", 
684           __PRETTY_FUNCTION__, delsql, error];
685   }
686   else if (!self->ofFlags.sameTableForQuick) {
687     /* content row deleted, now delete the quick row */
688     delsql = [@"DELETE FROM " stringByAppendingString:[self quickTableName]];
689     delsql = [delsql stringByAppendingString:@" WHERE c_name="];
690     delsql = [delsql stringByAppendingString:[self _formatRowValue:_name]];
691     if ((error = [quickChannel evaluateExpressionX:delsql]) != nil) {
692       [self errorWithFormat:
693               @"%s: cannot delete quick row '%@': %@", 
694             __PRETTY_FUNCTION__, delsql, error];
695       /* 
696          Note: we now have a "broken" record, needs to be periodically GCed by
697                a script!
698       */
699     }
700   }
701   
702   /* release channels and return */
703   
704   [self releaseChannel:storeChannel];
705   if (!self->ofFlags.sameTableForQuick)
706     [self releaseChannel:quickChannel];
707   return error;
708 }
709
710 - (NSException *)deleteFolder {
711   EOAdaptorChannel *channel;
712   NSString *delsql;
713   NSString *table;
714   
715   /* open channels */
716   
717   if ((channel = [self acquireStoreChannel]) == nil) {
718     [self errorWithFormat:@"could not open channel!"];
719     return nil;
720   }
721   
722   /* delete rows */
723
724   table = [self storeTableName];
725   if ([table length] > 0) {
726     delsql = [@"DROP TABLE " stringByAppendingString: table];
727     [channel evaluateExpressionX:delsql];
728   }
729   table = [self quickTableName];
730   if ([table length] > 0) {
731     delsql = [@"DROP TABLE " stringByAppendingString: table];
732     [channel evaluateExpressionX:delsql];
733   }
734   table = [self aclTableName];
735   if ([table length] > 0) {
736     delsql = [@"DROP TABLE  " stringByAppendingString: table];
737     [channel evaluateExpressionX:delsql];
738   }
739   
740   [self releaseChannel:channel];
741
742   return nil;
743 }
744
745 - (NSString *)columnNameForFieldName:(NSString *)_fieldName {
746   return _fieldName;
747 }
748
749 /* SQL generation */
750
751 - (NSString *)generateSQLForSortOrderings:(NSArray *)_so {
752   NSMutableString *sql;
753   unsigned i, count;
754
755   if ((count = [_so count]) == 0)
756     return nil;
757   
758   sql = [NSMutableString stringWithCapacity:(count * 16)];
759   for (i = 0; i < count; i++) {
760     EOSortOrdering *so;
761     NSString *column;
762     SEL      sel;
763     
764     so     = [_so objectAtIndex:i];
765     sel    = [so selector];
766     column = [self columnNameForFieldName:[so key]];
767     
768     if (i > 0) [sql appendString:@", "];
769     
770     if (sel_eq(sel, EOCompareAscending)) {
771       [sql appendString:column];
772       [sql appendString:@" ASC"];
773     }
774     else if (sel_eq(sel, EOCompareDescending)) {
775       [sql appendString:column];
776       [sql appendString:@" DESC"];
777     }
778     else if (sel_eq(sel, EOCompareCaseInsensitiveAscending)) {
779       [sql appendString:@"UPPER("];
780       [sql appendString:column];
781       [sql appendString:@") ASC"];
782     }
783     else if (sel_eq(sel, EOCompareCaseInsensitiveDescending)) {
784       [sql appendString:@"UPPER("];
785       [sql appendString:column];
786       [sql appendString:@") DESC"];
787     }
788     else {
789       [self logWithFormat:@"cannot handle sort selector in store: %@",
790               NSStringFromSelector(sel)];
791     }
792   }
793   return sql;
794 }
795
796 - (NSString *)generateSQLForQualifier:(EOQualifier *)_q {
797   NSMutableString *ms;
798   
799   if (_q == nil) return nil;
800   ms = [NSMutableString stringWithCapacity:32];
801   [_q _gcsAppendToString:ms];
802   return ms;
803 }
804
805 /* fetching */
806
807 - (NSArray *)fetchFields:(NSArray *)_flds 
808   fetchSpecification:(EOFetchSpecification *)_fs
809 {
810   EOQualifier      *qualifier;
811   NSArray          *sortOrderings;
812   EOAdaptorChannel *channel;
813   NSException      *error;
814   NSMutableString  *sql;
815   NSArray          *attrs;
816   NSMutableArray   *results;
817   NSDictionary     *row;
818   
819   qualifier     = [_fs qualifier];
820   sortOrderings = [_fs sortOrderings];
821   
822 #if 0
823   [self logWithFormat:@"FETCH: %@", _flds];
824   [self logWithFormat:@"  MATCH: %@", _q];
825 #endif
826   
827   /* generate SQL */
828
829   sql = [NSMutableString stringWithCapacity:256];
830   [sql appendString:@"SELECT "];
831   if (_flds == nil)
832     [sql appendString:@"*"];
833   else {
834     unsigned i, count;
835     
836     count = [_flds count];
837     for (i = 0; i < count; i++) {
838       if (i > 0) [sql appendString:@", "];
839       [sql appendString:[self columnNameForFieldName:[_flds objectAtIndex:i]]];
840     }
841   }
842   [sql appendString:@" FROM "];
843   [sql appendString:[self quickTableName]];
844   
845   if (qualifier != nil) {
846     [sql appendString:@" WHERE "];
847     [sql appendString:[self generateSQLForQualifier:qualifier]];
848   }
849   if ([sortOrderings count] > 0) {
850     [sql appendString:@" ORDER BY "];
851     [sql appendString:[self generateSQLForSortOrderings:sortOrderings]];
852   }
853 #if 0
854   /* limit */
855   [sql appendString:@" LIMIT "]; // count
856   [sql appendString:@" OFFSET "]; // index from 0
857 #endif
858   
859   /* open channel */
860
861   if ((channel = [self acquireStoreChannel]) == nil) {
862     [self errorWithFormat:@" could not open storage channel!"];
863     return nil;
864   }
865   
866   /* run SQL */
867
868   if ((error = [channel evaluateExpressionX:sql]) != nil) {
869     [self errorWithFormat:@"%s: cannot execute quick-fetch SQL '%@': %@", 
870             __PRETTY_FUNCTION__, sql, error];
871     [self releaseChannel:channel];
872     return nil;
873   }
874   
875   /* fetch results */
876   
877   results = [NSMutableArray arrayWithCapacity:64];
878   attrs   = [channel describeResults:NO /* do not beautify names */];
879   while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil)
880     [results addObject:row];
881   
882   /* release channels */
883   
884   [self releaseChannel:channel];
885   
886   return results;
887 }
888 - (NSArray *)fetchFields:(NSArray *)_flds matchingQualifier:(EOQualifier *)_q {
889   EOFetchSpecification *fs;
890
891   if (_q == nil)
892     fs = nil;
893   else {
894     fs = [EOFetchSpecification fetchSpecificationWithEntityName:
895                                  [self folderName]
896                                qualifier:_q
897                                sortOrderings:nil];
898   }
899   return [self fetchFields:_flds fetchSpecification:fs];
900 }
901
902 - (NSArray *)fetchAclWithSpecification:(EOFetchSpecification *)_fs {
903   EOQualifier      *qualifier;
904   NSArray          *sortOrderings;
905   EOAdaptorChannel *channel;
906   NSException      *error;
907   NSMutableString  *sql;
908   NSArray          *attrs;
909   NSMutableArray   *results;
910   NSDictionary     *row;
911   
912   qualifier     = [_fs qualifier];
913   sortOrderings = [_fs sortOrderings];
914   
915 #if 0
916   [self logWithFormat:@"FETCH: %@", _flds];
917   [self logWithFormat:@"  MATCH: %@", _q];
918 #endif
919   
920   /* generate SQL */
921
922   sql = [NSMutableString stringWithCapacity:256];
923   [sql appendString:@"SELECT c_uid, c_object, c_role"];
924   [sql appendString:@" FROM "];
925   [sql appendString:[self aclTableName]];
926   
927   if (qualifier != nil) {
928     [sql appendString:@" WHERE "];
929     [sql appendString:[self generateSQLForQualifier:qualifier]];
930   }
931   if ([sortOrderings count] > 0) {
932     [sql appendString:@" ORDER BY "];
933     [sql appendString:[self generateSQLForSortOrderings:sortOrderings]];
934   }
935 #if 0
936   /* limit */
937   [sql appendString:@" LIMIT "]; // count
938   [sql appendString:@" OFFSET "]; // index from 0
939 #endif
940   
941   /* open channel */
942
943   if ((channel = [self acquireAclChannel]) == nil) {
944     [self errorWithFormat:@"could not open acl channel!"];
945     return nil;
946   }
947   
948   /* run SQL */
949
950   if ((error = [channel evaluateExpressionX:sql]) != nil) {
951     [self errorWithFormat:@"%s: cannot execute acl-fetch SQL '%@': %@", 
952             __PRETTY_FUNCTION__, sql, error];
953     [self releaseChannel:channel];
954     return nil;
955   }
956   
957   /* fetch results */
958   
959   results = [NSMutableArray arrayWithCapacity:64];
960   attrs   = [channel describeResults:NO /* do not beautify names */];
961   while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil)
962     [results addObject:row];
963   
964   /* release channels */
965   
966   [self releaseChannel:channel];
967   
968   return results;
969 }
970 - (NSArray *) fetchAclMatchingQualifier:(EOQualifier *)_q {
971   EOFetchSpecification *fs;
972
973   if (_q == nil)
974     fs = nil;
975   else {
976     fs = [EOFetchSpecification fetchSpecificationWithEntityName:
977                                  [self folderName]
978                                qualifier:_q
979                                sortOrderings:nil];
980   }
981   return [self fetchAclWithSpecification:fs];
982 }
983
984 - (void) deleteAclMatchingQualifier:(EOQualifier *)_q {
985   EOFetchSpecification *fs;
986
987   if (_q != nil) {
988     fs = [EOFetchSpecification fetchSpecificationWithEntityName:
989                                  [self folderName]
990                                qualifier:_q
991                                sortOrderings:nil];
992     [self deleteAclWithSpecification:fs];
993   }
994 }
995
996 - (void)deleteAclWithSpecification:(EOFetchSpecification *)_fs
997 {
998   EOQualifier      *qualifier;
999   EOAdaptorChannel *channel;
1000   NSException      *error;
1001   NSMutableString  *sql;
1002   
1003   qualifier     = [_fs qualifier];
1004   if (qualifier != nil) {
1005     sql = [NSMutableString stringWithCapacity:256];
1006     [sql appendString:@"DELETE FROM "];
1007     [sql appendString:[self aclTableName]];
1008     [sql appendString:@" WHERE "];
1009     [sql appendString:[self generateSQLForQualifier:qualifier]];
1010   }
1011   
1012   /* open channel */
1013
1014   if ((channel = [self acquireAclChannel]) == nil) {
1015     [self errorWithFormat:@"could not open acl channel!"];
1016     return;
1017   }
1018   
1019   /* run SQL */
1020
1021   if ((error = [channel evaluateExpressionX:sql]) != nil) {
1022     [self errorWithFormat:@"%s: cannot execute acl-fetch SQL '%@': %@", 
1023             __PRETTY_FUNCTION__, sql, error];
1024     [self releaseChannel:channel];
1025     return;
1026   }
1027   
1028   [self releaseChannel:channel];
1029 }
1030
1031 /* description */
1032
1033 - (NSString *)description {
1034   NSMutableString *ms;
1035   id tmp;
1036   
1037   ms = [NSMutableString stringWithCapacity:256];
1038   [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
1039
1040   if (self->folderId)
1041     [ms appendFormat:@" id=%@", self->folderId];
1042   else
1043     [ms appendString:@" no-id"];
1044
1045   if ((tmp = [self path]))           [ms appendFormat:@" path=%@", tmp];
1046   if ((tmp = [self folderTypeName])) [ms appendFormat:@" type=%@", tmp];
1047   if ((tmp = [self location]))
1048     [ms appendFormat:@" loc=%@", [tmp absoluteString]];
1049   
1050   [ms appendString:@">"];
1051   return ms;
1052 }
1053
1054 @end /* GCSFolder */