]> err.no Git - sope/blob - sope-gdl1/GDLAccess/EOSQLExpression.m
renamed PostgreSQL72 to PostgreSQL, install in Library/GDLAdaptors-1.1
[sope] / sope-gdl1 / GDLAccess / EOSQLExpression.m
1 /* 
2    EOSQLExpression.m
3
4    Copyright (C) 1996 Free Software Foundation, Inc.
5
6    Author: Ovidiu Predescu <ovidiu@bx.logicnet.ro>
7    Date: September 1996
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
27 #include "EOSQLExpression.h"
28 #include "common.h"
29 #import "EOAdaptor.h"
30 #import "EOAdaptorChannel.h"
31 #import "EOAdaptorContext.h"
32 #import "EOAttribute.h"
33 #import "EOAttributeOrdering.h"
34 #import "EOEntity.h"
35 #import "EOFExceptions.h"
36 #import "EOSQLQualifier.h"
37 #import "EORelationship.h"
38 #import <EOControl/EOFetchSpecification.h>
39 #import <EOControl/EONull.h>
40 #import <EOControl/EOQualifier.h>
41 #import <EOControl/EOSortOrdering.h>
42
43 #if LIB_FOUNDATION_LIBRARY
44 #  include <extensions/DefaultScannerHandler.h>
45 #  include <extensions/PrintfFormatScanner.h>
46 #else
47 #  include "DefaultScannerHandler.h"
48 #  include "PrintfFormatScanner.h"
49 #endif
50
51 /*
52 A SQL command is generated for an entity when you fetch, insert, update or
53 delete an object from the database. The command includes all the attributes
54 from entity.
55
56 The biggest problem in generation of SQL commands is the generation of SELECT
57 statements, because the tables have to be properly aliased. If an entity has
58 only simple attributes then you have a single table in the FROM clause. If the
59 entity has flattened attributes, then several tables appear in the FROM list;
60 each one with a different alias.
61
62 The algorithm uses a dictionary that has as keys either entities or
63 relationships. The values in dictionary are aliases.
64
65 For a simple attribute we insert in the above dictionary the attribute's entity
66 as key and an alias for it.
67
68 For a flattened attribute with the following definition: `toEntity1.toEntity2.
69 ... toEntityn.attribute', we have to assign to each toEntityi object an alias.
70
71 Let's see how each component of the SELECT command is generated. The columns
72 list is generated by iterating over the attributes of entity. If the attribute
73 is simple, its entity is looked up in the aliases dictionary. If the attribute
74 is flattened, the alias of attribute is the alias of toEntityn. If the
75 attribute is derived, each component of it is generated applying the same
76 rules.
77
78 The FROM list is generated like this. For each key in the aliases dictionary:
79 * if the key is an entity, its external name is written followed by the alias
80 corresponding to entity
81 * if the key is a relationship, the external name of its destination entity is
82 written followed by the alias corresponding to relationship.
83
84 A little bit complicated is the WHERE clause. For each flattened attribute we
85 have to generate the logic expression that selects the attribute by expressing
86 the joins over several entities. The algorithm starts with the first
87 relationship.  An additional variable, named context is used. Its initial value
88 is the current entity. The expression
89
90 context-alias.source-attribute join-operator
91             relationship-alias.destination-attribute
92
93 is added using the AND operator to the where clause. Then the context variable
94 is set to the current relationship. The algorithm continues with the next
95 relationship until there are no more relationships to process.
96
97 */
98
99 static EONull *null = nil;
100 NSString *EOBindVariableNameKey        = @"name";
101 NSString *EOBindVariablePlaceHolderKey = @"placeHolder";
102 NSString *EOBindVariableAttributeKey   = @"attribute";
103 NSString *EOBindVariableValueKey       = @"value";
104
105
106 @interface EOInsertUpdateScannerHandler : DefaultScannerHandler
107 {
108     NSString    *value;
109     EOAttribute *attribute;
110     EOAdaptor   *adaptor;
111 }
112
113 - (void)setValue:(NSString*)value
114   attribute:(EOAttribute*)attribute
115   adaptor:(EOAdaptor*)adaptor;
116 @end
117
118
119 @implementation EOInsertUpdateScannerHandler
120
121 - (id)init {
122   [super init];
123
124   specHandler['V']
125     = [self methodForSelector:@selector(convertValue:scanner:)];
126   return self;
127 }
128
129 - (void)dealloc {
130   RELEASE(self->value);
131   RELEASE(self->attribute);
132   RELEASE(self->adaptor);
133   [super dealloc];
134 }
135
136 - (void)setValue:(NSString *)_value
137   attribute:(EOAttribute*)_attribute
138   adaptor:(EOAdaptor*)_adaptor
139 {
140     ASSIGNCOPY(self->value,     _value);
141     ASSIGN(self->attribute, _attribute);
142     ASSIGN(self->adaptor,   _adaptor);
143 }
144
145 - (NSString *)convertValue:(va_list *)pString scanner:(FormatScanner *)scanner{
146   return (self->adaptor)
147     ? [self->adaptor formatValue:self->value?self->value:(id)null 
148            forAttribute:attribute]
149       : self->value;
150 }
151
152 @end /* EOInsertUpdateScannerHandler */
153
154
155 @implementation EOSQLExpression
156
157 + (int)version {
158   return 1;
159 }
160
161 + (void)initialize {
162   if (null == nil) null = [[NSNull null] retain];
163 }
164
165 - (id)initWithEntity:(EOEntity *)_entity {
166   if ((self = [super init])) {
167     ASSIGN(self->entity, _entity);
168   }
169   return self;
170 }
171 - (id)init {
172   return [self initWithEntity:nil];
173 }
174
175 + (id)deleteExpressionWithQualifier:(EOSQLQualifier*)qualifier
176   channel:(EOAdaptorChannel*)channel
177 {
178     EOSQLExpression* sqlExpression
179         = AUTORELEASE([[self deleteExpressionClass] new]);
180     [sqlExpression deleteExpressionWithQualifier:qualifier channel:channel];
181     return sqlExpression;
182 }
183 + (id)insertExpressionForRow:(NSDictionary *)row
184   entity:(EOEntity*)_entity
185   channel:(EOAdaptorChannel*)channel
186 {
187     EOSQLExpression* sqlExpression
188         = AUTORELEASE([[self insertExpressionClass] new]);
189     
190     [sqlExpression insertExpressionForRow:row entity:_entity channel:channel];
191     return sqlExpression;
192 }
193
194 - (id)deleteExpressionWithQualifier:(EOSQLQualifier *)qualifier
195   channel:(EOAdaptorChannel*)channel
196 {
197   NSString *sql;
198   
199   self = [self initWithEntity:[qualifier entity]];
200   
201   self->adaptor = RETAIN([[channel adaptorContext] adaptor]);
202
203   sql = [self assembleDeleteStatementWithQualifier:qualifier
204               tableList:[entity externalName]
205               whereClause:[self whereClauseForQualifier:qualifier]];
206   
207   self->content = [sql mutableCopy];
208   [self finishBuildingExpression];
209   
210   return self;
211 }
212
213 - (id)insertExpressionForRow:(NSDictionary *)row
214   entity:(EOEntity*)_entity
215   channel:(EOAdaptorChannel*)channel
216 {
217   NSString *sql;
218   
219   self = [self initWithEntity:_entity];
220
221   self->adaptor = RETAIN([[channel adaptorContext] adaptor]);
222
223   sql = [self assembleInsertStatementWithRow:row
224               tableList:[entity externalName]
225               columnList:[self columnListForRow:row]
226               valueList:[self valueListForRow:row]];
227   
228   self->content = [sql mutableCopy];
229   
230   [self finishBuildingExpression];
231
232   return self;
233 }
234
235 + (id)selectExpressionForAttributes:(NSArray *)attributes
236   lock:(BOOL)flag
237   qualifier:(EOSQLQualifier *)qualifier
238   fetchOrder:(NSArray *)fetchOrder
239   channel:(EOAdaptorChannel *)channel
240 {
241     EOSQLExpression* sqlExpression
242         = AUTORELEASE([[self selectExpressionClass] new]);
243     [sqlExpression selectExpressionForAttributes:attributes
244                     lock:flag
245                     qualifier:qualifier
246                     fetchOrder:fetchOrder
247                     channel:channel];
248     return sqlExpression;
249 }
250
251 - (id)selectExpressionForAttributes:(NSArray *)attributes
252   lock:(BOOL)flag
253   qualifier:(EOSQLQualifier *)qualifier
254   fetchOrder:(NSArray *)fetchOrder
255   channel:(EOAdaptorChannel *)channel
256 {
257     NSArray  *relationshipPaths;
258     NSString *sql;
259     
260     self = [self initWithEntity:[qualifier entity]];
261     
262     self->adaptor = [[[channel adaptorContext] adaptor] retain];
263     // self->content = [NSMutableString new]; // mh: BUG!!!
264     
265     relationshipPaths = [self relationshipPathsForAttributes:attributes
266                               qualifier:qualifier
267                               fetchOrder:fetchOrder];
268     
269     sql = [self assembleSelectStatementWithAttributes:attributes
270                 lock:flag
271                 qualifier:qualifier
272                 fetchOrder:fetchOrder
273                 selectString:
274                   [qualifier usesDistinct] ? @"SELECT DISTINCT" : @"SELECT"
275                 columnList:
276                   [self selectListWithAttributes:attributes 
277                         qualifier:qualifier]
278                 tableList:[self fromClause]
279                 whereClause:[self whereClauseForQualifier:qualifier]
280                 joinClause:
281                   [self joinExpressionForRelationshipPaths:relationshipPaths]
282                 orderByClause:[self orderByClauseForFetchOrder:fetchOrder]
283                 lockClause:flag ? [self lockClause] : nil];
284     
285     self->content = [sql mutableCopy];
286     
287     [self finishBuildingExpression];
288     
289     return self;
290 }
291 + (id)updateExpressionForRow:(NSDictionary *)row
292   qualifier:(EOSQLQualifier *)qualifier
293   channel:(EOAdaptorChannel *)channel
294 {
295     EOSQLExpression* sqlExpression
296         = AUTORELEASE([[self updateExpressionClass] new]);
297     [sqlExpression updateExpressionForRow:row
298                    qualifier:qualifier
299                    channel:channel];
300     return sqlExpression;
301 }
302
303 - (id)updateExpressionForRow:(NSDictionary *)row
304   qualifier:(EOSQLQualifier *)qualifier
305   channel:(EOAdaptorChannel *)channel
306 {
307   NSString *sql;
308
309   self = [self initWithEntity:[qualifier entity]];
310   if (self->entity == nil) {
311     [[[InvalidQualifierException alloc]
312                initWithFormat:@"entity for qualifier %@ is nil",
313                  [qualifier expressionValueForContext:nil]] raise];
314   }
315   
316   self->adaptor = [[[channel adaptorContext] adaptor] retain];
317   self->content = [[NSMutableString alloc] init];
318
319   sql = [self assembleUpdateStatementWithRow:row
320               qualifier:qualifier
321               tableList:[entity externalName]
322               updateList:[self updateListForRow:row]
323               whereClause:[self whereClauseForQualifier:qualifier]];
324   [self->content appendString:sql];
325   
326   [self finishBuildingExpression];
327
328   return self;
329 }
330
331 - (void)dealloc {
332   RELEASE(self->bindings);
333   RELEASE(self->listString);
334   RELEASE(self->whereClauseString);
335   RELEASE(self->entity);
336   RELEASE(self->adaptor);
337   RELEASE(self->entitiesAndPropertiesAliases);
338   RELEASE(self->fromListEntities);
339   RELEASE(self->content);
340   [super dealloc];
341 }
342
343 - (NSString *)selectListWithAttributes:(NSArray *)attributes
344   qualifier:(EOSQLQualifier *)qualifier
345 {
346     NSMutableString *selectList;
347     NSEnumerator    *enumerator;
348     BOOL            first = YES;
349     EOAttribute     *attribute;
350     BOOL            isDistinct;
351
352     selectList = [NSMutableString stringWithCapacity:128];
353     isDistinct = [qualifier usesDistinct];
354
355     /* check whether DISTINCT is allowed */
356     enumerator = [attributes objectEnumerator];
357     while ((attribute = [enumerator nextObject])) {
358       if (self->adaptor) {
359         if (![self->adaptor attributeAllowedInDistinctSelects:attribute]) {
360 #if DEBUG && 0
361           NSLog(@"WARNING: tried to select attribute type %@ with DISTINCT "
362                 @"which is not allowed, disabling DISTINCT: %@",
363                 [attribute externalType], [attribute name]);
364 #endif
365           isDistinct = NO;
366           break;
367         }
368       }
369     }
370     
371     enumerator = [attributes objectEnumerator];
372     while ((attribute = [enumerator nextObject])) {
373         if(first)
374             first = NO;
375         else
376             [selectList appendString:@", "];
377
378         [selectList appendString:[self expressionValueForAttribute:attribute]];
379     }
380
381     return selectList;
382 }
383
384 - (NSString *)fromClause {
385     NSMutableString *fromClause;
386     NSEnumerator *enumerator;
387     BOOL first = YES;
388     id key;
389
390     fromClause = [NSMutableString stringWithCapacity:64];
391     enumerator = [self->fromListEntities objectEnumerator];
392     
393     /* Compute the FROM list from all the aliases found in
394        entitiesAndPropertiesAliases dictionary. Note that this dictionary
395        contains entities and relationships. The last ones are there for
396        flattened attributes over reflexive relationships. */
397     while ((key = [enumerator nextObject])) {
398         if (first)
399             first = NO;
400         else
401             [fromClause appendString:@", "];
402
403         if ([key isKindOfClass:[EORelationship class]]) {
404             /* This key corresponds to a flattened attribute. */
405             [fromClause appendFormat:@"%@ %@",
406                             [[key destinationEntity] externalName],
407                             [entitiesAndPropertiesAliases objectForKey:key]];
408         }
409         else {
410             /* This is an EOEntity. */
411             [fromClause appendFormat:@"%@ %@",
412                             [key externalName],
413                             [entitiesAndPropertiesAliases objectForKey:key]];
414         }
415     }
416
417     return fromClause;
418 }
419
420 - (NSString *)whereClauseForQualifier:(EOSQLQualifier *)_qualifier {
421   return [_qualifier expressionValueForContext:self];
422 }
423
424 - (id)joinExpressionForRelationshipPaths:(NSArray *)relationshipPaths
425 {
426   NSMutableString *expression = [NSMutableString stringWithCapacity:64];
427   NSEnumerator    *enumerator = [relationshipPaths objectEnumerator];
428   NSMutableArray  *rels = [NSMutableArray new];
429   NSArray         *relationshipPath;
430   EORelationship  *relationship;
431   BOOL            first = YES;
432     
433   while ((relationshipPath = [enumerator nextObject])) {
434     NSEnumerator *componentRelationshipsEnumerator;
435     id context;
436
437     componentRelationshipsEnumerator = [relationshipPath objectEnumerator];
438     context = entity;
439
440     while((relationship = [componentRelationshipsEnumerator nextObject])) {
441       if (![rels containsObject:relationship]) {
442         id sourceAttribute, destinationAttribute;
443
444         [rels addObject:relationship];
445             
446         /* Compute the SQL expression string corresponding to each join in
447            the relationship. */
448         sourceAttribute = [self expressionValueForAttribute:
449                                 [relationship sourceAttribute]
450                                 context:context];
451         destinationAttribute = [self expressionValueForAttribute:
452                                      [relationship destinationAttribute]
453                                      context:
454                                      [relationship destinationEntity]];
455
456         //NSLog(@"sourceAttribute %@", sourceAttribute);
457         //NSLog(@"destinationAttribute %@", destinationAttribute);
458
459         if (first) 
460           first = NO;
461         else
462           [expression appendString:@" AND "];
463
464         [expression appendString:[sourceAttribute description]];
465         [expression appendString:@" = "];
466         [expression appendString:[destinationAttribute description]];
467       }
468       /* Compute the next context which is the current relationship. */
469       context = [relationship destinationEntity];
470     }
471   }
472   [rels release];
473   return expression;
474 }
475
476 - (NSString *)orderByClauseForFetchOrder:(NSArray *)fetchOrder
477 {
478   int             i, count;
479   NSMutableString *orderBy;
480   
481   if ((count = [fetchOrder count]) == 0)
482     return @"";
483   
484   orderBy = (id)[NSMutableString stringWithCapacity:32];
485   for(i = 0; i < count; i++) {
486     id eorder;
487
488     eorder = [fetchOrder objectAtIndex:i];
489     
490     if (i != 0) [orderBy appendString:@", "];
491     
492     if ([eorder isKindOfClass:[EOSortOrdering class]]) {
493       EOSortOrdering *order;
494       EOAttribute    *attribute;
495       SEL            ordering;
496       NSString       *fmt;
497       
498       order     = eorder;
499       ordering  = [order selector];
500       attribute = [self->entity attributeNamed:[order key]];
501
502       if (sel_eq(ordering, EOCompareCaseInsensitiveAscending) ||
503           sel_eq(ordering, EOCompareCaseInsensitiveDescending))
504         fmt = @"LOWER(%@)";
505       else
506         fmt = @"%@";
507       
508       [orderBy appendFormat:fmt, [self expressionValueForAttribute:attribute]];
509
510       if (sel_eq(ordering, EOCompareCaseInsensitiveAscending) ||
511           sel_eq(ordering, EOCompareAscending)) {
512         [orderBy appendString:@" ASC"];
513       }
514       else if (sel_eq(ordering, EOCompareCaseInsensitiveDescending) ||
515                sel_eq(ordering, EOCompareDescending)) {
516         [orderBy appendString:@" DESC"];
517       }
518     }
519     else {
520       EOAttributeOrdering *order;
521       EOOrdering ordering;
522       
523       order    = eorder;
524       ordering = [order ordering];
525       
526       [orderBy appendFormat:@"%@",
527                [self expressionValueForAttribute:[order attribute]]];
528       if (ordering != EOAnyOrder)
529         [orderBy appendString:
530                  ([order ordering] == EOAscendingOrder ? @" ASC" : @" DESC")];
531     }
532   }
533
534   return orderBy;
535 }
536
537 - (NSString *)literalForAttribute:(EOAttribute *)_attribute
538   withValue:(id)_value fromRow:(NSDictionary *)_row
539 {
540   return (self->adaptor)
541     ? [self->adaptor formatValue:_value?_value:null forAttribute:_attribute]
542     : [_value stringValue];
543 }
544
545 - (id)updateListForRow:(NSDictionary *)row {
546   PrintfFormatScanner          *formatScanner  = nil;
547   EOInsertUpdateScannerHandler *scannerHandler = nil;
548   NSMutableString *expression    = nil;
549   NSEnumerator    *enumerator;
550   NSString        *attributeName = nil;
551   BOOL            first          = YES;
552
553   enumerator    = [row keyEnumerator];
554   expression = [NSMutableString stringWithCapacity:256];
555     
556   formatScanner = [[PrintfFormatScanner alloc] init];
557   AUTORELEASE(formatScanner);
558   scannerHandler = [[EOInsertUpdateScannerHandler alloc] init];
559   AUTORELEASE(scannerHandler);
560
561   [formatScanner setAllowOnlySpecifier:YES];
562   [formatScanner setFormatScannerHandler:scannerHandler];
563
564   while((attributeName = [enumerator nextObject])) {
565     EOAttribute *attribute;
566     NSString    *updateFormat;
567     NSString    *columnName   = nil;
568     id          value         = nil;
569
570     attribute    = [entity attributeNamed:attributeName];
571     updateFormat = [attribute updateFormat];
572         
573     NSAssert1(attribute, @"attribute %@ should be non nil", attributeName);
574     columnName = adaptor
575       ? [adaptor formatAttribute:attribute]
576       : [attribute columnName];
577
578     value = [row objectForKey:attributeName];
579         
580     if (updateFormat) {
581       [scannerHandler setValue:value
582                       attribute:attribute
583                       adaptor:adaptor];
584 #if defined(__s390__)
585       value = [formatScanner performSelector:
586                              @selector(stringWithFormat:arguments:)
587                              withObject:updateFormat
588                              withObject:nil];
589 #else
590       value = [formatScanner stringWithFormat:updateFormat
591                              arguments:NULL];
592 #endif
593     }
594     else {
595       value = [self literalForAttribute:attribute
596                     withValue:value fromRow:row];
597     }
598
599     if(first) first = NO;
600     else [expression appendString:@", "];
601
602     [expression appendString:columnName];
603     [expression appendString:@" = "];
604     [expression appendString:value];
605   }
606
607   return expression;
608 }
609
610 - (id)columnListForRow:(NSDictionary *)row {
611   NSMutableString *expression;
612   NSEnumerator    *enumerator;
613   NSString        *attributeName = nil;
614   BOOL            first          = YES;
615
616   expression = [NSMutableString stringWithCapacity:128];
617   enumerator = [row keyEnumerator];
618     
619   while ((attributeName = [enumerator nextObject])) {
620     EOAttribute *attribute;
621     NSString    *columnName;
622
623     attribute = [entity attributeNamed:attributeName];
624         
625     NSAssert1(attribute, @"attribute %@ should be non nil", attributeName);
626     columnName = adaptor
627       ? [adaptor formatAttribute:attribute]
628       : [attribute columnName];
629
630     if (first) first = NO;
631     else [expression appendString:@", "];
632
633     [expression appendString:columnName];
634   }
635     
636   return expression;
637 }
638
639 - (id)valueListForRow:(NSDictionary *)row  {
640   EOInsertUpdateScannerHandler *scannerHandler;
641   PrintfFormatScanner          *formatScanner;
642   NSEnumerator                 *enumerator;
643   NSString                     *attributeName  = nil;
644   NSMutableString              *expression     = nil;
645   BOOL                         first           = YES;
646
647   formatScanner = [[PrintfFormatScanner alloc] init];
648   AUTORELEASE(formatScanner);
649   scannerHandler = [[EOInsertUpdateScannerHandler alloc] init];
650   AUTORELEASE(scannerHandler);
651
652   expression = [NSMutableString stringWithCapacity:256];
653   enumerator     = [row keyEnumerator];
654
655   [formatScanner setAllowOnlySpecifier:YES];
656   [formatScanner setFormatScannerHandler:scannerHandler];
657
658   while ((attributeName = [enumerator nextObject])) {
659     EOAttribute *attribute;
660     NSString    *insertFormat;
661     id          value;
662
663     attribute    = [entity attributeNamed:attributeName];
664     insertFormat = [attribute insertFormat];
665     value        = [row objectForKey:attributeName];
666         
667     NSAssert1(attribute, @"attribute %@ should be non nil", attributeName);
668     if (insertFormat) {
669       [scannerHandler setValue:value
670                       attribute:attribute
671                       adaptor:self->adaptor];
672 #if defined(__s390__)
673       value = [formatScanner performSelector:
674                                @selector(stringWithFormat:arguments:)
675                              withObject:insertFormat
676                              withObject:nil];
677 #else
678       value = [formatScanner stringWithFormat:insertFormat
679                              arguments:NULL];
680 #endif
681     }
682     else {
683       value = [self literalForAttribute:attribute
684                     withValue:value fromRow:row];
685     }
686
687     if(first) first = NO;
688     else [expression appendString:@", "];
689
690     [expression appendString:value];
691   }
692     
693   return expression;
694 }
695
696 - (NSArray *)relationshipPathsForAttributes:(NSArray *)attributes
697   qualifier:(EOSQLQualifier *)qualifier
698   fetchOrder:(NSArray *)fetchOrder
699 {
700     int          i, count;
701     NSMutableSet *entities;
702     NSMutableSet *relationshipPaths;
703     NSEnumerator *enumerator;
704     id           entityOrRelationship;
705
706     entities          = [NSMutableSet set];
707     relationshipPaths = [NSMutableSet set];
708     
709     NSAssert3(self->entity,
710               @"entity should be non nil (attrs=%@, qual=%@, order=%@)",
711               attributes, qualifier, fetchOrder);
712     
713     for (i = 0, count = [attributes count]; i < count; i++) {
714       EOAttribute *attribute;
715
716       attribute = [attributes objectAtIndex:i];
717         
718       if ([attribute entity] != entity) {
719         [[[InvalidAttributeException alloc]
720                     initWithFormat:@"all attributes must be from the same "
721                         @"entity (attribute '%@' is not in '%@')",
722                         [attribute name],
723                         [entity name]] raise];
724       }
725       if ([attribute isFlattened]) {
726         id definitionArray = [attribute definitionArray];
727         NSRange range = { 0, [definitionArray count] - 1 };
728         id relationshipPath = [definitionArray subarrayWithRange:range];
729
730         [relationshipPaths addObject:relationshipPath];
731         [entities addObjectsFromArray:relationshipPath];
732       }
733       else {
734         /* attribute is normal. */
735         [entities addObject:[attribute entity]];
736       }
737     }
738     
739     [relationshipPaths unionSet:[qualifier relationshipPaths]];
740     [entities unionSet:[qualifier additionalEntities]];
741     
742     for (i = 0, count = [fetchOrder count]; i < count; i++) {
743       EOAttribute *attribute;
744       id eorder;
745
746       eorder = [fetchOrder objectAtIndex:i];
747
748       attribute = ([eorder isKindOfClass:[EOSortOrdering class]])
749         ? [self->entity attributeNamed:[(EOSortOrdering *)eorder key]]
750         : [(EOAttributeOrdering *)eorder attribute];
751       
752       if ([attribute entity] != entity) {
753             [[[InvalidAttributeException alloc]
754                     initWithFormat:@"all attributes must be from the same "
755                         @"entity (attribute '%@' is not in '%@')",
756                         [attribute name],
757                         [entity name]] raise];
758       }
759       if ([attribute isFlattened]) {
760         id      definitionArray = [attribute definitionArray];
761         NSRange range = { 0, [definitionArray count] - 1 };
762         id      relationshipPath = [definitionArray subarrayWithRange:range];
763
764         [relationshipPaths addObject:relationshipPath];
765         [entities addObjectsFromArray:relationshipPath];
766       }
767     }
768
769     entitiesAndPropertiesAliases = [NSMutableDictionary new];
770     fromListEntities = [NSMutableArray new];
771     enumerator = [entities objectEnumerator];
772     i = 1;
773     while ((entityOrRelationship = [enumerator nextObject])) {
774         NSString* alias = [NSString stringWithFormat:@"t%d", i++];
775         
776         [entitiesAndPropertiesAliases setObject:alias
777                                       forKey:entityOrRelationship];
778         [fromListEntities addObject:entityOrRelationship];
779     }
780
781     return [relationshipPaths allObjects];
782 }
783
784 - (EOEntity *)entity {
785   return self->entity;
786 }
787 - (id)finishBuildingExpression {
788   return self;
789 }
790 - (NSString *)lockClause {
791   return @"";
792 }
793
794 - (NSString *)expressionValueForAttribute:(EOAttribute *)attribute
795   context:(id)context
796 {
797     NSString *alias      = [entitiesAndPropertiesAliases objectForKey:context];
798     NSString *columnName = nil;
799
800     columnName = adaptor
801         ? [adaptor formatAttribute:attribute]
802         : [attribute columnName];
803
804     return alias
805         ? [NSString stringWithFormat:@"%@.%@", alias, columnName]
806         : columnName;
807 }
808
809 - (NSString *)expressionValueForAttribute:(EOAttribute *)attribute {
810     if([attribute isFlattened])
811         return [self expressionValueForAttributePath:
812                                     [attribute definitionArray]];
813     else if([attribute isDerived])
814         return [[attribute definitionArray] expressionValueForContext:self];
815
816     /* attribute is a normal attribute. Its alias is the alias
817        of its entity. */
818     return [self expressionValueForAttribute:attribute
819                  context:[attribute entity]];
820 }
821
822 - (NSString *)expressionValueForAttributePath:(NSArray *)definitionArray {
823     /* Take the alias of the last relationship. */
824   id       relationship;
825   NSString *alias;
826
827   relationship
828         = [definitionArray objectAtIndex:([definitionArray count] - 2)];
829   alias = [entitiesAndPropertiesAliases objectForKey:relationship];
830   
831   return AUTORELEASE(([[NSString alloc]
832                            initWithFormat:@"%@.%@",
833                            alias, [[definitionArray lastObject] columnName]]));
834 }
835
836 - (NSString *)expressionValueForContext:(id<EOExpressionContext>)context {
837     return self->content;
838 }
839
840 + (Class)selectExpressionClass {
841   return [EOSelectSQLExpression class];
842 }
843 + (Class)insertExpressionClass {
844   return [EOInsertSQLExpression class];
845 }
846 + (Class)deleteExpressionClass {
847   return [EODeleteSQLExpression class];
848 }
849 + (Class)updateExpressionClass {
850   return [EOUpdateSQLExpression class];
851 }
852
853 - (EOAdaptor *)adaptor {
854   return self->adaptor;
855 }
856
857 /* description */
858
859 - (NSString *)description {
860   NSMutableString *ms;
861
862   ms = [NSMutableString stringWithCapacity:128];
863   [ms appendFormat:@"<0x%08X[%@]:\n", self, NSStringFromClass([self class])];
864   
865   if (self->entity)  [ms appendFormat:@"  entity=%@\n",  self->entity];
866   if (self->adaptor) [ms appendFormat:@"  adaptor=%@\n", self->adaptor];
867   if (self->content) [ms appendFormat:@"  content='%@\n'", self->content];
868   
869   if (self->entitiesAndPropertiesAliases) 
870     [ms appendFormat:@"  aliases=%@\n", self->entitiesAndPropertiesAliases];
871   if (self->fromListEntities)
872     [ms appendFormat:@"  from-entities=%@\n", self->fromListEntities];
873
874   if (self->whereClauseString)
875     [ms appendFormat:@"  where=%@\n", self->whereClauseString];
876   
877   if (self->listString) [ms appendFormat:@"  list=%@\n",     self->listString];
878   if (self->bindings)   [ms appendFormat:@"  bindings=%@\n", self->bindings];
879   
880   [ms appendString:@">"];
881   return ms;
882 }
883
884 @end /* EOSQLExpression */
885
886
887 @implementation EOInsertSQLExpression
888 @end /* EOInsertSQLExpression */
889
890 @implementation EOUpdateSQLExpression
891 @end /* EOUpdateSQLExpression */
892
893 @implementation EODeleteSQLExpression
894 @end /* EODeleteSQLExpression */
895
896 @implementation EOSQLExpression(NewInEOF2)
897
898 + (EOSQLExpression *)selectStatementForAttributes:(NSArray *)_attributes
899   lock:(BOOL)_flag
900   fetchSpecification:(EOFetchSpecification *)_fspec
901   entity:(EOEntity *)_entity
902 {
903     if (_fspec == nil) {
904       [NSException raise:NSInvalidArgumentException
905                    format:@"missing fetch specification argument .."];
906     }
907     if ([_attributes count] == 0) {
908       [NSException raise:NSInvalidArgumentException
909                    format:@"missing attributes for select .."];
910     }
911
912     return [self selectExpressionForAttributes:_attributes
913                  lock:_flag
914                  qualifier:[[_fspec qualifier] sqlQualifierForEntity:_entity]
915                  fetchOrder:[_fspec sortOrderings]
916                  channel:nil];
917 }
918
919 + (EOSQLExpression *)expressionForString:(NSString *)_sql {
920   EOSQLExpression *se;
921
922   se = [[EOSQLExpression alloc] init];
923   [se setStatement:_sql];
924   return AUTORELEASE(se);
925 }
926
927 /* accessors */
928
929 - (void)setStatement:(NSString *)_stmt {
930   id tmp;
931   tmp = self->content;
932   self->content = [_stmt mutableCopy];
933   RELEASE(tmp);
934 }
935 - (NSString *)statement {
936   return self->content;
937 }
938
939 - (NSString *)whereClauseString {
940   return self->whereClauseString;
941 }
942
943 /* tables */
944
945 - (NSString *)tableListWithRootEntity:(EOEntity *)_entity {
946   return ([self->fromListEntities count] > 0)
947     ? [self fromClause]
948     : [_entity externalName];
949 }
950
951 /* assembly */
952
953 - (NSString *)assembleDeleteStatementWithQualifier:(EOQualifier *)_qualifier
954   tableList:(NSString *)_tableList
955   whereClause:(NSString *)_whereClause
956 {
957   NSMutableString *s;
958   
959   s = [NSMutableString stringWithCapacity:64];
960   [s appendString:@"DELETE FROM "];
961   [s appendString:_tableList];
962   [s appendString:@" WHERE "];
963   [s appendString:_whereClause];
964   return s;
965 }
966
967 - (NSString *)assembleInsertStatementWithRow:(NSDictionary *)_row
968   tableList:(NSString *)_tables
969   columnList:(NSString *)_columns
970   valueList:(NSString *)_values
971 {
972   NSMutableString *s;
973   
974   s = [NSMutableString stringWithCapacity:256];
975   [s appendString:@"INSERT INTO "];
976   [s appendString:_tables];
977   [s appendString:@" ("];
978   [s appendString:_columns];
979   [s appendString:@") VALUES ("];
980   [s appendString:_values];
981   [s appendString:@")"];
982   return s;
983 }
984
985 - (NSString *)assembleSelectStatementWithAttributes:(NSArray *)_attributes
986   lock:(BOOL)_lock
987   qualifier:(EOQualifier *)_qualifier
988   fetchOrder:(NSArray *)_fetchOrder
989   selectString:(NSString *)_selectString
990   columnList:(NSString *)_columns
991   tableList:(NSString *)_tables
992   whereClause:(NSString *)_whereClause
993   joinClause:(NSString *)_joinClause
994   orderByClause:(NSString *)_orderByClause
995   lockClause:(NSString *)_lockClause
996 {
997   NSMutableString *s;
998   unsigned wlen, jlen;
999
1000 #if 0
1001 #warning DEBUG LOG, REMOVE!
1002   [self logWithFormat:@"%s: '%@': %@", __PRETTY_FUNCTION__, _whereClause,self];
1003 #endif
1004   
1005   s = [NSMutableString stringWithCapacity:256];
1006   
1007   [s appendString:_selectString ? _selectString : @"SELECT"];
1008   [s appendString:@" "];
1009   [s appendString:_columns];
1010   [s appendString:@" FROM "];
1011   [s appendString:_tables];
1012   
1013   if ([_lockClause length] > 0) {
1014     [s appendString:@" "];
1015     [s appendString:_lockClause];
1016   }
1017   
1018   wlen = [_whereClause length];
1019   jlen = [_joinClause  length];
1020
1021   if ((wlen > 0) || (jlen > 0))
1022     [s appendString:@" WHERE "];
1023   
1024   if (wlen > 0)
1025     [s appendString:_whereClause];
1026
1027   if ((wlen > 0) && (jlen > 0))
1028     [s appendString:@" AND "];
1029
1030   if (jlen > 0)
1031     [s appendString:_joinClause];
1032   
1033   if ([_orderByClause length] > 0) {
1034     [s appendString:@" ORDER BY "];
1035     [s appendString:_orderByClause];
1036   }
1037   
1038   return s;
1039 }
1040
1041 - (NSString *)assembleUpdateStatementWithRow:(NSDictionary *)_row
1042   qualifier:(EOQualifier *)_qualifier
1043   tableList:(NSString *)_tables
1044   updateList:(NSString *)_updates
1045   whereClause:(NSString *)_whereClause
1046 {
1047   NSMutableString *s;
1048
1049   s = [NSMutableString stringWithCapacity:256];
1050
1051   [s appendString:@"UPDATE "];
1052   [s appendString:_tables];
1053   [s appendString:@" SET "];
1054   [s appendString:_updates];
1055   [s appendString:@" WHERE "];
1056   [s appendString:_whereClause];
1057   
1058   return s;
1059 }
1060
1061 - (NSString *)assembleJoinClauseWithLeftName:(NSString *)_leftName
1062   rightName:(NSString *)_rightName
1063   joinSemantic:(EOJoinSemantic)_semantic
1064 {
1065   NSMutableString *s;
1066   
1067   s = [NSMutableString stringWithCapacity:64];
1068   [s appendString:_leftName];
1069   switch (_semantic) {
1070     case EOInnerJoin:
1071       [s appendString:@" = "];
1072       break;
1073     case EOFullOuterJoin:
1074       [s appendString:@" *=* "];
1075       break;
1076     case EOLeftOuterJoin:
1077       [s appendString:@" *= "];
1078       break;
1079     case EORightOuterJoin:
1080       [s appendString:@" =* "];
1081       break;
1082   }
1083   [s appendString:_rightName];
1084   return s;
1085 }
1086
1087 /* attributes */
1088
1089 - (NSString *)sqlStringForAttribute:(EOAttribute *)_attribute {
1090   NSLog(@"ERROR(%s): subclasses need to override this method!",
1091         __PRETTY_FUNCTION__);
1092   return nil;
1093 }
1094
1095 - (NSString *)sqlStringForAttributePath:(NSString *)_attrPath {
1096   NSLog(@"ERROR(%s): subclasses need to override this method!",
1097         __PRETTY_FUNCTION__);
1098   return nil;
1099 }
1100
1101 - (NSString *)sqlStringForAttributeNamed:(NSString *)_attrName {
1102   EOAttribute *a;
1103
1104   if ((a = [[self entity] attributeNamed:_attrName]))
1105     return [self sqlStringForAttribute:a];
1106   
1107   return [self sqlStringForAttributePath:_attrName];
1108 }
1109
1110 /* bind variables */
1111
1112 + (BOOL)useBindVariables {
1113   return NO;
1114 }
1115 - (BOOL)mustUseBindVariableForAttribute:(EOAttribute *)_attr {
1116   return NO;
1117 }
1118 - (BOOL)shouldUseBindVariableForAttribute:(EOAttribute *)_attr {
1119   return NO;
1120 }
1121
1122 - (NSMutableDictionary *)bindVariableDictionaryForAttribute:(EOAttribute *)_attr
1123   value:(id)_value
1124 {
1125   NSMutableDictionary *d;
1126   
1127   d = [NSMutableDictionary dictionaryWithCapacity:8];
1128   [d setObject:_attr                  forKey:EOBindVariableAttributeKey];
1129   [d setObject:_value ? _value : null forKey:EOBindVariableValueKey];
1130   return d;
1131 }
1132
1133 - (void)addBindVariableDictionary:(NSMutableDictionary *)_dictionary {
1134   if (self->bindings == nil)
1135     self->bindings = [[NSMutableArray alloc] init];
1136 }
1137 - (NSArray *)bindVariableDictionaries {
1138   return self->bindings;
1139 }
1140
1141 /* values */
1142
1143 + (NSString *)formatValue:(id)_value forAttribute:(EOAttribute *)_attribute {
1144   return _value ? _value : null;
1145 }
1146
1147 - (NSString *)sqlStringForValue:(id)_value attributeNamed:(NSString *)_attrName {
1148   NSMutableDictionary *bindVars;
1149   EOAttribute *attribute;
1150
1151   attribute = [[self entity] attributeNamed:_attrName];
1152
1153   if ([self mustUseBindVariableForAttribute:attribute])
1154     bindVars = [self bindVariableDictionaryForAttribute:attribute value:_value];
1155   else if ([[self class] useBindVariables] &&
1156            [self shouldUseBindVariableForAttribute:attribute]) {
1157     bindVars = [self bindVariableDictionaryForAttribute:attribute value:_value];
1158   }
1159   else
1160     bindVars = nil;
1161
1162   if (bindVars) {
1163     [self addBindVariableDictionary:bindVars];
1164     return [bindVars objectForKey:EOBindVariablePlaceHolderKey];
1165   }
1166   
1167   return [[self class] formatValue:_value?_value:null forAttribute:attribute];
1168 }
1169
1170 + (NSString *)sqlPatternFromShellPattern:(NSString *)_pattern {
1171   unsigned len;
1172
1173   if ((len = [_pattern length]) > 0) {
1174     unsigned   cstrLen = [_pattern cStringLength];
1175     char       cstrBuf[cstrLen + 1];
1176     const char *cstr;
1177     char       buf[len * 3 + 1];
1178     unsigned   i;
1179     BOOL       didSomething = NO;
1180
1181     [_pattern getCString:cstrBuf];
1182     cstr = cstrBuf;
1183     
1184     for (i = 0; *cstr; cstr++) {
1185       switch (*cstr) {
1186         case '*':
1187           buf[i] = '%'; i++; didSomething = YES;
1188           break;
1189         case '?':
1190           buf[i] = '_'; i++; didSomething = YES;
1191           break;
1192           
1193         case '%':
1194           buf[i] = '['; i++;
1195           buf[i] = '%'; i++;
1196           buf[i] = ']'; i++;
1197           didSomething = YES;
1198           break;
1199           
1200         case '_':
1201           buf[i] = '['; i++;
1202           buf[i] = '_'; i++;
1203           buf[i] = ']'; i++;
1204           didSomething = YES;
1205           break;
1206           
1207         default:
1208           buf[i] = *cstr;
1209           i++;
1210           break;
1211       }
1212     }
1213     buf[i] = '\0';
1214     
1215     return (didSomething)
1216       ? [NSString stringWithCString:buf length:i]
1217       : _pattern;
1218   }
1219   return _pattern;
1220 }
1221
1222 /* SQL formats */
1223
1224 + (NSString *)formatSQLString:(NSString *)_sqlString format:(NSString *)_fmt {
1225   return _sqlString;
1226 }
1227
1228 /* qualifier operators */
1229
1230 - (NSString *)sqlStringForSelector:(SEL)_selector value:(id)_value {
1231   if ((_value == null) || (_value == nil)) {
1232     if (sel_eq(_selector, EOQualifierOperatorEqual))
1233       return @"is";
1234     else if (sel_eq(_selector, EOQualifierOperatorNotEqual))
1235       return @"is not";
1236   }
1237   else {
1238     if (sel_eq(_selector, EOQualifierOperatorEqual))
1239       return @"=";
1240     else if (sel_eq(_selector, EOQualifierOperatorNotEqual))
1241       return @"<>";
1242   }
1243   
1244   if (sel_eq(_selector, EOQualifierOperatorLessThan))
1245     return @"<";
1246   else if (sel_eq(_selector, EOQualifierOperatorGreaterThan))
1247     return @">";
1248   else if (sel_eq(_selector, EOQualifierOperatorLessThanOrEqualTo))
1249     return @"<=";
1250   else if (sel_eq(_selector, EOQualifierOperatorGreaterThanOrEqualTo))
1251     return @">=";
1252   else if (sel_eq(_selector, EOQualifierOperatorLike))
1253     return @"LIKE";
1254   else {
1255     return [NSString stringWithFormat:@"UNKNOWN<%@>",
1256                        NSStringFromSelector(_selector)];
1257   }
1258 }
1259
1260 /* qualifiers */
1261
1262 - (NSString *)sqlStringForKeyComparisonQualifier:(EOKeyComparisonQualifier *)_q {
1263   NSMutableString *s;
1264   NSString        *sql;
1265   EOAttribute     *a;
1266   
1267   s = [NSMutableString stringWithCapacity:64];
1268   
1269   sql = [self sqlStringForAttributeNamed:[_q leftKey]];
1270   a   = [[self entity] attributeNamed:[_q leftKey]]; /* relationships ? */
1271   sql = [[self class] formatSQLString:sql format:[a readFormat]];
1272   [s appendString:sql];
1273   
1274   [s appendString:@" "];
1275   [s appendString:[self sqlStringForSelector:[_q selector] value:nil]];
1276   [s appendString:@" "];
1277   
1278   sql = [self sqlStringForAttributeNamed:[_q rightKey]];
1279   a   = [[self entity] attributeNamed:[_q rightKey]]; /* relationships ? */
1280   sql = [[self class] formatSQLString:sql format:[a readFormat]];
1281   [s appendString:sql];
1282   
1283   return s;
1284 }
1285
1286 - (NSString *)sqlStringForKeyValueQualifier:(EOKeyValueQualifier *)_q {
1287   NSMutableString *s;
1288   NSString        *sql;
1289   EOAttribute     *a;
1290   id              v;
1291
1292   v = [_q value];
1293   s = [NSMutableString stringWithCapacity:64];
1294
1295   sql = [self sqlStringForAttributeNamed:[_q key]];
1296   a   = [[self entity] attributeNamed:[_q key]]; /* relationships ? */
1297   sql = [[self class] formatSQLString:sql format:[a readFormat]];
1298   [s appendString:sql];
1299   
1300   [s appendString:@" "];
1301   sql = [self sqlStringForSelector:[_q selector] value:v];
1302   [s appendString:sql];
1303   [s appendString:@" "];
1304   
1305   if (([_q selector] == EOQualifierOperatorLike) ||
1306       ([_q selector] == EOQualifierOperatorCaseInsensitiveLike))
1307     v = [[self class] sqlPatternFromShellPattern:v];
1308   
1309   sql = [self sqlStringForValue:v attributeNamed:[_q key]];
1310   [s appendString:sql];
1311   return s;
1312 }
1313
1314 - (NSString *)sqlStringForNegatedQualifier:(EOQualifier *)_q {
1315   NSMutableString *s;
1316   NSString *sql;
1317
1318   sql = [(id<EOQualifierSQLGeneration>)_q sqlStringForSQLExpression:self];
1319   s = [NSMutableString stringWithCapacity:[sql length] + 8];
1320   [s appendString:@"NOT ("];
1321   [s appendString:sql];
1322   [s appendString:@")"];
1323   return s;
1324 }
1325
1326 - (NSString *)sqlStringForConjoinedQualifiers:(NSArray *)_qs {
1327   NSMutableString *s;
1328   unsigned i, count;
1329   id (*objAtIdx)(id,SEL,unsigned);
1330
1331   objAtIdx = (void *)[_qs methodForSelector:@selector(objectAtIndex:)];
1332   for (i = 0, count = [_qs count], s = nil; i < count; i++) {
1333     id<EOQualifierSQLGeneration> q;
1334     
1335     q = objAtIdx(self, @selector(objectAtIndex:), i);
1336
1337     if (s == nil)
1338       s = [NSMutableString stringWithCapacity:128];
1339     else
1340       [s appendString:@" AND "];
1341     
1342     [s appendString:[q sqlStringForSQLExpression:self]];
1343   }
1344   return s;
1345 }
1346
1347 - (NSString *)sqlStringForDisjoinedQualifiers:(NSArray *)_qs {
1348   NSMutableString *s;
1349   unsigned i, count;
1350   id (*objAtIdx)(id,SEL,unsigned);
1351
1352   objAtIdx = (void *)[_qs methodForSelector:@selector(objectAtIndex:)];
1353   for (i = 0, count = [_qs count], s = nil; i < count; i++) {
1354     id<EOQualifierSQLGeneration> q;
1355     
1356     q = objAtIdx(self, @selector(objectAtIndex:), i);
1357
1358     if (s == nil)
1359       s = [NSMutableString stringWithCapacity:128];
1360     else
1361       [s appendString:@" OR "];
1362     
1363     [s appendString:[q sqlStringForSQLExpression:self]];
1364   }
1365   return s;
1366 }
1367
1368 /* list strings */
1369
1370 - (NSMutableString *)listString {
1371   if (self->listString == nil)
1372     self->listString = [[NSMutableString alloc] initWithCapacity:128];
1373   return self->listString;
1374 }
1375
1376 - (void)appendItem:(NSString *)_itemString toListString:(NSMutableString *)_ls {
1377   if ([_ls length] > 0)
1378     [_ls appendString:@","];
1379   [_ls appendString:_itemString];
1380 }
1381
1382 /* deletes */
1383
1384 - (void)prepareDeleteExpressionForQualifier:(EOQualifier *)_qual {
1385   NSString *tableList, *sql;
1386   
1387   self->whereClauseString =
1388     [[(id<EOQualifierSQLGeneration>)_qual sqlStringForSQLExpression:self] copy];
1389
1390   tableList = [self tableListWithRootEntity:[self entity]];
1391   
1392   sql = [self assembleDeleteStatementWithQualifier:_qual
1393               tableList:tableList
1394               whereClause:[self whereClauseString]];
1395   
1396   [self setStatement:sql];
1397 }
1398
1399 /* updates */
1400
1401 - (void)addUpdateListAttribute:(EOAttribute *)_attr value:(NSString *)_value {
1402   NSMutableString *s;
1403
1404   s = [[NSMutableString alloc] initWithCapacity:32];
1405   [s appendString:[_attr columnName]];
1406   [s appendString:@"="];
1407   _value = [[self class] formatSQLString:_value format:[_attr writeFormat]];
1408   [s appendString:_value];
1409   
1410   [self appendItem:s toListString:[self listString]];
1411   RELEASE(s);
1412 }
1413
1414 - (void)prepareUpdateExpressionWithRow:(NSDictionary *)_row
1415   qualifier:(EOQualifier *)_qual
1416 {
1417   NSEnumerator *keys;
1418   NSString     *key;
1419   NSString     *tableList, *sql;
1420   
1421   keys = [_row keyEnumerator];
1422   while ((key = [keys nextObject])) {
1423     EOAttribute *attribute;
1424     id          value;
1425     
1426     attribute = [self->entity attributeNamed:key];
1427     value     = [_row objectForKey:key];
1428     
1429     [self addUpdateListAttribute:attribute value:value];
1430   }
1431   
1432   self->whereClauseString =
1433     [[(id<EOQualifierSQLGeneration>)_qual sqlStringForSQLExpression:self] copy];
1434
1435   tableList = [self tableListWithRootEntity:[self entity]];
1436
1437   sql = [self assembleUpdateStatementWithRow:_row
1438               qualifier:_qual
1439               tableList:tableList
1440               updateList:[self listString]
1441               whereClause:[self whereClauseString]];
1442   
1443   [self setStatement:sql];
1444 }
1445
1446 @end /* EOSQLExpression(NewInEOF2) */