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
41NSString *EOPrimaryKeyAttributeNamesHint = @"EOPrimaryKeyAttributeNamesHint";
42NSString *EOPrimaryKeyAttributesHint     = @"EOPrimaryKeyAttributesHint";
43NSString *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
53static 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
91static Class NSCalendarDateClass = nil;
92static NSNotificationCenter *nc = nil;
93
94static NSNotificationCenter *getNC(void ) {
95  if (nc == nil)
96    nc = [[NSNotificationCenter defaultCenter] retain];
97  return nc;
98}
99
100+ (void)initialize {
101  null = [[EONull null] retain];
102  NSCalendarDateClass = [NSCalendarDate class];
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  // TODO: all the SQL gen code should be moved to an appropriate object
241  NSString         *entityName  = nil;
242  NSString         *whereExpr   = nil;
243  NSString         *orderByExpr = nil;
244  NSMutableString  *selectList  = nil;
245  NSMutableString  *expression  = nil;
246  NSMutableArray   *result      = nil;
247  NSArray          *attrs       = 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    // TODO: make that a lastException and just return nil
257    [NSException raise:NSInvalidArgumentException
258		 format:@"fetchSpecification required for table name"];
259    return nil;
260  }
261
262  entityName = [self->fetchSpecification entityName];
263
264  if (entityName == nil || [entityName length] == 0) {
265    [NSException raise:NSInvalidArgumentException
266		 format:@"missing entity name"];
267  }
268  localComTrans = [[self->adChannel adaptorContext] hasOpenTransaction]
269    ? NO : YES;
270
271  adChan  = [self beginTransaction];
272  pKeys   = [self _primaryKeyAttributeNamesForTableName:entityName
273                  channel:adChan];
274
275  if ((pKeyCnt = [pKeys count]) == 0) {
276    NSLog(@"ERROR[%s]: missing primary keys for table %@",
277          __PRETTY_FUNCTION__, entityName);
278    return nil;
279  }
280  qual  = [self->fetchSpecification qualifier];
281
282  if (qual == nil)
283    qual = [EOQualifier qualifierWithQualifierFormat:@"1=1"];
284
285  ASSIGN(self->__qualifier, qual);
286
287  attrs = [adChan attributesForTableName:entityName];
288
289  if (attrs == nil) {
290    RELEASE(self->__qualifier); self->__qualifier = nil;
291
292    NSLog(@"ERROR[%s]: could not find table '%@' in database.",
293          __PRETTY_FUNCTION__, entityName);
294    [self rollbackTransaction];
295    return nil;
296  }
297  if ([attrs count] == 0) {
298    RELEASE(self->__qualifier); self->__qualifier = nil;
299
300    NSLog(@"ERROR[%s]: missing columns in table '%@'.",
301          __PRETTY_FUNCTION__, entityName);
302    [self rollbackTransaction];
303    return nil;
304  }
305  tz = [[self->fetchSpecification hints] objectForKey:EOFetchResultTimeZone];
306
307  ASSIGN(self->__attributes, attrs);
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  orderByExpr = [self _orderByExprForAttributes:attrs
340		      andPatchSelectList:selectList
341		      withChannel:adChan];
342
343  expression = [[NSMutableString alloc] initWithCapacity:256];
344  [expression appendString:@"SELECT "];
345
346  if ([self->fetchSpecification usesDistinct])
347    [expression appendString:@"DISTINCT "];
348
349  [expression appendString:selectList];
350  [expression appendString:@" FROM "];
351  [expression appendString:entityName];
352  if ([whereExpr length] > 0) {
353    [expression appendString:@" WHERE "];
354    [expression appendString:whereExpr];
355  }
356  if (orderByExpr != nil && [orderByExpr length] > 0) {
357    [expression appendString:@" ORDER BY "];
358    [expression appendString:orderByExpr];
359  }
360
361  if (![adChan evaluateExpression:expression]) {
362    RELEASE(self->__attributes); self->__attributes = nil;
363    RELEASE(self->__qualifier);  self->__qualifier  = nil;
364    AUTORELEASE(expression);
365    [adChan cancelFetch];
366    [self rollbackTransaction];
367    [[[EOAdaptorException alloc]
368       initWithFormat:@"evaluateExpression of %@ failed", expression] raise];
369  }
370  result = [NSMutableArray arrayWithCapacity:64];
371  {
372    NSMutableDictionary *row = nil;
373    unsigned fetchCnt   = 0;
374    unsigned fetchLimit = 0;
375    unsigned attrCnt    = 0;
376    id       *values    = NULL;
377    id       *keys      = NULL;
378
379    /* Note: those are reused in the inner loop */
380    attrCnt    = [attrs count];
381    values     = calloc(attrCnt + 2, sizeof(id));
382    keys       = calloc(attrCnt + 2, sizeof(id));
383    fetchLimit = [self->fetchSpecification fetchLimit];
384
385    while ((row = [adChan fetchAttributes:attrs withZone:NULL]) != nil) {
386      NSEnumerator        *enumerator = nil;
387      id                  attr        = nil;
388      int                 rowCnt      = 0;
389      NSDictionary        *r          = nil;
390      id                  *pKeyVs     = NULL;
391      int                 pKeyVCnt    = 0;
392
393      pKeyVs     = calloc(pKeyCnt, sizeof(id));
394      enumerator = [attrs objectEnumerator];
395
396      while ((attr = [enumerator nextObject]) != nil) {
397        id       obj;
398        NSString *cn;
399
400        obj = [row objectForKey:[(EOAttribute *)attr name]];
401
402        if (obj == nil)
403          continue;
404
405        if (tz != nil && [obj isKindOfClass:NSCalendarDateClass])
406	  [obj setTimeZone:tz];
407
408        cn             = [[attr columnName] lowercaseString];
409        values[rowCnt] = obj;
410        keys[rowCnt]   = cn;
411        rowCnt++;
412
413        if ([pKeys containsObject:cn]) {
414          int idx;
415
416          idx = [pKeys indexOfObject:cn];
417          NSAssert4(idx <= (pKeyCnt - 1) && pKeyVs[idx] == nil,
418                    @"internal inconsistency in EOAdaptorDataSource "
419                    @"while fetch idx[%d] > (pKeyCnt - 1)[%d] "
420                    @"pKeyVs[idx] (%@[%d]);", idx, (pKeyCnt - 1),
421                    pKeyVs[idx], idx);
422
423          pKeyVs[idx] = obj;
424          pKeyVCnt++;
425        }
426      }
427      if (pKeyCnt != pKeyVCnt)
428        NSAssert(NO, @"internal inconsistency in EOAdaptorDataSource "
429                 @"while fetch");
430
431      {
432        EOGlobalID *gid;
433
434        gid = [EOKeyGlobalID globalIDWithEntityName:entityName
435                             keys:pKeyVs keyCount:pKeyVCnt zone:NULL];
436
437        if (self->connectionDictionary) {
438          gid = [[EOAdaptorGlobalID alloc] initWithGlobalID:gid
439                                           connectionDictionary:
440                                           self->connectionDictionary];
441          AUTORELEASE(gid);
442        }
443        values[rowCnt] = gid;
444        keys[rowCnt]   = @"globalID";
445        rowCnt++;
446      }
447      fetchCnt++;
448      r = [[NSMutableDictionary alloc]
449	    initWithObjects:values forKeys:keys count:rowCnt];
450      [result addObject:r];
451      [r release]; r = nil;
452      if (pKeyVs) free(pKeyVs); pKeyVs = NULL;
453      if (fetchLimit == fetchCnt)
454        break;
455    }
456    if (values) free(values); values = NULL;
457    if (keys)   free(keys);   keys   = NULL;
458  }
459  [adChan cancelFetch];
460  if (localComTrans)
461    [self commitTransaction];
462
463  [expression         release]; expression         = nil;
464  [self->__qualifier  release]; self->__qualifier  = nil;
465  [self->__attributes release]; self->__attributes = nil;
466  return result;
467}
468
469- (id)createObject {
470  return [NSMutableDictionary dictionary];
471}
472
473- (void)insertObject:(id)_obj {
474  NSString         *key        = nil;
475  NSString         *tableName  = nil;
476  NSMutableString  *expression = nil;
477  EOAdaptor        *adaptor    = nil;
478  NSArray          *pKeys      = nil;
479  id               obj         = nil;
480  EOAdaptorChannel *adChan     = nil;
481
482  int      oVCnt                = 0;
483  NSString **objectKeyAttrValue = NULL;
484
485  NSEnumerator *enumerator = nil;
486  id           pKey        = nil;
487
488  BOOL         localComTrans;
489
490  if ([[self->adChannel adaptorContext] hasOpenTransaction])
491    localComTrans = NO;
492  else
493    localComTrans = YES;
494
495  adChan  = [self beginTransaction];
496  adaptor = [[adChan adaptorContext] adaptor];
497
498  if ((tableName = [self->fetchSpecification entityName]) == nil) {
499    [self rollbackTransaction];
500    [NSException raise:NSInvalidArgumentException
501		 format:@"couldn`t insert obj %@ missing entityName in "
502		 @"fetchSpecification", _obj];
503  }
504
505  /* create or apply primary keys */
506#if EOAdaptorDataSource_DEBUG
507  NSLog(@"insert obj %@", _obj);
508#endif
509
510  pKeys = [self _primaryKeyAttributeNamesForTableName:tableName channel:adChan];
511
512#if EOAdaptorDataSource_DEBUG
513  NSLog(@"got primary keys %@", pKeys);
514#endif
515
516  objectKeyAttrValue = calloc([pKeys count], sizeof(id));
517  enumerator         = [pKeys objectEnumerator];
518
519  while ((pKey = [enumerator nextObject])) {
520    id pKeyObj;
521
522    pKeyObj = [_obj valueForKey:pKey];
523
524#if EOAdaptorDataSource_DEBUG
525    NSLog(@"pk in obj %@:<%@> ", pKey, pKeyObj);
526#endif
527
528    if (![pKeyObj isNotNull]) {
529      /* try to build primary key */
530      NSString     *newKeyExpr = nil;
531      NSDictionary *row        = nil;
532
533#if EOAdaptorDataSource_DEBUG
534      NSLog(@"pKeyObj !isNotNull");
535#endif
536
537      if ([pKeys count] != 1) {
538        [self rollbackTransaction];
539	[NSException raise:NSInternalInconsistencyException
540		     format:@"more than one primary key, "
541		     @"and primary key for %@ is not set", pKey];
542      }
543      if (![adaptor respondsToSelector:@selector(newKeyExpression)]) {
544        [self rollbackTransaction];
545	[NSException raise:NSInternalInconsistencyException
546		     format:@"got no newkey expression, insert failed"];
547      }
548      newKeyExpr = [(id)adaptor newKeyExpression];
549      if (newKeyExpr == nil) {
550        [self rollbackTransaction];
551	[NSException raise:NSInternalInconsistencyException
552		     format:@"missing newKeyExpression for adaptor[%@] %@...",
553		       NSStringFromClass([adaptor class]), adaptor];
554      }
555      if (![adChan evaluateExpression:newKeyExpr]) {
556        [adChan cancelFetch];
557        [self rollbackTransaction];
558        [[[EOAdaptorException alloc]
559                 initWithFormat:@"couldn`t evaluate new key expression %@",
560                 newKeyExpr] raise];
561      }
562      row = [adChan fetchAttributes:[adChan describeResults]
563                    withZone:NULL];
564      [adChan cancelFetch];
565      if ((key = [[row objectEnumerator] nextObject]) == nil) {
566        [self rollbackTransaction];
567        [[[EOAdaptorException alloc]
568                        initWithFormat:@"couldn`t fetch primary key"] raise];;
569      }
570      objectKeyAttrValue[oVCnt++] = key;
571      [_obj takeValue:key forKey:pKey];
572#if EOAdaptorDataSource_DEBUG
573      NSLog(@"_obj %@ after takeValue:%@ forKey:%@", _obj, key, pKey);
574#endif
575    }
576    else {
577      objectKeyAttrValue[oVCnt++] = pKeyObj;
578#if EOAdaptorDataSource_DEBUG
579      NSLog(@"objectKeyAttrValue takeValue %@ for idx %d", pKeyObj, oVCnt);
580#endif
581    }
582  }
583
584  /* construct SQL INSERT expression .. */
585
586  expression = [[NSMutableString alloc] initWithCapacity:256];
587  [expression appendString:@"INSERT INTO "];
588  [expression appendString:tableName];
589  [expression appendString:@"("];
590  {
591    NSDictionary *objects    = nil;
592    NSEnumerator *enumerator = nil;
593    NSArray      *allKeys    = nil;
594    BOOL         isFirst     = YES;
595
596    objects    = [self _mapAttrsWithValues:_obj
597                       tableName:tableName
598                       channel:adChan];
599    allKeys    = [objects allKeys];
600    enumerator = [allKeys objectEnumerator];
601    while ((obj = [enumerator nextObject])) {
602      if (isFirst)
603	isFirst = NO;
604      else
605	[expression appendString:@", "];
606      [expression appendString:[adaptor formatAttribute:obj]];
607    }
608    [expression appendString:@") VALUES ("];
609    enumerator = [allKeys objectEnumerator];
610    isFirst = YES;
611    while ((obj = [enumerator nextObject])) {
612      id value;
613
614      if (isFirst)
615	isFirst = NO;
616      else
617	[expression appendString:@", "];
618      value = [objects objectForKey:obj];
619      if (value == nil) value = null;
620      [expression appendString:[adaptor formatValue:value forAttribute:obj]];
621    }
622  }
623  [expression appendString:@")"];
624
625  /* execute insert in SQL server .. */
626
627  if (![adChan evaluateExpression:expression]) {
628    [adChan cancelFetch];
629    enumerator = [pKeys objectEnumerator];
630    while ((pKey = [enumerator nextObject])) {
631      [_obj takeValue:[EONull null] forKey:pKey];
632    }
633    [self rollbackTransaction];
634    AUTORELEASE(expression);
635    [[[EOAdaptorException alloc]
636       initWithFormat:@"evaluateExpression %@ failed", expression] raise];
637  }
638  [adChan cancelFetch];
639  if (localComTrans)
640    [self commitTransaction];
641
642  /* construct new global id for record */
643
644  {
645    EOGlobalID *gid;
646
647    gid = [EOKeyGlobalID globalIDWithEntityName:tableName
648                         keys:objectKeyAttrValue keyCount:oVCnt zone:NULL];
649    if (self->connectionDictionary != nil) {
650      EOAdaptorGlobalID *agid = nil;
651
652      agid = [[EOAdaptorGlobalID alloc] initWithGlobalID:gid
653                                        connectionDictionary:
654                                        self->connectionDictionary];
655      AUTORELEASE(agid);
656      gid = agid;
657    }
658    [_obj takeValue:gid forKey:@"globalID"];
659  }
660
661  RELEASE(expression); expression = NULL;
662
663  /* mark datasource as changed */
664
665  [self postDataSourceChangedNotification];
666}
667
668- (void)updateObject:(id)_obj {
669  NSString         *whereClause = nil;
670  NSMutableString  *expression  = nil;
671  EOAdaptor        *adaptor     = nil;
672  EOKeyGlobalID    *gid         = nil;
673  NSEnumerator     *enumerator  = nil;
674  NSString         *tableName   = nil;
675  EOAttribute      *attr        = nil;
676  BOOL             isFirst      = YES;
677  NSDictionary     *objects     = nil;
678  EOAdaptorChannel *adChan      = nil;
679
680  BOOL localComTrans;
681
682  if ((gid = [_obj valueForKey:@"globalID"]) == nil) {
683    [NSException raise:NSInvalidArgumentException
684		 format:@"missing globalID, couldn`t update"];
685  }
686  if ([gid isKindOfClass:[EOAdaptorGlobalID class]]) {
687    NSDictionary *conD = nil;
688
689    conD = [(EOAdaptorGlobalID *)gid connectionDictionary];
690    if (![conD isEqualToDictionary:self->connectionDictionary]) {
691      [NSException raise:NSInvalidArgumentException
692		   format:@"try to update object %@ in "
693		   @"wrong AdaptorDataSource %@", _obj, self];
694    }
695    gid = (EOKeyGlobalID *)[(EOAdaptorGlobalID *)gid globalID];
696  }
697  if ([[self->adChannel adaptorContext] hasOpenTransaction])
698    localComTrans = NO;
699  else
700    localComTrans = YES;
701
702  adChan      = [self beginTransaction];
703  tableName   = [gid entityName];
704  adaptor     = [[adChan adaptorContext] adaptor];
705  whereClause = [self _whereClauseForGlobaID:gid adaptor:adaptor
706                      channel:adChan];
707  if (whereClause == nil) {
708    [self rollbackTransaction];
709    return;
710  }
711  expression = [[NSMutableString alloc] initWithCapacity:256];
712  [expression appendString:@"UPDATE "];
713  [expression appendString:[gid entityName]];
714  [expression appendString:@" SET "];
715
716  objects    = [self _mapAttrsWithValues:_obj tableName:tableName
717                     channel:adChan];
718  enumerator = [objects keyEnumerator];
719
720  while ((attr = [enumerator nextObject])) {
721    id value;
722
723    if (isFirst)
724      isFirst = NO;
725    else
726      [expression appendString:@", "];
727    [expression appendString:[adaptor formatAttribute:attr]];
728    [expression appendString:@"="];
729
730    value = [objects objectForKey:attr];
731    if (value == nil) value = null;
732
733    [expression appendString:[adaptor formatValue:value forAttribute:attr]];
734  }
735  [expression appendString:@" WHERE "];
736  [expression appendString:whereClause];
737  if (![adChan evaluateExpression:expression]) {
738    [adChan cancelFetch];
739    [self rollbackTransaction];
740    AUTORELEASE(expression);
741    [[[EOAdaptorException alloc]
742       initWithFormat:@"evaluate expression %@ failed", expression] raise];
743  }
744  [adChan cancelFetch];
745  if (localComTrans)
746    [self commitTransaction];
747
748  RELEASE(expression); expression = nil;
749
750  {
751    EOGlobalID   *newGID;
752    NSArray      *attrs;
753    NSEnumerator *enumerator;
754    id           *objs;
755    int          objCnt;
756    NSString     *attr;
757
758    attrs      = [self _primaryKeyAttributeNamesForTableName:[gid entityName]
759                       channel:adChan];
760    enumerator = [attrs objectEnumerator];
761    objCnt     = 0;
762    objs       = calloc([attrs count], sizeof(id));
763
764    while ((attr = [enumerator nextObject])) {
765      objs[objCnt] = [_obj valueForKey:attr];
766      objCnt++;
767    }
768    newGID = [EOKeyGlobalID globalIDWithEntityName:[gid entityName]
769                            keys:objs keyCount:objCnt zone:NULL];
770    if (self->connectionDictionary != nil) {
771      newGID = [[EOAdaptorGlobalID alloc] initWithGlobalID:newGID
772                                          connectionDictionary:
773                                          self->connectionDictionary];
774      [newGID autorelease];
775    }
776    [(NSMutableDictionary *)_obj setObject:newGID forKey:@"globalID"];
777  }
778  [self postDataSourceChangedNotification];
779}
780
781- (void)deleteObject:(id)_obj {
782  NSString         *whereClause = nil;
783  NSMutableString  *expression  = nil;
784  EOKeyGlobalID    *gid         = nil;
785  EOAdaptorChannel *adChan      = nil;
786
787  BOOL localComTrans;
788
789  if ((gid = [_obj valueForKey:@"globalID"]) == nil) {
790    [NSException raise:NSInvalidArgumentException
791		 format:@"missing globalID, could not delete"];
792  }
793  if ([gid isKindOfClass:[EOAdaptorGlobalID class]]) {
794    NSDictionary *conD = nil;
795
796    conD = [(EOAdaptorGlobalID *)gid connectionDictionary];
797    if (![conD isEqualToDictionary:self->connectionDictionary]) {
798      [NSException raise:NSInvalidArgumentException
799		   format:@"try to delete object %@ in wrong "
800		   @"AdaptorDataSource %@", _obj, self];
801    }
802    gid = (EOKeyGlobalID *)[(EOAdaptorGlobalID *)gid globalID];
803  }
804
805  if ([[self->adChannel adaptorContext] hasOpenTransaction])
806    localComTrans = NO;
807  else
808    localComTrans = YES;
809
810  adChan      = [self beginTransaction];
811  whereClause = [self _whereClauseForGlobaID:gid
812                      adaptor:[[adChan adaptorContext] adaptor] channel:adChan];
813  if (whereClause == nil) {
814    [self rollbackTransaction];
815    return;
816  }
817  expression = [[NSMutableString alloc] initWithCapacity:256];
818  [expression appendString:@"DELETE FROM "];
819  [expression appendString:[gid entityName]];
820  [expression appendString:@" WHERE "];
821  [expression appendString:whereClause];
822  if (![adChan evaluateExpression:expression]) {
823    [adChan cancelFetch];
824    [self rollbackTransaction];
825    AUTORELEASE(expression);
826    [[[EOAdaptorException alloc]
827       initWithFormat:@"couldn`t evaluate expression %@ failed",
828       expression] raise];
829  }
830  [adChan cancelFetch];
831  if (localComTrans)
832    [self commitTransaction];
833  RELEASE(expression); expression = nil;
834  [self postDataSourceChangedNotification];
835}
836
837- (void)setFetchSpecification:(EOFetchSpecification *)_fs {
838  if (![self->fetchSpecification isEqual:_fs]) {
839#if DEBUG && 0
840    NSLog(@"%s: 0x%p: fetch-spec mismatch:\n%@\n%@",
841          __PRETTY_FUNCTION__, self,
842          self->fetchSpecification, _fs);
843#endif
844
845    ASSIGNCOPY(self->fetchSpecification, _fs);
846
847    [self postDataSourceItselfChangedNotification];
848  }
849#if DEBUG && 0
850  else {
851    NSLog(@"%s: 0x%p: no fetch-spec mismatch:\n%@\n%@\n",
852          __PRETTY_FUNCTION__, self,
853          self->fetchSpecification, _fs);
854  }
855#endif
856}
857
858- (EOFetchSpecification *)fetchSpecification {
859  /*
860     Note: the copy is intended, since the fetchspec is mutable, the consumer
861           could otherwise modify it "behind the scenes"
862  */
863  return [[self->fetchSpecification copy] autorelease];
864}
865
866/* description */
867
868- (NSString *)description {
869  NSMutableString *ms;
870
871  ms = [NSMutableString stringWithCapacity:128];
872  [ms appendFormat:@"<%@[0x%p]:", NSStringFromClass([self class]), self];
873
874  if (self->fetchSpecification != nil)
875    [ms appendFormat:@" fspec=%@", self->fetchSpecification];
876  if (self->adChannel != nil)
877    [ms appendFormat:@" channel=%@", self->adChannel];
878
879  [ms appendString:@">"];
880  return ms;
881}
882
883@end /* EOAdaptorDataSource */
884
885@implementation EOAdaptorDataSource(Private)
886
887- (NSArray *)_primaryKeyAttributeNamesForTableName:(NSString *)_entityName
888  channel:(EOAdaptorChannel *)_adChannel
889{
890  NSDictionary *hints;
891  NSArray *attrs;
892
893  hints = [self->fetchSpecification hints];
894  attrs = [hints objectForKey:EOPrimaryKeyAttributeNamesHint];
895  if (attrs)
896    return attrs;
897
898  attrs = [hints objectForKey:EOPrimaryKeyAttributesHint];
899
900  if (attrs == nil) {
901    if (!(attrs = [_adChannel primaryKeyAttributesForTableName:_entityName])) {
902      attrs = [_adChannel attributesForTableName:_entityName];
903    }
904  }
905
906  attrs = [[attrs map:@selector(columnName)] map:@selector(lowercaseString)];
907  attrs = [attrs sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
908
909  return attrs;
910}
911
912- (NSArray *)_primaryKeyAttributesForTableName:(NSString *)_entityName
913  channel:(EOAdaptorChannel *)_adChannel
914{
915  NSArray      *attrs;
916  NSDictionary *hints;
917
918  hints = [self->fetchSpecification hints];
919
920  attrs = [hints objectForKey:EOPrimaryKeyAttributesHint];
921  if (attrs != nil)
922    return attrs;
923
924  attrs = [hints objectForKey:EOPrimaryKeyAttributeNamesHint];
925  if (attrs != nil) {
926    NSArray      *allAttrs;
927    NSEnumerator *enumerator;
928    id           *objs;
929    int          objCnt;
930    id           obj;
931
932    allAttrs   = [_adChannel attributesForTableName:_entityName];
933    objs       = malloc(sizeof(id) * [allAttrs count]);
934    enumerator = [allAttrs objectEnumerator];
935
936    objCnt = 0;
937
938    while ((obj = [enumerator nextObject])) {
939      if ([attrs containsObject:[[obj columnName] lowercaseString]]) {
940        objs[objCnt++] = obj;
941      }
942    }
943    attrs = [NSArray arrayWithObjects:objs count:objCnt];
944    free(objs); objs = NULL;
945    return attrs;
946  }
947  if (!(attrs = [_adChannel primaryKeyAttributesForTableName:_entityName])) {
948    attrs = [_adChannel attributesForTableName:_entityName];
949  }
950  return attrs;
951}
952
953- (NSString *)_whereExprWithChannel:(EOAdaptorChannel *)_adChan {
954  EOQualifier *qual       = nil;
955  NSArray     *attrs      = nil;
956  NSString    *entityName = nil;
957  EOAdaptor   *adaptor;
958
959  entityName = [self->fetchSpecification entityName];
960
961  if ((attrs = self->__attributes) == nil)
962    attrs = [_adChan attributesForTableName:entityName];
963
964  if ((qual = self->__qualifier) == nil)
965    qual = [self->fetchSpecification qualifier];
966
967  if (qual == nil)
968    return nil;
969
970  adaptor = [[_adChan adaptorContext] adaptor];
971
972  return [qual sqlExpressionWithAdaptor:adaptor attributes:attrs];
973}
974
975- (NSException *)_couldNotFindSortAttributeInAttributes:(NSArray *)_attrs
976  forSortOrdering:(EOSortOrdering *)_so
977{
978  return [[InvalidAttributeException alloc]
979	   initWithFormat:@"could not find EOAttribute for SortOrdering"
980	   @" %@ Attributes %@", _so, _attrs];
981}
982
983- (EOAttribute *)findAttributeForKey:(NSString *)key
984  inAttributes:(NSArray *)_attrs
985{
986  NSEnumerator *en;
987  EOAttribute  *obj;
988
989  key = [key lowercaseString];
990  en  = [_attrs objectEnumerator];
991  while ((obj = [en nextObject]) != nil) {
992    if ([[[obj columnName] lowercaseString] isEqualToString:key])
993      break;
994  }
995  return obj;
996}
997
998- (NSString *)_orderByExprForAttributes:(NSArray *)_attrs
999  andPatchSelectList:(NSMutableString *)selectList
1000  withChannel:(EOAdaptorChannel *)_adChan
1001{
1002  NSMutableString *orderByExpr;
1003  NSEnumerator   *enumerator   = nil;
1004  EOSortOrdering *sortOrdering = nil;
1005  int            orderCnt      = 0;
1006  EOAdaptor      *adaptor;
1007
1008  adaptor = [[_adChan adaptorContext] adaptor];
1009
1010  orderByExpr = nil;
1011  enumerator = [[self->fetchSpecification sortOrderings] objectEnumerator];
1012  while ((sortOrdering = [enumerator nextObject]) != nil) {
1013      SEL         selector    = NULL;
1014      NSString    *key        = nil;
1015      EOAttribute *keyAttr    = nil;
1016      int         order       = 0; /* 0 - not used; 1 - asc; 2 - desc */
1017      BOOL        inSensitive = NO;
1018      NSString    *orderTmp   = nil;
1019
1020      if (orderByExpr == nil)
1021        orderByExpr = [NSMutableString stringWithCapacity:64];
1022      else
1023        [orderByExpr appendString:@", "];
1024
1025      if ((selector = [sortOrdering selector])) {
1026        if (sel_isEqual(selector, EOCompareAscending))
1027          order = 1;
1028        else if (sel_isEqual(selector, EOCompareDescending))
1029          order = 2;
1030        else if (sel_isEqual(selector, EOCompareCaseInsensitiveAscending)) {
1031	  order       = 1;
1032	  inSensitive = YES;
1033	}
1034        else if (sel_isEqual(selector, EOCompareCaseInsensitiveDescending)) {
1035	  order       = 2;
1036	  inSensitive = YES;
1037	}
1038      }
1039      key = [sortOrdering key];
1040
1041      if (key == nil || [key length] == 0) {
1042        NSLog(@"WARNING[%s]: no key in sortordering %@",
1043	      __PRETTY_FUNCTION__, key);
1044        continue;
1045      }
1046      {
1047        EOAttribute  *obj;
1048
1049        key = [key lowercaseString];
1050	obj = [self findAttributeForKey:key inAttributes:_attrs];
1051        if (obj == nil) {
1052          [self->__attributes release]; self->__attributes = nil;
1053          [self->__qualifier  release]; self->__qualifier  = nil;
1054#if 0 // TODO: memleak in error case
1055          [expression         release]; expression         = nil;
1056#endif
1057          [self rollbackTransaction];
1058
1059	  [[self _couldNotFindSortAttributeInAttributes:_attrs
1060		 forSortOrdering:sortOrdering] raise];
1061	  return nil;
1062        }
1063
1064        keyAttr = obj;
1065      }
1066      key      = [adaptor formatAttribute:keyAttr];
1067      orderTmp = [NSString stringWithFormat:@"order%d", orderCnt];
1068      orderCnt++;
1069      [orderByExpr appendString:orderTmp];
1070      if (order == 1)
1071        [orderByExpr appendString:@" ASC"];
1072      else if (order == 2)
1073        [orderByExpr appendString:@" DESC"];
1074
1075      /* manipulate select expr */
1076      if (inSensitive) {
1077          if ([[keyAttr valueClassName] isEqualToString:@"NSString"]) {
1078              key = [NSString stringWithFormat:@"LOWER(%@)", key];
1079          }
1080          else
1081            NSLog(@"WARNING[%s]: inSensitive expression for no text attribute",
1082                  __PRETTY_FUNCTION__);
1083      }
1084      {
1085	NSString *str = nil;
1086
1087	str = [key stringByAppendingString:@" AS "];
1088	str = [str stringByAppendingString:orderTmp];
1089	str = [str stringByAppendingString:@", "];
1090
1091	[selectList insertString:str atIndex:0];
1092      }
1093  }
1094  return orderByExpr;
1095}
1096
1097- (NSMutableString *)_selectListWithChannel:(EOAdaptorChannel *)_adChan {
1098  NSArray         *attrs      = nil;
1099  NSEnumerator    *enumerator = nil;
1100  EOAttribute     *attribute  = nil;
1101  BOOL            first       = YES;
1102  NSMutableString *select     = nil;
1103  EOAdaptor       *adaptor    = nil;
1104  NSString        *entityName = nil;
1105
1106  adaptor    = [[_adChan adaptorContext] adaptor];
1107  entityName = [self->fetchSpecification entityName];
1108
1109  if ((attrs = self->__attributes) == nil)
1110    attrs = [_adChan attributesForTableName:entityName];
1111
1112  attrs  = [_adChan _sortAttributesForSelectExpression:attrs];
1113  select = [NSMutableString stringWithCapacity:128];
1114  enumerator = [attrs objectEnumerator];
1115  while ((attribute = [enumerator nextObject])) {
1116    if (first)
1117      first = NO;
1118    else
1119      [select appendString:@", "];
1120
1121    [select appendString:[adaptor formatAttribute:attribute]];
1122  }
1123  return select;
1124}
1125
1126- (NSString *)_whereClauseForGlobaID:(EOKeyGlobalID *)_gid
1127  adaptor:(EOAdaptor *)_adaptor
1128  channel:(EOAdaptorChannel *)_adChan
1129{
1130  NSEnumerator    *enumerator;
1131  NSMutableString *result;
1132  NSArray         *pKeys;
1133  NSArray         *pkAttrs;
1134  NSString        *pKey;
1135  int             pkCnt;
1136
1137
1138  pKeys   = [self _primaryKeyAttributeNamesForTableName:[_gid entityName]
1139                  channel:_adChan];
1140  pkAttrs = [self _primaryKeyAttributesForTableName:[_gid entityName]
1141                  channel:_adChan];
1142
1143
1144  if ([pKeys count] != [_gid keyCount]) {
1145    NSLog(@"ERROR[%s]: internal inconsitency pkeys %@ gid %@",
1146          __PRETTY_FUNCTION__, pKeys, _gid);
1147    return nil;
1148  }
1149  enumerator = [pKeys objectEnumerator];
1150
1151  pkCnt  = 0;
1152  result = nil;
1153  while ((pKey = [enumerator nextObject])) {
1154    EOAttribute *attr;
1155    id          value;
1156
1157    if (result == nil)
1158      result = [NSMutableString stringWithCapacity:128];
1159    else
1160      [result appendString:@" AND "];
1161
1162    {
1163      NSEnumerator *enumerator;
1164
1165      enumerator = [pkAttrs objectEnumerator];
1166      while ((attr = [enumerator nextObject])) {
1167        if ([[[attr columnName] lowercaseString] isEqual:pKey])
1168          break;
1169      }
1170      NSAssert2(attr != nil, @"missing attribute for pkName %@ attrs %@",
1171                pKey, pkAttrs);
1172    }
1173    [result appendString:[_adaptor formatAttribute:attr]];
1174
1175
1176    value = [(EOKeyGlobalID *)_gid keyValues][pkCnt++];
1177    if (value == nil) value = null;
1178
1179    [result appendString:[value isNotNull] ? @"=" : @" IS "];
1180    [result appendString:[_adaptor formatValue:value forAttribute:attr]];
1181  }
1182  return result;
1183}
1184
1185- (NSDictionary *)_mapAttrsWithValues:(NSDictionary *)_keyValues
1186  tableName:(NSString *)_tableName
1187  channel:(EOAdaptorChannel *)_adChan
1188{
1189  id           *keys, *values;
1190  int          mapCnt;
1191  NSEnumerator *en;
1192  EOAttribute  *attr;
1193  NSDictionary *result;
1194  NSArray      *attrs;
1195
1196  attrs  = [_adChan attributesForTableName:_tableName];
1197  mapCnt = [attrs count];
1198  keys   = calloc(mapCnt + 1, sizeof(id));
1199  values = calloc(mapCnt + 1, sizeof(id));
1200  en     = [attrs objectEnumerator];
1201  mapCnt = 0;
1202
1203  while ((attr = [en nextObject])) {
1204    id v;
1205
1206    v = (v = [_keyValues valueForKey:[[attr columnName] lowercaseString]])
1207      ? v : (id)null;
1208
1209    keys[mapCnt]   = attr;
1210    values[mapCnt] = v;
1211    mapCnt++;
1212  }
1213  result = [[NSDictionary alloc]
1214                          initWithObjects:values forKeys:keys count:mapCnt];
1215  free(keys);   keys   = NULL;
1216  free(values); values = NULL;
1217  return [result autorelease];
1218}
1219
1220@end /* EOAdaptorDataSource(Private) */
1221
1222@implementation EOAndQualifier(SqlExpression)
1223
1224- (NSString *)sqlExpressionWithAdaptor:(EOAdaptor *)_adaptor
1225  attributes:(NSArray *)_attributes
1226{
1227  NSMutableString *str        = nil;
1228  NSEnumerator    *enumerator = nil;
1229  EOQualifier     *qual       = nil;
1230  BOOL            isFirst     = YES;
1231  NSString        *result     = nil;
1232
1233  str = [[NSMutableString alloc] initWithCapacity:128];
1234
1235  enumerator = [self->qualifiers objectEnumerator];
1236  while ((qual = [enumerator nextObject])) {
1237    NSString *s;
1238
1239    s = [qual sqlExpressionWithAdaptor:_adaptor attributes:_attributes];
1240    if (isFirst) {
1241      [str appendFormat:@"(%@)", s];
1242      isFirst = NO;
1243    }
1244    else
1245      [str appendFormat:@" AND (%@)", s];
1246  }
1247  result = [str copy];
1248  [str release]; str = nil;
1249  return [result autorelease];
1250}
1251@end /* EOAndQualifier(SqlExpression) */
1252
1253@implementation EOOrQualifier(SqlExpression)
1254
1255- (NSString *)sqlExpressionWithAdaptor:(EOAdaptor *)_adaptor
1256  attributes:(NSArray *)_attributes
1257{
1258  NSMutableString *str        = nil;
1259  NSEnumerator    *enumerator = nil;
1260  EOQualifier     *qual       = nil;
1261  BOOL            isFirst     = YES;
1262  NSString        *result     = nil;
1263
1264  str = [[NSMutableString alloc] initWithCapacity:128];
1265
1266  enumerator = [self->qualifiers objectEnumerator];
1267  while ((qual = [enumerator nextObject])) {
1268    NSString *s;
1269
1270    s = [qual sqlExpressionWithAdaptor:_adaptor attributes:_attributes];
1271    if (isFirst) {
1272      [str appendFormat:@"(%@)", s];
1273      isFirst = NO;
1274    }
1275    else
1276      [str appendFormat:@" OR (%@)", s];
1277  }
1278  result = [str copy];
1279  [str release]; str = nil;
1280  return [result autorelease];
1281}
1282
1283@end /* EOOrQualifier(SqlExpression) */
1284
1285@implementation EOKeyValueQualifier(SqlExpression)
1286
1287+ (NSString *)sqlStringForOperatorSelector:(SEL)_sel {
1288  static NSMapTable *selectorToOperator = NULL;
1289  NSString *s, *ss;
1290
1291  if ((s = NSStringFromSelector(_sel)) == nil)
1292    return nil;
1293
1294  if (selectorToOperator == NULL) {
1295    selectorToOperator = NSCreateMapTable(NSObjectMapKeyCallBacks,
1296                                          NSObjectMapValueCallBacks,
1297                                          10);
1298    NSMapInsert(selectorToOperator,
1299                NSStringFromSelector(EOQualifierOperatorEqual),
1300                @"=");
1301    NSMapInsert(selectorToOperator,
1302                NSStringFromSelector(EOQualifierOperatorNotEqual),
1303                @"<>");
1304    NSMapInsert(selectorToOperator,
1305                NSStringFromSelector(EOQualifierOperatorLessThan),
1306                @"<");
1307    NSMapInsert(selectorToOperator,
1308                NSStringFromSelector(EOQualifierOperatorGreaterThan),
1309                @">");
1310    NSMapInsert(selectorToOperator,
1311                NSStringFromSelector(EOQualifierOperatorLessThanOrEqualTo),
1312                @"<=");
1313    NSMapInsert(selectorToOperator,
1314                NSStringFromSelector(EOQualifierOperatorGreaterThanOrEqualTo),
1315                @">=");
1316  }
1317
1318  if ((ss = NSMapGet(selectorToOperator, s)))
1319    return ss;
1320
1321  return nil;
1322}
1323
1324- (NSString *)sqlExpressionWithAdaptor:(EOAdaptor *)_adaptor
1325  attributes:(NSArray *)_attributes
1326{
1327  EOAttribute  *attr = nil;
1328  NSEnumerator *en   = nil;
1329  NSString     *k    = nil;
1330  NSString     *sql  = nil;
1331  NSString     *sqlKey, *sqlValue;
1332
1333  k  = [self->key lowercaseString];
1334  en = [_attributes objectEnumerator];
1335
1336  while ((attr = [en nextObject])) {
1337    if ([[[attr columnName] lowercaseString] isEqualToString:k]) {
1338      break;
1339    }
1340  }
1341  if (!attr) {
1342    en = [_attributes objectEnumerator];
1343    while ((attr = [en nextObject])) {
1344      if ([[attr name] isEqualToString:self->key])
1345        break;
1346    }
1347  }
1348  if (!attr) {
1349    NSLog(@"WARNING[%s]: missing attribute [%@] for qualifier %@",
1350          __PRETTY_FUNCTION__,
1351          _attributes, self);
1352    return @"1=2";
1353  }
1354
1355  sqlKey   = [_adaptor formatAttribute:attr];
1356
1357  sqlValue = [_adaptor formatValue:(self->value ? self->value : (id)null)
1358                       forAttribute:attr];
1359
1360  sql = nil;
1361
1362  if (sel_isEqual(EOQualifierOperatorEqual, self->operator)) {
1363    if ([self->value isNotNull])
1364      sql = [NSString stringWithFormat:@"%@ = %@", sqlKey, sqlValue];
1365    else
1366      sql = [NSString stringWithFormat:@"%@ IS NULL", sqlKey];
1367  }
1368  else if (sel_isEqual(EOQualifierOperatorNotEqual, self->operator)) {
1369    if ([self->value isNotNull])
1370      sql = [NSString stringWithFormat:@"NOT (%@ = %@)", sqlKey, sqlValue];
1371    else
1372      sql = [NSString stringWithFormat:@"%@ IS NOT NULL", sqlKey];
1373  }
1374  else if (sel_isEqual(EOQualifierOperatorLessThan, self->operator)) {
1375    sql = [NSString stringWithFormat:@"%@ < %@", sqlKey, sqlValue];
1376  }
1377  else if (sel_isEqual(EOQualifierOperatorLessThanOrEqualTo, self->operator)) {
1378    sql = [NSString stringWithFormat:@"%@ <= %@", sqlKey, sqlValue];
1379  }
1380  else if (sel_isEqual(EOQualifierOperatorGreaterThan, self->operator)) {
1381    sql = [NSString stringWithFormat:@"%@ > %@", sqlKey, sqlValue];
1382  }
1383  else if (sel_isEqual(EOQualifierOperatorGreaterThanOrEqualTo, self->operator)) {
1384    sql = [NSString stringWithFormat:@"%@ >= %@", sqlKey, sqlValue];
1385  }
1386  else if (sel_isEqual(EOQualifierOperatorLike, self->operator)) {
1387    sqlValue = [[self->value stringValue]
1388                             stringByReplacingString:@"*" withString:@"%"];
1389    sqlValue = [_adaptor formatValue:sqlValue forAttribute:attr];
1390
1391    sql = [NSString stringWithFormat:@"%@ LIKE %@", sqlKey, sqlValue];
1392  }
1393  else if (sel_isEqual(EOQualifierOperatorCaseInsensitiveLike, self->operator)) {
1394    sqlValue = [[self->value stringValue]
1395                             stringByReplacingString:@"*" withString:@"%"];
1396    sqlValue = [sqlValue lowercaseString];
1397    sqlValue = [_adaptor formatValue:sqlValue forAttribute:attr];
1398
1399    sql = [NSString stringWithFormat:@"LOWER(%@) LIKE %@", sqlKey, sqlValue];
1400  }
1401#if 0
1402  else if (sel_isEqual(EOQualifierOperatorLessThanOrEqualTo, self->operator)) {
1403  }
1404  else if (sel_isEqual(EOQualifierOperatorGreaterThanOrEqualTo, self->operator)) {
1405  }
1406#endif
1407  else {
1408    NSLog(@"ERROR(%s): unsupported SQL operator: %@", __PRETTY_FUNCTION__,
1409          [EOQualifier stringForOperatorSelector:self->operator]);
1410    [self notImplemented:_cmd];
1411    return nil;
1412  }
1413
1414  return sql;
1415}
1416
1417@end /* EOKeyValueQualifier(SqlExpression) */
1418
1419@implementation EONotQualifier(SqlExpression)
1420
1421- (NSString *)sqlExpressionWithAdaptor:(EOAdaptor *)_adaptor
1422  attributes:(NSArray *)_attributes
1423{
1424  NSString *s;
1425
1426  s = [self->qualifier sqlExpressionWithAdaptor:_adaptor
1427	               attributes:_attributes];
1428  return [NSString stringWithFormat:@"NOT(%@)", s];
1429}
1430
1431@end /* EONotQualifier(SqlExpression) */
1432
1433@implementation EOKeyComparisonQualifier(SqlExpression)
1434
1435- (NSString *)sqlExpressionWithAdaptor:(EOAdaptor *)_adaptor
1436  attributes:(NSArray *)_attributes
1437{
1438  NSLog(@"ERROR(%s): subclass needs to override this method!",
1439	__PRETTY_FUNCTION__);
1440  return nil;
1441}
1442
1443@end /* EOKeyComparisonQualifier(SqlExpression) */
1444