]> err.no Git - sope/blob - sope-gdl1/GDLAccess/EOAdaptorDataSource.m
added missing inline pathes
[sope] / sope-gdl1 / GDLAccess / EOAdaptorDataSource.m
1 /* 
2    EOAdaptorDataSource.m
3    
4    Copyright (C) SKYRIX Software AG and Helge Hess
5
6    Date:   1999-2005
7
8    This file is part of the GNUstep Database Library.
9
10    This library is free software; you can redistribute it and/or
11    modify it under the terms of the GNU Library General Public
12    License as published by the Free Software Foundation; either
13    version 2 of the License, or (at your option) any later version.
14
15    This library is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18    Library General Public License for more details.
19
20    You should have received a copy of the GNU Library General Public
21    License along with this library; see the file COPYING.LIB.
22    If not, write to the Free Software Foundation,
23    59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 */
25
26 /*
27   column-names must have small letterso
28   hints:
29     EOPrimaryKeyAttributeNamesHint - name of primary key attributes
30     EOPrimaryKeyAttributesHint     - primary key attributes
31     EOFetchResultTimeZone          - NSTimeZone object for dates
32 */
33
34 #define EOAdaptorDataSource_DEBUG 0
35
36 #include <NGExtensions/NGExtensions.h>
37 #include <GDLAccess/GDLAccess.h>
38 #include <EOControl/EOControl.h>
39 #include "common.h"
40
41 NSString *EOPrimaryKeyAttributeNamesHint = @"EOPrimaryKeyAttributeNamesHint";
42 NSString *EOPrimaryKeyAttributesHint     = @"EOPrimaryKeyAttributesHint";
43 NSString *EOFetchResultTimeZone          = @"EOFetchResultTimeZoneHint";
44
45 @interface NSObject(Private)
46 - (NSString *)newKeyExpression;
47 - (NSArray *)_primaryKeyAttributesForTableName:(NSString *)_entityName
48   channel:(EOAdaptorChannel *)_adChannel;
49 - (NSArray *)_primaryKeyAttributeNamesForTableName:(NSString *)_entityName
50   channel:(EOAdaptorChannel *)_adChannel;
51 @end
52
53 static EONull *null = nil;
54
55 @interface EOAdaptorChannel(Internals)
56 - (NSArray *)_sortAttributesForSelectExpression:(NSArray *)_attrs;
57 @end /* EOAdaptorChannel(Internals) */
58
59 @interface EOQualifier(SqlExpression)
60 - (NSString *)sqlExpressionWithAdaptor:(EOAdaptor *)_adaptor
61                             attributes:(NSArray *)_attrs;
62 @end /* EOQualifier(SqlExpression) */
63
64 @interface EOAdaptorDataSource(Private)
65
66 - (NSMutableString *)_selectListWithChannel:(EOAdaptorChannel *)_adChan;
67 - (NSString *)_whereExprWithChannel:(EOAdaptorChannel *)_adChan;
68 - (NSString *)_whereClauseForGlobaID:(EOKeyGlobalID *)_gid
69   adaptor:(EOAdaptor *)_adaptor channel:(EOAdaptorChannel *)_adChan;
70
71 - (NSString *)_orderByExprForAttributes:(NSArray *)_attrs 
72   andPatchSelectList:(NSMutableString *)selectList
73   withChannel:(EOAdaptorChannel *)_adChan;
74
75 - (NSDictionary *)_mapAttrsWithValues:(NSDictionary *)_keyValues
76   tableName:(NSString *)_tableName channel:(EOAdaptorChannel *)_adChan;
77
78 @end /* EOAdaptorDataSource(Private) */
79
80 @interface EOAdaptorDataSource(Internals)
81 - (id)initWithAdaptorChannel:(EOAdaptorChannel *)_channel
82   connectionDictionary:(NSDictionary *)_connDict;
83 @end /* EOAdaptorDataSource(Internals) */
84
85 @interface EODataSource(Notificiations)
86 - (void)postDataSourceChangedNotification;
87 @end
88
89 @implementation EOAdaptorDataSource
90
91 static Class NSCalendarDateClass = nil;
92 static NSNotificationCenter *nc = nil;
93
94 static NSNotificationCenter *getNC(void ) {
95   if (nc == nil)
96     nc = [[NSNotificationCenter defaultCenter] retain];
97   return nc;
98 }
99
100 + (void)initialize {
101   NSAssert2([super version] == 1,
102             @"invalid superclass (%@) version %i !",
103             NSStringFromClass([self superclass]), [super version]);
104
105   null = [[EONull null] retain];
106   NSCalendarDateClass = [NSCalendarDate class];
107 }
108 + (int)version {
109   return [super version] + 1; /* v2 */
110 }
111
112 - (id)initWithAdaptorName:(NSString *)_adName
113   connectionDictionary:(NSDictionary *)_dict
114   primaryKeyGenerationDictionary:(NSDictionary *)_pkGen
115 {
116   EOAdaptor        *ad  = nil;
117   EOAdaptorContext *ctx = nil;
118   EOAdaptorChannel *adc = nil;
119
120   ad  = [EOAdaptor adaptorWithName:_adName];
121   [ad setConnectionDictionary:_dict];
122   [ad setPkeyGeneratorDictionary:_pkGen];
123   ctx = [ad createAdaptorContext];
124   adc = [ctx createAdaptorChannel];
125   
126   return [self initWithAdaptorChannel:adc connectionDictionary:_dict];
127 }
128
129 - (id)initWithAdaptorChannel:(EOAdaptorChannel *)_channel {
130   return [self initWithAdaptorChannel:_channel connectionDictionary:nil];
131 }
132
133 - (id)initWithAdaptorChannel:(EOAdaptorChannel *)_channel
134   connectionDictionary:(NSDictionary *)_connDict
135 {
136   if ((self = [super init])) {
137     self->adChannel            = [_channel retain];
138     self->connectionDictionary = [_connDict copy];
139     self->commitTransaction    = NO;
140     
141     [getNC()
142           addObserver:self selector:@selector(_adDataSourceChanged:)
143           name:@"EOAdaptorDataSourceChanged" object:nil];
144   }
145   return self;
146 }
147
148 - (void)dealloc {
149   [getNC() removeObserver:self];
150   [self->fetchSpecification   release];
151   [self->connectionDictionary release];
152   [self->adChannel            release];
153   [self->__attributes         release];
154   [self->__qualifier          release];
155   [super dealloc];
156 }
157
158 /* notifications */
159
160 - (void)postDataSourceItselfChangedNotification {
161   [super postDataSourceChangedNotification];
162 }
163
164 - (void)postDataSourceChangedNotification {
165   [getNC() postNotificationName:@"EOAdaptorDataSourceChanged" object:self];
166   [self postDataSourceItselfChangedNotification];
167 }
168
169 - (void)_adDataSourceChanged:(NSNotification *)_notification {
170   EOAdaptorDataSource *ads;
171   
172   if ((ads = [_notification object]) == self)
173     return;
174   if (ads == nil)
175     return;
176   
177   [self postDataSourceItselfChangedNotification];
178   
179 #if 0
180   if (![ads->connectionDictionary isEqual:self->connectionDictionary])
181     /* different database ... */
182     return;
183   
184   if ((ads->fetchSpecification == nil) || (self->fetchSpecification == nil)) {
185     [self postDataSourceChangedNotification];
186     return;
187   }
188   
189   /* check fspecs for entity ... */
190   if ([[ads->fetchSpecification entityName]
191                                 isEqualToString:
192                                   [self->fetchSpecification entityName]]) {
193     [self postDataSourceChangedNotification];
194     return;
195   }
196 #endif
197 }
198
199 /* fetching */
200
201 - (EOAdaptorChannel *)beginTransaction {
202   EOAdaptorContext *ctx = nil;
203
204   [self openChannel];
205   ctx = [self->adChannel adaptorContext];
206   if ([ctx hasOpenTransaction] == NO) {
207     [ctx beginTransaction];
208     self->commitTransaction = YES;
209   }
210   return self->adChannel;
211 }
212   
213 - (void)commitTransaction {
214   if (self->commitTransaction) {
215     [[self->adChannel adaptorContext] commitTransaction];
216     //    [self->adChannel closeChannel];
217     self->commitTransaction = NO;
218   }
219 }
220
221 - (void)rollbackTransaction {
222   [[self->adChannel adaptorContext] rollbackTransaction];
223   //  [self->adChannel closeChannel];  
224   self->commitTransaction = NO;
225 }
226
227 - (void)openChannel {
228   if (![self->adChannel isOpen]) {
229     [self->adChannel openChannel];
230   }
231 }
232
233 - (void)closeChannel {
234   if (![self->adChannel isOpen])
235     return;
236
237   if ([[self->adChannel adaptorContext] transactionNestingLevel]) {
238     NSLog(@"%s was called while transaction in progress, rollback will called",
239           __PRETTY_FUNCTION__);
240     [self rollbackTransaction];
241   }
242   [self->adChannel closeChannel];
243 }
244
245 - (NSArray *)fetchObjects {
246   // TODO: split up this HUGE method!
247   // TODO: all the SQL gen code should be moved to an appropriate object
248   NSString         *entityName  = nil;
249   NSString         *whereExpr   = nil;
250   NSString         *orderByExpr = nil;
251   NSMutableString  *selectList  = nil;  
252   NSMutableString  *expression  = nil;
253   NSMutableArray   *result      = nil;
254   NSArray          *attrs       = nil;
255   EOAdaptor        *adaptor     = nil;
256   NSArray          *pKeys       = nil;
257   EOQualifier      *qual        = nil;
258   EOAdaptorChannel *adChan      = nil;
259   int              pKeyCnt      = 0;
260   NSTimeZone       *tz          = nil;
261   BOOL             localComTrans;
262
263   if (self->fetchSpecification == nil) {
264     // TODO: make that a lastException and just return nil
265     [NSException raise:NSInvalidArgumentException
266                  format:@"fetchSpecification required for table name"];
267     return nil;
268   }
269   
270   entityName = [self->fetchSpecification entityName];
271   
272   if (entityName == nil || [entityName length] == 0) {
273     [NSException raise:NSInvalidArgumentException
274                  format:@"missing entity name"];
275   }
276   localComTrans = [[self->adChannel adaptorContext] hasOpenTransaction]
277     ? NO : YES;
278   
279   adChan  = [self beginTransaction];
280   pKeys   = [self _primaryKeyAttributeNamesForTableName:entityName
281                   channel:adChan];
282
283   if ((pKeyCnt = [pKeys count]) == 0) {
284     NSLog(@"ERROR[%s]: missing primary keys for table %@",
285           __PRETTY_FUNCTION__, entityName);
286     return nil;
287   }
288   qual  = [self->fetchSpecification qualifier];
289   
290   if (qual == nil)
291     qual = [EOQualifier qualifierWithQualifierFormat:@"1=1"];
292   
293   ASSIGN(self->__qualifier, qual);
294   
295   attrs = [adChan attributesForTableName:entityName];
296     
297   if (attrs == nil) {
298     RELEASE(self->__qualifier); self->__qualifier = nil;
299
300     NSLog(@"ERROR[%s]: could not find table '%@' in database.",
301           __PRETTY_FUNCTION__, entityName);
302     [self rollbackTransaction];
303     return nil;
304   }
305   if ([attrs count] == 0) {
306     RELEASE(self->__qualifier); self->__qualifier = nil;
307       
308     NSLog(@"ERROR[%s]: missing columns in table '%@'.",
309           __PRETTY_FUNCTION__, entityName);
310     [self rollbackTransaction];
311     return nil;
312   }
313   tz = [[self->fetchSpecification hints] objectForKey:EOFetchResultTimeZone];
314     
315   ASSIGN(self->__attributes, attrs);
316   adaptor = [[adChan adaptorContext] adaptor];
317   {
318     NSArray *a;
319     NSSet *tableKeys     = nil;
320     NSSet *qualifierKeys = nil;
321     
322     a = [[[qual allQualifierKeys] allObjects] map:@selector(lowercaseString)];
323     qualifierKeys = [[NSSet alloc] initWithArray:a];
324     a = [[attrs map:@selector(columnName)] map:@selector(lowercaseString)];
325     tableKeys     = [[NSSet alloc] initWithArray:a];
326     
327     if ([qualifierKeys isSubsetOfSet:tableKeys] == NO) {
328       NSString *format = nil;
329         
330       format = [NSString stringWithFormat:
331                          @"EOAdaptorDataSource: using unmapped key in "
332                          @"qualifier tableKeys <%@>  qualifierKeys <%@> "
333                          @"qualifier <%@>",
334                          tableKeys, qualifierKeys, qual];
335         
336       RELEASE(self->__attributes); self->__attributes = nil;
337       RELEASE(self->__qualifier);  self->__qualifier  = nil;
338       RELEASE(tableKeys);          tableKeys          = nil;
339       [self rollbackTransaction];
340       [[[InvalidQualifierException alloc] initWithFormat:format] raise];
341     }
342     RELEASE(tableKeys);     tableKeys     = nil;
343     RELEASE(qualifierKeys); qualifierKeys = nil;
344   }
345   
346   whereExpr   = [self _whereExprWithChannel:adChan];
347   selectList  = [self _selectListWithChannel:adChan];
348   orderByExpr = [self _orderByExprForAttributes:attrs
349                       andPatchSelectList:selectList
350                       withChannel:adChan];
351   
352   expression = [[NSMutableString alloc] initWithCapacity:256];
353   [expression appendString:@"SELECT "];
354
355   if ([self->fetchSpecification usesDistinct])
356     [expression appendString:@"DISTINCT "];
357
358   [expression appendString:selectList];
359   [expression appendString:@" FROM "];
360   [expression appendString:entityName];
361   if ([whereExpr length] > 0) {
362     [expression appendString:@" WHERE "];
363     [expression appendString:whereExpr];
364   }
365   if (orderByExpr != nil && [orderByExpr length] > 0) {
366     [expression appendString:@" ORDER BY "];
367     [expression appendString:orderByExpr];
368   }
369   
370   if (![adChan evaluateExpression:expression]) {
371     RELEASE(self->__attributes); self->__attributes = nil;
372     RELEASE(self->__qualifier);  self->__qualifier  = nil;
373     AUTORELEASE(expression);
374     [adChan cancelFetch];
375     [self rollbackTransaction];
376     [[[EOAdaptorException alloc]
377        initWithFormat:@"evaluateExpression of %@ failed", expression] raise];
378   }
379   result = [NSMutableArray arrayWithCapacity:64];
380   {
381     NSMutableDictionary *row = nil;
382     unsigned fetchCnt   = 0;
383     unsigned fetchLimit = 0;
384     unsigned attrCnt    = 0;
385     id       *values    = NULL;
386     id       *keys      = NULL;
387     
388     /* Note: those are reused in the inner loop */
389     attrCnt    = [attrs count];
390     values     = calloc(attrCnt + 2, sizeof(id));
391     keys       = calloc(attrCnt + 2, sizeof(id));
392     fetchLimit = [self->fetchSpecification fetchLimit];
393     
394     while ((row = [adChan fetchAttributes:attrs withZone:NULL]) != nil) {
395       NSEnumerator        *enumerator = nil;
396       id                  attr        = nil;
397       int                 rowCnt      = 0;
398       NSDictionary        *r          = nil;
399       id                  *pKeyVs     = NULL;
400       int                 pKeyVCnt    = 0;
401
402       pKeyVs     = calloc(pKeyCnt, sizeof(id));
403       enumerator = [attrs objectEnumerator];
404       
405       while ((attr = [enumerator nextObject]) != nil) {
406         id       obj;
407         NSString *cn;
408
409         obj = [row objectForKey:[(EOAttribute *)attr name]];
410         
411         if (obj == nil)
412           continue;
413         
414         if (tz != nil && [obj isKindOfClass:NSCalendarDateClass])
415           [obj setTimeZone:tz];
416         
417         cn             = [[attr columnName] lowercaseString];
418         values[rowCnt] = obj;
419         keys[rowCnt]   = cn;
420         rowCnt++;
421
422         if ([pKeys containsObject:cn]) {
423           int idx;
424
425           idx = [pKeys indexOfObject:cn]; 
426           NSAssert4(idx <= (pKeyCnt - 1) && pKeyVs[idx] == nil,
427                     @"internal inconsistency in EOAdaptorDataSource "
428                     @"while fetch idx[%d] > (pKeyCnt - 1)[%d] "
429                     @"pKeyVs[idx] (%@[%d]);", idx, (pKeyCnt - 1),
430                     pKeyVs[idx], idx);
431
432           pKeyVs[idx] = obj;
433           pKeyVCnt++;
434         }
435       }
436       if (pKeyCnt != pKeyVCnt)
437         NSAssert(NO, @"internal inconsistency in EOAdaptorDataSource "
438                  @"while fetch");
439       
440       {
441         EOGlobalID *gid;
442
443         gid = [EOKeyGlobalID globalIDWithEntityName:entityName
444                              keys:pKeyVs keyCount:pKeyVCnt zone:NULL];
445
446         if (self->connectionDictionary) {
447           gid = [[EOAdaptorGlobalID alloc] initWithGlobalID:gid
448                                            connectionDictionary:
449                                            self->connectionDictionary];
450           AUTORELEASE(gid);
451         }
452         values[rowCnt] = gid;
453         keys[rowCnt]   = @"globalID";
454         rowCnt++;
455       }
456       fetchCnt++;
457       r = [[NSMutableDictionary alloc]
458             initWithObjects:values forKeys:keys count:rowCnt];
459       [result addObject:r];
460       [r release]; r = nil;
461       if (pKeyVs) free(pKeyVs); pKeyVs = NULL;
462       if (fetchLimit == fetchCnt)
463         break;
464     }
465     if (values) free(values); values = NULL;
466     if (keys)   free(keys);   keys   = NULL;
467   }
468   [adChan cancelFetch];
469   if (localComTrans)
470     [self commitTransaction];
471   
472   [expression         release]; expression         = nil;
473   [self->__qualifier  release]; self->__qualifier  = nil;
474   [self->__attributes release]; self->__attributes = nil;
475   return result;
476 }
477
478 - (id)createObject {
479   return [NSMutableDictionary dictionary];
480 }
481
482 - (void)insertObject:(id)_obj {
483   NSString         *key        = nil;
484   NSString         *tableName  = nil;
485   NSMutableString  *expression = nil;
486   EOAdaptor        *adaptor    = nil;
487   NSArray          *pKeys      = nil;
488   id               obj         = nil;
489   EOAdaptorChannel *adChan     = nil;
490   
491   int      oVCnt                = 0;
492   NSString **objectKeyAttrValue = NULL;
493
494   NSEnumerator *enumerator = nil;
495   id           pKey        = nil;
496
497   BOOL         localComTrans;
498
499   if ([[self->adChannel adaptorContext] hasOpenTransaction])
500     localComTrans = NO;
501   else
502     localComTrans = YES;
503
504   adChan  = [self beginTransaction];
505   adaptor = [[adChan adaptorContext] adaptor];
506
507   if ((tableName = [self->fetchSpecification entityName]) == nil) {
508     [self rollbackTransaction];
509     [NSException raise:NSInvalidArgumentException
510                  format:@"couldn`t insert obj %@ missing entityName in "
511                  @"fetchSpecification", _obj];
512   }
513   
514   /* create or apply primary keys */
515 #if EOAdaptorDataSource_DEBUG
516   NSLog(@"insert obj %@", _obj);
517 #endif  
518   
519   pKeys = [self _primaryKeyAttributeNamesForTableName:tableName channel:adChan];
520
521 #if EOAdaptorDataSource_DEBUG  
522   NSLog(@"got primary keys %@", pKeys);
523 #endif
524   
525   objectKeyAttrValue = calloc([pKeys count], sizeof(id));
526   enumerator         = [pKeys objectEnumerator];
527   
528   while ((pKey = [enumerator nextObject])) {
529     id pKeyObj;
530     
531     pKeyObj = [_obj valueForKey:pKey];
532
533 #if EOAdaptorDataSource_DEBUG
534     NSLog(@"pk in obj %@:<%@> ", pKey, pKeyObj);
535 #endif
536     
537     if (![pKeyObj isNotNull]) {
538       /* try to build primary key */
539       NSString     *newKeyExpr = nil;
540       NSDictionary *row        = nil;
541
542 #if EOAdaptorDataSource_DEBUG
543       NSLog(@"pKeyObj !isNotNull");
544 #endif      
545       
546       if ([pKeys count] != 1) {
547         [self rollbackTransaction];
548         [NSException raise:NSInternalInconsistencyException
549                      format:@"more than one primary key, "
550                      @"and primary key for %@ is not set", pKey];
551       }
552       if (![adaptor respondsToSelector:@selector(newKeyExpression)]) {
553         [self rollbackTransaction];
554         [NSException raise:NSInternalInconsistencyException
555                      format:@"got no newkey expression, insert failed"];
556       }
557       newKeyExpr = [(id)adaptor newKeyExpression];
558       if (newKeyExpr == nil) {
559         [self rollbackTransaction];
560         [NSException raise:NSInternalInconsistencyException
561                      format:@"missing newKeyExpression for adaptor[%@] %@...",
562                        NSStringFromClass([adaptor class]), adaptor];
563       }
564       if (![adChan evaluateExpression:newKeyExpr]) {
565         [adChan cancelFetch];
566         [self rollbackTransaction];
567         [[[EOAdaptorException alloc]
568                  initWithFormat:@"couldn`t evaluate new key expression %@",
569                  newKeyExpr] raise];
570       }
571       row = [adChan fetchAttributes:[adChan describeResults]
572                     withZone:NULL];
573       [adChan cancelFetch];
574       if ((key = [[row objectEnumerator] nextObject]) == nil) {
575         [self rollbackTransaction];
576         [[[EOAdaptorException alloc]
577                         initWithFormat:@"couldn`t fetch primary key"] raise];;
578       }
579       objectKeyAttrValue[oVCnt++] = key;
580       [_obj takeValue:key forKey:pKey];
581 #if EOAdaptorDataSource_DEBUG
582       NSLog(@"_obj %@ after takeValue:%@ forKey:%@", _obj, key, pKey);
583 #endif      
584     }
585     else {
586       objectKeyAttrValue[oVCnt++] = pKeyObj;
587 #if EOAdaptorDataSource_DEBUG
588       NSLog(@"objectKeyAttrValue takeValue %@ for idx %d", pKeyObj, oVCnt);
589 #endif      
590     }
591   }
592
593   /* construct SQL INSERT expression .. */
594   
595   expression = [[NSMutableString alloc] initWithCapacity:256];
596   [expression appendString:@"INSERT INTO "];
597   [expression appendString:tableName];
598   [expression appendString:@"("];
599   {
600     NSDictionary *objects    = nil;
601     NSEnumerator *enumerator = nil;
602     NSArray      *allKeys    = nil;
603     BOOL         isFirst     = YES;
604     
605     objects    = [self _mapAttrsWithValues:_obj
606                        tableName:tableName
607                        channel:adChan];
608     allKeys    = [objects allKeys];
609     enumerator = [allKeys objectEnumerator];
610     while ((obj = [enumerator nextObject])) {
611       if (isFirst) 
612         isFirst = NO;
613       else 
614         [expression appendString:@", "];
615       [expression appendString:[adaptor formatAttribute:obj]];
616     }
617     [expression appendString:@") VALUES ("];
618     enumerator = [allKeys objectEnumerator];
619     isFirst = YES;
620     while ((obj = [enumerator nextObject])) {
621       id value;
622         
623       if (isFirst)
624         isFirst = NO;
625       else
626         [expression appendString:@", "];
627       value = [objects objectForKey:obj];
628       if (value == nil) value = null;
629       [expression appendString:[adaptor formatValue:value forAttribute:obj]];
630     }
631   }
632   [expression appendString:@")"];
633
634   /* execute insert in SQL server .. */
635   
636   if (![adChan evaluateExpression:expression]) {
637     [adChan cancelFetch];
638     enumerator = [pKeys objectEnumerator];
639     while ((pKey = [enumerator nextObject])) {
640       [_obj takeValue:[EONull null] forKey:pKey];
641     }
642     [self rollbackTransaction];
643     AUTORELEASE(expression);
644     [[[EOAdaptorException alloc]
645        initWithFormat:@"evaluateExpression %@ failed", expression] raise];
646   }
647   [adChan cancelFetch];
648   if (localComTrans)
649     [self commitTransaction];
650
651   /* construct new global id for record */
652   
653   {
654     EOGlobalID *gid;
655       
656     gid = [EOKeyGlobalID globalIDWithEntityName:tableName
657                          keys:objectKeyAttrValue keyCount:oVCnt zone:NULL];
658     if (self->connectionDictionary != nil) {
659       EOAdaptorGlobalID *agid = nil;
660
661       agid = [[EOAdaptorGlobalID alloc] initWithGlobalID:gid
662                                         connectionDictionary:
663                                         self->connectionDictionary];
664       AUTORELEASE(agid);
665       gid = agid;
666     }
667     [_obj takeValue:gid forKey:@"globalID"];
668   }
669   
670   RELEASE(expression); expression = NULL;
671   
672   /* mark datasource as changed */
673   
674   [self postDataSourceChangedNotification];
675 }
676
677 - (void)updateObject:(id)_obj {
678   NSString         *whereClause = nil;
679   NSMutableString  *expression  = nil;
680   EOAdaptor        *adaptor     = nil;
681   EOKeyGlobalID    *gid         = nil;
682   NSEnumerator     *enumerator  = nil;
683   NSString         *tableName   = nil;
684   EOAttribute      *attr        = nil;
685   BOOL             isFirst      = YES;
686   NSDictionary     *objects     = nil;
687   EOAdaptorChannel *adChan      = nil;
688
689   BOOL localComTrans;
690   
691   if ((gid = [_obj valueForKey:@"globalID"]) == nil) {
692     [NSException raise:NSInvalidArgumentException
693                  format:@"missing globalID, couldn`t update"];
694   }
695   if ([gid isKindOfClass:[EOAdaptorGlobalID class]]) {
696     NSDictionary *conD = nil;
697
698     conD = [(EOAdaptorGlobalID *)gid connectionDictionary];
699     if (![conD isEqualToDictionary:self->connectionDictionary]) {
700       [NSException raise:NSInvalidArgumentException
701                    format:@"try to update object %@ in "
702                    @"wrong AdaptorDataSource %@", _obj, self];
703     }
704     gid = (EOKeyGlobalID *)[(EOAdaptorGlobalID *)gid globalID];
705   }
706   if ([[self->adChannel adaptorContext] hasOpenTransaction])
707     localComTrans = NO;
708   else
709     localComTrans = YES;
710
711   adChan      = [self beginTransaction];
712   tableName   = [gid entityName];
713   adaptor     = [[adChan adaptorContext] adaptor];
714   whereClause = [self _whereClauseForGlobaID:gid adaptor:adaptor
715                       channel:adChan];
716   if (whereClause == nil) {
717     [self rollbackTransaction];
718     return;
719   }
720   expression = [[NSMutableString alloc] initWithCapacity:256];
721   [expression appendString:@"UPDATE "];
722   [expression appendString:[gid entityName]];
723   [expression appendString:@" SET "];
724
725   objects    = [self _mapAttrsWithValues:_obj tableName:tableName
726                      channel:adChan];
727   enumerator = [objects keyEnumerator];
728
729   while ((attr = [enumerator nextObject])) {
730     id value;
731       
732     if (isFirst)
733       isFirst = NO;
734     else
735       [expression appendString:@", "];
736     [expression appendString:[adaptor formatAttribute:attr]];
737     [expression appendString:@"="];
738       
739     value = [objects objectForKey:attr];
740     if (value == nil) value = null;
741       
742     [expression appendString:[adaptor formatValue:value forAttribute:attr]];
743   }
744   [expression appendString:@" WHERE "];
745   [expression appendString:whereClause];
746   if (![adChan evaluateExpression:expression]) {
747     [adChan cancelFetch];
748     [self rollbackTransaction];
749     AUTORELEASE(expression);
750     [[[EOAdaptorException alloc]
751        initWithFormat:@"evaluate expression %@ failed", expression] raise];
752   }
753   [adChan cancelFetch];
754   if (localComTrans)
755     [self commitTransaction];
756   
757   RELEASE(expression); expression = nil;
758
759   {
760     EOGlobalID   *newGID;
761     NSArray      *attrs;
762     NSEnumerator *enumerator;
763     id           *objs;
764     int          objCnt;
765     NSString     *attr;
766
767     attrs      = [self _primaryKeyAttributeNamesForTableName:[gid entityName]
768                        channel:adChan];
769     enumerator = [attrs objectEnumerator];
770     objCnt     = 0;
771     objs       = calloc([attrs count], sizeof(id));
772     
773     while ((attr = [enumerator nextObject])) {
774       objs[objCnt] = [_obj valueForKey:attr];
775       objCnt++;
776     }
777     newGID = [EOKeyGlobalID globalIDWithEntityName:[gid entityName]
778                             keys:objs keyCount:objCnt zone:NULL];
779     if (self->connectionDictionary != nil) {
780       newGID = [[EOAdaptorGlobalID alloc] initWithGlobalID:newGID
781                                           connectionDictionary:
782                                           self->connectionDictionary];
783       [newGID autorelease];
784     }
785     [(NSMutableDictionary *)_obj setObject:newGID forKey:@"globalID"];
786   }
787   [self postDataSourceChangedNotification];  
788 }
789
790 - (void)deleteObject:(id)_obj {
791   NSString         *whereClause = nil;
792   NSMutableString  *expression  = nil;
793   EOKeyGlobalID    *gid         = nil;
794   EOAdaptorChannel *adChan      = nil;
795
796   BOOL localComTrans;
797
798   if ((gid = [_obj valueForKey:@"globalID"]) == nil) {
799     [NSException raise:NSInvalidArgumentException
800                  format:@"missing globalID, could not delete"];
801   }
802   if ([gid isKindOfClass:[EOAdaptorGlobalID class]]) {
803     NSDictionary *conD = nil;
804
805     conD = [(EOAdaptorGlobalID *)gid connectionDictionary];
806     if (![conD isEqualToDictionary:self->connectionDictionary]) {
807       [NSException raise:NSInvalidArgumentException
808                    format:@"try to delete object %@ in wrong "
809                    @"AdaptorDataSource %@", _obj, self];
810     }
811     gid = (EOKeyGlobalID *)[(EOAdaptorGlobalID *)gid globalID];
812   }
813   
814   if ([[self->adChannel adaptorContext] hasOpenTransaction])
815     localComTrans = NO;
816   else
817     localComTrans = YES;
818   
819   adChan      = [self beginTransaction];
820   whereClause = [self _whereClauseForGlobaID:gid
821                       adaptor:[[adChan adaptorContext] adaptor] channel:adChan];
822   if (whereClause == nil) {
823     [self rollbackTransaction];
824     return;
825   }
826   expression = [[NSMutableString alloc] initWithCapacity:256];
827   [expression appendString:@"DELETE FROM "];
828   [expression appendString:[gid entityName]];
829   [expression appendString:@" WHERE "];
830   [expression appendString:whereClause];
831   if (![adChan evaluateExpression:expression]) {
832     [adChan cancelFetch];
833     [self rollbackTransaction];
834     AUTORELEASE(expression);
835     [[[EOAdaptorException alloc]
836        initWithFormat:@"couldn`t evaluate expression %@ failed",
837        expression] raise];
838   }
839   [adChan cancelFetch];
840   if (localComTrans)
841     [self commitTransaction];
842   RELEASE(expression); expression = nil;
843   [self postDataSourceChangedNotification];    
844 }
845
846 - (void)setFetchSpecification:(EOFetchSpecification *)_fs {
847   if (![self->fetchSpecification isEqual:_fs]) {
848 #if DEBUG && 0
849     NSLog(@"%s: 0x%08X: fetch-spec mismatch:\n%@\n%@",
850           __PRETTY_FUNCTION__, self,
851           self->fetchSpecification, _fs);
852 #endif
853     
854     ASSIGNCOPY(self->fetchSpecification, _fs);
855     
856     [self postDataSourceItselfChangedNotification];
857   }
858 #if DEBUG && 0
859   else {
860     NSLog(@"%s: 0x%08X: no fetch-spec mismatch:\n%@\n%@\n",
861           __PRETTY_FUNCTION__, self,
862           self->fetchSpecification, _fs);
863   }
864 #endif
865 }
866
867 - (EOFetchSpecification *)fetchSpecification {
868   /* 
869      Note: the copy is intended, since the fetchspec is mutable, the consumer
870            could otherwise modify it "behind the scenes"
871   */
872   return [[self->fetchSpecification copy] autorelease];
873 }
874
875 /* description */
876
877 - (NSString *)description {
878   NSMutableString *ms;
879   
880   ms = [NSMutableString stringWithCapacity:128];
881   [ms appendFormat:@"<%@[0x%08X]:", NSStringFromClass([self class]), self];
882
883   if (self->fetchSpecification != nil)
884     [ms appendFormat:@" fspec=%@", self->fetchSpecification];
885   if (self->adChannel != nil)
886     [ms appendFormat:@" channel=%@", self->adChannel];
887   
888   [ms appendString:@">"];
889   return ms;
890 }
891
892 @end /* EOAdaptorDataSource */
893
894 @implementation EOAdaptorDataSource(Private)
895
896 - (NSArray *)_primaryKeyAttributeNamesForTableName:(NSString *)_entityName
897   channel:(EOAdaptorChannel *)_adChannel
898 {
899   NSDictionary *hints;
900   NSArray *attrs;
901   
902   hints = [self->fetchSpecification hints];
903   attrs = [hints objectForKey:EOPrimaryKeyAttributeNamesHint];
904   if (attrs)
905     return attrs;
906   
907   attrs = [hints objectForKey:EOPrimaryKeyAttributesHint];
908   
909   if (attrs == nil) {
910     if (!(attrs = [_adChannel primaryKeyAttributesForTableName:_entityName])) {
911       attrs = [_adChannel attributesForTableName:_entityName];
912     }
913   }
914   
915   attrs = [[attrs map:@selector(columnName)] map:@selector(lowercaseString)];
916   attrs = [attrs sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
917   
918   return attrs;
919 }
920
921 - (NSArray *)_primaryKeyAttributesForTableName:(NSString *)_entityName
922   channel:(EOAdaptorChannel *)_adChannel
923 {
924   NSArray      *attrs;
925   NSDictionary *hints;
926
927   hints = [self->fetchSpecification hints];
928
929   attrs = [hints objectForKey:EOPrimaryKeyAttributesHint];
930   if (attrs != nil)
931     return attrs;
932   
933   attrs = [hints objectForKey:EOPrimaryKeyAttributeNamesHint];
934   if (attrs != nil) {
935     NSArray      *allAttrs;
936     NSEnumerator *enumerator;
937     id           *objs;
938     int          objCnt;
939     id           obj;
940
941     allAttrs   = [_adChannel attributesForTableName:_entityName];
942     objs       = malloc(sizeof(id) * [allAttrs count]);
943     enumerator = [allAttrs objectEnumerator];
944
945     objCnt = 0;
946     
947     while ((obj = [enumerator nextObject])) {
948       if ([attrs containsObject:[[obj columnName] lowercaseString]]) {
949         objs[objCnt++] = obj;
950       }
951     }
952     attrs = [NSArray arrayWithObjects:objs count:objCnt];
953     free(objs); objs = NULL;
954     return attrs;
955   }
956   if (!(attrs = [_adChannel primaryKeyAttributesForTableName:_entityName])) {
957     attrs = [_adChannel attributesForTableName:_entityName];
958   }
959   return attrs;
960 }
961
962 - (NSString *)_whereExprWithChannel:(EOAdaptorChannel *)_adChan {
963   EOQualifier *qual       = nil;
964   NSArray     *attrs      = nil;
965   NSString    *entityName = nil;
966   EOAdaptor   *adaptor;
967
968   entityName = [self->fetchSpecification entityName];
969   
970   if ((attrs = self->__attributes) == nil)
971     attrs = [_adChan attributesForTableName:entityName];
972   
973   if ((qual = self->__qualifier) == nil)
974     qual = [self->fetchSpecification qualifier];
975
976   if (qual == nil)
977     return nil;
978   
979   adaptor = [[_adChan adaptorContext] adaptor];
980
981   return [qual sqlExpressionWithAdaptor:adaptor attributes:attrs];
982 }
983
984 - (NSException *)_couldNotFindSortAttributeInAttributes:(NSArray *)_attrs
985   forSortOrdering:(EOSortOrdering *)_so
986 {
987   return [[InvalidAttributeException alloc]
988            initWithFormat:@"could not find EOAttribute for SortOrdering"
989            @" %@ Attributes %@", _so, _attrs];
990 }
991
992 - (EOAttribute *)findAttributeForKey:(NSString *)key 
993   inAttributes:(NSArray *)_attrs
994 {
995   NSEnumerator *en;
996   EOAttribute  *obj;
997   
998   key = [key lowercaseString];
999   en  = [_attrs objectEnumerator];
1000   while ((obj = [en nextObject]) != nil) {
1001     if ([[[obj columnName] lowercaseString] isEqualToString:key])
1002       break;
1003   }
1004   return obj;
1005 }
1006
1007 - (NSString *)_orderByExprForAttributes:(NSArray *)_attrs 
1008   andPatchSelectList:(NSMutableString *)selectList
1009   withChannel:(EOAdaptorChannel *)_adChan
1010 {
1011   NSMutableString *orderByExpr;
1012   NSEnumerator   *enumerator   = nil;
1013   EOSortOrdering *sortOrdering = nil;
1014   int            orderCnt      = 0;
1015   EOAdaptor      *adaptor;
1016
1017   adaptor = [[_adChan adaptorContext] adaptor];
1018   
1019   orderByExpr = nil;
1020   enumerator = [[self->fetchSpecification sortOrderings] objectEnumerator];
1021   while ((sortOrdering = [enumerator nextObject]) != nil) {
1022       SEL         selector    = NULL;
1023       NSString    *key        = nil;
1024       EOAttribute *keyAttr    = nil;
1025       int         order       = 0; /* 0 - not used; 1 - asc; 2 - desc */
1026       BOOL        inSensitive = NO;
1027       NSString    *orderTmp   = nil;
1028
1029       if (orderByExpr == nil)
1030         orderByExpr = [NSMutableString stringWithCapacity:64];
1031       else
1032         [orderByExpr appendString:@", "];
1033       
1034       if ((selector = [sortOrdering selector])) {
1035         if (SEL_EQ(selector, EOCompareAscending))
1036           order = 1;
1037         else if (SEL_EQ(selector, EOCompareDescending))
1038           order = 2;
1039         else if (SEL_EQ(selector, EOCompareCaseInsensitiveAscending)) {
1040           order       = 1;
1041           inSensitive = YES;
1042         }
1043         else if (SEL_EQ(selector, EOCompareCaseInsensitiveDescending)) {
1044           order       = 2;
1045           inSensitive = YES;
1046         }
1047       }
1048       key = [sortOrdering key];
1049       
1050       if (key == nil || [key length] == 0) {
1051         NSLog(@"WARNING[%s]: no key in sortordering %@",
1052               __PRETTY_FUNCTION__, key);
1053         continue;
1054       }
1055       {
1056         EOAttribute  *obj;
1057         
1058         key = [key lowercaseString];
1059         obj = [self findAttributeForKey:key inAttributes:_attrs];
1060         if (obj == nil) {
1061           [self->__attributes release]; self->__attributes = nil;
1062           [self->__qualifier  release]; self->__qualifier  = nil;
1063 #if 0 // TODO: memleak in error case
1064           [expression         release]; expression         = nil;
1065 #endif
1066           [self rollbackTransaction];
1067           
1068           [[self _couldNotFindSortAttributeInAttributes:_attrs
1069                  forSortOrdering:sortOrdering] raise];
1070           return nil;
1071         }
1072         
1073         keyAttr = obj;
1074       }
1075       key      = [adaptor formatAttribute:keyAttr];
1076       orderTmp = [NSString stringWithFormat:@"order%d", orderCnt];
1077       orderCnt++;
1078       [orderByExpr appendString:orderTmp];
1079       if (order == 1)
1080         [orderByExpr appendString:@" ASC"];
1081       else if (order == 2)
1082         [orderByExpr appendString:@" DESC"];
1083       
1084       /* manipulate select expr */
1085       if (inSensitive) {
1086           if ([[keyAttr valueClassName] isEqualToString:@"NSString"]) {
1087               key = [NSString stringWithFormat:@"LOWER(%@)", key];
1088           }
1089           else
1090             NSLog(@"WARNING[%s]: inSensitive expression for no text attribute",
1091                   __PRETTY_FUNCTION__);
1092       }
1093       {
1094         NSString *str = nil;
1095
1096         str = [key stringByAppendingString:@" AS "];
1097         str = [str stringByAppendingString:orderTmp];
1098         str = [str stringByAppendingString:@", "];
1099         
1100         [selectList insertString:str atIndex:0];
1101       }
1102   }
1103   return orderByExpr;
1104 }
1105
1106 - (NSMutableString *)_selectListWithChannel:(EOAdaptorChannel *)_adChan {
1107   NSArray         *attrs      = nil;
1108   NSEnumerator    *enumerator = nil;
1109   EOAttribute     *attribute  = nil;
1110   BOOL            first       = YES;
1111   NSMutableString *select     = nil;
1112   EOAdaptor       *adaptor    = nil;
1113   NSString        *entityName = nil;
1114   
1115   adaptor    = [[_adChan adaptorContext] adaptor];
1116   entityName = [self->fetchSpecification entityName];
1117     
1118   if ((attrs = self->__attributes) == nil)
1119     attrs = [_adChan attributesForTableName:entityName];
1120
1121   attrs  = [_adChan _sortAttributesForSelectExpression:attrs];
1122   select = [NSMutableString stringWithCapacity:128];
1123   enumerator = [attrs objectEnumerator];
1124   while ((attribute = [enumerator nextObject])) {
1125     if (first)
1126       first = NO;
1127     else
1128       [select appendString:@", "];
1129
1130     [select appendString:[adaptor formatAttribute:attribute]];
1131   }
1132   return select;
1133 }
1134
1135 - (NSString *)_whereClauseForGlobaID:(EOKeyGlobalID *)_gid
1136   adaptor:(EOAdaptor *)_adaptor
1137   channel:(EOAdaptorChannel *)_adChan
1138 {
1139   NSEnumerator    *enumerator;
1140   NSMutableString *result;
1141   NSArray         *pKeys;
1142   NSArray         *pkAttrs;
1143   NSString        *pKey;
1144   int             pkCnt;
1145
1146
1147   pKeys   = [self _primaryKeyAttributeNamesForTableName:[_gid entityName]
1148                   channel:_adChan];
1149   pkAttrs = [self _primaryKeyAttributesForTableName:[_gid entityName]
1150                   channel:_adChan];
1151   
1152
1153   if ([pKeys count] != [_gid keyCount]) {
1154     NSLog(@"ERROR[%s]: internal inconsitency pkeys %@ gid %@",
1155           __PRETTY_FUNCTION__, pKeys, _gid);
1156     return nil;
1157   }
1158   enumerator = [pKeys objectEnumerator];
1159
1160   pkCnt  = 0;
1161   result = nil;
1162   while ((pKey = [enumerator nextObject])) {
1163     EOAttribute *attr;
1164     id          value;
1165
1166     if (result == nil)
1167       result = [NSMutableString stringWithCapacity:128];
1168     else
1169       [result appendString:@" AND "];
1170
1171     {
1172       NSEnumerator *enumerator;
1173
1174       enumerator = [pkAttrs objectEnumerator];
1175       while ((attr = [enumerator nextObject])) {
1176         if ([[[attr columnName] lowercaseString] isEqual:pKey])
1177           break;
1178       }
1179       NSAssert2(attr != nil, @"missing attribute for pkName %@ attrs %@",
1180                 pKey, pkAttrs);
1181     }
1182     [result appendString:[_adaptor formatAttribute:attr]];
1183
1184     
1185     value = [(EOKeyGlobalID *)_gid keyValues][pkCnt++];
1186     if (value == nil) value = null;
1187     
1188     [result appendString:[value isNotNull] ? @"=" : @" IS "];
1189     [result appendString:[_adaptor formatValue:value forAttribute:attr]];
1190   }
1191   return result;
1192 }
1193
1194 - (NSDictionary *)_mapAttrsWithValues:(NSDictionary *)_keyValues
1195   tableName:(NSString *)_tableName
1196   channel:(EOAdaptorChannel *)_adChan
1197 {
1198   id           *keys, *values;
1199   int          mapCnt;
1200   NSEnumerator *en;
1201   EOAttribute  *attr;
1202   NSDictionary *result;
1203   NSArray      *attrs;
1204
1205   attrs  = [_adChan attributesForTableName:_tableName];
1206   mapCnt = [attrs count];  
1207   keys   = calloc(mapCnt + 1, sizeof(id));
1208   values = calloc(mapCnt + 1, sizeof(id));
1209   en     = [attrs objectEnumerator];
1210   mapCnt = 0;
1211   
1212   while ((attr = [en nextObject])) {
1213     id v;
1214
1215     v = (v = [_keyValues valueForKey:[[attr columnName] lowercaseString]])
1216       ? v : null;
1217     
1218     keys[mapCnt]   = attr;
1219     values[mapCnt] = v;
1220     mapCnt++;
1221   }
1222   result = [[NSDictionary alloc]
1223                           initWithObjects:values forKeys:keys count:mapCnt];
1224   free(keys);   keys   = NULL;
1225   free(values); values = NULL;
1226   return [result autorelease];
1227 }
1228
1229 @end /* EOAdaptorDataSource(Private) */
1230
1231 @implementation EOAndQualifier(SqlExpression)
1232
1233 - (NSString *)sqlExpressionWithAdaptor:(EOAdaptor *)_adaptor
1234   attributes:(NSArray *)_attributes
1235 {
1236   NSMutableString *str        = nil;
1237   NSEnumerator    *enumerator = nil;
1238   EOQualifier     *qual       = nil;
1239   BOOL            isFirst     = YES;
1240   NSString        *result     = nil;
1241
1242   str = [[NSMutableString alloc] initWithCapacity:128];
1243
1244   enumerator = [self->qualifiers objectEnumerator];
1245   while ((qual = [enumerator nextObject])) {
1246     NSString *s;
1247     
1248     s = [qual sqlExpressionWithAdaptor:_adaptor attributes:_attributes];
1249     if (isFirst) {
1250       [str appendFormat:@"(%@)", s];
1251       isFirst = NO;
1252     }
1253     else
1254       [str appendFormat:@" AND (%@)", s];
1255   }
1256   result = [str copy];
1257   [str release]; str = nil;
1258   return [result autorelease];
1259 }
1260 @end /* EOAndQualifier(SqlExpression) */
1261
1262 @implementation EOOrQualifier(SqlExpression)
1263
1264 - (NSString *)sqlExpressionWithAdaptor:(EOAdaptor *)_adaptor
1265   attributes:(NSArray *)_attributes
1266 {
1267   NSMutableString *str        = nil;
1268   NSEnumerator    *enumerator = nil;
1269   EOQualifier     *qual       = nil;
1270   BOOL            isFirst     = YES;
1271   NSString        *result     = nil;
1272
1273   str = [[NSMutableString alloc] initWithCapacity:128];
1274
1275   enumerator = [self->qualifiers objectEnumerator];
1276   while ((qual = [enumerator nextObject])) {
1277     NSString *s;
1278
1279     s = [qual sqlExpressionWithAdaptor:_adaptor attributes:_attributes];
1280     if (isFirst) {
1281       [str appendFormat:@"(%@)", s];
1282       isFirst = NO;
1283     }
1284     else
1285       [str appendFormat:@" OR (%@)", s];
1286   }
1287   result = [str copy];
1288   [str release]; str = nil;
1289   return [result autorelease];
1290 }
1291
1292 @end /* EOOrQualifier(SqlExpression) */
1293
1294 @implementation EOKeyValueQualifier(SqlExpression)
1295
1296 + (NSString *)sqlStringForOperatorSelector:(SEL)_sel {
1297   static NSMapTable *selectorToOperator = NULL;
1298   NSString *s, *ss;
1299   
1300   if ((s = NSStringFromSelector(_sel)) == nil)
1301     return nil;
1302
1303   if (selectorToOperator == NULL) {
1304     selectorToOperator = NSCreateMapTable(NSObjectMapKeyCallBacks,
1305                                           NSObjectMapValueCallBacks,
1306                                           10);
1307     NSMapInsert(selectorToOperator,
1308                 NSStringFromSelector(EOQualifierOperatorEqual),
1309                 @"=");
1310     NSMapInsert(selectorToOperator,
1311                 NSStringFromSelector(EOQualifierOperatorNotEqual),
1312                 @"<>");
1313     NSMapInsert(selectorToOperator,
1314                 NSStringFromSelector(EOQualifierOperatorLessThan),
1315                 @"<");
1316     NSMapInsert(selectorToOperator,
1317                 NSStringFromSelector(EOQualifierOperatorGreaterThan),
1318                 @">");
1319     NSMapInsert(selectorToOperator,
1320                 NSStringFromSelector(EOQualifierOperatorLessThanOrEqualTo),
1321                 @"<=");
1322     NSMapInsert(selectorToOperator,
1323                 NSStringFromSelector(EOQualifierOperatorGreaterThanOrEqualTo),
1324                 @">=");
1325   }
1326   
1327   if ((ss = NSMapGet(selectorToOperator, s)))
1328     return ss;
1329   
1330   return nil;
1331 }
1332
1333 - (NSString *)sqlExpressionWithAdaptor:(EOAdaptor *)_adaptor
1334   attributes:(NSArray *)_attributes
1335 {
1336   EOAttribute  *attr = nil;
1337   NSEnumerator *en   = nil;
1338   NSString     *k    = nil;
1339   NSString     *sql  = nil;
1340   NSString     *sqlKey, *sqlValue;
1341
1342   k  = [self->key lowercaseString];
1343   en = [_attributes objectEnumerator];
1344   
1345   while ((attr = [en nextObject])) {
1346     if ([[[attr columnName] lowercaseString] isEqualToString:k]) {
1347       break;
1348     }
1349   }
1350   if (!attr) {
1351     en = [_attributes objectEnumerator];
1352     while ((attr = [en nextObject])) {
1353       if ([[attr name] isEqualToString:self->key])
1354         break;
1355     }
1356   }
1357   if (!attr) {
1358     NSLog(@"WARNING[%s]: missing attribute [%@] for qualifier %@",
1359           __PRETTY_FUNCTION__,
1360           _attributes, self);
1361     return @"1=2";
1362   }
1363   
1364   sqlKey   = [_adaptor formatAttribute:attr];
1365
1366   sqlValue = [_adaptor formatValue:self->value ? self->value : null
1367                        forAttribute:attr];
1368
1369   sql = nil;
1370   
1371   if (SEL_EQ(EOQualifierOperatorEqual, self->operator)) {
1372     if ([self->value isNotNull])
1373       sql = [NSString stringWithFormat:@"%@ = %@", sqlKey, sqlValue];
1374     else
1375       sql = [NSString stringWithFormat:@"%@ IS NULL", sqlKey];
1376   }
1377   else if (SEL_EQ(EOQualifierOperatorNotEqual, self->operator)) {
1378     if ([self->value isNotNull])
1379       sql = [NSString stringWithFormat:@"NOT (%@ = %@)", sqlKey, sqlValue];
1380     else
1381       sql = [NSString stringWithFormat:@"%@ IS NOT NULL", sqlKey];
1382   }
1383   else if (SEL_EQ(EOQualifierOperatorLessThan, self->operator)) {
1384     sql = [NSString stringWithFormat:@"%@ < %@", sqlKey, sqlValue];
1385   }
1386   else if (SEL_EQ(EOQualifierOperatorLessThanOrEqualTo, self->operator)) {
1387     sql = [NSString stringWithFormat:@"%@ <= %@", sqlKey, sqlValue];
1388   }
1389   else if (SEL_EQ(EOQualifierOperatorGreaterThan, self->operator)) {
1390     sql = [NSString stringWithFormat:@"%@ > %@", sqlKey, sqlValue];
1391   }
1392   else if (SEL_EQ(EOQualifierOperatorGreaterThanOrEqualTo, self->operator)) {
1393     sql = [NSString stringWithFormat:@"%@ >= %@", sqlKey, sqlValue];
1394   }
1395   else if (SEL_EQ(EOQualifierOperatorLike, self->operator)) {
1396     sqlValue = [[self->value stringValue]
1397                              stringByReplacingString:@"*" withString:@"%"];
1398     sqlValue = [_adaptor formatValue:sqlValue forAttribute:attr];
1399     
1400     sql = [NSString stringWithFormat:@"%@ LIKE %@", sqlKey, sqlValue];
1401   }
1402   else if (SEL_EQ(EOQualifierOperatorCaseInsensitiveLike, self->operator)) {
1403     sqlValue = [[self->value stringValue]
1404                              stringByReplacingString:@"*" withString:@"%"];
1405     sqlValue = [sqlValue lowercaseString];
1406     sqlValue = [_adaptor formatValue:sqlValue forAttribute:attr];
1407     
1408     sql = [NSString stringWithFormat:@"LOWER(%@) LIKE %@", sqlKey, sqlValue];
1409   }
1410 #if 0
1411   else if (SEL_EQ(EOQualifierOperatorLessThanOrEqualTo, self->operator)) {
1412   }
1413   else if (SEL_EQ(EOQualifierOperatorGreaterThanOrEqualTo, self->operator)) {
1414   }
1415 #endif
1416   else {
1417     NSLog(@"ERROR(%s): unsupported SQL operator: %@", __PRETTY_FUNCTION__,
1418           [EOQualifier stringForOperatorSelector:self->operator]);
1419     [self notImplemented:_cmd];
1420     return nil;
1421   }
1422   
1423   return sql;
1424 }
1425
1426 @end /* EOKeyValueQualifier(SqlExpression) */
1427
1428 @implementation EONotQualifier(SqlExpression)
1429
1430 - (NSString *)sqlExpressionWithAdaptor:(EOAdaptor *)_adaptor
1431   attributes:(NSArray *)_attributes
1432 {
1433   NSString *s;
1434   
1435   s = [self->qualifier sqlExpressionWithAdaptor:_adaptor 
1436                        attributes:_attributes];
1437   return [NSString stringWithFormat:@"NOT(%@)", s];
1438 }
1439
1440 @end /* EONotQualifier(SqlExpression) */
1441
1442 @implementation EOKeyComparisonQualifier(SqlExpression)
1443
1444 - (NSString *)sqlExpressionWithAdaptor:(EOAdaptor *)_adaptor
1445   attributes:(NSArray *)_attributes
1446 {
1447   NSLog(@"ERROR(%s): subclass needs to override this method!", 
1448         __PRETTY_FUNCTION__);
1449   return nil;
1450 }
1451
1452 @end /* EOKeyComparisonQualifier(SqlExpression) */