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